My Master's Thesis Problems and solutions encountered…

22Jul/1016

Open Child Window in a MVVM’ish design pattern

This post explains how to open a ChildWindow, pass data to the ChildWindow, and how to pass data back from the ChildWindow to the MainPage, using a MVVM Design pattern in a Silverlight 4 application.

The method I am using is not strickly MVVM, because I actually create my ChildWindows in the ViewModel, which is usually a no-go. But considering the endless Googling after alternatives only to find guides that use all kinds of toolkits in order for a ChildWindow to appear (see fx., here, here and (the one that takes the price in length and complexity) here), I consider this workaround the best way to do it. I hope and expect that an easier and strickly MVVM solution will be possible with Silverlight 5.

This post is just meant as a proof of concept, and is based on a very simple example: The MainPage.xaml contains two textboxes, Name and Address, respectively, and the ChildWindow, the same. When the user enters their Name in the MainPage and presses the button, the ChildWindow appears, with the Name value entered (see picture below). The user can then enter an address in the ChildWindow and press the button, and return to the MainPage, where the value has been inserted in to the Address textbox of the MainPage.

ChildWindow, MVVM

So. The solution is simple. Just create the ChildWindow in the View Model project, so you can parse data through the ChildWindow and MainPage. Then create properties for each of the 4 textboxes (2 for each), and create the commands for the buttons, that will open/close the Child Window, and connect the properties. Notice that the ChildWindow does not have a ViewModel, and everything thus happens in the ViewModel of the MainPage:

Step 1: Create a new ChildWindow in your ViewModel. Right-click on project >> Add >> Silverlight ChildWindow. I have named mine MyChildWindow.xaml.

Step 2: Create a ViewModel for your MainPage. I have named mine MainPage_ViewModel.cs.

Step 3: Connect the MainPage.xaml.cs and MyChildWindow.xaml.cs to the MainPage_ViewModel.cs:

MainPage.xaml.cs:


namespace View
{
    public partial class MainPage : UserControl
    {

        public MainPage()
        {
            InitializeComponent();
            this.DataContext = new MainPage_ViewModel();
         }
     }
}

MyChildWindow.xaml.cs:


namespace ViewModel
{
    public partial class MyChildWindow : ChildWindow
    {
        public MyChildWindow(MainPage_ViewModel ma)
        {
            InitializeComponent();
            this.DataContext = ma;
        }

    }
}

Step 4: Create properties for the textboxes of the MainPage and ViwModel (both have two textboxes), in the MainPage_ViewModel.cs:

using System.ComponentModel;
using System.Collections.ObjectModel;
using ViewModel; 

namespace ViewModel
{
    public class MainPage_ViewModel : INotifyPropertyChanged
    {
//Properties of Mainpage
private string myNameVM = "";
        public string MyNameVM
        {

            get { return myNameVM; }
            set {
                myNameVM = value;
                RaisePropertyChanged("myNameVM");
            }
        }

        private string myAddressVM = "";
        public string MyAddressVM
        {

            get { return myAddressVM; }
            set
            {
                myAddressVM = value;
                RaisePropertyChanged("MyAddressVM");
            }
        }

//Properties of ChildWindow
        private string myNameCW = "";
        public string MyNameCW
        {

            get { return myNameCW; }
            set
            {
                myNameCW = value;
                RaisePropertyChanged("MyNameCW");
            }
        }

        private string myAddressCW = "";
        public string MyAddressCW
        {

            get { return myAddressCW; }
            set
            {
                myAddressCW = value;
                RaisePropertyChanged("MyAddressCW");
            }
        }

          //EventHandler
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string propertyname)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
            }
        }
    }

 }

(Step 5): If you have not created one eralier, you will need to create a DelegateCommand class in your ViewModel, that will handle the button commands needed for the next step. So. Create a new class, name it DelegateCommand.cs, and insert the following:


using System;
using System.Windows.Input;

namespace ViewModel
{
    public class DelegateCommand : ICommand //
    {
        private Predicate<object> _canExecute;
        private Action<object> _method;
        public event EventHandler CanExecuteChanged;

