【问题标题】:WPF User Control hell with MVVM and Dependency Properties带有 MVVM 和依赖属性的 WPF 用户控制地狱
【发布时间】:2010-04-14 13:23:48
【问题描述】:

这就是我想要做的:

  • 我正在写一个UserControl,我想被其他开发者使用。
  • 我希望最终用户能够使用依赖属性来使用我的控件。

    <lib:ControlView ControlsText={Binding Path=UsersOwnViewModelText} />
    
  • 我正在使用 MVVM 模式。

  • 我正在使用 &lt;DataTemplates&gt; 将我的 ViewModel 绑定到他们的 View 上

    <DataTemplate DataType="{x:Type local:ControlViewModel}">  
        <local:ControlView />  
    </DataTemplate>
    

所以我有两个问题:

  1. 如果在 XAML 中使用 UserControl,那么当控件的 Loaded 事件触发而不是使用 &lt;DataTemplate&gt; 方法时,UserControl 必须将 ViewModel 设置为其 DataContext,我的想法是否正确?

  2. 如何允许用户将数据绑定到我的控件的依赖属性,同时仍将数据绑定到我的 ViewModel?

【问题讨论】:

  • 当我准备这个问题时,我在这里看到了。谢谢你的提问 :)

标签: wpf user-controls mvvm dependency-properties


【解决方案1】:

您应该将两个用例分开:

  1. 其他开发人员将使用的(用户)控件。
  2. 您的应用程序将使用的用户控件。

重要的是,后者依赖于前者 - 反之亦然。

用例 1 将使用依赖属性、模板绑定以及制作常规 WPF 控件的所有内容:

MyControl.cs:

public class MyControl : Control
{
    // dependency properties and other logic
}

Generic.xaml:

<ControlTemplate Type="local:MyControl">
    <!-- define the default look in here, using template bindings to bind to your d-props -->
</ControlTemplate>

然后您将用例 2 定义为:

MyViewModel.cs:

public class MyViewModel : ViewModel
{
    // properties and business logic
}

MyView.xaml:

<UserControl ...>
    <local:MyControl SomeProperty="{Binding SomePropertyOnViewModel}" .../>
</UserControl>

两全其美的干净分离。其他开发人员仅依赖于控件,它可能(并且可能应该)位于与您的视图模型和视图完全不同的程序集中。

【讨论】:

  • 感谢您的回复。我正在创建一个复合控件,想想级联组合框。以这种方式为我的需要创建控件似乎有点重。不过感谢您的回复,因为知道它仍然很有用。
  • 非常好的模式,谢谢。没关系,但是如果您的组件没有设计好的 ViewModel 就毫无意义,您需要进行管理。例如在您的 ViewModel 中有 CollectionViewSource 他们过滤或排序项目。如果没有 viewmodel 内部变量移动,其他 dep.properties 将失去意义。所以这个问题的答案会根据你的意图而改变。
【解决方案2】:

首先,如果您正在开发将被其他人使用的 UserControl,我认为 MVVM 不是一个好的选择。一个无外观的控件是你真正应该开发的。 Jeremiah Morrill 有一个关于这个主题的blog post

话虽如此,如果您有默认的公共构造函数,则可以使用 XAML 设置数据上下文。

在 ControlView.xaml 中放置:

<UserControl.DataContext>
    <local:ControlViewModel />
</UserControl.DataContext>

【讨论】:

  • 看起来 MVVM 不适合创建用户控件。因此,有了这个心理障碍,我就能够解决我的问题。我必须将 UserControl 中子控件的 DataContext 设置为 UserControl 本身,这意味着消费控件可以为我的 UserControl 提供 DataContext 并因此绑定到我的 DependencyProperties。 (我认为这是正确的......)无论如何,它有效,所以我很高兴!
  • -1 在 XAML 中将 UserControl 的视图模型分配给 UserControl 的 DataContext 将破坏尝试使用 UserControl 的 DependencyProperties 的父级的绑定。特别是对于 OP 中的此绑定,它将导致绑定引用 UserControl 的视图模型而不是父视图模型。唯一的解决方法是让父级以某种方式知道这一点并改用 ElementName 绑定。
  • 我认为 MVVM 对于用户控件来说很好,如果它说从数据库中获取项目列表并且必须反映在用户控件中的控件中。但我认为,如果您想直接向控件的使用者公开属性,您将在代码中公开属性,以便重新路由可能将某些子控件属性转换为与控件相关的数据类型。
  • 如前所述,在UserControl 内设置UserControl.DataContext(无论如何完成)会覆盖否则将从最终使用控件的位置继承的上下文,这会导致绑定问题。如果需要 ViewModel,我通常在最外层容器(通常是 Grid)上的控件内设置 DC,而不是 UserControl 元素本身。这允许控件本身的DataContext 被继承(对于尝试绑定到其 DP 的用户来说并不奇怪),并允许 ViewModel 正常用于控件内的所有元素。
【解决方案3】:

基本上,与其将 UserControl 的 datacontext 绑定到 userControlViewModel,不如在用户控件的第一个子元素上进行绑定。这样,您在控件中所做的所有引用都将绑定到 userControlViewModel,但可以从要使用 UserControl 的数据上下文集中设置依赖项属性。

这是来自我正在从事的一个项目:

<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

然后,当您在主视图上使用它时,您可以使用要传递给 MyUSerControl 的值设置依赖属性

【讨论】:

    【解决方案4】:

    UserControl 是“MVVM”中“视图”的一部分,就像 TextBoxListView 控件是视图的一部分一样。

    无论您决定使用 MVVM 来开发您的 UserControl 本身还是用 QBASIC 编写(不推荐)它都不会破坏您 UserControl 的消费者的 MVVM 模式,只要他们可以做他们需要的每一件事通过绑定到DependencyProperty 与您的UserControl 一起暴露在您的UserControl 上。即您的UserControl 应该公开它依赖所依赖的属性(因此得名)。一旦你掌握了这个 DependencyProperty 突然变得很有意义,你希望它们对更改的事件处理程序和你在其构造函数中指定的默认值有所帮助。

    如果您的 UserControl 是否在不同的程序集中,我看不出有什么不同。

    这就是说,出于 MVVM 带来的所有充分理由,例如帮助其他开发人员查看您的代码。然而,有些事情根本不可能和/或更复杂和性能更低的黑客攻击 XAML 来做到这一点 - 我不是在谈论您的花园品种添加用户表单,而是例如 UserControl 处理数千个视觉效果的布局。此外,由于您在 View 中工作,您确实希望您的 UserControl 的 ViewModel 与您的应用程序混合!

    基本上我的意思是在 MVVM 中不要在视图上使用 MVVM!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-02-03
      • 1970-01-01
      • 2014-10-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多