I once worked for a company that developed an application that ran as an OS/2 process in a console like window on a Windows server. The users of the application were unaccustomed to that type of windows, and from time to time, someone would close the window by clicking on the "x" close button. Now, the application HAD to be terminated correctly, otherwise it would result in all kinds of corrupt files and database entries, so every time it happened, it generated a lot of work for us at the support desk.
So I wrote a small application in VB6 (yes, it was THAT long ago) that used PInvoke to disable the close button on the OS/2 window to avoid that.
Yesterday, there was a question in Q/A about a similar scenario, so I thought that I would take the opportunity to look at my old code and adapt it to .NET, this time in C#.
So, What IS PInvoke really? I found a very good explanation in Dave Amenta's blog. And since I can't explain it any better, I'm going to rip the explanation directly from the blog:
P/Invoke, or Pinvoke stands for Platform Invocation Services. PInvoke is a feature of the Microsoft .NET Framework that allows a developer to make calls to native code inside Dynamic Link Libraries (DLL’s). When Pinvoking, the .NET framework (or Common Language Routine) will load the DLL and handle the type conversions automatically. The most common use of P/Invoke is to use a feature of Windows that is only contained in the Win32 API. The API in Windows is extremely extensive and only some of the features are encapsulated in .NET libraries. For example, Form.Show(); is really a wrapper for the ShowWindow() API found in shell32.dll.
Please go and have a look at his blog for more details.
A lot of the PInvoke methods are now wrapped in the .NET framework, and it is no longer necessary to access them directly. But some functionality is not, and in those cases, it is good to know that the "old" way still works.
Using the code
To achieve our objective and disable the close button on another window, we first need to find the windows handle.
In .NET, it's very easy to list the running process on the system and find their handles. Actually, in the old days you even had to use PInvoke for that, so here's an example of something that has been wrapped in the .NET framework to help developers.
In my attached sample app, I enumerate all the running processes, and I list those that have a window handle (because those are the ones with a UI window we can manipulate) in a ListView:
Process processlist = Process.GetProcesses();
foreach (Process process in processlist)
Process object contains the
MainWindowHandle we need to manipulate the window.
Now, to disable the Close button, we don't actually manipulate the button itself. What we do is that we manipulate the window's System menu. The system menu is the one that pops up when you click the application icon in the upper left corner of the title bar.
This is the system menu from Notepad (sorry that it is in Swedish):
The last menu item on the system menu is "Close". If we disable that item (or remove it completely), we will disable the close button at the same time. That's actually quite nice, because we wouldn't want to disable the close button but leave users with the option to shut down the window using the Close menu item on the System menu.
When we have the Window handle, we need to use that to find the System menu handle. First we need to declare the PInvoke method needed:
static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
We can then use it to find the System menu handle:
IntPtr sysMenuPtr = GetSystemMenu(mainWindowPtr, false);
And with that, we can disable it. First we need to define two more PInvoke methods:
static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);
static extern bool DrawMenuBar(IntPtr hWnd);
The first one sets the enabled/disabled state of the menu items, and the second refreshes the menu once changed.
EnableMenuItem takes some parameters. The first one,
hMenu is the handle to the menu that should be changed.
The LAST parameter (to take that first),
uEnable is NOT - like you could be tempted to believe - a mere boolean value of enabled or not. It is a flag variable you use to send information to the method about what you actually want to do.
The second one,
uIDEnableItem identifies the menuitem we want to change. It can either be a specific item identified by a constant id or it can be an item identified by it's index in the menu. In our case, we already know that we want to disable the "Close" item, so we can specify the item directly regardless of where in the menu it is.
We do that by specifying it's constant id,
SC_CLOSE and calling
EnableMenuItem with the menu items id in
uIDEnableItem, and as
uEnable flag we send both
MF_BYCOMMAND to let the method know that we have specified the menu items id instead of it's index AND
MF_GRAYED to gray out and disable the item.
private const int MF_BYCOMMAND = 0x0;
private const int MF_BYPOSITION = 0x400;
private const int MF_REMOVE = 0x1000;
private const int MF_ENABLED = 0x0;
private const int MF_GRAYED = 0x1;
private const int MF_DISABLED = 0x2;
private const int SC_CLOSE = 0xF060;
To disable the item, we then call:
EnableMenuItem(CurrentSystemMenuHandle, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
We COULD (if we wanted to) have disabled it by index instead. In that case, we would have called:
EnableMenuItem(CurrentSystemMenuHandle, 6, MF_BYPOSITION | MF_GRAYED);
Where 6 is the index of the Close item (even the divider has an index).
Of course, that won't work if someone has manipulated and rearranged the menu items...
Note: This will disable the Close menu item in the System menu AND the Close button in the upper right corner. But normally, an application will have another menu that might feature a close funtion. That one is NOT disabled. We wouldn't want to do that either, because that one provides the user with the option to close the application CORRECTLY.
File menu from Notepad where you can close the application correctly (Also in Swedish - sorry!):
I have done a demo application that can enable and disable the close functionality as well as REMOVE the option completely, should you want to do that. It also shows how to get the current state of the close menu item.
PInvoke can be used to manipulate other windows in a lot of interesting ways, their size, position, minimized and maximized property, Zorder and get them to stay on top or not.
In the demo app, I have also shown how to set a window topmost and remove it again.
When you work with PInvoke, this is a useful site that describes most of the methods:
The point of this article is to do exactly what I have described above. You are free to add new functionality, but please don't send me a lot of suggestions like "You should really add this and that functionality..." - It is only a demonstration of a few methods and I'm not going to add any other functionality to this article.
Version 1.00 - Initial publish