Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Including partial views when applying the Mode-View-ViewModel design pattern

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

  • MessageView.xaml
  • UserView.xaml

Just to make it a bit easier, both the Message Model and the User Model looks like this:

  • Name
  • Description

Now, I have the following three ViewModels:

  • MainWindowViewModel
  • UsersViewModel
  • MessagesViewModel

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:

  • ShowMessagesCommand
  • ShowUsersCommand

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?

like image 238
Filip Ekberg Avatar asked Mar 08 '11 07:03

Filip Ekberg


3 Answers

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));
        }
    }
like image 153
blindmeis Avatar answered Oct 13 '22 19:10

blindmeis


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:

  1. ViewModelBase needs to be a 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).
  2. It doesn't scale; changing the layout will require significant amounts of work.

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.

like image 42
Jon Avatar answered Oct 13 '22 20:10

Jon


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>
like image 1
devdigital Avatar answered Oct 13 '22 20:10

devdigital