Consider that I have an application that just handles Messages
and Users
I want my Window to have a common Menu
and an area where the current View
is displayed.
I can only work with either Messages or Users so I cannot work simultaniously with both Views. Therefore I have the following Controls
Just to make it a bit easier, both the Message Model
and the User Model
looks like this:
Now, I have the following three ViewModels:
The UsersViewModel
and the MessagesViewModel
both just fetch an ObserverableCollection<T>
of its regarding Model
which is bound in the corresponding View
like this:
<DataGrid ItemSource="{Binding ModelCollection}" />
The MainWindowViewModel
hooks up two different Commands
that have implemented ICommand
that looks something like the following:
public class ShowMessagesCommand : ICommand
{
private ViewModelBase ViewModel { get; set; }
public ShowMessagesCommand (ViewModelBase viewModel)
{
ViewModel = viewModel;
}
public void Execute(object parameter)
{
var viewModel = new ProductsViewModel();
ViewModel.PartialViewModel = new MessageView { DataContext = viewModel };
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
And there is another one a like it that will show Users. Now this introduced ViewModelBase
which only holds the following:
public UIElement PartialViewModel
{
get { return (UIElement)GetValue(PartialViewModelProperty); }
set { SetValue(PartialViewModelProperty, value); }
}
public static readonly DependencyProperty PartialViewModelProperty =
DependencyProperty.Register("PartialViewModel", typeof(UIElement), typeof(ViewModelBase), new UIPropertyMetadata(null));
This dependency property is used in the MainWindow.xaml
to display the User Control
dynamicly like this:
<UserControl Content="{Binding PartialViewModel}" />
There are also two buttons on this Window
that fires the Commands:
And when these are fired, the UserControl changes because PartialViewModel
is a dependency property.
I want to know if this is bad practice? Should I not inject the User Control like this? Is there another "better" alternative that corresponds better with the design pattern? Or is this a nice way of including partial views?
why not use a ContentPresenter/ContentControl with a datatemplate in your mainwindow?
instead of UserControl Content="{Binding PartialViewModel}" />, you can use a:
<ContentPresenter Content="{Binding Path=PartialViewModel}" />
all you have to do: is set your PartialViewmodel to your child viewmodel and create a datatemplate, so wpf will know how to render your childviewmodel
<DataTemplate DataType={x:Type UserViewModel}>
<UserView/>
</DataTemplate>
<DataTemplate DataType={x:Type MessageViewModel}>
<MessageView/>
</DataTemplate>
when ever you set your PartialViewmodel in your MainViewmodel, the right View will render in your ContenControl.
Edit 1 at least you have to implement INotifyPropertyChanged in your ViewModel and fire it when ever the PartViewModel property is set.
Edit 2 if you use Commands in your viewmodels take a look at some mvvm framework implementations like DelegateCommand or RelayCommand. handling ICommand become much easier with this. within your mainviewmodel you can create commands simple like that
private DelegateCommand _showMessageCommand;
public ICommand ShowMessageCommand
{
get
{
return this._showMessageCommand ?? (this._showMessageCommand = new DelegateCommand(this.ShowMessageExecute, this.CanShowMessageExecute));
}
}
This isn't a bad approach at first sight, it might be just fine to use in a small app.
However, there are a couple of things that aren't that nice:
DependencyObject
to have a DependencyProperty
. In the real world I 've found that it's very annoying to have to treat ViewModels in a single-threaded manner (there are lots of async operations one might want to perform).Any decent MVVM framework makes UI composition easy by providing infrastructure to compose sub-Views into your main View. In Prism (which is my personal preference), this happens with Regions
.
I would look at using an MVVM framework such as Caliburn.Micro which makes view composition incredibly easy. If you have a property on your view model which is a view model type, and a ContentControl
on your view which is named the same as your property, then Caliburn.Micro will locate that view models corresponding view via conventions, do the binding for you automatically, and inject the view into the ContentControl
.
I would also avoid using dependency properties on your view models, and instead implement INotifyPropertyChanged. Caliburn.Micro comes with a PropertyChangedBase
type which implements this interface, and also provides a helper method for invoking the PropertyChanged
event using lambda expressions rather than magic strings (which is much better for refactoring later).
EDIT
http://msdn.microsoft.com/en-us/library/ms743695.aspx shows an example of implementing INotifyPropertyChanged.
To achieve what you want to do in Caliburn.Micro, you would do something like the following (a crude example, but it shows you how easy it is doing view composition using an MVVM framework):
public class MainViewModel : Conductor<IScreen>.Collection.OneActive
{
private UsersViewModel usersViewModel;
private MessagesViewModel messagesViewModel;
public UsersViewModel UsersViewModel
{
get { return this.usersViewModel; }
set { this.usersViewModel = value; this.NotifyOfPropertyChanged(() => this.UsersViewModel);
}
public MessagesViewModel MessagesViewModel
{
get { return this.messagesViewModel; }
set { this.messagesViewModel = value; this.NotifyOfPropertyChanged(() => this.MessagesViewModel);
}
public MainViewModel()
{
this.UsersViewModel = new UsersViewModel();
this.MessagesViewModel = new MessagesViewModel();
this.Items.Add(this.UsersViewModel);
this.Items.Add(this.MessagesViewModel);
// set default view
this.ActivateItem(this.UsersViewModel);
}
public ShowUsers()
{
this.ActivateItem(this.UsersViewModel);
}
public ShowMessages()
{
this.ActivateItem(this.MessagesViewModel);
}
}
Note that UsersViewModel
and MessagesViewModel
would derive from Screen
.
To invoke the ShowUsers
or ShowMessages
verbs with Caliburn.Micro, you just need to create view controls with the same name. The conductor type has an ActiveItem
property which is the currently conducted item, so you can add a ContentControl
to your MainView.xaml which is named ActiveItem
, and Caliburn.Micro will take care of injecting the correct view.
So your MainView.xaml may look like:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinition>
<!-- Menu in left hand column -->
<StackPanel Grid.Column="0">
<Button x:Name="ShowUsers">Show Users</Button>
<Button x:Name="ShowMessages">Show Messages</Button>
</StackPanel>
<!-- Currently active item -->
<ContentControl x:Name="ActiveItem" Grid.Column="1" />
</Grid>
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