Passing value from child window to parent window using WPF and MVVM pattern

12,088

Solution 1

My solution is to bind both the windows to the same ViewModel, then define a property to hold the resulting value for codes, lets call it CurrentSchoolCodes, Bind the label to this property. Make sure that CurrentSchoolCodes raises the INotifyPropertyChanged event. then in the DoUseSelectedSchoolNameItem set the value for CurrentSchoolCodes.

For properties in your models I suggest you to load them as they are required(Lazy Load patttern). I this method your property's get accessor checks if the related field is still null, loads and assigns the value to it. The code would be like this code snippet:

private ObservableCollection<SchoolModel> _schoolList;
public ObservableCollection<SchoolModel> SchoolList{
    get {
        if ( _schoolList == null )
            _schoolList = LoadSchoolList();
        return _schoolList;
    }
}

In this way the first time your WPF control which is binded to this SchoolList property tries to get the value for this property the value will be loaded and cached and then returned.

Note: I have to say that this kind of properties should be used carefully, since loading data could be a time consuming process. And it is better to load data in a background thread to keep UI responsive.

Solution 2

The Solution Sam suggested here is a correct one. What you didn't get is that you should have only one instance of you viewmodel and your main and child page should refer to the same one. Your viewmodel should be instanciated once: maybe you need a Locator and get the instance there... Doing like this the code in your ctor will fire once, have a look at the mvvmLight toolkit, I think it will be great for your usage, you can get rid of those Classes implementing ICommand too... You can find a great example of using that pattern here: http://blogs.msdn.com/b/kylemc/archive/2011/04/29/mvvm-pattern-for-ria-services.aspx basically what happens is this:

you have a Locator

public class ViewModelLocator 
{
    private readonly ServiceProviderBase _sp;

    public ViewModelLocator()
    {
        _sp = ServiceProviderBase.Instance;

        // 1 VM for all places that use it. Just an option
        Book = new BookViewModel(_sp.PageConductor, _sp.BookDataService); 
    }

    public BookViewModel Book { get; set; }
    //get { return new BookViewModel(_sp.PageConductor, _sp.BookDataService); }

    // 1 new instance per View 
    public CheckoutViewModel Checkout
    {
        get { return new CheckoutViewModel(_sp.PageConductor, _sp.BookDataService); }
    }
}

that Locator is a StaticResource, in App.xaml

<Application.Resources>
        <ResourceDictionary>
        <app:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
        </ResourceDictionary>
    </Application.Resources>

in your views you refer you viewmodels trough the Locator:

   DataContext="{Binding Book, Source={StaticResource Locator}}"

here Book is an instance of BookViewModel, you can see it in the Locator class

BookViewModel has a SelectedBook:

 private Book _selectedBook;
        public Book SelectedBook
        {
            get { return _selectedBook; }
            set
            {
                _selectedBook = value;
                RaisePropertyChanged("SelectedBook");
            }
        }

and your child window should have the same DataContext as your MainView and work like this:

<Grid Name="grid1" DataContext="{Binding SelectedBook}"> 
Share:
12,088
Shai
Author by

Shai

Updated on June 04, 2022

Comments

  • Shai
    Shai almost 2 years

    I have parent window which has textBox called "SchoolName", and a button called "Lookup school Name".

    That Button opens a child window with list of school names. Now when user selects school Name from child window, and clicks on "Use selected school" button. I need to populate selected school in parent view's textbox.

    Note: I have adopted Sam’s and other people’s suggestion to make this code work. I have updated my code so other people can simply use it.

    SelectSchoolView.xaml (Parent Window)

    <Window x:Class="MyProject.UI.SelectSchoolView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Parent" Height="202" Width="547">
    <Grid>
        <TextBox Height="23" Width="192" 
         Name="txtSchoolNames"  
         Text="{Binding Path=SchoolNames, UpdateSourceTrigger=PropertyChanged, 
         Mode=TwoWay}" 
         />
    
        <Label Content="School Codes" Height="28" HorizontalAlignment="Left" 
         Margin="30,38,0,0" Name="label1" VerticalAlignment="Top" />
        <Button Content="Lookup School Code" Height="30" HorizontalAlignment="Left" 
         Margin="321,36,0,0" Name="button1" VerticalAlignment="Top" Width="163" 
         Command="{Binding Path=DisplayLookupDialogCommand}"/>
    </Grid>
    </Window>
    

    SchoolNameLookup.xaml (Child Window for Look up School Name)

    <Window x:Class="MyProject.UI.SchoolNameLookup"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:toolkit="http://schemas.microsoft.com/wpf/2008/toolkit"
        Title="SchoolCodeLookup" Height="335" Width="426">
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="226*" />
            <RowDefinition Height="70*" />
        </Grid.RowDefinitions>
    
        <toolkit:DataGrid Grid.Row="0" Grid.Column="1"   x:Name="dgSchoolList" 
                              ItemsSource="{Binding Path=SchoolList}" 
                              SelectedItem="{Binding Path=SelectedSchoolItem, Mode=TwoWay}" 
                              Width="294"
                              AutoGenerateColumns="False"
                              CanUserAddRows="False" 
                              CanUserDeleteRows="False"
                              CanUserResizeRows="False" 
                              CanUserSortColumns="True" 
                              SelectionMode="Single">
    
            <Button Grid.Row="1" Grid.Column="1" Content="Use Selected School Name" 
             Height="23" Name="btnSelect" Width="131" Command="{Binding 
             Path=UseSelectedSchoolNameCommand}"  />
     </Grid>
    </Window>
    

    SchoolNameLookupViewModel

       private string _schoolNames;
       public string SchoolNames
       {
            get { return _schoolNames; }
            set
            {
                _schoolNames= value;
                OnPropertyChanged(SchoolNames);
            }
       }
    
       private ICommand _useSelectedSchoolNameCommand;
       public ICommand UseSelectedSchoolNameCommand{
       get
        {
        if (_useSelectedSchoolNameCommand== null)
            _useSelectedSchoolNameCommand= new RelayCommand(a => 
                DoUseSelectedSchollNameItem(), p => true);
                return _useSelectedSchoolNameCommand;
          }
        set
           {
             _useSelectedSchoolNameCommand= value;
           }
    
        }
    
        private void DoUseSelectedSchoolNameItem() {
            StringBuilder sfiString = new StringBuilder();
            ObservableCollection<SchoolModel> oCol = 
                    new ObservableCollection<SchoolModel>();
            foreach (SchoolModel itm in SchollNameList)
            {
                if (itm.isSelected) {
                    sfiString.Append(itm.SchoolName + "; ");
                    _schoolNames = sfiString.ToString();
                }
            }
                    OnPropertyChanged(SchoolNames);
        }
    
        private ICommand _displayLookupDialogCommand;
        public ICommand DisplayLookupDialogCommand
        {
            get
            {
                if (_displayLookupDialogCommand== null)
                    _displayLookupDialogCommand= new
                        RelayCommand(a => DoDisplayLookupDialog(), p => true);
                return _displayLookupDialogCommand;
            }
            set
            {
                _displayLookupDialogCommand= value;
            }
        }
    
        private void DoDisplayLookupDialog()
        {
            SchoolNameLookup snl = new SchoolNameLookup();
            snl.DataContext = this; //==> This what I was missing. Now my code works as I was expecting
            snl.Show();
        }
    
  • Shai
    Shai over 12 years
    I tried to combine ParentViewModel and SchoolNameLookupViewModel into one. But since I have constructure SchooNameLookup which loads school name in SchoolNameLookupView. So how do i handle this when ParentView does not use this constructor. Do you have any further suggestion?
  • 000
    000 over 12 years
    Personally I sometimes prefer using Lazy Load evaluation, I mean you can change the get accessor in your SchoolList property to look at its related field and if it is still null, then load it then. I hope this helps.
  • Shai
    Shai over 12 years
    I have revised code based on suggestion and asking the same question in different way. Please see my comment in DoUseSelectedSchoolNameItem() under SchoolNameLookupViewModel. Thank you
  • 000
    000 over 12 years
    I am just suspecious about the way you called OnPropertyChanged, propbably it should be like OnPropertyChanged("SchoolNames"). Note that in your code you pass the value of SchoolNames instead of the property name.
  • Shai
    Shai over 12 years
    Thank you for pointing out double quote mark. It was one of the cause of issue. But I am still having issue with making the textbox "txtSchoolNames" to get populated with selected shool name from child window via property OnPropertyChanged("SchoolNames"). I can see OnPropertyChaged is getting triggered but binding Path=SchoolNames in text box is not picking up that change. I found if works if I put text box in child window insread of parent, but it does not work if i put that text box in the parent window. Do you have any further suggestion?
  • 000
    000 over 12 years
    Your both parent and child windows should be bound to the same instance of ViewModel, have you done it? I mean as soon as you open the child window you need to set it's DataContext to the parent's viewmodel.
  • Shai
    Shai over 12 years
    Thank you Sam. I have added snl.DataContext = this; in DoDisplayLookupDialog(). Now it works.
  • mack
    mack about 5 years
    The link is dead.