Komponenty - gubienie komunikatów CM_MOUSELEAVE

0

Pisałem już wielokrotnie pod Delphi7 i Lazarusem różne komponenty, które standardowo nie posiadają zdarzeń OnMouseEnter i OnMouseLeave; Dodać obsługę komunikatów CM_MOUSEENTER i CM_MOUSELEAVE nie problem, są one przechwytywane i obsługiwane; Jednak za każdym razem jest taka sama sytuacja - problem pojawia się z komunikatem CM_MOUSELEAVE w momencie, gdy komponent jest duży, ustawimy nad nim kursor, a następnie bardzo szybko przesuwamy kursor poza komponent i formularz - wtedy komponent komunikatu nie dostaje;

Z obecnymi komponentami kombinowałem, ale nie udało mi się rozwiązać problemu; Napisałem więc malutki komponent do testów, dziedziczący z klasy TGraphicControl; Jego kod poniżej:

{$mode objfpc}{$H+}

interface

uses
  LMessages, Classes, Controls, Graphics;

type
  TMouseTest = class(TGraphicControl)
  private
    FHover: Boolean;
  protected
    procedure CMMouseEnter(var AMessage: TLMessage); message CM_MOUSEENTER;
    procedure CMMouseLeave(var AMessage: TLMessage); message CM_MOUSELEAVE;
  protected
    procedure Paint(); override;
  public
    constructor Create(AOwner: TComponent); override;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Misc', [TMouseTest]);
end;

constructor TMouseTest.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Parent := AOwner as TWinControl;

  FHover := False;
end;

procedure TMouseTest.CMMouseEnter(var AMessage: TLMessage);
begin
  inherited;

  FHover := True;
  inherited Invalidate();
end;

procedure TMouseTest.CMMouseLeave(var AMessage: TLMessage);
begin
  inherited;

  FHover := False;
  inherited Invalidate();
end;

procedure TMouseTest.Paint();
const
  FRAME_COLORS: array [Boolean] of TColor = (clGray, clMenuHighlight);
  BACKG_COLORS: array [Boolean] of TColor = (clLtGray, clWhite);
begin
  with Self.Canvas do
  begin
    Pen.Color := FRAME_COLORS[FHover];
    Brush.Color := BACKG_COLORS[FHover];

    Rectangle(0, 0, Self.Width, Self.Height);
  end;
end;

end.

Jak widać nic skomplikowanego - szary prostokąt, po najechaniu kursorem zmienia się na biały z kolorową ramką; Jeśli komponent jest mały i/lub daleko od krawędzi formularza, wszystko działa dobrze; Natomiast jeśli jest duży i znajduje się blisko krawędzi formularza, szybki ruch kursorem wzdłuż komponentu aż poza formularz, nie spowoduje jego deaktywacji (nie zmieni kolorów z powrotem na szare); Komponent dostaje ten komunikat dopiero po ponownym najechaniu kursorem na formularz, czyli nieco za późno;

Standardowe komponenty nie mają takich problemów - bez względu na to jak są duże i jak szybko ruszam kursorem, zawsze aktywują się i deaktywują prawidłowo; Niestety w ich źródłach nie znalazłem nic pomocnego;

Wie ktoś co jest przyczyną gubienia wspomnianego komunikatu i jak temu zaradzić? Będę wdzięczny za wszelką pomoc, bo już nie mam do tego nerwów;

W załączniku dorzucam kod komponentu, paczkę z tym komponentem do instalacji oraz aplikację testową (TestApp\project.exe) - można sprawdzić o co się rozchodzi.

0

To jest problem z samym Windows i obawiam się że w normalny sposób nic nie wykombinujesz. Jedyna rzecz jaka przychodzi mi na myśl to w Timerze sprawdzać czy kursor nadal jest nad kontrolką. Wiem że nie jest to zbyt eleganckie ale to chyba jedyna możliwość.

0

Albo nie wiem o co chodzi albo u mnie exek działa prawidłowo. Nie zerkałem w kod, ale exek działa zgodnie z założeniami. Sprawdzałeś aplikację testową spoza IDE?

2

zobacz to bo nie mam fpc a nie chce mi się na Delphi zmieniać :p http://stackoverflow.com/questions/3176977/how-to-detect-when-the-mouse-move-away-from-a-tpanel-in-delphi-6

