应用Mode-View-ViewModel设计模式时包括部分视图

时间:2011-03-08 07:34:31

标签: c# wpf design-patterns mvvm

考虑到我有一个只处理MessagesUsers的应用程序我希望我的Window有一个公共Menu和一个显示当前View的区域。

我只能使用消息或用户,所以我无法同时使用两个视图。因此,我有以下控件

  • MessageView.xaml
  • UserView.xaml

为了让它更容易一点,Message ModelUser Model都是这样的:

  • 名称
  • 描述

现在,我有以下三个ViewModel:

  • MainWindowViewModel
  • UsersViewModel
  • MessagesViewModel

UsersViewModelMessagesViewModel两者都只获取其ObserverableCollection<T>的约ModelView绑定在相应的<DataGrid ItemSource="{Binding ModelCollection}" />中,如下所示:

MainWindowViewModel

Commands与已实施ICommand的两个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; } 相关联,如下所示:

ViewModelBase

还有另外一个会向用户显示的内容。现在介绍 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)); 只包含以下内容:

MainWindow.xaml

User Control中使用此依赖项属性以动态显示<UserControl Content="{Binding PartialViewModel}" />

Window

PartialViewModel上还有两个按钮可以激活命令:

  • ShowMessagesCommand
  • ShowUsersCommand

当这些被触发时,UserControl会发生变化,因为{{1}}是依赖属性。

我想知道如果这是不好的做法?我不应该像这样注入用户控件吗?是否有另一种“更好”的替代方案更符合设计模式?或者这是包含部分视图的好方法吗?

4 个答案:

答案 0 :(得分:2)

初看起来这不是一个糟糕的方法,在小应用程序中使用可能会很好。

然而,有一些事情并不那么好:

  1. ViewModelBase需要DependencyObject才能拥有DependencyProperty。在现实世界中,我发现必须以单线程方式处理ViewModel是非常烦人的(人们可能想要执行许多异步操作)。
  2. 不规模;改变布局需要大量的工作。
  3. 任何体面的MVVM框架都可以通过提供基础结构将子视图组合到主视图中来简化UI组合。在Prism中(这是我个人的偏好),这发生在Regions

答案 1 :(得分:2)

为什么不在主窗口中使用带有datatemplate的ContentPresenter / ContentControl?

代替UserControl Content =“{Binding PartialViewModel}”/&gt;,您可以使用:

 <ContentPresenter Content="{Binding Path=PartialViewModel}" />

所有你需要做的:将你的PartialViewmodel设置为你的子视图模型并创建一个datatemplate,所以wpf将知道如何渲染你的childviewmodel

<DataTemplate DataType={x:Type UserViewModel}>
    <UserView/>
</DataTemplate> 

<DataTemplate DataType={x:Type MessageViewModel}>
    <MessageView/>
</DataTemplate> 

当您在MainViewmodel中设置PartialViewmodel时,右侧视图将在ContenControl中呈现。

修改1 至少你必须在ViewModel中实现INotifyPropertyChanged,并在设置PartViewModel属性时触发它。

编辑2 如果在viewmodels中使用Commands,请查看一些mvvm框架实现,如DelegateCommand或RelayCommand。处理ICommand变得更加容易。在您的mainviewmodel中,您可以创建简单的命令

private DelegateCommand _showMessageCommand;
public ICommand ShowMessageCommand
{
    get
    {
         return this._showMessageCommand ?? (this._showMessageCommand = new DelegateCommand(this.ShowMessageExecute, this.CanShowMessageExecute));
        }
    }

答案 2 :(得分:1)

我会考虑使用像Caliburn.Micro这样的MVVM框架,这使得视图合成非常容易。如果视图模型上的属性是视图模型类型,并且视图上的ContentControl名称与您的属性相同,那么Caliburn.Micro将通过约定找到该视图模型对应的视图,自动绑定,并将视图注入ContentControl

我还会避免在视图模型上使用依赖项属性,而是实现INotifyPropertyChanged。 Caliburn.Micro带有一个实现此接口的PropertyChangedBase类型,并且还提供了一个帮助方法,用于使用lambda表达式而不是魔术字符串调用PropertyChanged事件(这对于稍后的重构更好)。 / p>

修改

http://msdn.microsoft.com/en-us/library/ms743695.aspx显示了实现INotifyPropertyChanged的示例。

要在Caliburn.Micro中实现您想要做的事情,您可以执行以下操作(一个粗略的示例,但它向您展示了使用MVVM框架进行视图合成是多么容易):

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);    
  }
}

请注意UsersViewModelMessagesViewModel来自Screen

要使用Caliburn.Micro调用ShowUsersShowMessages谓词,您只需要创建具有相同名称的视图控件。导体类型具有ActiveItem属性,这是当前执行的项目,因此您可以将ContentControl添加到名为ActiveItem的MainView.xaml中,并且Caliburn.Micro将负责注入正确的视图。

因此,您的MainView.xaml可能如下所示:

<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>

答案 3 :(得分:0)

你应该看看prism。它为您提供区域处理。 我还要看看MEF导出视图,并以这种方式保持项目的可扩展性。