        public DelegateCommand(Action<object> method)
            : this(method, null)
        {
        }

        public DelegateCommand(Action<object> method, Predicate<object> canExecute)
        {
            _method = method;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            if (_canExecute == null)
            {
                return true;
            }

            return _canExecute(parameter);
        }

        public void Execute(object parameter)
        {
            _method.Invoke(parameter);
        }

        protected virtual void OnCanExecuteChanged(EventArgs e)
        {
            var canExecuteChanged = CanExecuteChanged;

            if (canExecuteChanged != null)
                canExecuteChanged(this, e);
        }

        public void RaiseCanExecuteChanged()
        {
            OnCanExecuteChanged(EventArgs.Empty);
        }
    }
}

Step 6: Create the commands in the MainPage_ViewModel.cs, that you will bind to the buttons in the MainPage.xaml and MyChildWindow, respectively.

using System.ComponentModel;
using System.Collections.ObjectModel;
using ViewModel; 

namespace ViewModel
{
    public class MainPage_ViewModel : INotifyPropertyChanged
    {
//Properties of Mainpage
private string myNameVM = "";
        public string MyNameVM
        {
            get { return myNameVM; }
            set {myNameVM = value;
                RaisePropertyChanged("myNameVM");
                 }
            }

        private string myAddressVM = "";
        public string MyAddressVM
        {

            get { return myAddressVM; }
            set
            { myAddressVM = value;
                RaisePropertyChanged("MyAddressVM");
            }
        }

//Properties of ChildWindow
        private string myNameCW = "";
        public string MyNameCW
        {

            get { return myNameCW; }
            set
            {  myNameCW = value;
                RaisePropertyChanged("MyNameCW");
            }
        }

        private string myAddressCW = "";
        public string MyAddressCW
        {

            get { return myAddressCW; }
            set
            {  myAddressCW = value;
                RaisePropertyChanged("MyAddressCW");
            }
        }

         //When the button is pressed in MainPage, executes method ExecuteOpenChildWindow
        private DelegateCommand _openChildWindow;
        public DelegateCommand OpenChildWindow
        {
            get
            {
                if (_openChildWindow == null)
                    _openChildWindow = new DelegateCommand(executeOpenChildWindow);

                return _openChildWindow;
            }
        }

        // New instance of ChildWindow. Sets the NameProperty of the ChildWindow equal to the Name entered in the MainPage.
        MyChildWindow cw;
        private void executeOpenChildWindow(object parameter)
        {
            cw = new MyChildWindow(this);
            MyNameCW = MyNameVM;
            cw.Show();
        }

      //When OK-button is pressed in ChildWindow
        private DelegateCommand _okChildWindow;
        public DelegateCommand OkChildWindow
        {
            get {
                if (_okChildWindow == null)
                    _okChildWindow = new DelegateCommand(OkSaveChildWindow);

                return _okChildWindow;
            }
        }
        //MainPage Address property is set to the value entered in the address textbox in Child Window. Child Window is closed.
        private void OkSaveChildWindow(object parameter)
        {
            MyAddressVM = MyAddressCW;
            cw.Close();
        }

          //EventHandler
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string propertyname)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
            }
        }
  }
}

Step 7: Add the textboxes in the MainPage.xaml and MyChildWindow.xaml, and bind them to the properties created in MainPage_ViewModel. Also Create the buttons, that will bind to the commands.

MainPage.xaml:

<StackPanel>

        <Grid Margin="0 10 0 5" Width="350">

    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>

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

    </Grid.ColumnDefinitions>

    <!--TextBlocks-->
    <TextBlock Text="Name:" TextWrapping="Wrap" Margin="5,5,0,5" Grid.Row="0"  />
    <TextBlock Text="Address:" Grid.Row="1" Grid.Column="0"  />

   <!--TextBox, where the users enters data. Binds to the properties of MainPage_ViewModel-->
  <TextBox Text="{Binding MyNameVM, Mode=TwoWay}" Grid.Row="0" Grid.Column="1"/>
  <TextBox Text="{Binding MyAddressVM, Mode=TwoWay}"  Grid.Row="1" Grid.Column="1"/>

    <Button Content="Open Child Window"
            VerticalAlignment="Center"
            HorizontalAlignment="left"
            Width="auto"
            Margin="5"
            Grid.Row="2"
            Command="{Binding OpenChildWindow}" <!--Binds to CommandDelegate from the ViewModel -->
        />
        </Grid>
    </StackPanel>