a wiąże się to z tym, że CM_MOUSELEAVE jest generowane przez aplikację a nie system i jeśli szybko "przejedziesz" myszką nad formą to aplikacja nie rejestruje tego faktu

1

@abrakadaber obawiam sie że to rozwiązanie nie przejdzie bo ta kontrolka nie ma uchwytu wiec to rozwiązanie odpada. Wiem że aby odbierać komunikaty można użyć funkcji AllocateHWnd ale to też odpada bo to tworzy dodatkowe ukryte okno które jest nieczułe na komunikaty myszki. Aby wywołać TrackMouseEvent potrzebny jest uchwyt którego nie ma.
EDIT:
Z ciekawości sprawdziłem jednak zadziała gdy poda się uchwyt rodzica. Wystarczy do komponentu dodać:

procedure TMouseTest.MouseMove(Shift: TShiftState; X, Y: Integer);
var
  mEvnt: TTrackMouseEvent;
begin
  inherited;
  if FHover then begin
    mEvnt.cbSize := SizeOf(mEvnt);
    mEvnt.dwFlags := TME_LEAVE;
    mEvnt.hwndTrack := Self.Parent.Handle;
    TrackMouseEvent(mEvnt);
  end;
end;
0
kAzek napisał(a)

Jedyna rzecz jaka przychodzi mi na myśl to w Timerze sprawdzać czy kursor nadal jest nad kontrolką. Wiem że nie jest to zbyt eleganckie ale to chyba jedyna możliwość.

Znam różne sposoby na obejście tego problemu, ale w dalszym ciągu to są obejścia, a nie rozwiązanie problemu;

Swoją drogą komponenty i tak korzystają z TTimer; Każdy z nich go posiada, albowiem zaimplementowany jest pewny ficzer, który zapobiega miganiu interfejsu; Komunikat CM_MOUSEENTER odpala timer z interwałem 50ms, jeśli po tych 50ms kursor nadal znajduje się nad komponentem, wykonywana jest akcja najechania (np. podświetlenie przycisku czy linku w etykiecie); Dzięki temu szybkie ruchy kursora po oknie nie powodują częstego przemalowywania komponentów i interfejs nie miga jak choinka;

Te timery mogę wykorzystać do śledzenia kursora, ale to ostateczność, później napiszę dlaczego;

danny napisał(a)

Albo nie wiem o co chodzi albo u mnie exek działa prawidłowo.

Spróbuj naprawdę szybko przesunąć kursor znad prawej części komponentu w lewą stronę, poza formularz;

Sprawdzałeś aplikację testową spoza IDE?

W sumie to komponenty testuję głównie poza IDE i tak też sprawdzałem ten komponent - kicha :]

abrakadaber napisał(a)

a wiąże się to z tym, że CM_MOUSELEAVE jest generowane przez aplikację a nie system i jeśli szybko "przejedziesz" myszką nad formą to aplikacja nie rejestruje tego faktu

O widzisz, tego nie wiedziałem - dzięki za informację;


@kAzek - którą masz wersję Lazarusa/FPC? U mnie 1.2.6/2.6.4 - trzymam się tej wersji, bo nowsze sprawiały kupę problemów (sporo bugów się objawiało, przede wszystkim z debugerem);

To też sprawdzałem - dość długo wertowałem Google w poszukiwaniu rozwiązania; Nadal szybki ruch kursora poza formularz nie powoduje deaktywacji komponentu;

Obecny kod komponentu:

unit MouseTest;

{$mode objfpc}{$H+}

interface

uses
  Windows, LMessages, Classes, Controls, Graphics;

type
  TMouseTest = class(TGraphicControl)
  private
    FHover: Boolean;
  protected
    procedure CMMouseEnter(var AMessage: TLMessage); message CM_MOUSEENTER;
    procedure CMMouseLeave(var AMessage: TLMessage); message CM_MOUSELEAVE;
  protected
    procedure MouseMove(AShift: TShiftState; AX, AY: Integer); override;
  protected
    procedure Paint(); override;
  public
    constructor Create(AOwner: TComponent); override;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Misc', [TMouseTest]);
end;

constructor TMouseTest.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  Parent := AOwner as TWinControl;

  FHover := False;
end;

procedure TMouseTest.CMMouseEnter(var AMessage: TLMessage);
begin
  inherited CMMouseEnter(AMessage);

  FHover := True;
  inherited Invalidate();
