ABOUT Visual Basic Programmieren Programmierung Download Downloads Tips & Tricks Tipps & Tricks Know-How Praxis VB VBA Visual Basic for Applications VBS VBScript Scripting Windows ActiveX COM OLE API ComputerPC Microsoft Office Microsoft Office 97 Office 2000 Access Word Winword Excel Outlook Addins ASP Active Server Pages COMAddIns ActiveX-Controls OCX UserControl UserDocument Komponenten DLL EXE
Diese Seite wurde zuletzt aktualisiert am 13.01.2000

Diese Seite wurde zuletzt aktualisiert am 13.01.2000
Aktuell im ABOUT Visual Basic-MagazinGrundlagenwissen und TechnologienKnow How, Tipps und Tricks rund um Visual BasicActiveX-Komponenten, Controls, Klassen und mehr...AddIns für die Visual Basic-IDE und die VBA-IDEVBA-Programmierung in MS-Office und anderen AnwendungenScripting-Praxis für den Windows Scripting Host und das Scripting-ControlTools, Komponenten und Dienstleistungen des MarktesRessourcen für Programmierer (Bücher, Job-Börse)Dies&Das...

Themen und Stichwörter im ABOUT Visual Basic-Magazin
Code, Beispiele, Komponenten, Tools im Überblick, Shareware, Freeware
Ihre Service-Seite, Termine, Job-Börse
Melden Sie sich an, um in den vollen Genuss des ABOUT Visual Basic-Magazins zu kommen!
Informationen zum ABOUT Visual Basic-Magazin, Kontakt und Impressum

Zurück...

Zurück...


Anzeige

(-hg) mailto:hg_praezisynch@aboutvb.de

Bitte, was? "Präzisynchrone" Controls? Keine Bange - dieser Begriff kommt nicht aus der Schlagwortschmiede in Redmond, sondern stammt ausnahmsweise von mir. Dahinter steckt nichts besonderes, nur eben die präzise synchrone Steuerung von Steuerlementen. Eigentlich wollte ich sie ja "Affen-Steuerlemente " nennen, weil unsere Primaten-Vettern ja bekanntlich alles nachmachen. Aber irgendwie klingt "Präzi...dingsbums" doch viel technischer und computerischer, nicht wahr?

Langer Rede kurzer Sinn: Es geht darum, wie Sie mit Hilfe eines einfachen Tricks zwei Steuerelemente zur synchronen Reaktion auf Maus- und Tastenereignisse bringen. Sie werden nämlich den Affen spielen... Pardon!... Sie werden genau das gleiche machen, was Windows macht, nämlich Ereignis-Nachrichten versenden. Wenn ein Maus- oder Tastenereignis bei einem Steuerelement eingetroffen ist, werden Sie dieses sofort über eine entsprechende Windows-Nachricht an ein anderes, fernzusteuerndes Steuerelement weiterreichen. Und darum geht es in diesem Beitrag: Sie nutzen die Maus- und Tastenereignisse eines Steuerelements, um in einem anderen genau die Resultate zu erzielen, als ob dort ebenfalls und gleichzeitig Maus- und/oder Tastatur-Tasten gedrückt worden wären.


Steuerelemente im paarweisen Gleichklang

Zunächst mag sich das nach komplizierter API-Trickserei anhören, nach Subclassing oder Hooks und dergleichen. Das ist gar nicht nötig. Die von VB ausgelösten Ereignisse reichen vollauf. Sie liefern bereits die benötigten Informationen, die Sie lediglich etwas anders verpacken müssen und dann einfach mit den Allerwelts-API-Funktionen SendMessage bzw. PostMessage abschicken. Bei den Tastenereignissen können wir uns sogar auf das KeyDown-Ereignis beschränken.

