Suppress system menu when right clicking on DevExpress skinned form's icon

Task

Today I've got a requirement from my customer to prevent showing the system menu when WinForm's icon (top left) is right-clicked. The form is painted by DevExpress skin. Here is how I've done it.

Solution

1. Intercept proper mouse messages

I had to intercept two mouse messages: WM_NCRBUTTONDOWN (0xa4) and WM_NCRBUTTONUP (0xa5). These two guys shows up when right click is performed on non client (NC) are of the form (where icon is located). That’s also the reason I couldn’t use Mouse* events which fire only for client area.

The interception is done by overriding Form's WndProc method and it looks something like this:

...
enum MouseMessage
{
WM_NCRBUTTONDOWN = 0xA4,
WM_NCRBUTTONUP = 0xA5
}
...
protected override void WndProc(ref Message msg)
{
switch ((MouseMessage)msg.Msg)
{
case MouseMessage.WM_NCRBUTTONDOWN:
case MouseMessage.WM_NCRBUTTONUP:
// code here
break;
}
base.WndProc(ref msg);
}

2. Finding icon bounds

If you are doing DevExpress form skinning it means that your form has to derive from XtraForm which does all of the skinning work for you. XtraForm holds a reference to FormPainter instance (which is responsible for painting the skin) through FormPainter property which in turn holds the icon bounds through its IconBounds property of Rectangle type. Now, the problem is that FormPainter.IconBounds property is protected and reflection has to be used to retrieve the value:

...
private PropertyInfo iconBoundsInfo;
...
public MyForm()
{
...
// store reference to IconBounds property to increase the performance
iconBoundsInfo = typeof(FormPainter).GetProperty("IconBounds", BindingFlags.Instance | BindingFlags.NonPublic);
...
}
protected override void WndProc(ref Message msg)
{
...
Rectangle iconBounds = (Rectangle)iconBoundsInfo.GetValue(FormPainter, null);
...
}

3. Figuring out if right click is within icon bounds and ignore such clicks

To get the click’s location I simply used Cursor.Position which is easier than retrieving the position from Message but less accurate – still enough for me.The position is in screen coordinates and they have to be transformed to form's coordinates. Form.PointToClient method won’t help because it transforms to form’s client area which border isn’t. The solution to this is rather simple – just Offset the position by Form’s –Left, –Top.

If click is positioned within icon bounds just return without delegating to base.WndProc. That’s it. Here is the complete code:

public IgnoreRightIconClickForm: XtraForm
{
enum MouseMessage
{
WM_NCRBUTTONDOWN = 0xA4,
WM_NCRBUTTONUP = 0xA5
}
private PropertyInfo iconBoundsInfo;

public IgnoreRightIconClickForm()
{
iconBoundsInfo = typeof(FormPainter).GetProperty("IconBounds", BindingFlags.Instance | BindingFlags.NonPublic);
}

protected override void WndProc(ref Message msg)
{
switch ((MouseMessage)msg.Msg)
{
case MouseMessage.WM_NCRBUTTONDOWN:
case MouseMessage.WM_NCRBUTTONUP:
// prevent showing system menu on window's icon rightclick
Rectangle iconBounds = (Rectangle)iconBoundsInfo.GetValue(FormPainter, null);
Point cursorPosition = Cursor.Position;
// offset to the raw window's coordinates (PointToClient transforms to window's client coordinates)
cursorPosition.Offset(-Left, -Top);
if (iconBounds.Contains(cursorPosition))
return;
break;
}
base.WndProc(ref msg);
}
}

Notes

This method works only for DevExpress skinned forms. If you want to use for normal forms the you should modify the icon bounds retrieval.