Delegate window event to ViewModel ICommand

 18-Mar-2013   NityaPrakash     WPF  ICommand    Comments  0

In most of the cases we use MVVM pattern in WPF to build application. Main essence of MVVM pattern is View doesn�t know about ViewModel. View and ViewModal interact to each other through DataContext property of Container controls on WPF Controls. There are some action on UI( View) mostly doesn�t directly notify the action taken to ViewModal, and there may be any business flow is dependent on that action. For example, when we click on close button of window in right top corner window get closed without notify to ViewModal. There may be some unsaved data on screen should get saved before window closes or there may be any other business algorithm needs to perform before it gets shutdown. In this article, I just want to talk about the windows Closing event. In ideal implementation of MVVM pattern, we don�t want to bring the logic on presentation layer.

As event handler only be created in View, I mean locally on window class, it cannot be defined outside of the view. It means we have to write code in UI to handle the event, but this is not the way MVVM pattern should behave. We know, ViewModel can deal with ICommand but not the click event. We need a mechanism to fill the gap between Windows events and ViewModel . Here defining new DependancyProperty. Here I am going to discuss, How did I solved this issue explained above.

Dependency Properties

Dependency properties are a WPF-specific creation. However, the dependency properties in the WPF libraries are always wrapped by ordinary .NET property procedures. This makes them usable in the normal way, even with code that has no understanding of the WPF dependency property system. It seems odd to think of an older technology wrapping a newer one, but that�s how WPF is able to change a fundamental ingredient such as properties without disrupting the rest of the .NET world.

Delegate window event to ViewModel Command

I have created class WindowsClosingBehavior which defines a dependency property named ClosingProperty and its implementation.  However property name is Closing, but when defined it should followed by Property word, so in this case it will be ClosingProperty.


    class WindowClosingBehaviour
    {
        /// Property Getter
        public static ICommand GetClosing(DependencyObject obj)
        {
            return (ICommand)obj.GetValue(ClosingProperty);
        }

        /// Property Setter
        public static void SetClosing(DependencyObject obj, ICommand value)
        {
            obj.SetValue(ClosingProperty, value);
        }

        /// 
        /// Defining new ClosingProperty which is type of ICommand and this type is the owner
        /// Whenever this property change than ClosingChanged callback get fires
        /// 
        public static readonly DependencyProperty ClosingProperty
            = DependencyProperty.RegisterAttached(
            "Closing", typeof(ICommand), typeof(WindowClosingBehaviour),
            new UIPropertyMetadata(new PropertyChangedCallback(ClosingChanged)));

        /// 
        /// Callback event for the window that responsible attaching Windows Closing handler to the window
        /// where property used in.
        /// 
        private static void ClosingChanged(
          DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            Window window = target as Window;

            if (window != null)
            {
                if (e.NewValue != null)
                {
                    window.Closing += Window_Closing;
                }
                else
                {
                    window.Closing -= Window_Closing;
                }
            }
        }

        /// 
        /// It gets the command bind to the dependency property and execute it,
        /// where the ModelView related code is written.
        /// 
        static void Window_Closing(object sender, CancelEventArgs e)
        {
            ICommand closing = GetClosing(sender as Window);
            if (closing != null)
            {
                if (closing.CanExecute(null))
                {
                    closing.Execute(e);
                }
                else
                {
                     e.Cancel = true;
                }
            }
        }
    }

We have to also provided Getter and Setter methods for the properties, as it comes handy when accessing the property. As you see in the code that we have provided a PropertyChange CallBack ClosingChanged,  which gets call back whenever value of the dependency property changes.  In our case it gets fire once only, when windows get initializes.  Here is the trick we are playing.  We are attaching the Closing Event handler Window_Closing to the window where Closing dependency property is initializing.  Window_Closing event getting the Closing property of type ICommand from the window object using GetClosing() getter method.  Here it return the reference of ClosingCommand which defined in ViewModel.  Window doesn�t know about the ClosingCommand, it just check if it is executable then execute it via ICommand Interface.  ClosingCommand define the execute method as below:


    class ClosingCommand : ICommand
    {
        Func m_execute;


        public ClosingCommand(Func method_)
        {
            this.m_execute = method_;
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute(object parameter)
        {
            CancelEventArgs e = parameter as CancelEventArgs;
            EnumDialogResult msg = m_execute();

            if (msg == EnumDialogResult.Cancel)
            {
                e.Cancel = true;
            }

        }
    }

m_execute() is a type of Func<string,string , EnumDialogResult > which defined in MainViewModel.  In this example it ask user to choose an action and based on that window will be closed or keep it open.  For example user want save/don�t save the data before closing the window.  Or user would have clicked on close button accidently so s/he can click on cancel button.  Please download the code to see complete example from download link.

Using the ClosingProperty in window


<Window x:Class="ClosingBehaviour.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:dnp ="clr-namespace:ClosingBehaviour"
        dnp:WindowClosingBehaviour.Closing="{Binding Path=ClosingCommand}"
        Title="{Binding Path=Title, Mode=TwoWay}" Height="350" Width="525">

Finally

This is a great way to leverage events other than Button Click, in ViewModel.  Any kind of windows event can be handled in ViewModel using Dependency Properties.  I will write in-depth about Dependency Properties shortly.


Nitya Prakash Sharma has over 10 years of experience in .NET technology. He is currently working as Senior Consultant in industry. He is always keen to learn new things in Technology and eager to apply wherever is possible. He is also has interest in Photography, sketching and painting.

My Blog
Post Comment

COMMENTS