WPF Window – Disable Minimize and Maximize Buttons Through Attached Properties From XAML


Overview
Yesterday I worked up a static helper class that encapsulates the functionality to disable/enable/toggle the minimize and maximize buttons on a standard WPF window. This morning I updated that code to fix some bugs that I overlooked. Finally just not long ago, I updated the code with a new static class called WindowCustomizer, which allows this same functionality through the use of attached properties straight from XAML-markup. The advantage here is that this is now a full MVVM-compliant solution.

WindowCustomizer.cs
This file contains the static class WindowCustomizer which enables the use of attached properties, and delegates its work to it’s nested static class WindowHelper which was seen in my previous post. On a low-level the bulk of the work is done by making calls to the Win32 API. I have chosen to use 32/64-bit compatible functions to allow for the most compatible solution. In addition I have surround the low-level code that modifies the WPF window with lock statements just as a precaution, although I don’t expect any problems either way.

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

namespace WindowCustomizerExample
{
    public static class WindowCustomizer
    {
        #region CanMaximize
        public static readonly DependencyProperty CanMaximize =
            DependencyProperty.RegisterAttached("CanMaximize", typeof(bool), typeof(Window),
                new PropertyMetadata(true, new PropertyChangedCallback(OnCanMaximizeChanged)));
        private static void OnCanMaximizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Window window = d as Window;
            if (window != null)
            {
                RoutedEventHandler loadedHandler = null;
                loadedHandler = delegate
                {
                    if ((bool)e.NewValue)
                    {
                        WindowHelper.EnableMaximize(window);
                    }
                    else
                    {
                        WindowHelper.DisableMaximize(window);
                    }
                    window.Loaded -= loadedHandler;
                };

                if (!window.IsLoaded)
                {
                    window.Loaded += loadedHandler;
                }
                else
                {
                    loadedHandler(null, null);
                }
            }
        }
        public static void SetCanMaximize(DependencyObject d, bool value)
        {
            d.SetValue(CanMaximize, value);
        }
        public static bool GetCanMaximize(DependencyObject d)
        {
            return (bool)d.GetValue(CanMaximize);
        }
        #endregion CanMaximize

