IT-Academy Logo
Sign Up Login Help
Home - Programmieren - Visual Basic - Subclassing mit VB



Subclassing mit VB

Subclassing ist eine sehr mächtige Funktion, um die Funktionalität eines Visual Basic Programms zu erweitern. Dieser Artikel zeigt, wie das geht.


Autor: Tobias Surmann (incsoft)
Datum: 26-09-2003, 22:11:44
Referenzen: keine
Schwierigkeit: Profis
Ansichten: 7660x
Rating: 10 (1x bewertet)

Hinweis:

Für den hier dargestellte Inhalt ist nicht der Betreiber der Plattform, sondern der jeweilige Autor verantwortlich.
Falls Sie Missbrauch vermuten, bitten wir Sie, uns unter missbrauch@it-academy.cc zu kontaktieren.

[Druckansicht] [Als E-Mail senden] [Kommentar verfassen]



Einleitung

Für Subclassing gibt es gleich zwei Definitionen. Im objektorientierten Sinn ist es eine Unterklasse (= Subclass), die von einer Basisklasse abgeleitet wurde, um ihre Funktionalität zu verändern oder zu erweitern. Im nicht-objektorientierten Sinn ist Subclassing eine Technik, die darauf beruht, dass in die Standardnachrichtenschlange von Windows eingegriffen wird. Man kann nicht jede Nachricht abfangen, die an ein VB-Fenster gesendet wurde, da man den Handle des Fensters benötigt, um die Nachrichten abfangen zu können. Das Fenster ist in Visual Basic schon erzeugt, bevor man auf die Eigenschaft hWnd (Handle des Fensters) zugreifen kann, deshalb verpasst man einige wenige Nachrichten.

Grundlagen

Windows ist ein nachrichtenbasiertes Betriebssystem. Jedes Fenster hat eine eigene Nachrichtenschlange. Das Betriebssystem schickt Nachrichten an die Schlange des Fensters, damit dieses die Nachricht verarbeiten kann. Solche Nachrichten reichen vom Bewegen der Maus durch den Benutzer bis zur Nachricht, die anzeigt, dass das Fenster zerstört wird. Alle Fenster befinden sich in einer immerwährend laufenden Schleife. In dieser Schleife überprüft das Fenster die Nachrichten und verarbeitet sie danach. Betrachten Sie den folgenden Pseudo-Code:

While (GetMessage(Nachricht))
	
	If Nachricht = Fenster Zerstören Then
		
		Call Aufräumarbeiten

	End If

Wend

Visual Basic behandelt Nachrichten ein bisschen anders:

While (GetMessage(Nachricht))

	If Nachricht = Fenster Zerstören Then

		VBs Message Handler aufrufen

	End If

Wend

Problemstellung

Allerdings werden nicht alle Nachrichten von VB's low-level Nachrichtenschleife verarbeitet. Einige selten genutzten Nachrichten wurden in Visual Basic ausgelassen, da sie die mit Visual Basic programmierten Anwendungen stark verlangsamen würden. Das Schlüsselwort im vorigen Satz ist "selten". Einige Entwickler würden es nämlich begrüßen, wenn man trotzdem manchmal herausfinden könnte, ob eine bestimmte Nachricht an das eigene Programmfenster gesendet wurde. Die Lösung hierfür ist Subclassing.

Lösungsmöglichkeit

Von Visual Basic 1.0 bis Visual Basic 4.0 gab es keine in Visual Basic eingebaute Methode, um auf die Nachrichten zuzugreifen, die in Visual Basic ausgelassen wurden. Mit Visual Basic 5.0 wurde ein neues Schlüsselwort namens AddressOf eingeführt. Der AddressOf-Operator gibt den Ort bzw. die Adresse des Eintrittpunktes einer Funktion im Arbeitsspeicher an. Dieser Eintrittspunkt wird von Windows benötigt, um die Funktion jedes Mal aufrufen zu können, wenn es eine neue Nachricht gibt, die verarbeitet werden kann. Aus diesem Grund muss unsere zwischengeschaltete Funktion einen bestimmten Funktionskopf (Parameter und Rückgabewert) haben. Die Funktion sollte in etwa so aussehen (der Funktionsname kann frei bestimmt werden, wird aber meistens WindowProc genannt):