Den VB-Ereignissen genau entsprechend gibt es Windows-Nachrichten: WM_XBUTTONDOWN bzw. WM_XBUTTONUP für das MouseDown- und das MouseUp-Ereignis, WM_MOUSEMOVE für das MouseMove-Ereignis, und schließlich noch WM_KEYDOWN für das KeyDown-Ereignis. In Wirklichkeit hat nämlich Windows eben diese Nachrichten an VB gesendet. VB übersetzt sie lediglich für uns, und wir brauchen nichts weiter zu tun, als für den Weiterversand die Rückübersetzung vorzunehmen. Das mag zwar widersinnig klingen. Aber so ist es für uns VBler wesentlich einfacher handzuhaben, als wenn wir uns mit jenen weitaus komplizierteren Subclassing- und Sonstwas-Techniken herumplagen würden - die sind immer eine etwas heikle und leicht wackelige Angelegenheit.

Ich habe allerdings ein klein wenig geschummelt. API-Kenner werden es schon längst gesehen haben: Von XBUTTONS hat noch nie jemand etwas gehört. Genau genommen verschickt Windows für jede Maustaste eine eigene Nachricht. Ersetzen Sie das X jeweils durch ein L, M oder R... - alles klar? VB sagt uns im Prinzip dasselbe. Welche Maustaste gedrückt oder losgelassen worden ist, entnehmen wir dem Button-Parameter der Mausereignisse. Windows ist jedoch einen Tick genauer. Neben der für jede Maustaste eindeutigen Nachricht bekommen wir noch im WParam-Parameter (warum das Ding bloß so heißt??) einen Wert, der die gedrückten Tasten repräsentiert. Das ist insofern genauer, als dass beim Loslassen einer Maustaste eine andere ja weiterhin gedrückt sein kann. Genau das erfahren wir hier, während VB uns das vorenthält - wir erfahren nur, welche Taste losgelassen wurde. Aber da wir hier ja nicht VB die Aufgabe der Interpretation einer Windows-Nachricht abnehmen wollen, sondern umgekehrt die VB-Nachricht wieder in eine Windows-Nachricht zurückverwandeln wollen, ist das kein großes Unglück. Beim MouseMove-Ereignis gibt es hingegen keine gerade erst niedergedrückte oder losgelassene Maustaste, sondern lediglich die Maustasten, die während der Mausbewegung konstant niedergedrückt sind. Daher gibt es von Windows auch nur die Nachricht WM_MOUSEMOVE mit der entsprechenden Maustastenkombination in WParam verpackt.

Aus Platzgründen - die eierlegende Wollmilch-Funktion MSDN Library - API SendMessageSendMessage (bzw. ihre Schwester MSDN Library - API PostMessagePostMessage) verfügt nun mal nur über eine begrenzte Anzahl an Parametern - steckt in WParam auch noch die Information, ob eine Strg- oder eine Umschalttaste gedrückt ist (ob es sich um die rechte oder linke davon handelt, spielt hier keine Rolle). Im letzten Parameter LParam stecken die Koordinaten des Mausereignisses. LParam ist vom Datentyp Long, der aus zwei Integer-Werten zusammengesetzt ist, den Koordinaten X und Y. Auch wenn im 32-bittigen Windows mittlerweile überall Long-Werte verwendet werden, und auch in anderen API-Funktionen X- oder Y-Koordinaten als Long-Werte gefragt sind, reichen dafür in Wirklichkeit Integer-Werte vollkommen aus - von Bildschirmen mit einer Superauflösung von mehr als 32.000 x 32.000 Pixels dürften wir wohl bis in alle Ewigkeit träumen.

Im Gegensatz zu den Mausereignissen, bei denen alle drei relevant sind, benötigen wir von den Tastatur-Ereignissen in den allermeisten Fällen nur KeyDown. Wahrscheinlich werden auch Sie das KeyUp-Ereignis in Ihren Programmen nur äußerst selten verwenden. Das KeyPress-Ereignis ist eigentlich kein echtes Ereignis, wie auch die entsprechende Windows-Nachricht WM_CHAR es nicht ist. Beide sind nur Vorauswertungen und Übersetzungen der in KeyDown bereits gemeldeten Tastendrücke in das lokale Zeichen- und Schreibsystem. Wir können also darauf ebenfalls verzichten.

