Introduction
In certain cases it may be desirable to completely replace the non-client area (title bar, title bar buttons and window borders) with a completely custom user interface. An example of such is when applying buttons that seemingly cross from non-client areas to client areas (e.g. the Start button found in Microsoft Office 2007 applications such as Microsoft Office Word). In these instances, a Windows Presentation Foundation (WPF) window may be created using no client area and the entire window recreated, gaining access to all areas of the new window as a client area.
Standard window functionality must be recreated for the user experience to remain constant. Most functionality (i.e. window move, close, maximise, restore, minimise) is easily achieved. However, window resizing by mouse drag is considerably more complex.
In this article, an approach to creating resizable windows with the standard resize drag functionality is presented. A utility library is provided which allows any WPF window to be resized via UIElement
s (not necessarily part of the window to be resized). The mouse pointer is adjusted to the relevant graphic automatically when the pointer is hovering over any registered window resizing control.
Using the Code
A single class called WindowResizer
is included in the library, which controls all functionality to do with resizing. To use the library, it should be included and linked into the UI as follows. Note that an ideal place is the constructor for the window since, in most cases, the resize components will be available from window birth to death.
WindowResizer wr = new WindowResizer(this);
wr.addResizerRight(rightSizeGrip);
wr.addResizerLeft(leftSizeGrip);
wr.addResizerUp(topSizeGrip);
wr.addResizerDown(bottomSizeGrip);
wr.addResizerLeftUp(topLeftSizeGrip);
wr.addResizerRightUp(topRightSizeGrip);
wr.addResizerLeftDown(bottomLeftSizeGrip);
wr.addResizerRightDown(bottomRightSizeGrip);
During the call to .addResizer...(UIElement)
, all event handler hooks are added to the UI element. From call completion, the component will be acting as a resizing element upon the target window. The window and elements must be valid and created before the calls are made.
Two event handlers are added to each UI element to control the pointer graphic (i.e. to achieve the arrow graphics while hovering the border), MouseEnter
and MouseLeave
. In this release, this option is sufficient. However, in later releases this will be changed to an unmanaged call to the underlying Windows APIs to prevent the pointer changing back to the standard arrow pointer when rapidly sizing the window up. By increasing the mouse polling speed (and hence making the resize operations more rapidly starting) this effect is reduced, although it is noticeable. A starting point for research to solve this effect would be investigation of user32.dll.
This problem is made significantly complex due to the different handling of mouse move events and mouse positioning. Under .NET 2.0, Cursor.GetPosition(...)
returned the position of the mouse relative to the specified component. However, under .NET 3.5, this has changed to the position of the mouse relative to the component, but clamped to the bounds of the component. For example, given that a component has a horizontal screen position of 100 pixels, a width of 200 pixels and a mouse screen position of 500 pixels, .NET 2.0 would return a relative position to the component of 400 pixels. However, .NET 3.5 returns a relative position of just 200 pixels.
A similar effect is found when using Cursor.GetPosition(...)
. The returned value is clamped to the relative bounds of the component. The solution to this is to get the absolute screen position via a user32.dll call. Specifically, the following code must be added and polled routinely (20 millisecond delay between polls) while dragging the mouse:
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(out PointAPI lpPoint);
private struct PointAPI
{
public int X;
public int Y;
}
The GetCursorPos()
method is polled via the following call:
PointAPI p = new PointAPI();
GetCursorPos(out p);
Using user32.dll is just one way of obtaining mouse position data outside the bounds of a WPF window. It is possible to use DirectInput
and obtain a Microsoft.DirectX.DirectInput.Device
. However, the mouse position is not reported rather than the mouse movement (i.e. 2 right, 10 down). Extreme care must be taken when using this form of input, as the position data generated may not correspond to the same resolution as the screen (i.e. 10 right from DirectInput
may not correspond to 10 screen pixels right).
Points of Interest
All other window functionality is easily implemented and mostly a case of using Grid
layout to position correctly. A close button is implemented via a button click handler and Window.Close()
. Maximise, restore and minimise are implemented via Window.WindowState
, and window dragging is implemented via MoveDrag()
on the MouseDown
or MouseLeftButtonDown
UIElement
handler. Curiously, no ResizeDrag()
is available currently.
History
Version 1.0.0.0 (January 23, 2008) Basic mouse drag functionality with auto cursor icon changing.
Contributors
Grateful thanks to Mike for his work on user32.dll.