Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: 'Złośliwiec'
Biblioteki C++

Obsługa podwójnego kliknięcia

[lekcja] Artykuł opisuje jak należy poprawnie obsługiwać komunikat podwójnego kliknięcia w aplikacjach okienkowych systemu Windows.
Podwójne kliknięcie myszą jako osobne zdarzenie (tj. niezwiązane ze zwykłym, pojedynczym kliknięciem) jest przydatnym rozszerzeniem możliwości UI, ale potrafi narobić kłopotów. Wbrew ewentualnym pozorom, nie chodzi o systemowe ustawienie szybkości podwójnego kliknięcia.

Spróbujmy wypisać w okienku jakiś tekst, by sprawdzić, czy podwójne kliknięcie jest wykrywane. Do procedury okna dodajemy następujący kod:
C/C++
case WM_LBUTTONDBLCLK:
OutputDebugString( "WM_LBUTTONDBLCLK\n" );
break;

case WM_LBUTTONDOWN:
OutputDebugString( "WM_LBUTTONDOWN\n" );
break;

case WM_LBUTTONUP:
OutputDebugString( "WM_LBUTTONUP\n" );
break;
Pierwszy case od góry powinien przechwycić komunikat o podwójnym kliknięciu lewym przyciskiem (jest też możliwe wykrycie podwójnego kliknięcia prawym czy środkowym przyciskiem, a także innymi, jeśli nasza mysz je posiada, ale raczej rzadko się z tej możliwości korzysta w praktyce). Pozostałe dwa obsługują elementy "zwykłego" kliknięcia: wciśnięcie przycisku myszy i puszczenie go. Jeśli uruchomimy nasz program i wykonamy podwójne kliknięcie na oknie, efekty mogą być różne (w zależności od tego, co mamy w reszcie naszego kodu). Całkiem możliwe, że w okienku "Output" dostaniemy coś takiego:
WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDOWN
WM_LBUTTONUP
Wygląda na to, że podwójne kliknięcie nie zostało wykryte. Może wykonaliśmy je po prostu zbyt wolno i system zaklasyfikował to jako dwa pojedyncze kliknięcia. Ale poza tym trzeba wiedzieć, że wykrywanie podwójnych kliknięć trzeba najpierw włączyć, ustawiając odpowiednią flagę podczas rejestrowania klasy okna:
C/C++
wcex.style = CS_DBLCLKS; // wykrywanie double-clicków

// ...

RegisterClassEx( & wcex );
Po sprawdzeniu, czy mamy powyższą flagę i ewentualnym jej ustawieniu uruchamiamy program jeszcze raz i znów robimy podwójne kliknięcie:

WM_LBUTTONDOWN
WM_LBUTTONUP
WM_LBUTTONDBLCLK
WM_LBUTTONUP
Ewidentnie lepiej, ale pojawił się, jak widać, dodatkowy problem. Co prawda podwójne kliknięcie tym razem zostało prawidłowo wykryte, a komunikat WM_LBUTTONDBLCLK dotarł do naszej procedury okna, ale oprócz niego dotarł również WM_LBUTTONDOWN (ale teraz tylko jeden!) i dwa razy WM_LBUTTONUP. Dzięki wypisaniu obsłużonych komunikatów możemy precyzyjnie ustalić, co zaszło:

# Wcisnęliśmy przycisk myszy, okno dostało komunikat WM_LBUTTONDOWN
# Puściliśmy przycisk, okno dostało WM_LBUTTONUP
# Wcisnęliśmy lewy przycisk ponownie i w tym dopiero momencie system zorientował się, że tak naprawdę robimy właśnie podwójne kliknięcie i wysłał WM_LBUTTONDBLCLK
# Puściliśmy przycisk po raz drugi, okno dostało WM_LBUTTONUP

Wynikają stąd dwa wnioski. Po pierwsze, system nie wie, że wykonujemy podwójne kliknięcie aż do momentu, gdy po raz drugi wciskamy przycisk myszy. Dopóki to nie nastąpi, nasze działania są interpretowane jako "zwykłe", pojedyncze kliknięcia. Po drugie, z całego drugiego kliknięcia tylko jego pierwsza część (wciśnięcie przycisku) wchodzi w skład tego, co dla systemu jest podwójnym kliknięciem – puszczenie przycisku jest już interpretowane jako osobne zdarzenie. Dlatego też komunikat WM_LBUTTONDBLCLK dociera do okna jeszcze zanim puścimy przycisk myszy.

Mając tę wiedzę jesteśmy w stanie obsługiwać jednocześnie pojedyncze i podwójne kliknięcia w naszym programie. Jednak w takiej sytuacji pozostaje jeden problem: gdy już dostaniemy WM_LBUTTONDOWN, skąd mamy wiedzieć, czy użytkownik za chwilę nie wciśnie przycisku myszy jeszcze raz, co spowoduje wysłanie WM_LBUTTONDBLCLK?

Oczywiście, przyszłości nie możemy przewidzieć i nie ma żadnej sprytnej funkcji WinAPI, która to dla nas zrobi ;-). Możemy jednak zrobić coś innego, mianowicie odczekać odpowiednio długo i jeśli w zadanym czasie nie nadejdzie kolejne wciśnięcie przycisku, możemy spokojnie wykonać akcję, którą przewidzieliśmy wyłącznie dla pojedynczego kliknięcia. Sztuczka ta powinna sprawdzić się całkiem nieźle pod warunkiem, że znamy czas, jaki musimy odczekać. Czas ten, czyli maksymalny odstęp między dwoma kliknięciami, w jakim te kliknięcia mogą być przez system uznane za jedno podwójne kliknięcie, możemy pobrać następująco:
C/C++
UINT dblClickTime = GetDoubleClickTime();
Czas ten, typowo dla WinAPI, jest wyrażony w milisekundach. To jednak nie wszystko, bo kursor myszy musi znajdować się mniej więcej w tym samym miejscu podczas obu "kliknięć składowych". To "mniej więcej" jest właściwie dokładnie określone i możemy tę wartość pobrać poprzez funkcję GetSystemMetrics:
C/C++
int xDblClick = GetSystemMetrics( SM_CXDOUBLECLK );
int yDblClick = GetSystemMetrics( SM_CYDOUBLECLK );
Tak więc jeśli chcemy się dowiedzieć, czy użytkownik kliknął podwójnie, czy tylko pojedynczo, musimy zapamiętać pozycję myszy, na której nastąpiło pierwsze kliknięcie, odczekać dblClickTime milisekund (np. wykorzystując funkcję SetTimer), a następnie jeśli użytkownik wcisnął lewy przycisk ponownie zanim upłynął czas dblClickTime – sprawdzić, czy współrzędne myszy przy drugim kliknięciu nie są oddalone o więcej, niż xDblClick w pionie i yDblClick w poziomie od miejsca pierwszego kliknięcia. Jeśli te warunki są spełnione, to znaczy, że musimy obsłużyć WM_LBUTTONDBLCLK, a ten drugi WM_LBUTTONDOWN (oraz ewentualnie pierwszy, to już zależy od specyfiki naszego programu) zignorować.

Jak widać, sprawa z podwójnymi kliknięciami nie jest wcale taka banalna, jak by się mogło wydawać. Teraz jednak dysponujemy, mam nadzieję, wystarczającą wiedzą, by bez problemu implementować podwójne kliknięcie w naszych programach.
Poprzedni dokument Następny dokument
Kolory w konsoli Okna o nieregularnym kształcie