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.

Powerful and easy installation authoring of .net applications with Advanced Installer

Lately I’ve built a .net class library that supports COM as well. To make setup file I usually use Visual Studio’s Setup Project because I rarely do anything complicated during the installation ant Setup Project does the work fine for me.

This time the setup was a bit different, more complicated, because I needed to register my COM stuff during the installation. So I’ve built the setup file and tried the installation inside VMWare Workstation’s guest as I always do. COM objects were registered fine but type library (TLB) wasn’t registered at all when setup project is built on Vista/Visual Studio 2008. The later isn’t strictly required for running the code but it surely helps developers with strong typing support. There are several options to solve the issue ranging from building on XP to coding the post install/uninstall actions. Well, none of them looks very appealing to me thus I’ve decided to try the Advanced Installer from Caphyon this time.

I can say that creating a setup file for my .net application with Advanced Installer was a breeze – I had a working MSI setup in 10 minutes. The process consisted of importing the Visual Studio Setup Project (which I already had – I could start from scratch or by importing the library project) and adjusting few properties. Truth, mine wasn’t a complicated one but it surely solved type library registration with a click on the checkbox. It actually solved my problem in 10 minutes. Any other option would be either more annoying or it would take more time.

Advanced Installer is not just easy to use. It looks like a powerful and flexible authoring tool as well and certainly not limited to (simple) .net applications only.