Beginnen wir die Konkretisierung unserer Absicht beim KeyDown-Ereignis. Doch zunächst noch die notwendigen API-Deklarationen:

Private Const MK_LBUTTON = &H1
Private Const MK_RBUTTON = &H2
Private Const MK_SHIFT = &H4
Private Const MK_CONTROL = &H8
Private Const MK_MBUTTON = &H10

Private Const WM_MOUSEMOVE = &H200
Private Const WM_LBUTTONDOWN = &H201
Private Const WM_LBUTTONUP = &H202
Private Const WM_RBUTTONDOWN = &H204
Private Const WM_RBUTTONUP = &H205
Private Const WM_MBUTTONDOWN = &H207
Private Const WM_MBUTTONUP = &H208

Private Declare Function SendMessage Lib "user32" _
 Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, _
 ByVal wParam As Long, ByVal lParam As Long) As Long

Public Const WM_KEYDOWN = &H100
Public Declare Function PostMessage Lib "user32" _
 Alias "PostMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, _
 ByVal wParam As Long, ByVal lParam As Long) As Long

Bei diesem brauchen Sie nichts weiter zu tun, als eine der Funktionen SendMessage oder PostMessage aufzurufen. Meistens tut es die geläufigere Funktion SendMessage voll und ganz, manchmal, etwa bei TextBoxen, müssen Sie dagegen PostMessage nehmen, damit der Trick funktioniert. Fragen Sie mich jetzt bitte nicht nach dem Unterschied - da müsste ich sehr weit ausholen. Als Anhaltspunkt mag Ihnen genügen (wenn Sie es nicht sowieso schon wissen), dass eine mit SendMessage gesendete Nachricht im Prinzip sofort bearbeitet wird, während eine mit PostMessage gesendete Nachricht erst später bearbeitet wird, sozusagen wenn Zeit dafür da ist. Aus Ihrer Sicht als Aufrufer werden beide Funktionen jedoch sofort bearbeitet - Ihr Programm läuft sofort nach dem Aufruf weiter.

Als ersten Parameter übergeben Sie das Windows-Handle des Steuerelements, das Sie synchronisieren wollen - daraus ergibt sich natürlich, dass nur Steuerelemente davon profitieren können, die über ein ebensolches Handle verfügen. Aber das ist sowieso bei den meisten maus- und tastatur-"empfindlichen" Steuerelementen der Fall. Im zweiten Parameter folgt die Nachricht WM_KEYDOWN und im dritten Parameter der KeyCode-Wert, den das VB-KeyDown-Ereignis übergeben hat. Der letzte Parameter (LParam) wird hier nicht benötigt und bleibt daher Null.

Bei den Mausereignissen wird es komplizierter. Auch hier wird wieder zuerst das Fenster-Handle des betreffenden Steuerelements übergeben. Dann müssen wir zunächst aus dem Button-Wert schließen, welche Maus-Nachricht zu senden ist. Diese Operation verpacken wir in eine Funktion, damit wir nicht den gleichen Code in allen drei Mausereignissen wiederholen müssen.

SendMessage CLng(hWnd), zMakeMsg(Button, True), _
 zMakeWParam(Button, Shift), zMakeLParam(X, Y)

Damit die Funktion weiß, ob es sich um das Drücken oder um das Loslassen der betreffenden Maustaste handelt, übergeben wir neben dem Button-Wert als Parameter den Wert True bei gedrückter Taste (MouseDown-Ereignis), anderenfalls bleibt er False (MouseUp-Ereignis). Im MouseMove-Ereignis erübrigt sich diese Prüfung - wir setzen dort die WM_MOUSEMOVE-Nachricht direkt ein.

