Disabling or hiding the minimize, maximize or close button of a WPF window
Posted: November 30, 2014 Filed under: Windows Forms, WPF | Tags: Windows Forms, WPF 1 CommentThere may be situations when you want to hide or disable the minimize, maximize, or close button of a window without having to modify the style of the window in any other way. In a Windows Forms application there are the MinimizeBox and MaximizeBox boolean properties that lets you disable the minimize and maximize button of a Form respecively or hide them both (setting both of these properties to false will effectively hide both these buttons):
public partial class Form1 : Form { public Form1() { InitializeComponent(); this.MinimizeBox = false; this.MaximizeBox = false; } }
WindowStyle
In WPF you can indeed set the WindowStyle property of a Window to System.Windows.WindowStyle.ToolWindow to get rid of the minimize and maximize buttons completely, but the window will then look slightly different compared to when the property is set to its default value of SingleBorderWindow.
ResizeMode
You can in fact also disable the minimize button by setting the ResizeMode property of the window to CanMinimize but this will prevent the user from being able to resize the window using the mouse as a side-effect. And what if you for example want to get rid of the minimize and maximize buttons and perhaps disable the close button? There are in fact no API:s available in the .NET Framework that lets you do this. The good news is that you can accomplish this pretty easily anyway by making use of native methods in Windows.
P/Invoke
The easiest way to call unmanaged code (C++ in this case) from managed (.NET) code is to use the Platform Invocation Services, often also referred to as P/Invoke. You simply provide the compiler with a declaration of the unmanaged function and call it like you would call any other managed method. There is an unmanaged SetWindowLong method that can be used to change an attribute of a specified window. To be able to call this method from your WPF window class using P/Invoke, you simply add the following declaration to the window class:
[DllImport("user32.dll")] private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
The DllImport attribute specifies the name of the DLL that contains the method and the extern keyword tells the C# compiler that the method is implemented externally and that it won’t find any implementation or method body for it when compiling the application. The first argument to be passed to the SetWindowLong method is a handle for the window for which you want to disable any of the mentioned buttons. You can get handle for a WPF window by creating an instance of the managed WindowInteropHelper class and access its Handle property in an event handler for the window’s SourceInitialized event. This event is raised when the handle has been completely created. The second argument of the SetWindowLong method specifies the attribute or value of the window to be set, expressed as a constant integer value. When you want to change the window style, you should pass the GWL_STYLE (= -16) constant as the second argument to the method.
private const int GWL_STYLE = -16;
Finally the third argument specifies the the replacement value. There are a set of constants that you could use here:
private const int WS_MAXIMIZEBOX = 0x10000; //maximize button private const int WS_MINIMIZEBOX = 0x20000; //minimize button
Note however that since you are supposed to pass in a DWORD that specifies the complete value for the “property” specified by the second argument, i.e. the window style in this case, you cannot simply pass any of these constants by themselves as the third argument to the method. There is another GetWindowLong method that retrieves the current value of a specific property – again the GWL_STYLE in this case – and you can then use bitwise operators to get the correct value of the third parameter to pass to the SetWindowLong method. Below is a complete code sample of how you for example could disable the minimize button for a window in WPF:
public partial class MainWindow : Window { [DllImport("user32.dll")] private static extern int GetWindowLong(IntPtr hWnd, int nIndex); [DllImport("user32.dll")] private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong); private const int GWL_STYLE = -16; private const int WS_MAXIMIZEBOX = 0x10000; //maximize button private const int WS_MINIMIZEBOX = 0x20000; //minimize button public MainWindow() { InitializeComponent(); this.SourceInitialized += MainWindow_SourceInitialized; } private IntPtr _windowHandle; private void MainWindow_SourceInitialized(object sender, EventArgs e) { _windowHandle = new WindowInteropHelper(this).Handle; //disable minimize button DisableMinimizeButton(); } protected void DisableMinimizeButton() { if (_windowHandle == IntPtr.Zero) throw new InvalidOperationException("The window has not yet been completely initialized"); SetWindowLong(_windowHandle, GWL_STYLE, GetWindowLong(_windowHandle, GWL_STYLE) & ~WS_MINIMIZEBOX); } }
Disabling the maximize button is then simply a matter of replacing the WS_MINIMIZEBOX constant with the WS_MAXIMIZEBOX constant in the call to the SetWindowLong method:
private void MainWindow_SourceInitialized(object sender, EventArgs e) { _windowHandle = new WindowInteropHelper(this).Handle; //disable the maximize button DisableMaximizeButton(); } protected void DisableMaximizeButton() { if (_windowHandle == null) throw new InvalidOperationException("The window has not yet been completely initialized"); SetWindowLong(_windowHandle, GWL_STYLE, GetWindowLong(_windowHandle, GWL_STYLE) & ~WS_MAXIMIZEBOX); }
If you want to hide both the minimize and the maximize buttons (disabling both these buttons will make them invisible) you could use the below method:
protected void HideMinimizeAndMaximizeButtons() { if (_windowHandle == null) throw new InvalidOperationException("The window has not yet been completely initialized"); SetWindowLong(_windowHandle, GWL_STYLE, GetWindowLong(_windowHandle, GWL_STYLE) & ~WS_MAXIMIZEBOX & ~WS_MINIMIZEBOX); }
When it comes to the close button you cannot use the SetWindowLong method to disable it but there is an EnableMenuItem method that works similarly. The reason why you cannot use the SetWindowLong to disable the close button has to do with the fact that there was no close button before the Windows 95. The window caption only had the the minimize and maximize buttons in the upper right corner and they were controlled with a window style. The EnableMenuItem method takes a handle to a menu item, an unsigned integer that specifies the particular menu item to be disabled or enabled and another unsigned integer argument that controls interpretation of the second argument and indicates whether the menu item should be enabled or disabled:
[DllImport("user32.dll")] private static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);
The handle to the menu item can be obtained by using the GetSystemMenu method:
[DllImport("user32.dll")] private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert); [DllImport("user32.dll")] private static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable); [DllImport("user32.dll")] private static extern IntPtr DestroyMenu(IntPtr hWnd); private const uint MF_BYCOMMAND = 0x00000000; private const uint MF_GRAYED = 0x00000001; private const uint SC_CLOSE = 0xF060; IntPtr menuHandle; protected void DisableCloseButton() { if (_windowHandle == null) throw new InvalidOperationException("The window has not yet been completely initialized"); menuHandle = GetSystemMenu(_windowHandle, false); if (menuHandle != IntPtr.Zero) { EnableMenuItem(menuHandle, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED); } }
When passing false as the second argument to the GetSystemMenu method, the function will return a handle to the copy of the window menu currently in use. For you to be able to enable the close button again after you have disabled it, you need to save the reference to this handle (menuHandle in the sample code above). You can then simply use the MF_ENABLED constant to enable it again:
private const uint MF_ENABLED = 0x00000000; protected void EnableCloseButton() { if (_windowHandle == null) throw new InvalidOperationException("The window has not yet been completely initialized"); if (menuHandle != IntPtr.Zero) { EnableMenuItem(menuHandle, SC_CLOSE, MF_BYCOMMAND | MF_ENABLED); } }
Also note that you should call a DestroyMenu function to release the managed memory associated with the handle once you are done using it in order to prevent memory leaks. You could for example do this when the window is closing down by overriding the OnClosing method of the Window class:
[DllImport("user32.dll")] private static extern IntPtr DestroyMenu(IntPtr hWnd); protected override void OnClosing(System.ComponentModel.CancelEventArgs e) { base.OnClosing(e); if(menuHandle != IntPtr.Zero) DestroyMenu(menuHandle); }
To hide the maximize, minimize and close buttons altogether you can simply use yet another constant (WS_SYSMENU) like you use the WS_MAXIMIZEBOX and WS_MINIMIZEBOX constants and call the SetWindowLong method as before:
private const int WS_SYSMENU = 0x80000; protected void HideAllButtons() { if (_windowHandle == null) throw new InvalidOperationException("The window has not yet been completely initialized"); SetWindowLong(_windowHandle, GWL_STYLE, GetWindowLong(_windowHandle, GWL_STYLE) & ~WS_SYSMENU); }
Enabling the buttons after you have disabled them is simply a matter of changing the operator from a logical AND and XOR to a logical OR and and use the same constants:
protected void EnableMinimizeButton() { if (_windowHandle == IntPtr.Zero) throw new InvalidOperationException("The window has not yet been completely initialized"); SetWindowLong(_windowHandle, GWL_STYLE, GetWindowLong(_windowHandle, GWL_STYLE) | WS_MINIMIZEBOX); } protected void EnableMaximizeButton() { if (_windowHandle == null) throw new InvalidOperationException("The window has not yet been completely initialized"); SetWindowLong(_windowHandle, GWL_STYLE, GetWindowLong(_windowHandle, GWL_STYLE) | WS_MAXIMIZEBOX); } protected void ShowMinimizeAndMaximizeButtons() { if (_windowHandle == null) throw new InvalidOperationException("The window has not yet been completely initialized"); SetWindowLong(_windowHandle, GWL_STYLE, GetWindowLong(_windowHandle, GWL_STYLE) | WS_MAXIMIZEBOX | WS_MINIMIZEBOX); } protected void ShowAllButtons() { if (_windowHandle == null) throw new InvalidOperationException("The window has not yet been completely initialized"); SetWindowLong(_windowHandle, GWL_STYLE, GetWindowLong(_windowHandle, GWL_STYLE) | WS_SYSMENU); }
Sample code
I have put together a sample solution that consists of a class library with a CustomWindow class that contains all the code from above and a sample WPF application containing only a window of this type with buttons and event handlers that call the different methods in the custom base window class. You can downloaded it from the migrated MSDN Samples Code Gallery here.
See also
GetWindowLong function: http://msdn.microsoft.com/en-us/library/windows/desktop/ms633584(v=vs.85).aspx (MSDN) SetWindowLong function: http://msdn.microsoft.com/en-us/library/windows/desktop/ms633591(v=vs.85).aspx (MSDN) EnableMenuItem function: http://msdn.microsoft.com/en-us/library/windows/desktop/ms647636(v=vs.85).aspx (MSDN) GetSystemMenu function: http://msdn.microsoft.com/en-us/library/windows/desktop/ms647985(v=vs.85).aspx (MSDN) DestroyMenu function: http://msdn.microsoft.com/en-us/library/windows/desktop/ms647631(v=vs.85).aspx (MSDN)
Hi
Great article.
Some things to watch out for:
1) if you set the WindowStyle to None, users can still press Alt-Spacebar to make the menu pop up and still minimize/maximize the window
2) If you disable the Close button using Interop, users can still press Alt-F4.
The best way to solve this issue: Use interop as you explained and override Closing to Cancel the event.
Greetings