        #region CanMinimize
        public static readonly DependencyProperty CanMinimize =
            DependencyProperty.RegisterAttached("CanMinimize", typeof(bool), typeof(Window),
                new PropertyMetadata(true, new PropertyChangedCallback(OnCanMinimizeChanged)));
        private static void OnCanMinimizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Window window = d as Window;
            if (window != null)
            {
                RoutedEventHandler loadedHandler = null;
                loadedHandler = delegate
                {
                    if ((bool)e.NewValue)
                    {
                        WindowHelper.EnableMinimize(window);
                    }
                    else
                    {
                        WindowHelper.DisableMinimize(window);
                    }
                    window.Loaded -= loadedHandler;
                };

                if (!window.IsLoaded)
                {
                    window.Loaded += loadedHandler;
                }
                else
                {
                    loadedHandler(null, null);
                }
            }
        }
        public static void SetCanMinimize(DependencyObject d, bool value)
        {
            d.SetValue(CanMinimize, value);
        }
        public static bool GetCanMinimize(DependencyObject d)
        {
            return (bool)d.GetValue(CanMinimize);
        }
        #endregion CanMinimize

        #region WindowHelper Nested Class
        public static class WindowHelper
        {
            private const Int32 GWL_STYLE = -16;
            private const Int32 WS_MAXIMIZEBOX = 0x00010000;
            private const Int32 WS_MINIMIZEBOX = 0x00020000;

            [DllImport("User32.dll", EntryPoint = "GetWindowLong")]
            private extern static Int32 GetWindowLongPtr(IntPtr hWnd, Int32 nIndex);

            [DllImport("User32.dll", EntryPoint = "SetWindowLong")]
            private extern static Int32 SetWindowLongPtr(IntPtr hWnd, Int32 nIndex, Int32 dwNewLong);

            /// <summary>
            /// Disables the maximize functionality of a WPF window.
            /// </summary>
            ///The WPF window to be modified.
            public static void DisableMaximize(Window window)
            {
                lock (window)
                {
                    IntPtr hWnd = new WindowInteropHelper(window).Handle;
                    Int32 windowStyle = GetWindowLongPtr(hWnd, GWL_STYLE);
                    SetWindowLongPtr(hWnd, GWL_STYLE, windowStyle & ~WS_MAXIMIZEBOX);
                }
            }

            /// <summary>
            /// Disables the minimize functionality of a WPF window.
            /// </summary>
            ///The WPF window to be modified.
            public static void DisableMinimize(Window window)
            {
                lock (window)
                {
                    IntPtr hWnd = new WindowInteropHelper(window).Handle;
                    Int32 windowStyle = GetWindowLongPtr(hWnd, GWL_STYLE);
                    SetWindowLongPtr(hWnd, GWL_STYLE, windowStyle & ~WS_MINIMIZEBOX);
                }
            }

            /// <summary>
            /// Enables the maximize functionality of a WPF window.
            /// </summary>
            ///The WPF window to be modified.
            public static void EnableMaximize(Window window)
            {
                lock (window)
                {
                    IntPtr hWnd = new WindowInteropHelper(window).Handle;
                    Int32 windowStyle = GetWindowLongPtr(hWnd, GWL_STYLE);
                    SetWindowLongPtr(hWnd, GWL_STYLE, windowStyle | WS_MAXIMIZEBOX);
                }
            }

            /// <summary>
            /// Enables the minimize functionality of a WPF window.
            /// </summary>
            ///The WPF window to be modified.
            public static void EnableMinimize(Window window)
            {
                lock (window)
                {
                    IntPtr hWnd = new WindowInteropHelper(window).Handle;
                    Int32 windowStyle = GetWindowLongPtr(hWnd, GWL_STYLE);
                    SetWindowLongPtr(hWnd, GWL_STYLE, windowStyle | WS_MINIMIZEBOX);
                }
            }

            /// <summary>
            /// Toggles the enabled state of a WPF window's maximize functionality.
            /// </summary>
            ///The WPF window to be modified.
            public static void ToggleMaximize(Window window)
            {
                lock (window)
                {
                    IntPtr hWnd = new WindowInteropHelper(window).Handle;
                    Int32 windowStyle = GetWindowLongPtr(hWnd, GWL_STYLE);

                    if ((windowStyle | WS_MAXIMIZEBOX) == windowStyle)
                    {
                        SetWindowLongPtr(hWnd, GWL_STYLE, windowStyle & ~WS_MAXIMIZEBOX);
                    }
                    else
                    {
                        SetWindowLongPtr(hWnd, GWL_STYLE, windowStyle | WS_MAXIMIZEBOX);
                    }
                }
            }

            /// <summary>
            /// Toggles the enabled state of a WPF window's minimize functionality.
            /// </summary>
            ///The WPF window to be modified.
            public static void ToggleMinimize(Window window)
            {
                lock (window)
                {
                    IntPtr hWnd = new WindowInteropHelper(window).Handle;
                    Int32 windowStyle = GetWindowLongPtr(hWnd, GWL_STYLE);

                    if ((windowStyle | WS_MINIMIZEBOX) == windowStyle)
                    {
                        SetWindowLongPtr(hWnd, GWL_STYLE, windowStyle & ~WS_MINIMIZEBOX);
                    }
                    else
                    {
                        SetWindowLongPtr(hWnd, GWL_STYLE, windowStyle | WS_MINIMIZEBOX);
                    }
                }
            }
        }
        #endregion WindowHelper Nested Class
    }
}

Example Usage (Window1.xaml)
Using the WindowCustomizer is extremely straightforward, provides minimal clutter, and allows for a fully MVVM-compliant solution. The XAML-markup below will disable both the minimize and maximize functionality from the WPF window, and in turn disable their associated system menu menuitems as well. Be aware the only way to completely remove both buttons from the WPF window’s titlebar (as opposed to just disabling them) is to set both CanMaximize and CanMinimize to False; In this example both buttons will therefore be completely removed.

