Click here to Skip to main content
Click here to Skip to main content

How to use PInvoke to disable the close button on another application's windows

, 25 Jun 2013
Rate this:
Please Sign up or sign in to vote.
With PInvoke it's possible to manipulate forms belonging to another application

 

Introduction 

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#. 

Background  

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:

using System.Diagnostics;
Process[] processlist = Process.GetProcesses();
foreach (Process process in processlist)
{
    //Add process to listview
}   

 The 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: 

[DllImport("user32.dll")]
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:

[DllImport("user32.dll")]
static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);

[DllImport("user32.dll")]
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);
DrawMenuBar(CurrentSystemMenuHandle);  

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!):

Notepad File Menu 

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.   

Useful  

When you work with PInvoke, this is a useful site that describes most of the methods:

http://www.pinvoke.net/  

Please Note   

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.

History 

Version 1.00 - Initial publish

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Johnny J.
Software Developer (Senior)
Sweden Sweden
Born in Copenhagen, Denmark
Have been living in Paris, France and L.A., The United States
Now live in Stockholm, Sweden
 
Started programming when I got my first VIC 20, and a few months later on Commodore 64. Those were the days!
 
Studied programming at the Copenhagen Engineering Academy
 
Professional console, winforms and webforms programming in Comal, x86 Assembler, Fortran, Pascal, Delphi, Visual Basic 3 through 6, Classic ASP, C# and VB.NET
 
I now work as Senior .NET developer building Airline Booking Systems, and have a number of projects in various states of progress to work on in the spare time...
 
PS: The cat on my profile is one of my three cats, Ramses. He's all white, odd-eyed, deaf and definitely the coolest cat there is!

Comments and Discussions

 
BugMy vote of 4 Pinmemberpitoloko5-Oct-13 1:32 
GeneralRe: My vote of 4 PinprofessionalJohnny J.9-Oct-13 22:58 
GeneralRe: My vote of 4 Pinmemberpitoloko13-Oct-13 20:42 
GeneralMy vote of 5 Pinmemberledtech328-Sep-13 8:11 
GeneralRe: My vote of 5 PinprofessionalJohnny J.28-Sep-13 9:07 
GeneralMy vote of 5 Pinmemberfredatcodeproject25-Jun-13 0:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140709.1 | Last Updated 25 Jun 2013
Article Copyright 2013 by Johnny J.
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid