I don't know what is the correct way to create a viewmodel relationship if I use the approach that Views create instances of ViewModel and ViewModel has no reference to View.
Suppose we have ChildView control that creates an instance of its ViewModel that has SaveCommand.
<UserControl x:Class="App.ChildView" ...>
<UserControl.DataContext>
<local:ChildViewModel/>
</UserControl.DataContext>
<!-- some controls -->
</UserControl>
public class ChildViewModel
{
public RelayCommand SaveCommand { get; set; }
public ChildViewModel()
{
SaveCommand = new RelayCommand(SaveExecute);
}
private void SaveExecute()
{
Debug.WriteLine("Child data has been saved.");
}
}
Now, I put two controls in the parent view and want to execute SaveCommand on all children.
<Window x:Class="App.ParentView" ...>
<Window.DataContext>
<local:ParentViewModel/>
</Window.DataContext>
<Grid>
<local:ChildView x:Name="child1"/>
<local:ChildView x:Name="child2"/>
<Button Content="Save All" Command="{Binding ParentSaveCommand}">
<Grid/>
</Window>
public class ParentViewModel
{
public RelayCommand ParentSaveCommand { get; set; }
public ParentViewModel()
{
ParentSaveCommand = new RelayCommand(ParentSaveExecute);
}
private void ParentSaveExecute()
{
Debug.WriteLine("Saving all has started...");
// <childVM1>.SaveCommand.Execute();
// <childVM2>.SaveCommand.Execute();
// From where should I get ChildViewModel?
}
}
How correctly should I refer to child's ViewModel?
I found possible solutions:
.
Or maybe it's the wrong approach and ParentViewModel should create a ChildViewModel instances, and than ParentView should set DataContext for child1 and child2 (by binding of course)?
I mostly always assign DataContext in code behind so I can inject dependencies to view model constructor. My approach is to pass parent view model to child view models via constructor. In the case you have I would create a SaveAll event in parent view model and have child view models subscribe to it.
Edit: I don't use any framework for what I mentioned. To give you a general idea on how I would handle SaveAll in Child view models -
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Child1ViewModel = new Child1ViewModel(this); // inject parent view model to child view model
//Child2ViewModel = new Child2ViewModel(this);
}
public event SaveAllEventHandler SaveAll; // Child view models can subscribe to this event
// ...
}
public class Child1ViewModel : ViewModelBase
{
public Child1ViewModel(MainViewModel parentViewModel)
{
parentViewModel.SaveAll += OnSaveAll;
}
private void OnSaveAll(object sender, SaveAllEventArgs e)
{
//
}
}
I hope that clarifies it a bit :)
Edit2: Like I had mentioned in my original response, for this to work you would need to set the DataContexts in MainWindow's constructor in code behind (and not in xaml).
Like RTF mentioned, for these types of operations it is generally an easier approach to have the parent VM create the children VMs so that the parent can maintain a reference to the children VMs. However, you can definitely do what you want without architectural changes.
In the view:
<Button
Content="Save All"
Height="37" Command="{Binding SaveAllCommand}">
<Button.CommandParameter>
<MultiBinding Converter="{StaticResource PassThroughConverter }">
<Binding Path=“DataContext” ElementName="child1"/>
<Binding Path=“DataContext” ElementName="child2"/>
</MultiBinding>
</Button.CommandParameter>
</Button>
The Converter:
public class PassThroughConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.ToList();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
In parent view model:
public class ParentViewModel
{
public RelayCommand<object> ParentSaveCommand { get; set; }
public ParentViewModel()
{
this.ParentSaveCommand = new RelayCommand<object>(obj => this.ParentSaveExecute(obj));
}
private void ParentSaveExecute(object items)
{
foreach (var item in (ChildViewModel[])items)
{
item.SaveCommand.Execute();
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With