WindowCustomizerExample.Window1"
        Title="Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:local="clr-namespace:WindowCustomizerExample"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        local:WindowCustomizer.CanMaximize="False"
        local:WindowCustomizer.CanMinimize="False">
</Window>

Conclusion
The code presented in this post I expect to be extremely helpful not only to the public, but to myself as well. In the future I plan to implement an IsIconVisible attached property on the WindowCustomizer, with its work being delegated to it’s nested static class WindowHelper. When I have done so I will update this post and post a comment in the comments below.

Source Code Link
http://thrash505.webs.com/WindowCustomizerExample.zip

, , , , , ,

  1. #1 by Yomodo on March 24, 2017 - 11:12 am

    Awesome, thank you!
    any change you will update the source to include the CanClose and IsIconVisible feature?

  2. #2 by James Relyea on August 17, 2014 - 2:02 pm

    This is awesome!!! It works perfectly and it’s well explained. GREAT job!

  3. #3 by Miro Janosik on June 8, 2012 - 5:04 am

    Thanks for the article, it works well. I have found that the same solution is presented on multiple places on the web, so I guess that it is good.

    By the way, you can remove minimize + maximize buttons by changing window style from
    WindowStyle = WindowStyle.SingleBorderWindow;
    to WindowStyle.ToolWindow,

    Window will still remain resizable.

    And if you want to remove ‘x’ close button, you can do it with your code and flag:
    private const int WsSysmenu = 0x80000;

  4. #4 by ADTC on January 3, 2012 - 10:52 am

    Quote: Be aware the only way to completely remove both buttons from the WPF window’s titlebar (as opposed to just disabling them) is to set both CanMaximize and CanMinimize to False;

    I guess then your entire code is just redundant in most cases, as you can simply set a normal WPF window’s ResizeMode to “CanMinimize” to disable Maximize button or “NoResize” to disable *and* remove both Minimize and Maximize buttons. I was looking for a solution to remove the Maximize button rather than just disable it (in the case of “CanMinimize”).

    I don’t know why Microsoft is so lame to not include an option to hide that button in WPF. The disabled Maximize button is just so ugly – sticking like a sore thumb between the Minimize and the Close button.

    • #5 by Tim Valentine on January 5, 2012 - 8:14 pm

      Correct, but then you can’t resize the window.. this solution keeps resize functionality and lets you toggle the min/max buttons. The reason why you can’t remove only the maximize button, without removing the minimize button as well, is because Windows doesn’t let you, nothing to do with WPF.

      Ultimately you can play around with the settings that the WPF Window provides, but it seems any combination has side-effects. The solution I presented gets you what you want without sacrificing other functionality. Wish it didn’t have to result in Win32 calls, but that’s all we have to work with.

  5. #6 by Bryan on December 19, 2011 - 5:50 pm

    Tim, appreciate the code. Is it ok to use in our software (even if the software goes commercial)?

    • #7 by Tim Valentine on December 20, 2011 - 3:26 pm

      Yes, feel free to use any of the code on this blog in your software, commercial or otherwise. But don’t forget to tell your friends!

      Just thought I’d mention that I’m planning to update this post soon with a CanClose, and IsIconVisible attached properties. I implemented the functionality over a year ago.. but have neglected to post it.

  6. #8 by Mahesh Kumar (@Cyberiafreak) on October 15, 2011 - 9:21 pm

    Works just perfect.Thanks.

  7. #9 by Anonymous on September 21, 2011 - 2:40 pm

    This is by far the nicest solution I’ve seen for the problem. How come the WPF team didn’t implement that at first?

  8. #10 by Tal on April 3, 2011 - 3:46 am

    Great!

    This is exactly what I was looking for.
    Thank you very much

  9. #11 by Tim Murphy on January 11, 2011 - 3:09 am

    Fantastic; exactly what I needed.

  10. #12 by AnHund on November 30, 2010 - 8:29 am

    That just works perfectly.
    Thanks a lot :-)

  11. #13 by Deep on June 25, 2010 - 1:42 am

    Great Man… Keep Goooing..

  1. WPF Window – Disabling/Enabling/Toggling Minimize and Maximize Buttons – WpfWindowHelper Class « Thrash505’s Code Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: