Often when I’m designing an MVVM application, the following scenario comes up. A Window, having multiple children, sharing the same data source.
I’m trying to decide on the best way to implement a single datasource for all children. There are 3 options I can think of, all with their own advantages and disadvantages.
Example
The Window has two child UserControls, each in their own tab.
UI is linked up like this.
In order to keep modularity and provide them with data, the same design is reflected in ViewModels.
The MainViewModel is set up like this.
public class MainViewModel : ViewModelBase
{
private readonly ChildViewModelA _childViewModelA = new ChildViewModelA();
private readonly ChildViewModelB _childViewModelB = new ChildViewModelB();
public ChildViewModelA ChildViewModelA { get { return this._childViewModelA; } }
public ChildViewModelB ChildViewModelB { get { return this._childViewModelB; } }
}
The MainWindow instantiates the MainViewModel and sets DataContext of the children. Child controls are bound to data properties.
public partial class MainWindow : Window
{
private readonly MainViewModel _viewModel = new MainViewModel();
public MainWindow()
{
InitializeComponent();
}
public MainViewModel ViewModel { get { return this._viewModel; } }
}
<Window x:Class="WpfApplication3.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:WpfApplication3.View.Controls"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<TabControl>
<TabItem Header="Tab 1">
<controls:ChildViewA DataContext="{Binding ViewModel.ChildViewModelA}"/>
</TabItem>
<TabItem Header="Tab 2">
<controls:ChildViewB DataContext="{Binding ViewModel.ChildViewModelB}"/>
</TabItem>
</TabControl>
</Grid>
</Window>
To prevent every ViewModel from retrieving the same data from the database, I want to load data in the MainViewModel and provide the children. However, there are multiple ways of doing this.
Example 1: Using a setter method on the children
public class MainViewModel : ViewModelBase
{
...
private readonly FakeDataManager _fakeDataManager = new FakeDataManager();
public MainViewModel()
{
this.CurrentPerson = _fakeDataManager.GetNextPerson();
}
private Person _currentPerson;
public Person CurrentPerson
{
get { return this._currentPerson; }
set
{
this._currentPerson = value;
this.RaisePropertyChanged("CurrentPerson");
this.ChildViewModelA.SetPerson(this.CurrentPerson);
this.ChildViewModelB.SetPerson(this.CurrentPerson);
}
}
public class ChildViewModelA : ViewModelBase
{
private Person _currentPerson;
public Person CurrentPerson
{
get { return this._currentPerson; }
set
{
this._currentPerson = value;
this.RaisePropertyChanged("CurrentPerson");
}
}
}
Easy to implement, however quickly get's hard to remain. Not a lot of code reuse. No loose coupling. Should not use this.
Example 2: Putting data in a container
public class MainViewDataContainer : INotifyPropertyChanged
{
private Person _currentPerson;
public Person CurrentPerson
{
get { return this._currentPerson; }
set
{
this._currentPerson = value;
this.RaisePropertyChanged("CurrentPerson");
}
}
}
public class MainViewModel : ViewModelBase
{
...
private readonly FakeDataManager _fakeDataManager = new FakeDataManager();
private readonly MainViewDataContainer _dataContainer = new MainViewDataContainer();
public MainViewModel()
{
this._childViewModelA = new ChildViewModelA(_dataContainer);
this._childViewModelB = new ChildViewModelB(_dataContainer);
this._dataContainer.CurrentPerson = _fakeDataManager.GetNextPerson();
}
public class ChildViewModelA : ViewModelBase
{
private readonly MainViewDataContainer _dataContainer;
public ChildViewModelA(MainViewDataContainer dataContainer)
{
this._dataContainer = dataContainer;
}
public MainViewDataContainer DataContainer { get { return this._dataContainer; } }
}
Easier to maintain, more code reuse. Looser coupling.
Example 3: Storing in MainViewModel Properties and provide to children through an interface
public interface IMainDataProvider
{
Person CurrentPerson { get; }
}
public class MainViewModel : ViewModelBase, IMainDataProvider
{
private readonly ChildViewModelA _childViewModelA;
private readonly ChildViewModelB _childViewModelB;
private readonly FakeDataManager _fakeDataManager = new FakeDataManager();
public MainViewModel()
{
this._childViewModelA = new ChildViewModelA(this);
this._childViewModelB = new ChildViewModelB(this);
this.CurrentPerson = _fakeDataManager.GetNextPerson();
}
private Person _currentPerson;
public Person CurrentPerson
{
get { return this._currentPerson; }
set
{
this._currentPerson = value;
this.RaisePropertyChanged("CurrentPerson");
}
}
}
public class ChildViewModelA : ViewModelBase
{
private readonly IMainDataProvider _dataProvider;
public ChildViewModelA(IMainDataProvider dataProvider)
{
this._dataProvider = dataProvider;
}
public IMainDataProvider DataProvider { get { return this._dataProvider; } }
}
Yet again easier to maintain, more code reuse. Loose coupling.
Example 3 seems to be the best solution, however is this true? How do you think about this? Are there better ways to solve this issue?