Design Problem mit MouseHook
-
Hallo,
ich implementiere gerade ein Programm, das Mausgesten erkennen soll, was ja an sich nicht schwierig ist. Um die Maus global abzufragen, benutze ich jedoch eine MouseHook Dll, was an sich auch ganz gut funktioniert.
Nun soll man bei gedrückter rechter Maustaste Gesten zeichnen können, die dann bei Loslassen der Taste erkannt und ausgeführt werden. Auch das habe ich halbwegs realisieren können:
Sobald ein WM_RBUTTONDOWN oder WM_NCRBUTTONDOWN kommt, wird eine flag gesetzt, sodass die MouseProc beim nächsten call weiss, dass sie an das Gesten App eine Message schicken muss. Bei WM_RBUTTONUP / WM_NCRBUTTONUP wird dann eine andre message geschickt und die flag wieder entfernt.
In der Theorie funktioniert das zwar, aber in der Praxis gibt es zwei Dinge die nicht funktionieren:
Ich weiss am Anfang nicht, ob ich WM_RBUTTONDOWN durchlassen darf, oder nicht, weil es ja ein Rechtsklick werden könnte, oder eben eine Geste, sofern die Maus zwischenzeitlich bewegt wird. Also bleibt nur, die Nachricht immer zu unterdrücken und bei einem evtl. Rechtsklick nochmal vornweg zu senden.
Das ist nicht nur ziemlich umständlich sondern auch sicher keineswegs so gedacht.
Das zweite Problem besteht darin, wenn ich die Geste auf dem Desktop z.b. beginne, dann wird erst RBUTTONDOWN an den desktop gesendet, und wenn ich dann die Maus auf ein andres Fenster bewege und dort loslasse, dann kommt keine RBUTTONUP, weil in diesem Fenster der RBUTTON nie down war.Ich habe versucht, das über GetMsgProc zu lösen und dann nur die Mouse Messages zu filtern, die mich interessieren, und ich hab diverse Workarounds im Mouse Hook versucht, die alle gewisse Schwachstellen hatten.
Meine Frage wäre jetzt folgende: Gibt es etwas anderes als diese beiden Hooks bzw. irgendwelche weiteren Ideen, wie man mit diesen Hooks die zwei genannten Probleme umgehen kann ?
( Und es muss so etwas geben, andere Programme machen das auch mit diesem Mouse Hook )Danke für euer Bemühen!
-m
-
Da bis jetzt keine Antworten gekommen sind, hier noch ein paar Details:
Die 3 Nachrichten von Interesse sind RBUTTONDOWN, MOUSEMOVE und RBUTTONUP.
Der halbverbale Algo würde jetzt so aussehen:
wenn nachricht RBUTTONDOWN: wenn das eine geste wird schicke nachricht an gesten programm fange nachricht ab. sonst wenn das rechtsklick wird lasse nachricht durch sonst wenn nachricht MOUSEMOVE: schicke nachricht an gesten programm fange nachricht ab sonst wenn nachricht RBUTTONUP: wenn mindestens einmal MOUSEMOVE war schicke nachricht an gesten programm fange nachricht ab sonst lasse nachricht durch sonst: lasse nachricht durchDas Problem besteht also einerseits darin, am Anfang schon zu wissen, ob ein Rechtsklick
oder eine Geste gemacht werden wird.
Das andere Problem betrifft den Fakt, dass RBUTTONUP nicht kommt, wenn vorher im gleichen
Fenster kein RBUTTONDOWN war. Wenn also der Cursor auf ein anderes Fenster bewegt wurde,
und dort die Maustaste losgelassen wird, dann gibt es kein RBUTTONUP und die geste wird ewig weitergezeichnet.Gibt es nun wirklich niemanden, der eine Idee hat, diese zwei Probleme, wenn auch nicht mit Hooks, auf irgendeine Art zu umgehen?
-
Nun habe ich das ganze über einen WH_MOUSE_LL (low level hook) gelöst.
Dieser löst das Fenster Problem. Über eine flag wird jetzt auch bei bedarf der buttondown gesendet bevor button up kommt, also für den rechtsklick.Nun ist nur noch ein Problem übrig: Wenn ich einen Rechtsklick in eine ListBox oder auf ein File im Explorer mache, ohne vorher mit linksklick das Objekt auszuwählen, dann kommt das kontext menü des controls, statt dass die datei gewählt wird und das kontext menü von ihr aufgeht.
Kann sich das jemand erklären?
Hier mal der Source von meiner LL MouseProc (Hab den Funktionsnamen aus Faulheit nicht geändert)
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) { // MSDN says: "Return if nCode < 0" if (nCode < 0) return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam); if (nCode == HC_ACTION) { if ( ((wParam == WM_RBUTTONDOWN)||(wParam == WM_NCRBUTTONDOWN)) && !rbdown) { MSLLHOOKSTRUCT *mhs = (MSLLHOOKSTRUCT*)lParam; int x = mhs->pt.x; int y = mhs->pt.y; firstx = x; firsty = y; rbdown = true; showContextMenu = false; // Gesture starts PostMessage(g_gestures, PM_GESTURE_BEGIN, 0, MAKELPARAM(mhs->pt.x, mhs->pt.y)); return 1; } // Gesture was already initiated, rb is down else if ( ((wParam == WM_MOUSEMOVE)||(wParam == WM_NCMOUSEMOVE)) && rbdown ) { MSLLHOOKSTRUCT *mhs = (MSLLHOOKSTRUCT*)lParam; //Post new position PostMessage(g_gestures, PM_GESTURE_CHANGE, 0, MAKELPARAM(mhs->pt.x, mhs->pt.y)); return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam); //return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam); } // Gesture ends / right click is finished else if ( (wParam == WM_RBUTTONUP)||(wParam == WM_NCRBUTTONUP) ) { MSLLHOOKSTRUCT *mhs = (MSLLHOOKSTRUCT*)lParam; int x = mhs->pt.x; int y = mhs->pt.y; if ( (abs(firstx-x) > 5) || (abs(firsty-y) > 5)) { showContextMenu = false; } else { showContextMenu = true; } rbdown = false; POINT WindowPos; WindowPos.x = x; WindowPos.y = y; if (showContextMenu) //Mouse wasn't moved { SetForegroundWindow(WindowFromPoint(WindowPos)); if ((wParam == WM_RBUTTONUP)) { PostMessage(g_gestures, PM_GESTURE_DISCARD, 0, MAKELPARAM(mhs->pt.x, mhs->pt.y)); SendMessage(GetForegroundWindow(), WM_RBUTTONDOWN, wParam, MAKELPARAM(mhs->pt.x, mhs->pt.y)); } else // NCRBUTTONUP { PostMessage(g_gestures, PM_GESTURE_DISCARD, 0, MAKELPARAM(mhs->pt.x, mhs->pt.y)); SendMessage(GetForegroundWindow(), WM_NCRBUTTONDOWN, wParam, MAKELPARAM(mhs->pt.x, mhs->pt.y)); } return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam); } else { PostMessage(g_gestures, PM_GESTURE_END, 0, MAKELPARAM(mhs->pt.x, mhs->pt.y)); return 1; // Keep other windows from handling this event } } else if ( ((wParam == WM_MOUSEMOVE)||(wParam == WM_NCMOUSEMOVE)) && rbdown ) { MSLLHOOKSTRUCT *mhs = (MSLLHOOKSTRUCT*)lParam; //Post new position PostMessage(g_gestures, PM_GESTURE_CHANGE, 0, MAKELPARAM(mhs->pt.x, mhs->pt.y)); return 1; //return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam); } else // non of the messages being of interest for us { return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam); } } else // nCode < 0 { return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam); } }
-
Nun habe ich es auch so geschafft,
für die nächste arme Seele:Man sollte den Hook kurzzeitig deaktivieren, um dann mit mouse_event ein rbuttondown zu simulieren, bevor man den hook weiterleitet. So lösen sich alle Probleme von selbst.
Hier nochmal der (nicht ganz optimale, aber funktionierende) code:
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) { // MSDN says: "Return if nCode < 0" if (nCode < 0) return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam); if (nCode == HC_ACTION) { if ( ((wParam == WM_RBUTTONDOWN)||(wParam == WM_NCRBUTTONDOWN)) && !rbdown) { MSLLHOOKSTRUCT *mhs = (MSLLHOOKSTRUCT*)lParam; int x = mhs->pt.x; int y = mhs->pt.y; firstx = x; firsty = y; rbdown = true; showContextMenu = false; // Gesture starts PostMessage(g_gestures, PM_GESTURE_BEGIN, 0, MAKELPARAM(mhs->pt.x, mhs->pt.y)); return 1; } // Gesture was already initiated, rb is down else if ( ((wParam == WM_MOUSEMOVE)||(wParam == WM_NCMOUSEMOVE)) && rbdown ) { MSLLHOOKSTRUCT *mhs = (MSLLHOOKSTRUCT*)lParam; //Post new position PostMessage(g_gestures, PM_GESTURE_CHANGE, 0, MAKELPARAM(mhs->pt.x, mhs->pt.y)); return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam); //return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam); } // Gesture ends / right click is finished else if ( (wParam == WM_RBUTTONUP)||(wParam == WM_NCRBUTTONUP) ) { MSLLHOOKSTRUCT *mhs = (MSLLHOOKSTRUCT*)lParam; int x = mhs->pt.x; int y = mhs->pt.y; if ( (abs(firstx-x) > 5) || (abs(firsty-y) > 5)) { showContextMenu = false; } else { showContextMenu = true; } POINT WindowPos; WindowPos.x = x; WindowPos.y = y; if (showContextMenu) //Mouse wasn't moved { SetForegroundWindow(WindowFromPoint(WindowPos)); if ((wParam == WM_RBUTTONUP)) { PostMessage(g_gestures, PM_GESTURE_DISCARD, 0, MAKELPARAM(mhs->pt.x, mhs->pt.y)); UninstallHook(); mouse_event(MOUSEEVENTF_RIGHTDOWN, mhs->pt.x, mhs->pt.y, 0, mhs->dwExtraInfo); InstallHook(); //SendMessage(GetForegroundWindow(), WM_RBUTTONDOWN, wParam, MAKELPARAM(mhs->pt.x, mhs->pt.y)); } else // NCRBUTTONUP { PostMessage(g_gestures, PM_GESTURE_DISCARD, 0, MAKELPARAM(mhs->pt.x, mhs->pt.y)); UninstallHook(); mouse_event(MOUSEEVENTF_RIGHTDOWN, mhs->pt.x, mhs->pt.y, 0, mhs->dwExtraInfo); InstallHook(); //SendMessage(GetForegroundWindow(), WM_NCRBUTTONDOWN, wParam, MAKELPARAM(mhs->pt.x, mhs->pt.y)); } rbdown = false; return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam); } else { PostMessage(g_gestures, PM_GESTURE_END, 0, MAKELPARAM(mhs->pt.x, mhs->pt.y)); rbdown = false; return 1; // Keep other windows from handling this event } } else if ( ((wParam == WM_MOUSEMOVE)||(wParam == WM_NCMOUSEMOVE)) && rbdown ) { MSLLHOOKSTRUCT *mhs = (MSLLHOOKSTRUCT*)lParam; //Post new position PostMessage(g_gestures, PM_GESTURE_CHANGE, 0, MAKELPARAM(mhs->pt.x, mhs->pt.y)); return 1; //return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam); } else // non of the messages being of interest for us { return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam); } } else // nCode < 0 { return CallNextHookEx(g_hMouseHook, nCode, wParam, lParam); } }Danke für eure vielen Antworten!