Public Function WindowProc(ByVal hWnd As Long, ByVal uMsg As Long, _
ByVal wParam As Long, ByVal lParam As  Long)

Die Parameter sind - wie gesagt - von Windows vorgegeben und haben die im folgenden beschriebenen Bedeutungen.


hWnd

Dies ist der Handle des Fensters, welche die Nachricht erhält.

uMsg

Dieser Parameter gibt den Typ der Nachricht an (es gibt sehr viele Konstanten hierfür).

lParam und wParam

Die beiden Parameter enthalten zusätzliche Informationen zur Nachricht. Wenn der Anwender z. B. mit der Maus klickt, werden gleichzeitig über diese Parameter noch die Mausposition und die Maustaste übertragen.

Jede Nachricht muss anders behandelt werden, und es gibt - wie oben bereits erwähnt - tausende von verschiedenen Nachrichten. In diesem Artikel können natürlich nicht alle Nachrichten im Detail behandelt werden, dies würde bei weitem den Rahmen sprengen (man könnte ein sehr dickes Buch über dieses Thema schreiben). Ich zeige Ihnen stattdessen, wie man Nachrichten verarbeitet und den Eingriff in das Windowsnachrichtensystem vorbereitet und abschließt.

Nachrichten werden normalerweise nicht aus der Nachrichtenschlange entfernt. Stattdessen ist es die Aufgabe unserer VB-Handler-Funktion, auf die Nachrichten zu reagieren und sie dann an den Standardfunktionshandler weiter zu reichen. Beispielsweise könnten wir eine Nachricht empfangen, die uns mitteilt, dass das Formular kurz davor steht, neu gezeichnet zu werden; wir sollten zunächst den Standardfunktionshandler aufrufen und dann unsere Grafik dem Formular hinzufügen. Würden wir zuerst unsere Grafik ausgeben, würde der Standardfunktionshandler diese nachher einfach wieder überschreiben, was natürlich nicht gewollt ist. Dies würde Windows ziemlich durcheinander bringen. Es ist also wichtig, immer nach bzw. vor der Bearbeitung (je nach Nachricht) den Standardfunktionshandler aufzurufen. In der WinAPI ist diese Funktion als DefWindowProc bekannt.

Code

Da man zur Initialisierung und zum Beenden des Subclassings immer wieder den gleichen Code benötigt, habe ich mir ein eigenes Modul programmiert, das ich dann einfach in verschiedene Projekte einbinden kann.

Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
(ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" _ 
(ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long

Public LastProc As Long
Public Const GWL_WNDPROC = -4

Bei der Initialisierung wird die Adresse der zwischengeschalteten Handlerfunktion über den AdressOf-Operator ermittelt und an die WinAPI-Funktion SetWindowLong übergeben. Die alte Adresse wird gespeichert, da wir sie beim Beenden benötigen. Die Initialisierung wird auch sehr häufig unter dem Begriff 'Hook setzen' verwendet.

Public Sub SetHook(frmHook As Form)
	
	LastProc = SetWindowLong(frmHook.hWnd, GWL_WNDPROC, AddressOf WindowProc)

End Sub

Hier die Funktion für das Beenden des Subclassings:

Public Sub UnSetHook(frmHook As Form)

	SetWindowLong frmHook.hWnd, GWL_WNDPROC, LastProc

End Sub

Den Funktionskopf des Standardhandlers kennen wir ja schon. Zusätzlich wird auch immer noch der Aufruf der VB-Handler-Funktion benötigt (Weiterleitung).

Public Function WindowProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam _
As Long, ByVal lParam As Long) As Long

	WindowProc = CallWindowProc(LastProc, hWnd, uMsg, wParam, lParam)

End Function

Im allgemeinen Deklarationsabschnitt wird eine wichtige Variable deklariert - LastProc. Diese speichert die Adresse der ursprünglich aufzurufenden Funktion. Bevor das Fenster geschlossen werden kann, muss immer erst der Standardhandler von VB eingebunden werden, und die Weiterleitung an unseren eigenen Handler somit unterbrochen werden. Geschieht dies nicht, stürzt das Programm ab. In UnSetHook wird deshalb wieder die Adresse des VB-Funktionshandlers gesetzt.

Bis jetzt kann unser eigener Handler WindowProc nichts anderes, als alle Nachrichten zu empfangen und diese an den Nachrichtenhandler von VB weiterzuleiten. Dies ist natürlich nicht sehr effizient, da durch den Zwischenschritt (geringe) Geschwindigkeitseinbußen zu verzeichnen sind. Da Sie mit dem soeben gezeigten Modul bereits in der Lage sind, Subclassing zu verwenden, können wir an dieser Stelle bereits ein kleines Beispielprojekt umsetzen. Erstellen Sie also ein neues Projekt und fügen Sie folgenden Code ein:

Private Sub Form_Load()
	
	SetHook Me

End Sub

Private Sub Form_Unload(Cancel As Integer)

	UnSetHook Me

End Sub

Somit haben wir jetzt schon fast erfolgreich ein gesubclasstes Fenster erstellt. Nun müssen wir nur noch bestimmen, auf welche Nachrichten wir gesondert reagieren möchten. In diesem Beispiel soll das die Nachricht zum Neuzeichnen des Fensters sein - WM_PAINT. Jedes Mal, wenn das Fenster gezeichnet wird, soll ein grüner Kreis gezeichnet werden.

Public Function WindowProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam _
As Long, ByVal lParam As Long) As Long
	
	WindowProc = CallWindowProc(LastProc, hWnd, uMsg, wParam, lParam)
	If uMsg = WM_PAINT Then Form1.Circle (300, 300), 150, vbGreen