end;

procedure TMouseTest.CMMouseLeave(var AMessage: TLMessage);
begin
  inherited CMMouseLeave(AMessage);

  FHover := False;
  inherited Invalidate();
end;

procedure TMouseTest.MouseMove(AShift: TShiftState; AX, AY: Integer);
var
  tmeMouse: TTrackMouseEvent;
begin
  inherited MouseMove(AShift, AX, AY);

  if FHover then
  begin
    tmeMouse.cbSize := SizeOf(tmeMouse);
    tmeMouse.dwFlags := TME_LEAVE;
    tmeMouse.hwndTrack := Self.Parent.Handle;

    TrackMouseEvent(tmeMouse);
  end;
end;

procedure TMouseTest.Paint();
const
  FRAME_COLORS: array [Boolean] of TColor = (clGray, clMenuHighlight);
  BACKG_COLORS: array [Boolean] of TColor = (clLtGray, clWhite);
begin
  with Self.Canvas do
  begin
    Pen.Color := FRAME_COLORS[FHover];
    Brush.Color := BACKG_COLORS[FHover];

    Rectangle(0, 0, Self.Width, Self.Height);
  end;
end;

end.

W załączniku podaję znów pełne źródła oraz aplikację testową, razem z exe;

PS: Komponent łatwo przeportować pod Delphi - wystarczy zamienić moduł LMessages na Messages oraz typy parametrów z TLMessage na TMessage; Reszta jest taka sama.

1

Sprawdź czy formatka przy takim ruchu dostaje OnMouseLeave, jeżeli tak ... to już wiesz co robić.
Jeżeli zaś nie - to już wiesz że nic z tym nie zrobisz.

2

Nie mam Lazarusa sprawdziłem na Delphi 7 i było ok nawet nie chciało mi się wierzyć bo podałem uchwyt rodzica i dziwnie działało to wykomentowałem kod i przestało później z powrotem skompilowałem i znowu działało tak że u mnie to pomaga. Twój EXE rzeczywiście u mnie nie działa... ciekawe jak u Ciebie mój z Delphi 7. Kod nie różni się niczym tylko tyle że nie chciało mi się rejestrować komponentu to tworzyłem go dynamicznie.

0

@_13th_Dragon: No niestety - formularz ma już zdarzenie OnMouseLeave, jednak przy szybkim ruchu ono także nie zostaje zawsze wywołane;

@kAzek: Twój działa bez problemu - nie udało mi się spowodować błędu... o.O


Czyli na to wychodzi, że VCL działa nieco inaczej niż LCL i w tym drugim czegoś brakuje; Tylko czego? Trzeba by teraz wertować kody tych bibliotek i znaleźć różnice; Ale najpierw zaktualizuję IDE - może już pozbyli się ostatnich bugów.

0

Więc na tym właśnie polega problem, skoro formatka nie łapie OnMouseLeave to jak może tą informacje przekazać kontrolkom.
Szukaj czemu formatka nie łapie.

0

No i kicha, w najnowszej wersji nic się nie zmieniło, więc pozostaje jedynie zgłosić bug i albo czekać na odzew i poprawkę, albo samemu znaleźć przyczynę i dorobić sobie brakujący kod; Coś mi się wydaje, że znalezienie przyczyny zajmie mi bardzo dużo czasu...

PS: Rozwiązanie podał @kAzek, więc jego post akceptuję; Pozostaje tylko poprawić LCL.

0

Odgrzeję jeszcze ten wątek;

Do tej pory nie udało mi się znaleźć błędu (za dużo nieznanego mi kodu, do tego różne formatowanie...), więc póki co zaimplementowałem sobie w klasie bazowej czujkę w postaci obiektu TTimer, która co 200ms sprawdza czy kursor znajduje się nad komponentem i jeśli nie - wywołuje wirtualną metodę deaktywacji komponentu (głównie do resetowania danych i przemalowania na postać domyślną); Kod odpowiedzialny za tę czujkę opatrzony jest dyrektywą $IFDEF, dzięki czemu po poprawieniu błędu w LCL, odpowiedni symbol wystarczy zaremować;

Muszę w końcu zgłosić ten bug, choć ciągle nie mogę się zalogować w bugtrackerze... No i wywaliłem Lazarusa 1.4.X, bo nie dałem rady debugować komponentów - wróciłem do wersji 1.2.6.

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