Saturday, May 10, 2014

WPF Child Window with MVVM Design Pattern

In this article I'll discuss how to develop reusable WPF child window using MVVM designed pattern.

Prerequisites :

1. Basic Knowledge of MVVM Design Pattern.
2. Basic Knowledge of Action<T> Delegate in C#.
3. Basic Knowledge of XAML development.
4. Visual Studio 2013 (You can use another version of VS).
5. Microsoft PRISM 4.1 - Microsoft.Practices.Prism.dll (We use this prism library to implement the WPF delegate command).
6. Basic knowledge about Singleton design pattern.

In this article, I will demonstrate how to pass the parameters from parent window to child window and also from child window to parent window.

So now we will start our journey .....  :)

1. Create new WPF project called "MVVMChildWindow" using your visual studio IDE.

2. Delete "MainWindow.xaml" page your solution. Add new Window (WPF) called "MainView.xaml" to your solution.



3. Open app.xaml file and update StartupUri as "MainView.xaml".


4. Add new class called "Person" to your project and implement the  class as bellow.
















Also add another class called "BaseViewModel" and implement that class as bellow.










5. Add new folder called "ChildWindow" to your solution. After that add another two folder called "View" and "ViewModel" to "ChildWindow" folder. Now your folder hierarchy should be like this.










(Anyway you don't need to follow same hierarchy and naming convention. But as a best practice I encourage you to  follow the similar hierarchy and naming convention.)

6. Add new class called "AddUserViewModel" to ViewModel folder. and Implement the class as bellow.

using Microsoft.Practices.Prism.Commands;
using System;
using System.Collections.Generic;
using System.Linq;

namespace MVVMChildWindow.ChildWindow.ViewModel
{
    public class AddUserViewModel : BaseViewModel
    {
        #region Events

        public event Action<Person> Closed;

        #endregion

        #region Constructor
        public AddUserViewModel(int personId)
        {
            persons = new List<Person>()
            {
                new Person(){Id=1,Address="Blk 244/A",Email="email1@gmail.com",FirstName="FirstNameA",LastName="LastNameA"},
                new Person(){Id=2,Address="Blk 244/B",Email="email2@gmail.com",FirstName="FirstNameB",LastName="LastNameB"},
                new Person(){Id=3,Address="Blk 244/C",Email="email3@gmail.com",FirstName="FirstNameC",LastName="LastNameC"},
                new Person(){Id=4,Address="Blk 244/D",Email="email4@gmail.com",FirstName="FirstNameD",LastName="LastNameD"},
                new Person(){Id=5,Address="Blk 244/E",Email="email5@gmail.com",FirstName="FirstNameE",LastName="LastNameE"},
                new Person(){Id=6,Address="Blk 244/F",Email="email6@gmail.com",FirstName="FirstNameF",LastName="LastNameF"},
                new Person(){Id=7,Address="Blk 244/G",Email="email7@gmail.com",FirstName="FirstNameG",LastName="LastNameG"},
                new Person(){Id=8,Address="Blk 244/H",Email="email8@gmail.com",FirstName="FirstNameH",LastName="LastNameH"},
            };

            okCommand = new DelegateCommand(SavePerson);

            this.PersonId = personId;

            Init();
        } 
        #endregion

        #region Public Properties

        public int PersonId { get; set; }

        private string firstName;
        public string FirstName
        {
            get { return firstName; }
            set 
            { 
                firstName = value;
                RaisePropertyChanged("FirstName");
            }
        }

        private string lastName;
        public string LastName
        {
            get { return lastName; }
            set
            {
                lastName = value;
                RaisePropertyChanged("LastName");
            }
        }

        private string address;
        public string Address
        {
            get { return address; }
            set
            {
                address = value;
                RaisePropertyChanged("Address");
            }
        }

        private string email;
        public string Email
        {
            get { return email; }
            set
            {
                email = value;
                RaisePropertyChanged("Email");
            }
        }

        private List<Person> persons;
        public List<Person> Persons
        {
            get { return persons; }
            set { persons = value; }
        }
        

        #endregion

        #region Command Properties

        private DelegateCommand okCommand;
        public DelegateCommand OkCommand
        {
            get { return okCommand; }
        }

        #endregion

        #region Private Methods
        private void SavePerson()
        {
            if (Closed != null)
            {
                var person = new Person()
                {
                    Address = address,
                    Email = email,
                    FirstName = firstName,
                    LastName = lastName,
                    Id = PersonId
                };

                Closed(person);
            }
        }

        private void Init()
        {
            var person = persons.FirstOrDefault(p => p.Id == PersonId);
            FirstName = person.FirstName;
            RaisePropertyChanged("FirstName");
            LastName = person.LastName;
            RaisePropertyChanged("LastName");
            Email = person.Email;
            RaisePropertyChanged("Email");
            Address = person.Address;
            RaisePropertyChanged("Address");
        }

        #endregion
    }
}

Here you can see that, we have declared event action delegate named by Changed. When SavePerson method called, Changed event action will fire and  all the objects who registered this event will  notified with modified person data..

7.  Add new UserControll (WPF) called "AddUserView.xaml" into view folder and implement the view as bellow.

<UserControl x:Class="MVVMChildWindow.ChildWindow.View.AddUserView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             Width="450"
             Height="200">
    <Grid Background="#FFB0D1EC" Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="5"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="5"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="5"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="5"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="5"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="5"/>
        </Grid.RowDefinitions>
        
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="150"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        
        
        <TextBlock Grid.Row="1" Text="First Name :" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="15,0,0,0"/>
        <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding FirstName,Mode=TwoWay}" Width="200" Height="25" VerticalAlignment="Center" HorizontalAlignment="Left"/>

        <TextBlock Grid.Row="3" Text="Last Name :" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="15,0,0,0"/>
        <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding LastName,Mode=TwoWay}" Width="200" Height="25" VerticalAlignment="Center" HorizontalAlignment="Left"/>

        <TextBlock Grid.Row="5" Text="Address :" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="15,0,0,0"/>
        <TextBox Grid.Row="5" Grid.Column="1" Text="{Binding Email,Mode=TwoWay}" Width="200" Height="25" VerticalAlignment="Center" HorizontalAlignment="Left"/>

        <TextBlock Grid.Row="7" Text="Email :" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="15,0,0,0"/>
        <TextBox Grid.Row="7" Grid.Column="1" Text="{Binding Address,Mode=TwoWay}" Width="200" Height="25" VerticalAlignment="Center" HorizontalAlignment="Left"/>

        <Button Grid.Row="9" Grid.Column="1" Width="150" HorizontalAlignment="Left" Content="OK" Command="{Binding OkCommand}"/>
    </Grid>