End Function

Speichern und kompilieren Sie den Code und führen Sie die EXE-Datei aus. Dies ist nötig, da Visual Basic meistens unter den Umständen abstürzt, wenn ein Fehler im Code ist und Sie diesen debuggen müssen. Außerdem stürzt die Anwendung auch ab, wenn Sie durch Ihren Code rekursive Aufrufe hervorrufen. Würden Sie z. B. in dem oben gezeigten Beispiel Form1.Refresh innerhalb von WindowProc aufrufen, so würde wieder ein WM_PAINT-Ereignis aufgerufen, was wiederum zur Folge hätte, dass unser Handler diese Nachricht bearbeitet und wieder Form1.Refresh aufruft usw. Jede Nachricht, die von Windows verschickt und durch Subclassing bearbeitet werden kann, wurde von Microsoft ausführlich in Platform SDK dokumentiert. Sie werden also alle benötigten Informationen zu einer Nachricht, wie z. B. Parameter und erwartete Rückgabewerte, finden. Ein Blick in diese Dokumentation lohnt sich!

Fazit und Zusammenfassung

Subclassing ist eine sehr mächtige Funktion, um die Funktionalität eines Visual Basic Programms zu erweitern. Subclassing steht erst ab der Version 5.0 von Visual Basic zur Verfügung, da es erst ab dieser Version den AddressOf-Operator gibt, der für Subclassing zwingend erforderlich ist. Denken Sie aber immer daran, dass Sie Subclassing nur einsetzen, wenn es unbedingt vonnöten ist, da Ihre Anwendung durch den Eingriff in die Windowsnachrichtenschlange extrem instabil wird, besonders wenn es ums Debuggen der Anwendung geht. Beenden Sie eine Anwendung in der IDE nie durch Benutzung der Beenden-Schaltfläche in der Symbolleiste (verursacht den Absturz Ihrer Anwendung und der Visual Basic Entwicklungsumgebung), sondern sorgen Sie dafür, dass sich Ihre Anwendung entweder korrekt über eine gesonderte Beenden-Schaltfläche im Programm oder durch das Schließen des Fensters (empfohlen) beenden lässt.

Hinweis: Die hier gezeigten Techniken können den Absturz des Programms bzw. des Systems verursachen. Der Autor übernimmt keine Haftung für Schäden jeglicher Art.


[back to top]



Userdaten
User nicht eingeloggt

Gesamtranking
Werbung
Datenbankstand
Autoren:04503
Artikel:00815
Glossar:04116
News:13565
Userbeiträge:16551
Queueeinträge:06236
News Umfrage
Ihre Anforderungen an ein Online-Zeiterfassungs-Produkt?
Mobile Nutzung möglich (Ipone, Android)
Externe API Schnittstelle/Plugins dritter
Zeiterfassung meiner Mitarbeiter
Exportieren in CSV/XLS
Siehe Kommentar



[Results] | [Archiv] Votes: 1136
Comments: 0