MyChildWindow.xaml:


    <Grid Margin="0 30 0 5" Width="350">

        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="205*"/>

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

        </Grid.ColumnDefinitions>

        <TextBlock Text="Name: " Grid.Row="0" />
        <TextBlock Text="Address:" Grid.Row="1" Grid.Column="0"  />

        <!-- TextBoxes are bind to the properties from the ViewModel.  -->
        <TextBox x:Name="InputName" Text="{Binding MyNameCW, Mode=TwoWay}" Grid.Row="0" Grid.Column="1" Height="20"/>
        <TextBox x:Name="OutputAddress" Text="{Binding MyAddressCW, Mode=TwoWay}" Grid.Row="1" Grid.Column="1" Height="20"/>

       <!-- Button comand bind to CommandDelegate from ViewModel -->
      <Button x:Name="OKButton" Command="{Binding OkChildWindow}" Content="OK" Width="75" Height="23" Margin="0,12,79,0" Grid.Row="2" Grid.Column="1"/>
    </Grid>

And that's it! :)

Comments (16) Trackbacks (2)
  1. You lost me at: ChildWindow in you ViewModel. What about separation of concerns?

  2. I know, the method is not strickly MVVM, but I find this to be the best alternative to the existing methods, that rely on toolkits and/or .dll files only shipped with Expression. So for now, this solution works for me, but I do realize the un-MVVM’ish core of it.

  3. nice post. thanks.

  4. this post is very usefull thx!

  5. ChildWindow is not available in WPF and XBAP
    how could i do that in WPF & XBAP application..

    public partial class MyChildWindow : ChildWindow

    Thanks

  6. Sorry for my bad english. Thank you so much for your good post. Your post helped me in my college assignment, If you can provide me more details please email me.

  7. Just…..no. Forget about breaking MVVM, the problem with this approach is that it violates some basic principles of OOD like encapsulation, separation of concerns and coupling. A developer is better off fudging on MVVM and instantiating a view directly in a viewmodel instead of massacring a class like this.

  8. VoiceOf Reason, can you please explain your concern. It will be helpful in understanding the shortcomings of the post if any which i am not able to figure out till now.

  9. Hello Michael,
    Can u please explore how we can implement seprate viewmodel for child window. actually i am implement ur code in my project but face some problem that unable to do validation. because when i check all validation for main window than child window validation fire at same time.

    can u please help me out
    thanks
    john

  10. I agree with launching a child window from VM , but associating the datacontext of the child window view with main page VM defeats the purpose. It would be better to handle the closing event, capture the child window as Sender, and get values from it.

  11. public void SearchEvent()
    {
    Training.Views.SearchEvent cw = new SearchEvent(); // Child window
    cw.Closed+=(s,e) =>
    {
    SearchEvent se = s as SearchEvent;
    if ((bool)se.DialogResult)
    {
    var vm = se.Resources["SearchModel"] as SearchEventViewModel;
    EventName = vm.SelectedRecord.eventName;
    SelectedEvent = vm.SelectedRecord;
    }
    };
    cw.Show();
    }

  12. this is not a mvvm

  13. ViewModel know NOTHING of View, so you can’t create a view in viewmodel, this solution is not MVVM.

  14. Its violation of MVVM.

  15. totally agree, this is VIOLATION OF MVVM! you have to replace that code where you create a MyChildWindow in the MainPage_ViewModel by something else. Does somebody else know it?

  16. I think one approach is to replace that code by the proper operations of the Messenger class, but I don’t know how to do that, because when using the Messaging in the end I Need to open the child Windows somewhere else….


Leave a comment



× 4 = twelve