Ein globales OnClick() in Delphi
Donnerstag, September 2nd, 2010Gestern abend konnte ich eines von Murpyhs Gesetzen verifizieren, es lautet: „In jedem kleinen Problem, steckt ein Großes, das gerne heraus möchte.“ Eigentlich wollte ich nur eine kleine Verbesserung an der Slideshow-Funktionalität vornehmen, aber dann entdeckte ich das große Problem, welches mich den ganzen Abend beschäftigte.
Folgendes Verhalten wollte ich bei der Slideshow umsetzen:
- die Slideshow soll über den Button in der Toolleiste aktivierbar sein
- ein zweiter Klick auf den Button soll sie wieder deaktivieren
- und ein Klick auf irgendein anderes Steuerelement soll die Slideshow ebenfalls deaktivieren
Die ersten beiden Punkte sind einfach umzusetzen. Das Problem liegt in dem dritten Punkt. Im alten BVASystem hatte ich die Funktionalität dadurch erreicht, indem ich bei jedem OnClick() Event ein:
procedure TfrmMain.Toolbutton1Click(Sender: TObject); begin if (Slideshow.Aktiv = true) then Slideshow.Aktiv := false; ... end;
eingebaut habe. Das wollte ich dieses mal aber um jeden Preis vermeiden, da die Slideshow sich im neuen Programm im Hauptdialog befindet. Dort wären weit mehr Funktionen zu erweitern, als es im alten Programm in der Vollbildansicht der Fall war.
Gelöst habe ich das Problem schlussendlich mit einem globalen OnClick Ereignis. In der Komponetensammlung von Delphi befindet sich eine Klasse TApplicationEvents, die einen Eventhandler für OnMessage() besitzt. Dieses Event wird vor jeder Windowsbotschaft, die die Anwendung empfängt, geworfen. Damit ist es relativ leicht möglich, hier die Botschaft „WM_LBUTTONUP“ abzufangen, die Slideshow gegebenenfalls zu deaktivieren und anschließend die Botschaft an das dementsprechende Steuerelement weiterzuleiten.
Aber Vorsicht Falle. Für den Button, der die Slideshow eh deaktivieren soll, darf entweder die Slideshow nicht im OnMessage() gestoppt werden, oder aber das Event darf den Button nie erreichen. Ich entschied mich für die zweitere Variante:
procedure TfrmMain.ApplicationEventsMessage(var Msg: tagMSG; var Handled: Boolean); var p:TPoint; wc:TWinControl; ctrl:TControl; begin case Msg.message of WM_LBUTTONUP:begin //Aktuelle Mausposition bestimmen P.X := Mouse.CursorPos.X; P.Y := Mouse.CursorPos.Y; //TWinControl bestimmen welches sich an der Mausposition befindet wc := FindVCLWindow(P); if Assigned(wc) then begin //TControl bestimmen welches sich an der Mausposition befinden ctrl := wc.ControlAtPos(wc.ScreenToClient((P)), false, false); if Assigned(ctrl) then begin if ctrl.Name = 'btnSlideshow' then Handled := true; end; end; If SlideShow.Aktiv then SlideShow.Aktiv := false; end; end; end;
Ich bestimme also zuerst die Position der Maus auf dem Desktop. Anschließend kann ich mit „FindVCLWindow“ das TWinControl bestimmen, welches sich an der MausPosition befindet. Da die Buttons aber nicht fensterorientierte Steuerelemente sind, werden sie von der Funktion nicht entdeckt. Daher ist als zweiter Schritt ControlAtPos() notwendig. Anschließend muss nur noch überprüft werden, ob das Control der Slideshowbutton ist. Ist er es, wird das Event durch „Handled := true“ aus der Windowsbotschaftenliste gelöscht.
Am Ende muss dann nur noch die Slideshow deaktiviert werden, um das von mir gewünschte Verhalten umzusetzen.