Introduction
Since one of my applications I wrote recently needed a way to detect left mouse button double clicks on the desktop, I accepted this challenge and started to investigate. What I found on the internet and by reading the usual docs was a heap of information that I finally got sorted out, and so I'm now able to present an easy step-by-step guide for those who are about to set out on the same quest. Fear no more!
Background
Installing and using a hook to monitor messages from all around the system sounded quite complicated to me, but in the end, it (naturally) turned out to be plain simple. It was all a matter of putting the right pieces together, so to help you spare your valuable time, I present all those pieces you'll need to accomplish your task.
Please be aware, I'll present some code fragments you'll probably won't need in the first place; I included them anyway to show certain techniques. The majority of the code came out of my head, but some snippets presented here I found on the internet (or elsewhere). It's not my intention to pass them along as my own ideas, so, if you see anything that came out of your head, drop me a line and I'll throw in appropriate remarks.
Using the Code
Since I wanted to avoid code injection using a DLL, I decided to install a system wide hook that would not only deliver Desktop
messages, but events from other applications too. This may seem like an overkill, but getting the Desktop
sorted out is easy, once you have the HWND
to its corresponding ListView
. And here's how to obtain this valuable handle:
HWND g_hFolderView;
BOOL CALLBACK FindDLV(HWND hWndPM, LPARAM lParam)
{
HWND hWnd = FindWindowEx(hWndPM, NULL, _T("SHELLDLL_DefView"), NULL);
if(hWnd)
{
HWND *phWnd = (HWND *)lParam;
*phWnd = hWnd;
return false;
}
return true;
}
HWND FindDesktopListView()
{
HWND hWndPM = FindWindowEx(NULL, NULL, _T("Progman"), NULL);
if(!hWndPM)
return NULL;
HWND hWnd = FindWindowEx(hWndPM, NULL, _T("SHELLDLL_DefView"), NULL);
if(!hWnd)
{
EnumWindows(FindDLV, LPARAM((HWND *)&hWnd));
if(!hWnd)
return NULL;
}
HWND hWndLV = FindWindowEx(hWnd, NULL, _T("SysListView32"), NULL);
return hWndLV;
}
If all goes well, a call to FindDesktopListView()
at the beginning of your application should return the HWND
corresponding to the Desktop
's ListView
. It's likely you'll use this handle more than once, so just declare a global variable (like g_hFolderView
in my case) representing the HWND
.
The next thing to do is to install our hook, which is simple:
HHOOK g_hDesktopHook; if((g_hDesktopHook = SetWindowsHookEx(WH_MOUSE_LL, OnDTMouseEvent, NULL, 0)) == NULL)
{
}
We're using WH_MOUSE_LL
, so we don't have to use an additional DLL for the callback function, etc. But there's a disadvantage: a (left) mouse button double click will never be reported at this low level, simply because the system doesn't even know what a double-click is down here. But we'll deal with that in our callback function:
DWORD g_tcLastLeftButtonClickTime = 0;
LRESULT CALLBACK OnDTMouseEvent(int nCode, WPARAM wParam, LPARAM lParam)
{
if(nCode < 0)
return CallNextHookEx(g_hDesktopHook, nCode, wParam, lParam);
if(nCode == HC_ACTION)
{
if(wParam == WM_LBUTTONDOWN)
{
if(GetTickCount() < g_tcLastLeftButtonClickTime + GetDoubleClickTime())
{
if(WindowFromPoint(((MSLLHOOKSTRUCT *)lParam)->pt) == g_hFolderView)
{
}
}
else
g_tcLastLeftButtonClickTime = GetTickCount();
}
}
return CallNextHookEx(g_hDesktopHook, nCode, wParam, lParam);
}
And that's all there is to it! What the callback function does is check for (mouse) events and filter out left mouse button presses. As I mentioned above, we will never receive WM_LMBUTTONDBLCLK
style events at this low level, so we have to roll our own.
As you can see, the function time stamps any left mouse button press and checks if the next click occurs within the system wide double click time, which can be adjusted using the mouse control panel. This is a reliable and convenient way to check for a double click, without using any additional timer functions, etc.
Finally, if the callback function decides a double click has occurred, it uses our g_hFolderView
handle to check whether the event occurred on the desktop. (In my application, I also ensure the mouse does not hover above any desktop icon before I further process the event. This is easy when you use the ListView_GetHotItem()
macro.)
And since we do things well, do not forget to unhook everything when your application exits; you can add the following to your OnDestroy()
handler:
if(g_hDesktopHook)
UnhookWindowsHookEx(g_hDesktopHook);
This will release the hook and free all used resources, etc.
Final Words
I spent quite some time to gather all the information and putting the code together since I couldn't find any article presenting an example like this. So I hope it'll save you some time!
History
- 28th April, 2011: Initial version