</UserControl>


8. Now add new folder called "Common" to your project and add new class called "ChildWindowManager"  into newly created folder. Implement the class as bellow.

using System.Windows;

namespace MVVMChildWindow.Common
{
    public class ChildWindowManager : BaseViewModel
    {
        public ChildWindowManager()
        {
            WindowVisibility = Visibility.Collapsed;
            XmlContent = null;
        }

        //Singleton pattern implementation
        private static ChildWindowManager instance;
        public static ChildWindowManager Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new ChildWindowManager();
                }
                return instance;
            }
        }

        #region Public Properties

        private Visibility windowVisibility;
        public Visibility WindowVisibility
        {
            get { return windowVisibility; }
            set 
            { 
                windowVisibility = value;
                RaisePropertyChanged("WindowVisibility");
            }
        }

        private FrameworkElement xmlContent;
        public FrameworkElement XmlContent
        {
            get { return xmlContent; }
            set 
            { 
                xmlContent = value;
                RaisePropertyChanged("XmlContent");
            }
        }
        
        #endregion

        #region Public Methods

        public void ShowChildWindow(FrameworkElement content)
        {
            XmlContent = content;
            RaisePropertyChanged("XmlContent");
            WindowVisibility = Visibility.Visible;
            RaisePropertyChanged("WindowVisibility");
        }

        public void CloseChildWindow()
        {
            WindowVisibility = Visibility.Collapsed;
            RaisePropertyChanged("WindowVisibility");
            XmlContent = null;
            RaisePropertyChanged("XmlContent");
        } 

        #endregion
    }
}


You can see that, We have used singleton designed pattern to implement this class.

9. Now select the ChildWindow folder and add new class called "ChildWindowView". Implement the class as bellow.

























10. Add new class called "MainViewModel" to project. Implement the class as below.

using Microsoft.Practices.Prism.Commands;
using MVVMChildWindow.ChildWindow;

