Globalny OnMouseMove

0

Cześć,
zaczęło mnie nurtować kolejne zagadnienie związane z onMouseMove. Działanie jest proste i oczywiste, natomiast zauważyłem, że zdarzenie jest wywoływanie wewnątrz obecnie aktywnej kontrolki. Czyli gdy mam Image i StatusBar to mogę zrobić tak, że znam pozycję kursora, gdy w Image.onMouseMove wpiszę:

StatusBar1.Panels.Items[0].Text := 'X: ' + IntToStr(X) ;
StatusBar1.Panels.Items[1].Text := 'Y: ' + IntToStr(Y);

Natomiast nie mam możliwości zrobienia tak, żeby to StatusBar jakby sam odświeżał się gdy zachodzi onMouseMove dla Image. Przykład jest prosty, żeby wprowadzić w moje rozumowanie. Dokładnie chcę to zastosować tak, że w konstruktorze własnej klasy tworzę komponent typu TImage i przypisuje mu onMouseMove, a szukam rozwiązania jak pobrać to z poziomu StatusBar. Chodzi mi o samo zagadnienie, ponieważ praktycznie łatwiej jest przekazać do klasy uchwyt do StatusBar i tak tym zarządzać.
Zacząłem szukać czy jest coś na zasadzie "globalnego MouseMove" trafiłem na TControl.SetCaptureControl() i nie umiem do końca użyć. Nie mogę napisać:

StatusBar.SetCaptureControl(Image);

lecz musi być samo

SetCaptureControl

Szukałem też w TMouse. Sprawdzałem jak działają akcje i czy jest coś na zasadzie wyzwalaczy (takie powiadomienie push od klasy do StatusBar). I tutaj w sumie powinienem się zatrzymać i wrócić do pierwotnych założeń.
Pytanie brzmi czy w Delphi można zrobić tak, że StatusBar odświeża się nawet gdy obecnie poruszamy myszą po innym komponencie?

Zastanawia mnie to, ponieważ MouseMove jednak pobiera parametr Sender: TObject;

0

Łatwiej by było coś doradzić, gdybyś napisał co chcesz zrobić, a nie co próbował i co nie działa. Z całego tego posta trudno wywnioskować jakiego efektu oczekujesz.


PanAndrzej napisał(a):

Działanie jest proste i oczywiste, natomiast zauważyłem, że zdarzenie jest wywoływanie wewnątrz obecnie aktywnej kontrolki.

Zawsze tak jest – zdarzenie wywołuje komponent, którego ono dotyczy.

Natomiast nie mam możliwości zrobienia tak, żeby to StatusBar jakby sam odświeżał się gdy zachodzi onMouseMove dla Image.

Dokładnie. Ale możesz jedno zdarzenie podłączyć pod kilka komponentów i w tym zdarzeniu napisać kod, który pobierze współrzędne kursora, przeliczy je na koordynaty względem konkretnego komponentu (np. TImage) i zaktualizuje zawartość paska stanu (albo cokolwiek innego).

Zacząłem szukać czy jest coś na zasadzie "globalnego MouseMove" trafiłem na TControl.SetCaptureControl() i nie umiem do końca użyć.

Działanie tej metody jest nieco inne niż zakładasz. Chodzi w niej o to, że komponent podany w parametrze dostaje komunikaty myszy nawet wtedy, gdy kursor znajduje się poza jego obszarem. I tak dostaje te komunikaty aż do wywołania ReleaseCapture.

Dzięki temu np. taki systemowy Paint pozwala na rozciąganie zaznaczonego obszaru nawet wtedy, gdy użytkownik przesunie kursor poza okno edytora. Taka funkcjonalność jest cenna, dlatego ma wiele zastosowań.

Czy taka funkcja jest Ci potrzebna?

Pytanie brzmi czy w Delphi można zrobić tak, że StatusBar odświeża się nawet gdy obecnie poruszamy myszą po innym komponencie?

Samoczynnie – nie.

Zastanawia mnie to, ponieważ MouseMove jednak pobiera parametr Sender: TObject;

Wszystkie zdarzenia posiadają parametr Sender, dlatego że jest to referencja komponentu, który to zdarzenie wywołuje. Jeśli zaglądniesz do kodu komponentów, to możesz znaleźć konstrukcje pokroju poniższej:

if Assigned(FOnClick) then
  FOnClick(Self);

Oznacza to tyle, że komponent wywołuje zdarzenie z pola FOnClick typu TNotifyEvent i przekazuje ”samego siebie” w parametrze. W typowych przypadkach tak to wygląda, ale zawsze istnieje możliwość ręcznego wywołania zdarzenia i podania innych danych w tym parametrze (albo nawet nil).

0

Cześć, wychodzi na to, że nie ma rozwiązania metodą, której poszukuję. Mimo wszystko sprawdziłem działanie SetCaptureControl() i mam takie zachowanie, że gdy uruchamiam program to pojawia się kursor ładowania i wszystko działa poprawnie, a jak nacisnę PPM to kursor zamienia się w standardowy, lecz StatusBar przestaje pobierać pozycję. Poniżej kod:

