Hi I am designing an application using WPF4, EF and MVVM. I want to be able to create reusable UserControls that I can use in multiple windows in the application, and have them draw data from the same source.
Lets say I have a GraphView component and a TableView component that can appear on the same page or in different places in the application, and I want them to both reflect the same col开发者_JAVA百科lection of filtered EF entities. MVVM common practice seems to require that each view has its own viewmodel. But should I be be using a joint viewmodel and bind both to it, so if you change the data or filter, both would update simultaneously? If not how should I handle this?
Thanks for any advice!
One approach could be to have two ViewModels, one for each of your Views/UserControls, and then nest them into some top or higher level ViewModel. If, for example, both Views reside in a MainWindow View, it could look like this:
public class MainWindowViewModel
{
public MainWindowViewModel(IRepository repository)
{
SharedUserControlData sharedData = new SharedUserControlData()
{
MyCollection = new ObservableCollection<MyEntity>(
repository.GetMyEntities()),
// instantiate other shared data properties
}
UserControl1ViewModel = new UserControl1ViewModel(sharedData);
UserControl2ViewModel = new UserControl2ViewModel(sharedData);
}
public UserControl1ViewModel UserControl1ViewModel { get; private set; }
public UserControl2ViewModel UserControl2ViewModel { get; private set; }
// more stuff...
}
You have a SharedUserControlData
class which contains properties both views can bind to:
public class SharedUserControlData : INotifyPropertyChanged
{
public ObservableCollection<MyEntity> MyCollection { get; set; }
// other properties
// INotifyPropertyChanged implementation
}
And the ViewModels of the UserControls get those data injected:
public class UserControl1ViewModel
{
public UserControl1ViewModel(SharedUserControlData data)
{
SharedUserControlData = data;
}
public SharedUserControlData SharedUserControlData { get; private set; }
// more stuff
}
// and the same for UserControl2ViewModel
Your UserControl Views are bound to the ViewModels by a DataTemplate:
<DataTemplate DataType="{x:Type vm:UserControl1ViewModel}" >
<v:UserControl1View />
</DataTemplate>
// and the same for UserControl2ViewModel
And some controls inside of the UserControls are bound then to SharedUserControlData.MyCollection
and other properties of the UserControlViewModels. The DataContext of the MainWindow is the MainWindowViewModel:
IRepository repository = new MyRepository(); // or use Dependency Injection
MainWindow window = new MainWindow();
MainWindowViewModel viewModel = new MainWindowViewModel(repository);
window.DataContext = viewModel;
In the XAML of your MainWindow we bind the UserControls to the nested ViewModels of the MainWindow's DataContext (which is the MainWindowViewModel):
<StackPanel>
<v:UserControl1View DataContext="{Binding UserControl1ViewModel}" />
<v:UserControl2View DataContext="{Binding UserControl2ViewModel}" />
</StackPanel>
This way both UserControls would have different ViewModels but both share the same SharedData instance which comes from the higher level ViewModel containing both UserControl's ViewModels. The Repository then has access to the EF data context. (Having repositories here is only an example, you could also inject instances of Service classes or something.)
Your EF classes, near as I've been able to tell after only four days using EF, reside at the project level. My first instinct would be to implement a singleton containing references to the entities you want to hold common across your viewmodels. That will create a class dependency on your singleton, of course.
This actually sounds like a design problem addressed by Unity, MEF, or something else that will do dependency injection. You'd have your EF classes in a module of one of those frameworks and use their protocols to coordinate between EF and your VM's. Then a change in your filter or your data in EF would also trigger a message your VM's could register to receive, in order to trigger UI changes or VM state changes or whatever.
I agree wholeheartedly with the one ViewModel per View approach. For shared data you can either pass references around (tedious and error prone), you can use DI (depending on your comfort level but doesn't play well with design time data), or you can create static properties in your App.xaml.cs which are then shared and accessible throughout the application. In the long run, DI will probably get the most support from other folks.
You might have a look at the BookLibrary sample application of the WPF Application Framework (WAF). It contains two different Views (BookListView [Master], BookView [Detail]) for the same data source which is provided by the Entity Framework.
精彩评论