Wpf用户控件和MVVM

27

我正在考虑为我的应用程序编写一个WPF用户控件。我在应用程序中使用MVVM。

当使用MVVM时,用户控件可能需要依赖属性,这些属性可以由父视图设置。想法是,父视图最终将创建UserControls DP与父视图的VM之间的绑定)

Dependency Properties需要在View类中创建,因为VM不从DependencyObject继承。这意味着需要在XAML代码后添加代码。

我想知道,在使用MVVM开发WPF应用程序时,您能否给出有关如何设计用户控件的建议...

3个回答

32

情况1:如果您创建此控件仅供应用程序使用,则可以创建一个ViewModel,但是您不需要创建 DP(Dependency Property),您的ViewModel只需实现INotifyPropertyChanged接口,您的父VM仍然可以绑定到它们。

在我们的情况下,对于用户控件,我们创建了单独的 VM,并且它的一个实例存在于ParentVM中。因此,父视图将包含此控件,并将UserControlVM绑定到此控件(ParentVM.UserControlVM),而用户控件将负责其他绑定。

情况2:如果您的控件将由其他应用程序/开发人员使用并且您不想保持其简单性,则可以创建自定义控件并遵循控件模板实现。这样,您可以创建无外观的控件并使用dependency properties。此外,使用该控件的任何人都不需要知道相关的视图模型并使用它。

一些类似的问题/帖子:

WPF设计问题(自定义控件或MVVM):WPF设计问题(自定义控件或MVVM)

使用MVVM概念在WPF中创建自定义控件:http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/6293b176-e1e9-4610-af49-d53e6d294969/

在MVVM和Dependency Properties中使用WPF用户控件:在MVVM和Dependency Properties中使用WPF用户控件


你好,akjoshi,你使用什么策略将父VM绑定到控件VM属性?你能给一个小例子吗? - byte
2
通常我会将子控件的DataContext设置为ParentVM中存在的UserControlVM对象。假设您有一个主窗口,其中包含您的用户控件。现在,MainWindow的数据上下文已设置为ParentVM,此ParentVM将公开一个类型为UserControlVM的属性。现在我们只需要像这样设置用户控件的数据上下文 - <local: UserControl DataContext = "{Binding Path = UserControlVM}" /> - akjoshi
谢谢akjoshi,我在我的应用程序中使用了这种策略,并且所有VM都实现了INotifyPropertyChanged。我的问题更多地涉及到您在评论中提到的属性绑定:“您的ViewModel可以只实现INotifyPropertyChanged,而您的父VM仍然可以绑定到它们。”您是否在ParentVM中创建控件VM并让ParentVM直接绑定到控件VM? - byte
哎呀,8年后收到了负评,能否知道原因?请明示。 - akjoshi

13
一个UserControl是"MVVM"中的"View"部分,就像TextBoxListView控件一样是View的一部分。
无论您决定使用MVVM开发UserControl本身还是在QBASIC中编写它(不建议这样做),只要消费者可以通过绑定到UserControl上公开的DependencyProperty来完成他们需要的所有操作,就不会破坏MVVM模式。即您的UserControl应该公开其所依赖的属性(因此得名Dependent)。一旦您理解了这一点,DependencyProperty就会突然变得非常有意义,并且您希望在构造函数中指定其默认值和有用的on changed事件处理程序。
如果您的UserControl位于不同的程序集中或不存在,我看不出这有什么区别。
虽然许多人会主张使用MVVM模式本身构建UserControl,原因是MVVM带来的好处,例如帮助另一个查看您代码的开发人员。但是,有些事情简单地不可能,并且/或者更难、更复杂且性能更差的方法是通过XAML进行操作-我不是指普通的添加用户表单,而是比如处理数千个视觉元素的UserControl。此外,由于您正在处理View,因此不要UserControl的ViewModel混入应用中!
基本上,我想说的是,在View上不使用MVVM是完全符合MVVM的!

1
好的建议是将应用程序的虚拟机和“UserControl”分开(+1)。 - Bob Sammers
1
你的UserControl应该公开它所依赖的属性(因此得名)。就像这样,我现在理解了Dependancy Properties背后的思想。谢谢您! - ENDEESA
根据@ENDEESA提供的DP定义,这是否意味着我们只需要在实现INotifyPropertyChanged接口的用户控件中使用常规属性即可?但是,用户控件的视图应该实现它,而不是拥有ViewModel吗?我认为在用户控件内设置DataContext并不可取。 - zar

2
基本上,与其将UserControl的数据上下文绑定到userControlViewModel,不如在用户控件的第一个子元素上执行此操作。这样,在控件内部进行的所有引用都将绑定到userControlViewModel,但是可以从您想要使用UserControl的数据上下文设置中设置依赖属性。
对我来说,这种模式效果很好,在您的UserControl XAML中:
<UserControl x:Class="Six_Barca_Main_Interface.MyUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:Six_Barca_Main_Interface"
             xmlns:System="clr-namespace:System;assembly=mscorlib" 
             mc:Ignorable="d" 
             d:DesignHeight="900" d:DesignWidth="900">

    <DockPanel  x:Name="rootDock" >
        <TextBlock>{Binding SomethingInMyUserControlViewModel}</TabControl>
    </DockPanel>
</UserControl>

然后在代码后台:
public partial class MyUserControl : UserControl
{
    UserControlViewModel _vm;

    public MyUserControl()
    {
        InitializeComponent();

        //internal viewModel set to the first child of MyUserControl
         rootDock.DataContext = new UserControlViewModel();

        _vm = (UserControlViewModel)rootDock.DataContext;    

        //sets control to be able to use the viewmodel elements

     }

     #region Dependency properties 
     public string textSetFromApplication
     {
         get{return (string)GetValue(textSetFromApplicationProperty);}
         set{SetValue(textSetFromApplicationProperty, value);}
     }

     public static readonly DependencyProperty textSetFromApplicationProperty = DependencyProperty.Register("textSetFromApplication", typeof(string), typeof(MyUserControl), new PropertyMetadata(null, OnDependencyPropertyChanged));

     private static void  OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
     {
        ((MyUserControl)d)._vm.SomethingInMyUserControlViewModel = 
             e.NewValue as string;
     }
     #endregion

+1 是为 PropertyChangedCallback 方法,但是你能告诉我为什么要设置 rootDock.DataContext = new UserControlViewModel(); 而不是 rootDock.DataContext = this; 吗? - Adam Silenko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接