Monday, January 28, 2013

Simple PRISM MVVM way to display a dialog

Let us do this in three small steps, with a very simple example. Upon clicking a button, we like to open a dialog box which will prompt user to enter some data and then we will use this data, for now we'll just display this data back to the original window which opened this dialog. Simple!!

Following steps in this order is not needed. It is created for understanding what functionality that we are achieving at the end and what it is replacing and what it is buying us.



You can directly go to step three where we implement using prism, mvvm, interactivity.

Step One (from code behind by handling click event, no prism no mvvm yet)

In the first step let us just open a message box from withing code behind by implementing button click event handler (if from from design view we double click on the button, it will provide the skeleton button click event handler method).



<Button Content="Button"
                Height="23"
                HorizontalAlignment="Left"
                Margin="92,84,0,0"
                Name="button1"
                VerticalAlignment="Top"
                Width="75" 
                Click="button1_Click" />


// In the code behind 

private void button1_Click(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("Hello World");
        }


Well, the message box here is just for the sake of example, let's get rid of that and replace with a window. We then well open that window from code behind again. 


Now let's create a new window called MessageWindow.xaml which we will bring up as dialog instead of message box.



<Window x:Class="WpfApplicationDialogExmaple.MessageWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MessageWindow" mc:Ignorable="d"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        d:DesignHeight="303" d:DesignWidth="423"  Width="480" Height="305">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="50"></RowDefinition>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition Height="50"></RowDefinition>
            <RowDefinition Height="25"></RowDefinition>
        </Grid.RowDefinitions>
            <TextBlock Grid.Row="1" Height="47" HorizontalAlignment="Center" Name="textBlock1" VerticalAlignment="Top" Width="290" >
            Enter something for goodness sake:
            </TextBlock>
        <TextBox Grid.Row="2" Height="32" HorizontalAlignment="Center" Name="textBox1" VerticalAlignment="Top" Width="289" />
        <StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Center">
            <Button Content="OK" Height="23" HorizontalAlignment="Left" Name="btnOK" VerticalAlignment="Top" Width="75" Click="btnOK_Click" />
            <Button Content="Cancel" Height="23" HorizontalAlignment="Center" Name="btnCancel" VerticalAlignment="Top" Width="75" Click="btnCancel_Click" />
        </StackPanel>
    </Grid>
</Window>


// and in code behind we process the button click event like before. Notice these are just to display dialog boxes in straight forward simple way and not yet Prism or MVVM way. In step two and three we will do that.



public partial class MessageWindow : Window
    {
        public MessageWindow()
        {
            InitializeComponent();
        }

        public string Message { get; set; }


        private void btnOK_Click(object sender, RoutedEventArgs e)
        {
            Message = this.textBox1.Text;
            this.Close();
        }


        private void btnCancel_Click(object sender, RoutedEventArgs e)
        {
            Message = string.Empty;
            this.Close();
        }

    }


// and in the code behnind of the main window we simple bring up MessageWindow as dialog.


public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
           // MessageBox.Show("Hello World");

            MessageWindow mess = new MessageWindow();
            mess.ShowDialog();

            textBlock1.Text = mess.Message;
        }     
    }









Second Step (from code behind using mvvm, however no prism yet)

Now let's use ViewModel. Just created a MessageViewModel as follows and plugged in this view model for data binding for our two windows (main window and dialog window) as their data context.





    public class MessageViewModel : INotifyPropertyChanged
    {
        private string _message;
        public event PropertyChangedEventHandler PropertyChanged;
        private static MessageViewModel _instance = new MessageViewModel();

        private MessageViewModel()
        {
        }
       
        public static MessageViewModel Instance
        {
            get { return _instance; }
        }

        public string Message {
            get { return _message; }
            set
            {
                if (_message != value)
                {
                    _message = value;
                    OnPropertyChanged("Message");
                }
            }
        }

        private void OnPropertyChanged(string property)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }



After this in code behind of main window and message window we added data context. 

public MainWindow()
        {
            InitializeComponent();
            this.DataContext = MessageViewModel.Instance;
        }


And also for message window

public MessageWindow()
        {
            InitializeComponent();
            this.DataContext = MessageViewModel.Instance;
        }




In main window XAML, defined path for binding to Message property of the view model.


<TextBlock Height="138" HorizontalAlignment="Left" Margin="98,132,0,0" Name="textBlock1" VerticalAlignment="Top" Width="321" Text="{Binding Path=Message}" />
  

