Google Search

Friday, May 18, 2012

Custom Resizable Windows in WPF and Winforms

The following post is relevant for both WPF and Winforms projects, wishing to create custom looking windows that can be dragged and resized from all directions (or specific ones).
We will need to use some native Win32 APIs. So first we need to declare the SendMessage function:
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, Int32 wParam, Int32 lParam);
We will use that function with the window handle, passing WM_NCLBUTTONDOWN (0x00A1) as message. The trick is to pass the correct wParam and lParams so Windows will think we're dragging the window or resizing it through any of it's resizing handles:
enum WindowHandles
{
    HTCAPTION = 2,
    HTLEFT = 10,
    HTRIGHT = 11,
    HTTOP = 12,
    HTTOPLEFT = 13,
    HTTOPRIGHT = 14,
    HTBOTTOM = 15,
    HTBOTTOMLEFT = 16,
    HTBOTTOMRIGHT = 17
}
There are actually more handles, for the client area, close button, minimize and maximize buttons, etc. They are all listed in the WM_NCHITTEST message page on MSDN. I only listed the handles relevant to our case.
We will be tricking Windows by simulating a mouse down event on a specific drag-handle. But since we know that the drag starts by listening to mouse-down even, we first need to tell Windows that the mouse button is not down anymore. To do this we will also need the ReleaseCapture function:
[DllImport("User32.dll")]
public static extern bool ReleaseCapture();
Now all there's left to do is to call the SendMessage the mouse button is down for each of our custom drag-handles with the native window handle (HWnd):
const int WM_NCLBUTTONDOWN = 0x00A1;

void RegisterCustomBorderEvents()
{
    //...
    // do similar things for all borders and for the caption.
    LeftBorder.MouseDown += OnLeftBorderMouseDown;
    //...
}

void OnLeftBorderMouseDown (object sender, EventArgs e)
{
    ReleaseCapture();
    SendMessage(windowHandle, WM_NCLBUTTONDOWN, (uint)WindowHandles.HTLeft, 0);
}

WPF Notes

In WPF you will need to use the interop API in order to get the window handle:

IntPtr windowHandle = new WindowInteropHelper(myWindow).Handle;
Where myWindow is the window you want to drag. If you implement this in the window code behind, just pass this.

Also in WPF there are built in methods in the Window level for dragging the window (DragMove) and in the UIElement level for releasing the mouse capture (ReleaseMouseCapture). You may use them instead of the PInvoke versions where possible.