应用模式-视图-视图模型设计模式时包括部分视图

发布于 2024-10-20 22:38:04 字数 2297 浏览 2 评论 0原文

假设我有一个仅处理消息用户的应用程序,我希望我的窗口有一个通用的菜单和一个区域,其中当前显示>查看

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

  • MessageView.xaml
  • UserView.xaml

只是为了让它更容易一点,消息模型用户模型看起来像这样:

  • 名称
  • 描述

现在,我有以下三个 ViewModel:

  • MainWindowViewModel
  • UsersViewModel
  • MessagesViewModel

UsersViewModelMessagesViewModel 都只获取 ObserverableCollection > 其相关的 Model 绑定在相应的 View 中,如下所示:

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中使用,以动态显示用户控件,如下所示:

Window 上还有两个按钮可以触发命令:

  • ShowMessagesCommand
  • ShowUsersCommand

当这些按钮被触发时, UserControl 发生变化,因为 PartialViewModel 是一个依赖属性。

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

尝蛊 2024-10-27 22:38:04

为什么不在主窗口中使用带有数据模板的 ContentPresenter/ContentControl ?

而不是 UserControl Content="{Binding PartialViewModel}" />,您可以使用:

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

您所要做的就是将您的 PartialViewmodel 设置为您的子视图模型并创建一个数据模板,这样 wpf 将知道如何渲染您的子视图

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

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

模型您在 MainViewmodel 中设置 PartialViewmodel,正确的 View 将在您的 ContenControl 中呈现。

编辑1
至少您必须在 ViewModel 中实现 INotifyPropertyChanged 并在设置 PartViewModel 属性时触发它。

编辑2
如果您在视图模型中使用命令,请查看一些 mvvm 框架实现,例如 DelegateCommand 或 RelayCommand。这样处理 ICommand 就变得容易多了。在你的主视图模型中,你可以创建像这样简单的命令

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

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));
        }
    }
客…行舟 2024-10-27 22:38:04

乍一看,这并不是一个坏方法,在小型应用程序中使用可能就很好。

但是,有一些事情不太好:

  1. ViewModelBase 需要是 DependencyObject 才能拥有 DependencyProperty。在现实世界中,我发现必须以单线程方式处理 ViewModel 是非常烦人的(可能需要执行很多异步操作)。
  2. 它无法扩展;改变布局将需要大量的工作。

任何像样的 MVVM 框架都可以通过提供将子视图组合到主视图中的基础设施来简化 UI 组合。在 Prism(这是我个人的偏好)中,这种情况发生在 区域

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.

小嗷兮 2024-10-27 22:38:04

我会考虑使用 MVVM 框架,例如 Caliburn.Micro,它使视图组合变得非常简单。如果您的视图模型上有一个属于视图模型类型的属性,并且视图上有一个与您的属性名称相同的 ContentControl ,那么 Caliburn.Micro 将通过以下方式找到该视图模型对应的视图:约定,自动为您进行绑定,并将视图注入到 ContentControl 中。

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

编辑

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 动词,您只需创建具有相同名称的视图控件。 Conductor 类型有一个 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>

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>
坏尐絯 2024-10-27 22:38:04

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

you should take a look at prism. It gives you region handling.
I would also take a look at MEF to Export Views and on this way maintain an extensibility for your project.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文