Up to this it is still using code behind, however at this point (in step 2) it is using view model.In step three we'll start using PRISM.



Step Three - A (using prism and mvvm)

First of all assuming we have prism librraies downloaded. We need to add reference to prism dll.
We'll use "command" from prism instead of handing click event from code behind. For this purpose besides adding the prism library reference, we'll add prism namespace in xaml and hook up to a command named DisplayDialogCommand which we'll define in out view model. Therefore the xaml of the main window will look like the following now.





<Window x:Class="WpfApplicationDialogExmaple.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prismCmd="clr-namespace:Microsoft.Practices.Prism.Commands;assembly=Microsoft.Practices.Prism"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Content="Button"
                Height="23"
                HorizontalAlignment="Left"
                Margin="92,84,0,0"
                Name="button1"
                VerticalAlignment="Top"
                Width="75" prismCmd:Click.Command="{Binding DisplayDialogCommand}" />
        <TextBlock Height="138" HorizontalAlignment="Left" Margin="98,132,0,0" Name="textBlock1" VerticalAlignment="Top" Width="321" Text="{Binding Path=Message}" />
    </Grid>
</Window>




So the view model code has been updated to add this new command definition (DisplayDialogCommand) as follows. We are taking advantage of DelegateCommand type which is part of prism library.




    public class MessageViewModel : INotifyPropertyChanged
    {
        private string _message;
        public event PropertyChangedEventHandler PropertyChanged;
        private static MessageViewModel _instance = new MessageViewModel();

        private MessageViewModel()
        {
            DisplayDialogCommand = new

DelegateCommand<string>(OnDisplayDialogCommand);
        }
       
        public static MessageViewModel Instance
        {
            get { return _instance; }
        }

        public string Message {
            get { return _message; }
            set
            {
                if (_message != value)
                {
                    _message = value;
                    OnPropertyChanged("Message");
                }
            }
        }


        public ICommand DisplayDialogCommand { get; set; }

        private void OnPropertyChanged(string property)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(property));
        }


        private void OnDisplayDialogCommand(string args)
        {
            MessageBox.Show("Hello Again, not intented but in progress ...."); 
            // TO BE REPLACED in STEP THREE B

// almost there, quite not there yet, because we do not want to do this, i.e
// open ui/view from within view-model.. so follow next steps ..
        }
    }


Step Three - B (continuation of Step Three)

In this step we'll replace the implementation of OnDisplaDialogCommand method above so that it creates and open up dialog window through prism's interactivity request, so that view model directly does not create any view and fully decoupled from creating view.


In order to implement this we'll use interactivity library from prism. Therefore add reference to prism interactivity dll as below.





Now let us add the following namespaces in main window xaml and add skeleton for interactivity triggers.



      xmlns:prismInter="clr-namespace:Microsoft.Practices.Prism.Interactivity.InteractionRequest;assembly=Microsoft.Practices.Prism.Interactivity"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

and in the body of xaml 

 <i:Interaction.Triggers>
           
 </i:Interaction.Triggers>


[Little gripe session : sadly PopupChildWindowAction is only in Silverlight!!]


Therefore, we can create a "custom trigger action". TriggerAction is an action that is executed upon trigger being fired.

http://msdn.microsoft.com/en-us/library/ff725477%28v=expression.40%29.aspx

Here is how to create TriggerAction and following this skeleton, we'll create our trigger action. We'll like to keep as simple as possible because seeing the steps and how everything is working together is the objective here.



public class PopupAction : TriggerAction<DependencyObject>
{
    public PopupAction()
    {
        // Insert code required for object creation below this point.
    }

    protected override void Invoke(object o)
    {
        // Insert code that defines what the Action will do when it is triggered or invoked.
    }
}




Following were done.

Created a PopupWindow.xaml where PopupWindow is inherited from Window class and will host any user control ( as well it will clear this upon close).

Created a sample PopupUserControl.xaml so that we can display this onto PopupWindow.

Finally implemented the "Invoke" method that is mentioned above (the method to override from TriggerAction class).





