다음을 통해 공유


WPF: Disabling or Hiding the Minimize, Maximize or Close Button Of a Window

Introduction

There 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 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);
  }
}

https://magnusmontin.files.wordpress.com/2014/11/windowbuttons.png?w=525&h=188
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 MSDN Samples Code Gallery here

https://magnusmontin.files.wordpress.com/2014/11/windowbuttons2.png?w=525&h=350

See also

GetWindowLong function: http://msdn.microsoft.com/en-us/library/windows/desktop/ms633584(v=vs.85).aspx

SetWindowLong function: http://msdn.microsoft.com/en-us/library/windows/desktop/ms633591(v=vs.85).aspx

EnableMenuItem function: http://msdn.microsoft.com/en-us/library/windows/desktop/ms647636(v=vs.85).aspx 

GetSystemMenu function: http://msdn.microsoft.com/en-us/library/windows/desktop/ms647985(v=vs.85).aspx

DestroyMenu function: http://msdn.microsoft.com/en-us/library/windows/desktop/ms647631(v=vs.85).aspx