Blog

WPF ICommand Close button
 

How can you bind Close button against ICommand, in a such way that if the ICommand becomes disabled, the “Close button” becomes disabled too (grayed out)? This is classical pattern: if user starts a critical process, you should disable the close button & other controls.

Just add this behavior to your project:

public class WindowCloseBehavior : Behavior<Window>
    {
        #region Pinvoke

        private const uint MF_BYCOMMAND = 0x00000000;
        private const uint MF_GRAYED = 0x00000001;
        private const uint MF_ENABLED = 0x00000000;

        private const uint SC_CLOSE = 0xF060;

        private const int WM_SHOWWINDOW = 0x00000018;
        private const int WM_CLOSE = 0x10;

        [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);

        #endregion

        public static DependencyProperty
            CloseCommandProperty = DependencyProperty.Register("CloseCommand", typeof (ICommand),
                typeof(WindowCloseBehavior), new FrameworkPropertyMetadata(null, PropertyChangedCallback));

        private Window _window;

        /// <summary>
        ///     Gets or sets a value indicating whether the Window can be closed
        /// </summary>
        public ICommand CloseCommand
        {
            get { return (ICommand) GetValue(CloseCommandProperty); }

            set { SetValue(CloseCommandProperty, value); }
        }

        private static void PropertyChangedCallback(DependencyObject dependencyObject,
            DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            var wcb = dependencyObject as WindowCloseBehavior;
            if (wcb == null) return;

            if (dependencyPropertyChangedEventArgs.Property == CloseCommandProperty)
            {
                var oldCommand = dependencyPropertyChangedEventArgs.OldValue as ICommand;
                if (oldCommand != null)
                    oldCommand.CanExecuteChanged -= wcb.CommandCanExecuteChanged;

                var newCommand = dependencyPropertyChangedEventArgs.NewValue as ICommand;
                if (newCommand != null)
                    newCommand.CanExecuteChanged += wcb.CommandCanExecuteChanged;

                wcb.CommandCanExecuteChanged(null, null);
            }
        }

        public void CommandCanExecuteChanged(object s, EventArgs e)
        {
            if (_window == null)
                return;

            var isCommandEnabled = CloseCommand != null && CloseCommand.CanExecute(null);

            IntPtr windowHandle = new WindowInteropHelper(_window).Handle;
            IntPtr hMenu = GetSystemMenu(windowHandle, false);

            if (hMenu != IntPtr.Zero)
            {
                uint flags = MF_BYCOMMAND;
                if (!isCommandEnabled)
                    flags |= MF_GRAYED;

                EnableMenuItem(hMenu, SC_CLOSE, flags);
            }
        }

        /// <summary>
        ///     Called after the behavior is attached to an AssociatedObject.
        /// </summary>
        /// <remarks>Override this to hook up functionality to the AssociatedObject.</remarks>
        protected override void OnAttached()
        {
            base.OnAttached();

            // Trap our loaded event, since we can't get a window until we've loaded
            AssociatedObject.Loaded += AssociatedObject_Loaded;
        }

        /// <summary>
        ///     Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
        /// </summary>
        /// <remarks>Override this to unhook functionality from the AssociatedObject.</remarks>
        protected override void OnDetaching()
        {
            AssociatedObject.Loaded -= AssociatedObject_Loaded;

            if (_window != null)
            {
                _window.Closed -= Window_Closed;
                _window = null;

                // todo; unsubscribe from the command?!
            }

            base.OnDetaching();
        }

        /// <summary>
        ///     Handles the Loaded event of the user control to which we attached.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs" /> instance containing the event data.</param>
        private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
        {
            _window = Window.GetWindow(AssociatedObject);

            if (_window != null)
            {
                _window.Closed += Window_Closed;
            }
        }

        /// <summary>
        ///     Handles the Closed event of the Window control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs" /> instance containing the event data.</param>
        private void Window_Closed(object sender, EventArgs e)
        {
            if (CloseCommand != null)
            {
                CloseCommand.Execute(null);
            }
        }
    }

After you have done it, it’s simply the case of using the behavior:

<Window ..attributes> 
   <i:Interaction.Behaviors>
        <behaviors:WindowCloseBehavior CloseCommand="{Binding CloseCommand}" />
    </i:Interaction.Behaviors>
</Window>

Notice, the CloseCommand is usually in the viewmodel, and you can control it as such:

CloseCommand = new RelayCommand(() => { }, () => !isConverting);

If you need to indicate that the isConverting has changed, you can raise an event:

CloseCommand.RaiseCanExecuteChanged()

That’s it folks. Super simple, super effective :)!

 

Reply

Your email addres will not be published. All fields are required.