procedure TForm1.FormCreate(Sender: TObject);
begin
  SetCaptureControl(StatusBar1);
end;

procedure TForm1.StatusBar1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  StatusBar1.Panels.Items[0].Text := IntToStr(X);
end;
2

No tak to właśnie działa – do momentu wywołania metody ReleaseCapture, nieważne czy sam ją wywołasz, czy wywoła ją któryś z mechanizmów VCL. Dlatego używanie do tego celu SetCaptureControl raczej się nie nadaje, bo musiałbyś ją wywoływać za każdym razem, gdy wciśnięty zostanie któryś z przycisków myszy, gdziekolwiek w oknie (nad formularzem lub każdym z komponentów).

Na otarcie łez pozostaje timer, który będzie aktualizował dane na pasku stanu np. co 250ms (częściej raczej nie potrzeba). Tyle tylko, że współrzędne kursora musisz odczytać (np. z obiektu Mouse), a następnie przekonwertować na współrzędne względem formularza (np. za pomocą metody ScreenToClient). Sam timer oczywiście możesz utworzyć dynamicznie, z poziomu kodu.

Póki co innego rozwiązania nie widzę – może ktoś inny wpadnie na lepszy pomysł. ;)

2

No dobra, mały PoC, jeśli chodzi o ten timer. W zdarzeniu OnTimer wystarczy taki kod:

var
  CursorPosition: TPoint;
begin
  CursorPosition := ScreenToClient(Mouse.CursorPos);

  if PtInRect(ClientRect, CursorPosition) then
    StatusBar1.Panels[0].Text := Format('X: %d, Y: %d', [CursorPosition.X, CursorPosition.Y])
  else
    StatusBar1.Panels[0].Text := 'outside form';
end;

Jeśli kursor znajduje się nad obszarem roboczym okna, to w pasku stanu pojawią się koordynaty. W przeciwnym razie pojawi się napis, że jest poza oknem. Aby wyświetlać pozycję kursora tylko wtedy, gdy ten znajduje się konkretnie nad oknem (a nie nad innym, przesłaniającym nasze), można skorzystać z funkcji GetForegroundWindow i porównać uchwyty:

var
  CursorPosition: TPoint;
begin
  CursorPosition := ScreenToClient(Mouse.CursorPos);

  if (GetForegroundWindow() = Handle) and PtInRect(ClientRect, CursorPosition) then
    StatusBar1.Panels[0].Text := Format('X: %d, Y: %d', [CursorPosition.X, CursorPosition.Y])
  else
    StatusBar1.Panels[0].Text := 'outside form';
end;

Funkcja ta jest o tyle dobra, że zwraca uchwyt okna, nawet jeśli kursor znajduje się nad przyciskiem lub innym komponentem posiadającym uchwyt. Dzięki temu nie trzeba cudować z iterowaniem i szukaniem rodzica (aby dostać się do referencji formularza).

Można też dodać więcej zabezpieczeń, w zależności od wymagań. W przypadku projektu wieloplatformowego, funkcję GetForegroundWindow należy zamieć na jakąś metodę z którejś tam klasy z bilioteki standardowej/komponentów. Bez szczegółów – nie wiem co tam w Delphi macie.

PoC w załączniku – plik wykonywalny plus źródła dla Lazarusa.

3

Ach, jeszcze jedno bym dodał – później połączę te trzy swoje posty. ;)

Jeśli budujesz wizualny komponent, który ma coś wykonywać podczas gdy porusza się nad nim kursor, to sugeruję całą logikę dotyczącą aktualizowania paska stanu umieścić wewnątrz klasy komponentu. Bo timer i temu podobne cuda to obejścia problemu, a nie sensowne rozwiązania.

Zrób klasę komponentu i dodaj do niej właściwość typu TStatusBar – to pozwoli w Inspektorze Obiektów przekazać referencję paska stanu (i ją przechować w odpowiednim polu), dzięki czemu nie będziesz musiał logiki aktualizacji paska implementować w klasie formularza. Przyda się też właściwość określająca indeks panelu na pasku stanu, w którym mają być wyświetlane koordynaty. Ostatnie co pozostaje w nadpisać chronioną metodę WMMouseMove, w której oprócz inherited zaktualizuj treść panelu – uprzednio sprawdzając, czy statusbar jest przypisany i czy posiada panel o zadanym indeksie. Ewentualnie dodaj sobie jeszcze właściwość jako ciąg znaków z formatem wyświetlania współrzędnych (możesz użyć konstrukcji znanej z funkcji Format).

Dobrze by było zamiast referencji całego paska stanu oraz indeksu docelowego panelu, zadeklarować po prostu właściwość typu TStatusPanel, coby uprościć całość. Ale nie wiem czy Inspektor Obiektów będzie widział tego typu obiekty i pozwoli je przypisać. Nie testowałem czegoś takiego.

1 użytkowników online, w tym zalogowanych: 0, gości: 1