Private Function zMakeMsg(ByVal iButton As Integer, _
 ByVal iDown As Boolean) As Long
  Select Case iDown
    Case True
      Select Case iButton
        Case vbLeftButton
          zMakeMsg = WM_LBUTTONDOWN
        Case vbRightButton
          zMakeMsg = WM_RBUTTONDOWN
        Case vbMiddleButton
          zMakeMsg = WM_MBUTTONDOWN
      End Select
    Case False
      Select Case iButton
        Case vbLeftButton
          zMakeMsg = WM_LBUTTONUP
        Case vbRightButton
          zMakeMsg = WM_RBUTTONUP
        Case vbMiddleButton
          zMakeMsg = WM_MBUTTONUP
      End Select
  End Select
End Function

Im folgenden Schritt, den wir wieder in eine Funktion verpacken, werten wir zunächst erneut den Button-Wert aus. Hier werden andere Konstanten-Werte als bei den Nachrichten verwendet (MK_xxx), um Windows die gedrückte(n) Maustaste(n) anzugeben. Aus dem gleichen Konstanten-Satz stammen diejenigen, die Windows das Gedrücktsein einer Strg- und/oder Umschalttaste angeben - dazu werten wir den Shift-Wert des VB-Mausereignisses aus (mit der Alt-Taste kann Windows in diesem Zusammenhang nichts anfangen). Alle Konstant-Werte zusammen werden über eine ODER-Verknüpfung kombiniert und schließlich als Wert für WParam zurückgegeben.

Private Function zMakeWParam(ByVal iButton As Integer, _
 ByVal iShift As Integer) As Long
    Dim wParam As Long
    
    If (iButton And vbLeftButton) = vbLeftButton Then
        wParam = MK_LBUTTON
    End If
    If (iButton And vbRightButton) = vbRightButton Then
        wParam = wParam Or MK_RBUTTON
    End If
    If (iButton And vbMiddleButton) = vbMiddleButton Then
        wParam = wParam Or MK_MBUTTON
    End If
    If (iShift And vbCtrlMask) = vbCtrlMask Then
        wParam = wParam Or MK_CONTROL
    End If
    If (iShift And vbShiftMask) = vbShiftMask Then
        wParam = wParam Or MK_SHIFT
    End If
    zMakeWParam = wParam
End Function

Auch das Zusammensetzen der X- und Y-Koordinaten zu dem geforderten Long-Wert nehmen wir wieder in einer Funktion vor. Die Regel lautet, dass der Y-Wert im höherwertigen Word des Long-Wertes landen soll. Was das heißt? Kurz umrissen bedeutet das folgendes. Ein Long-Wert wird mit genau doppelt so vielen Bytes im Arbeitsspeicher abgelegt, wie ein Integer-Wert. Anders könnte man auch sagen, dass es für die Rechnerlogik eigentlich egal ist, ob im Arbeitsspeicher ein Long-Wert liegt, oder ob zwei Integer-Werte genau hintereinander liegen. Genauso sieht und behandelt Windows das auch, wenn im LParam als Long-Wert eigentlich die zwei Integer-Koordinaten aufeinanderfolgend ankommen. Ich will Sie hier nicht lange mit hexadezimaler Speicher-Arithmetik aufhalten - glauben Sie mir einfach, dass Sie den Wert von Y mit 65536 bzw. hexadezimal &H10000 multiplizieren müssen und dann einfach den X-Wert hinzuaddieren. Vergessen Sie aber nicht, vorher die beiden Koordinaten in Pixels umzurechnen. In den allermeisten Fällen kommen die Koordinaten in der Maßeinheit TWIPS an (Ausnahmen gibt es wohl nur bei Steuerelementen wie der PictureBox, bei denen Sie die Skalierung über die Eigenschaft ScaleMode einstellen können).

Private Function zMakeLParam(ByVal iX As Single, _
 ByVal iY As Single) As Long
  zMakeLParam = (CLng(iX) \ Screen.TwipsPerPixelX) + _
 (CLng(iY) \ Screen.TwipsPerPixelY) * &H10000
