GripPanel






4.13/5 (7 votes)
Oct 19, 2003
3 min read

90104
A WinForms Panel that shows a size grip when docked at the bottom of a form.
Introduction
This is not an earth-shattering new development but you might find a use for it in your toolbox.
It is simply a control derived from the standard Winforms Panel
control that draws a sizing grip when the panel
is docked at the bottom of a form.
Background
This control was written to get around a bug in Winforms that prevents controls anchored to the right and bottom of a form working correctly in a derived form.
I simply wanted a base form with OK and Cancel buttons anchored to the bottom right of the form but unfortunately, when a derived form was resized in the designer, the next compile moved the buttons to incorrect positions.
This is documented in Microsoft Knowledge Base Article 316560[1] but the workarounds are:-
- Do not resize the form in the designer. (Which is not convenient!)
- Change the modifier from
private
toprotected
. (Which my controls already were, so this is incorrect)
My solution to this problem was to add a panel
, all docked to the bottom of the form, with the required buttons (and some empty labels to provide spacing) docked to the right within that panel. This worked fine but it means that the form sizing grip was no longer visible.
So I decided to write a derived Panel
that would have a sizing grip! What should have been a simple 5-minute job left me burning the midnight oil determined to find out how to do this.
How it works
I used Lutz Roeder's excellent Reflector for .NET[2] to look into System.Windows.Forms.Dll to see how Microsoft did it for a form and I also found an article by Karl E. Peterson called "Get a Grip With SubClassing"[3] written for VB which does a similar thing.
The first part, drawing the size grip itself is easy. ControlPaint.DrawSizeGrip
(part of WinForms) will draw a size grip of any size onto a Graphics
object such as that provided in the PaintEventArgs
parameter the OnPaint
method.
The next bit was to make the cursor change shape and actually resize the form when the mouse pointer is over the size grip. To do this, I had to override the WndProc
method and look for WM_NCHITTEST
message. When this message is received, I check whether the mouse pointer is within the size grip and if so, basically lie to Windows and tell it that the mouse pointer is in the lower-right corner of a border of a resizable window. At this point, Windows takes over and does all that is necessary!
Finally, when the window is being resized, I need to invalidate the rectangle containing the size grip so that it will be redrawn and not leave 'bits' behind. At first, I Invalidated just the rectangle containing the grip but when the form was resized quickly, some bits were still left behind. Using Reflector again, I found that Microsoft took the easy option and Invalidated the whole control so I did the same!
Oh, and I also added a dividing line at the top of the panel using ControlPaint
in OnPaint
again. This is optional.
References
- Microsoft website
- http://www.aisto.com/roeder/dotnet/
- http://archive.devx.com/premier/mgznarch/vbpj/1999/06jun99/ap0699.pdf
Source code
Here is the source code in full:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace SimmoTech.Forms {
/// <summary>
/// This control works exactly the same as a normal
/// panel but, if the panel is docked at the bottom of the form,
/// then a sizing grip is added and a horizontal 3D line
/// is added at the top of panel.
/// </summary>
public class GripPanel: Panel {
private const int HTBOTTOMRIGHT = 17;
private const int WM_NCHITTEST = 0x84;
private const int WM_SIZE = 0x05;
/// <summary>
/// Catch some windows messages
/// </summary>
/// <param name="m"></param>
protected override void WndProc(ref Message m) {
// Only catch messages if the panel is docked to the bottom
if (Dock == DockStyle.Bottom) {
switch (m.Msg) {
// If the panel is being resized then we
// need to redraw its contents
case WM_SIZE:
Invalidate();
break;
// If the system is asking where the mouse pointer is,
// we need to check whether it is over our sizing grip
case WM_NCHITTEST:
// Convert to client co-ordinates of parent
Point p = FindForm().PointToClient(new Point((int) m.LParam));
int x = p.X;
int y = p.Y;
Rectangle rect = Bounds;
// Is the mouse pointer over our sizing group?
// (Use 12 pixels rather than 16 otherwise
// too large an area is checked)
if (x >= rect.X + rect.Width - 12 &&
x <= rect.X + rect.Width &&
y >= rect.Y + rect.Height - 12 &&
y <= rect.Y + rect.Height) {
// Yes, so tell windows it is in the lower-right
// corner of a border of a resizable window
// Windows will then do the neccessary resizing
m.Result = new IntPtr(HTBOTTOMRIGHT);
return;
}
break;
}
}
// Do the normal message handling
base.WndProc(ref m);
}
/// <summary>
/// Override to add the border line at the top
/// of the panel and the size grip itself
/// </summary>
/// <param name="e"></param>
protected override void OnPaint(PaintEventArgs e) {
// Do the normal painting
base.OnPaint(e);
// Are we docked at the bottom?
if (Dock == DockStyle.Bottom) {
// Yes, so paint the adornments
ControlPaint.DrawBorder3D(e.Graphics, ClientRectangle,
Border3DStyle.Raised, Border3DSide.Top);
ControlPaint.DrawSizeGrip(e.Graphics, BackColor,
Width - 16, Height -16, 16, 16);
}
}
}
}