public class PopupAction : TriggerAction<DependencyObject>
    {
        public PopupAction() { }

        public UserControl PopupUserControl { get; set; }

        protected override void Invoke(object parameter)
        {
            var args = parameter as InteractionRequestedEventArgs;
            if(args != null)
            {
                Action callback = args.Callback;
                Notification notification = args.Context;

                if (notification != null)
                {
                    PopupWindow popupView = new PopupWindow { Title = notification.Title };

                    popupView.DataContext = notification;
                    popupView.Height = PopupUserControl.Height;
                    popupView.Width = PopupUserControl.Width;
                    popupView.Content = PopupUserControl;
                    popupView.ShowDialog();

                    EventHandler handler = null;
                    handler = (o, e) =>
                        {
                            popupView.Closed -= handler;
                            callback();
                        };

                    popupView.Closed += handler;
                }                 
            }
        }
    }
 


The above PopupAction will basically execute upon trigger event that would be raised from view model to notify the view that a popup is needed.




<Window x:Class="WpfApplicationDialogExmaple.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prismCmd="clr-namespace:Microsoft.Practices.Prism.Commands;assembly=Microsoft.Practices.Prism"
        xmlns:prismInter="clr-namespace:Microsoft.Practices.Prism.Interactivity.InteractionRequest;assembly=Microsoft.Practices.Prism.Interactivity"
        xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:local="clr-namespace:WpfApplicationDialogExmaple"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <i:Interaction.Triggers>
            <prismInter:InteractionRequestTrigger SourceObject="{Binding PromptUserRequest}">
                <local:PopupAction>
                    <local:PopupAction.PopupUserControl>
                        <local:PopupUserControl></local:PopupUserControl>             
                    </local:PopupAction.PopupUserControl>
                </local:PopupAction>
            </prismInter:InteractionRequestTrigger>
        </i:Interaction.Triggers>

        <Button Content="Button"
                Height="23"
                HorizontalAlignment="Left"
                Margin="92,84,0,0"
                Name="button1"
                VerticalAlignment="Top"
                Width="75"
                prismCmd:Click.Command="{Binding DisplayDialogCommand}">
                   
        </Button>
                <TextBlock Height="138" HorizontalAlignment="Left" Margin="98,132,0,0" Name="textBlock1" VerticalAlignment="Top" Width="321" Text="{Binding Path=Message}" />
    </Grid>
</Window>



And of course the PopupUserControl xaml will have now the TextBlock Text property bound to notification's Content.


<TextBlock Grid.Row="1" Height="47" HorizontalAlignment="Center" Name="textBlock1" VerticalAlignment="Top" Width="290" Text="{Binding Path=Content}">



And the view model looks like this now.



public class MessageViewModel : INotifyPropertyChanged
    {
        private string _message;
        public event PropertyChangedEventHandler PropertyChanged;
        private static MessageViewModel _instance = new MessageViewModel();

        private MessageViewModel()
        {
            DisplayDialogCommand = new DelegateCommand<string>(OnDisplayDialogCommand);
            PromptUserRequest = new InteractionRequest<Confirmation>();           
        }
       
        public static MessageViewModel Instance
        {
            get { return _instance; }
        }

        public string Message {
            get { return _message; }
            set
            {
                if (_message != value)
                {
                    _message = value;
                    OnPropertyChanged("Message");
                }
            }
        }


        public ICommand DisplayDialogCommand { get; set; }
        public InteractionRequest<Confirmation> PromptUserRequest { get; private set; }


        private void OnPropertyChanged(string property)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(property));
        }


        private void OnDisplayDialogCommand(string args)
        {
            this.PromptUserRequest.Raise(new Confirmation { Content = "Hello Wrold from prism based popup", Title = "Popup from prism" }, null);
        }
    }








The above exmaple uses prism and mvvm and uses interactivity to open a popop from view. This is done from one wpf aplication. Next we can also modularize this. In order to use prism it is not necessary that the appliction must be a modular one or should come up from shell as composite application. As we can see above, we have used numerous prism features to develope a non-modular, non-container based standalone mvvm application following prism guidelines and using prism library. However, in the next post let's take the last part (which is Step Three above - prism, mvvm, interactivity based popup application) and enhance it and at the same time we'll use prism shell, bootstrapper, modules to structure theapplication as a modular one. (I'll put the link to next post here ..)

3 comments:

  1. Do you have a source code for the MVVM dialog part? I've tried implementing the code here but I get errors.

    Thanks.

    ReplyDelete
  2. Got it to work. Now how do you handle when OK or Cancel button is clicked?

    ReplyDelete