End Function

Nun haben wir alle Werte umgesetzt und verpackt und können jeweils im MouseDown, im MouseMove- und im MouseUp-Ereignis die Nachricht mit SendMessage oder PostMessage verschicken.

Eine kleine Falle müssen wir jedoch noch umgehen. Ohne dass wir das ohne größeren Aufwand (zumindest nicht so einfach von VB aus) beeinflussen könnten, verschiebt Windows den Eingabe-Fokus immer sofort zu einem Steuerelement, wenn über diesem eine Maustaste niedergedrückt wird - also das MouseDown-Ereignis ausgelöst wird. Für unsere synchronisierten Steuerelemente hat das zur Folge, dass der Fokus beim Eintreffen des via SendMessage weitergereichten MouseDown-Ereignisses sofort zum zweiten Steuerelement weiterspringt. Solange Sie nichts Besonderes mit den LostFocus- und GotFocus-Ereignissen der mitspielenden Steuerelemente vorhaben (auch das Validate-Ereignis ist betroffen), macht dies nicht allzu viel aus. Sie schubsen den Fokus einfach nach dem Eintreffen der Nachricht im MouseDown-Ereignis des zweiten Steuerelements wieder per SetFocus-Methode zum ersten zurück.

Damit Sie den ganzen, wenn auch geringen Aufwand nicht immer wieder treiben und schreiben müssen, habe ich den notwendigen Code samt Funktions- und Konstanten-Deklarationen in ein separates Modul verschoben. Am Prinzip ändert sich dabei nichts, außer dass Sie nun die Maus-Ereignisse erst einmal an gleichartig aufgebaute (und nur um den vorangestellten hWnd-Parameter erweiterte) Prozeduren in diesem Modul weiterreichen. Die Tastatur-Ereignisse weiterzuschieben und besonders zu verpacken, lohnt hingegen nicht. Die Details dazu können Sie problemlos dem Code entnehmen.

Eines wäre noch wichtig anzumerken. Wie das Beispiel-Projekt zeigt, funktioniert der Trick fast hundertprozentig bei ListBoxen und TextBoxen. Er wird nicht immer funktionieren, zum Beispiel bei CommandButtons nicht. Aber bei diesen sehe ich auch wenig Sinn darin, da dessen Click-Ereignis ja die wesentliche Funktion darstellt und auch direkt angesprochen werden kann. Andere, vor allem komplexer aufgebaute Steuerelemente, können sich auch widerborstig verhalten - das müssen Sie im Einzelfall einfach ausprobieren. Sie werden dann auch schnell merken, dass es zum Beispiel kein Mausereignis gibt, wenn etwa der Rollbalken einer ListBox angeklickt wird - Sie können also auch keines weitersenden. Um eine andere ListBox dennoch zu synchronisieren, greifen Sie zu "gewöhnlichen" Tricks, wie etwa dem der Gleichsetzung der TopIndex-Eigenschaft im Scroll-Ereignis...

Code des Moduls modSync aus ControlSyncTest Code des Moduls modSync
Code des Forms aus ControlSyncTest Code des Test-Forms

Beispiel-Projekt mit Modul modSync (ctrlsync.zip - ca. 2,8 KB)






Themen - Allgemeines
Themen - Entwicklungsumgebung (VB-IDE)
Themen - Forms
Themen - Steuerelemente (Controls)
Themen - Grafik
Themen - Dateien
Themen - UserControls
Themen - Einsteiger-Tipps
Themen - Wussten Sie...?

Übersicht nach Titeln in alphabetischer Reihenfolge
Übersicht nach Erscheinungsdatum

Schnellsuche



Zum Seitenanfang

Copyright © 1999 - 2017 Harald M. Genauck, ip-pro gmbh  /  Impressum

Zum Seitenanfang

Zurück...

Zurück...

Download Internet Explorer