Filed under Development
I was one of those lucky guys who got the Windows 7 virtual machine at the PDC for the first time and I already liked. When the new beta was released I decided to install it on my old laptop and I love it, I really mean it. I think it's great, there are a lot of small features that you immediately like with almost not noticing them. One of them is the cool docking feature that allows you drag a window over the limits of the screen to change it's size and position.
When I saw I immediately wondered how this could be done using C#. The idea would be to detect when a window is being dragged to the limits of our screen, when it reaches them display a frame and finally change the size and position of the window being dragged.
At a first view, using some Windows Hook and capturing a couple of messages can do the trick. The problem is that we can only install global hooks for WH_KEYBOARD_LL and WH_MOUSE_LL, the other global hooks require a native DLL export to inject itself in another process. This is already a problem because discards the possibility to monitor messages before and after they are processed by a window, in addition we cannot use either the MouseProc that gives us information about the mouse and more important the value of the HitTest, which is necessary to know what part of the window the user is clicking. What we will do is to try to use the WH_MOUSE_LL with some work around. I already wrote about hooks more than one year ago, you can read it here if you are not familiarized with them.
With that hook we will obtain information about mouse events, we will focus on the messages WM_LBUTTONDOWN, WM_MOUSEMOVE and WM_LBUTTONUP. In the attached code I created a hook helper that captures the hook callbacks and generates .NET events to facilitate its use. You can see below the event handler:
1: void hookHelper_HookMouseEvent(object sender,
2: HookMouseEventArgs e)
6: case NativeConstants.WM_LBUTTONDOWN:
10: case NativeConstants.WM_MOUSEMOVE:
14: case NativeConstants.WM_LBUTTONUP:
In the method CheckWindow we decide if we Window where the user has clicked is a window we are interested to monitor, to do it we retrieve the window handle using the API WindowFromPoint and doing a Hit test over the window. The Hit test can be done by sending the message WM_NCHITTEST specifying the handle of the window and the coordinates of the point, this will return us a value indicating what part of the window has been clicked, in our case we are interested only if the user clicks the Title of the window, which value is HTCAPTION.
1: private void CheckWindow(Point point)
3: Win32.NativeStructs.Point p =
4: new Win32.NativeStructs.Point();
5: p.x = point.X;
6: p.y = point.Y;
8: windowHandle = NativeMethods.WindowFromPoint(p);
9: if (windowHandle != IntPtr.Zero)
11: IntPtr lParam = new IntPtr(65536 * p.y + p.x);
12: IntPtr hitTestPtr = NativeMethods.SendMessage(windowHandle,
13: (uint)NativeConstants.WM_NCHITTEST, (uint)0, lParam);
14: if ((int)hitTestPtr == NativeConstants.HTCAPTION)
16: dragging = true;
The messages, return values and functions are declared in the header Winuser.h, the easier to have all the information about the windows API is to go to MSDN, of course, but also to download the Windows SDK.
Next step is to give to the user the visual clue that something will happen with the window, to do it Windows 7 shows an animation below the pointer and a frame simulating where the window will be placed. To do it we will not code any fancy thing, we can simulate it by just opening a standard .NET form setting its properties ControlBox, ShowInTaskBar and TransparencyKey to our desired values. To know the size we need to give to our frame we just need to check the property WorkingArea from the .NET class System.Windows.Forms.SystemInformation. The only trick we will do is to display the form with the function SetWindowPos, because it allow us controlling the Z-Order of the window and to open the form without activate it, this is handy to avoid our fake frame is placed on top of the window we are actually moving. To do it we use the function SetWindowPos including the handle of the window to insert after and the parameter SWP_NOACTIVATE.
3: 0, 0,
4: SystemInformation.WorkingArea.Width / 2,
6: NativeConstants.SWP_SHOWWINDOW |
We are near to the end, we just need to complete the last step, change the size and position of the window to the desired the values using again the function SetWindowPos for the left and right placement. To maximize we just use the function ShowWindow passing as parameter SW_SHOWMAXIMIZED.
Windows 7 also restores the original size of the window after you drag away from the docked position, this could be done by capturing first the size and position of the window using the functions GetWindowPlacement and SetWindowPlacement, if you use them do not forget to set the length of your WindowPlacement structure using Marshal.SizeOf().
If you are not familiarized with P/Invoke in .NET you can make use of tools like PInvoke Interop Assistant from Microsoft CLR Interop Team or the P/Invoke wiki.
Now that all seems to be under control, if you download the code you will see that we have a big problem. When we release the button the size doesn't seem to change, but it actually changes to the size and position we have given. The problem is that the window is in the moving state and when we release the button after set our size, it comes back to the last size the window had before releasing the mouse button. If we make use of Microsoft Spy++ we can see that after set the position the window still receives the messages like WM_WINDOWPOSCHANGED or WM_EXITSIZEMOVE.
I've tried with some of the messages captured previously using the function SendMessage to see if I could exit the moving/sizing state but without success. So, at the end I couldn't resolve it using C#, if you have ideas on how this can be solved they will be welcome. As I was joking with a friend a couple of days ago I feel I've lost my "mojo".
It is curious to see how our mind works, when I completely stopped thinking about what message send in order to make it work, I thought I could try the last trick by setting the size after the resize was completed. The way to do that is pretty simply, we just need to enable a timer that starts after we capture the WM_LBUTTONUP.
MadAboutNet.WindowsDocker.zip (404.63 kb)