namespace MVVMChildWindow
{
    public class MainViewModel:BaseViewModel
    {
        #region Construction
        public MainViewModel()
        {
            showChildWindowCommand = new DelegateCommand(ShowChildWindow);
        } 

        #endregion

        #region Public Properties

        public int PersonId { get; set; }

        private string firstName;
        public string FirstName
        {
            get { return firstName; }
            set
            {
                firstName = value;
                RaisePropertyChanged("FirstName");
            }
        }

        private string lastName;
        public string LastName
        {
            get { return lastName; }
            set
            {
                lastName = value;
                RaisePropertyChanged("LastName");
            }
        }

        private string address;
        public string Address
        {
            get { return address; }
            set
            {
                address = value;
                RaisePropertyChanged("Address");
            }
        }

        private string email;
        public string Email
        {
            get { return email; }
            set
            {
                email = value;
                RaisePropertyChanged("Email");
            }
        }

        private DelegateCommand showChildWindowCommand;
        public DelegateCommand ShowChildWindowCommand
        {
            get { return showChildWindowCommand; }
        } 

        #endregion

        #region Private Methods

        private void ShowChildWindow()
        {
            var childWindow = new ChildWindowView();
            childWindow.Closed += (r =>
                {
                    FirstName = r.FirstName;
                    RaisePropertyChanged("FirstName");

                    LastName = r.LastName;
                    RaisePropertyChanged("LastName");

                    Email = r.Email;
                    RaisePropertyChanged("Email");

                    Address = r.Address;
                    RaisePropertyChanged("Address");
                });
            childWindow.Show(1);
        } 

        #endregion   
    }
}


11. Add following XAML code to MainView.xaml.

<Window x:Class="MVVMChildWindow.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainView">
    <Grid Margin="5">

        <ContentControl
            VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="Transparent"
            VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="50"/>
                    <RowDefinition Height="*"/>
                </Grid.RowDefinitions>

                <Grid Grid.Row="0" Margin="0,20,0,0">
                    <Button Content="Search Person" Command="{Binding ShowChildWindowCommand}" Width="150"/>
                </Grid>
                <Grid Grid.Row="1" Margin="0,20,0,0">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="5"/>
                        <RowDefinition Height="30"/>
                        <RowDefinition Height="5"/>
                        <RowDefinition Height="30"/>
                        <RowDefinition Height="5"/>
                        <RowDefinition Height="30"/>
                        <RowDefinition Height="5"/>
                        <RowDefinition Height="30"/>
                        <RowDefinition Height="5"/>
                    </Grid.RowDefinitions>

                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="150"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>

                    <TextBlock Grid.Row="1" Text="First Name :" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="15,0,0,0"/>
                    <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding FirstName,Mode=TwoWay}" Width="300" Height="25" VerticalAlignment="Center" HorizontalAlignment="Left"/>

                    <TextBlock Grid.Row="3" Text="Last Name :" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="15,0,0,0"/>
                    <TextBox Grid.Row="3" Grid.Column="1" Text="{Binding LastName,Mode=TwoWay}" Width="300" Height="25" VerticalAlignment="Center" HorizontalAlignment="Left"/>

                    <TextBlock Grid.Row="5" Text="Address :" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="15,0,0,0"/>
                    <TextBox Grid.Row="5" Grid.Column="1" Text="{Binding Email,Mode=TwoWay}" Width="300" Height="25" VerticalAlignment="Center" HorizontalAlignment="Left"/>

                    <TextBlock Grid.Row="7" Text="Email :" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="15,0,0,0"/>
                    <TextBox Grid.Row="7" Grid.Column="1" Text="{Binding Address,Mode=TwoWay}" Width="300" Height="25" VerticalAlignment="Center" HorizontalAlignment="Left"/>

                </Grid>
            </Grid>
        </ContentControl>

        <Border Name="childWindow" Visibility="{Binding WindowVisibility}" Background="#88000000" >
            <ContentControl Content="{Binding XmlContent}" HorizontalAlignment="Center" VerticalAlignment="Center" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"></ContentControl>
        </Border>
        
    </Grid>
</Window>

12. Go to MainView.xaml.cs class and add following code to class constructor.





















13. Now build your project and run the application.If everything is going fine , you will see the following interface.

















14. Click on Search Person button. Then child window will be opened.


















15. You can edit person detail if you want and click on OK button. Then child window will be closed and person data will be appeared in main window.




















Happy Codding !!!!!!!!!!!!!!!!!!!!!!