【问题标题】:Binding UserControl Dependency Property and MVVM绑定 UserControl 依赖属性和 MVVM
【发布时间】:2014-04-10 10:55:36
【问题描述】:

我有一个包含 UserControl 的 MainWindow,两者都以 MVVM 模式实现。 MainWindowVM 具有我想绑定到 UserControl1VM 中的属性的属性。但这不起作用。

这是一些代码(视图模型使用某种 mvvm 框架,在 ViewModelBase 类中实现 INotifyPropertyChanged,但希望没问题):

MainWindow.xaml:

<Window x:Class="DPandMVVM.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:DPandMVVM"
    Title="MainWindow" Height="300" Width="300">
    <Grid>
        <local:UserControl1 TextInControl="{Binding Text}" />
    </Grid>
</Window>

CodeBehind MainWindow.xaml.cs:

using System.Windows;
namespace DPandMVVM
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new MainWindowVM();
        }
    }
}

MainWindow-ViewModel MainWindowVM.cs:

namespace DPandMVVM
{
    public class MainWindowVM : ViewModelBase
    {
        private string _text;
        public string Text { get { return _text; } }

        public MainWindowVM()
        {
            _text = "Text from MainWindowVM";
        }
    }
}

这里是 UserControl1.xaml:

<UserControl x:Class="DPandMVVM.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <TextBlock Text="{Binding TextInTextBlock}" />  
    </Grid>
</UserControl>

代码隐藏 UserControl1.xaml.cs:

using System.Windows.Controls;    
namespace DPandMVVM
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
            DataContext = new UserControl1VM();
        }
    }
}

以及 Viewmodel UserControl1VM.cs:

using System.Windows;    
namespace DPandMVVM
{
    public class UserControl1VM : DependencyObject
    {
        public UserControl1VM()
        {
            TextInControl = "TextfromUserControl1VM";
        }

        public string TextInControl
        {
            get { return (string)GetValue(TextInControlProperty); }
            set { SetValue(TextInControlProperty, value); }
        }

        public static readonly DependencyProperty TextInControlProperty =
            DependencyProperty.Register("TextInControl", typeof(string), typeof(UserControl1VM));
    }
}

有了这个星座,在 MainWindow.xaml 中找不到 DP。

我做错了什么?

【问题讨论】:

    标签: wpf mvvm dependency-properties


    【解决方案1】:

    您的控件的 XAML 现在通过 DataContext 引用属性 TextInTextBlock,而后者又“指向”您的主窗口的视图模型。引用控件的数据就完成了(顺便说一句,不要因为这个原因设置 DataContext - 绑定将不再起作用):

    <UserControl x:Class="DPandMVVM.UserControl1"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 mc:Ignorable="d" 
                 d:DesignHeight="300" d:DesignWidth="300"
                 x:Name="self">
        <Grid>
            <TextBlock Text="{Binding TextInTextBlock, ElementName=self}" />  
       </Grid>
    </UserControl>
    

    【讨论】:

      【解决方案2】:

      首先如果您想从外部绑定 DependencyProperty TextInControl,您希望在 UserControl1 内声明它。

      将 DP 的声明移到 UserControl1 内。

      public partial class UserControl1 : UserControl
      {
          public UserControl1()
          {
              InitializeComponent();
          }
      
          public string TextInControl
          {
              get { return (string)GetValue(TextInControlProperty); }
              set { SetValue(TextInControlProperty, value); }
          }
      
          public static readonly DependencyProperty TextInControlProperty =
              DependencyProperty.Register("TextInControl", typeof(string), 
                                             typeof(UserControl1));
      }
      

      第二你已经将UserControl的DataContext外部设置为UserControl1VM

          public UserControl1()
          {
              InitializeComponent();
              DataContext = new UserControl1VM(); <-- HERE (Remove this)
          }
      

      所以 WPF 绑定引擎在 UserControl1VM 中寻找属性 Text 而不是 MainWindowVM。删除设置 DataContext 并将 UserControl1 的 XAML 更新为此:

      <UserControl x:Class="DPandMVVM.UserControl1"
                   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                   xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                   mc:Ignorable="d" 
                   d:DesignHeight="300" d:DesignWidth="300"
                   x:Name="userControl1">
          <Grid>
              <TextBlock Text="{Binding TextInTextBlock, ElementName=userControl1}" />  
          </Grid>
      </UserControl>
      

      通过在 UserControl 上设置 x:Name 使用 ElementName 绑定 DP。


      更新

      如果您想让ViewModelUserControl 保持不变,您必须在MainWindow 中更新绑定。明确告诉 WPF 绑定引擎使用 ElementName 在 MainWindow 的 DataContext 中查找属性,如下所示:

      <local:UserControl1 TextInControl="{Binding DataContext.Text,
                          ElementName=mainWindow}" />
      

      为此,您需要在窗口根级别设置x:Name="mainWindow"

      【讨论】:

      • 使用这种方法,我不再有工作的 ViewModel。但我也想拥有这种模式并在其中使用一些其他属性(不是 DP)。
      • Viewmodels 无论如何都不应该包含 DP。它们应该只包含普通的 CLR 属性。
      • 把 UserControl 想象成其他人的“黑匣子”。这只是其他用户也会使用的控件。所以我们有一个控件开发人员(在 mvvm 中工作)和一个 MainWindow 开发人员(在 mvvm 中工作)。 MainWindow 的开发人员只想“轻松”地使用 UserControl,而无需了解内部细节。他只想将他的 VM-Properties 绑定到 UserControl 的 DP。
      • 我如何从 VM 访问 UserControl 中的这些 DP?
      • 您不能从 VM 访问 UserControl 内的 DP。你与那些 DP 绑定。就像Text 是在TextBox 内声明的DP,您从XAML 绑定到VM 中的某些属性。这也是自定义 DP 的做法。
      【解决方案3】:

      这就是我使用 MVVM 和 DP 绑定执行 UserControls 的方式。它类似于 Rohit 的答案,但有一些细微的变化。基本上你需要将 Control 的内部视图模型设置为 UserControl 中根容器的 DataContext 而不是 UserControl 本身,这样它就不会干扰 DP 绑定。

      例如

      用户控件 XAML

      <UserControl x:Class="DPandMVVM.UserControl1"
               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
               xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
               xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
               mc:Ignorable="d" 
               d:DesignHeight="300" d:DesignWidth="300"
               x:Name="userControl1">
      <Grid x:Name="Root">
          <TextBlock Text="{Binding TextFromVM}" />  
      </Grid>
      

      UserControl 代码隐藏

      public partial class UserControl1 : UserControl
      {
          public UserControl1()
          {
              InitializeComponent();            
              this.ViewModel = new UserControlVM();
          }
      
          public UserControlVM ViewModel
          {
              get { return this.Root.DataContext as UserControlVM ; }
              set { this.Root.DataContext = value; }
          }
      
          public string TextFromBinding
          {
              get { return (string)GetValue(TextFromBindingProperty); }
              set { SetValue(TextFromBindingProperty, value); }
          }
      
          public static readonly DependencyProperty TextFromBindingProperty =
              DependencyProperty.Register("TextFromBinding", typeof(string), typeof(UserControl1), new FrameworkPropertyMetadata(null, OnTextBindingChanged));
      
          private static void OnTextBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
          {
              var uc = d as UserControl1;
              uc.ViewModel.TextFromVM = e.NewValue as string;
          }
      }
      

      这意味着控件从作为我们的 ViewModel 的根元素 DataContext 派生它的值,但 ViewModel 可以通过控件外部的 DP 绑定更新(在您的情况下,绑定到父窗口的 ViewModel,见下文)

      窗口 XAML

      <Window  x:Class="DPandMVVM.Window1"
               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
               xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
               xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
               xmlns:local="clr-namespace:DPandMVVM"
               mc:Ignorable="d" 
               d:DesignHeight="300" d:DesignWidth="300"
               x:Name="window1">
      <Grid x:Name="Root">
          <local:userControl1 TextFromBinding="{Binding TextFromWindowVM}" />  
      </Grid>
      

      【讨论】:

      【解决方案4】:

      这是一个可行的解决方案。但是,我在上面的评论中指出,这将在代码中起作用,并且可能(就像我的情况一样)会在设计器中显示为错误(未找到对象):

      <local:UserControl1 TextInControl="{Binding DataContext.Text,
                      Source={x:Reference <<Your control that contains the DataContext here>>}}" />
      

      不过,我希望有一个更简洁的解决方案,没有任何设计错误。我希望了解如何将用户控件中的依赖属性正确绑定到来自它所包含的窗口的值。我发现无论我尝试做什么(与上面显示的相比),例如使用 ElementName 和/或 AncestorType/Level 等,调试器抱怨它找不到源并显示它正在用户控件的上下文中寻找源!就像我在使用该控件时执行绑定逻辑时无法跳出用户控件上下文一样(除了上面的“打破设计者”的解决方案)。

      更新: 我注意到这可能对您不起作用,因为如果我将自己的源更改为引用窗口而不是具有数据上下文的控件,那么您的情况可能会导致我刚刚注意到的问题。如果我引用窗口,那么我最终会得到循环冗余。也许您会想出一种方法来使用适合您的绑定的源版本。

      我还必须补充一点,我的情况可能有点复杂,因为我的用户控件是在弹出的上下文中使用的。

      【讨论】:

        【解决方案5】:

        我有一个我认为更简单的方法,并且可能更适合 MVVM。

        在 XAML 主窗口中:

        <myNameSpace:myUserControl DataContext="{Binding Status}"/>
        

        在你的主视图模型中(主窗口的数据上下文:

        public myUserControlViewModel Status { set; get; }
        

        现在你可以在构造函数中(或者你想实例化它的时候):

        Status = new myUserControlViewModel();
        

        那么如果你想设置文本属性:

        Status.Text = "foo";
        

        并确保您已对 myUserControlViewModel 类中名为 Text 的属性进行绑定设置:

        <TextBox Text="{Binding Text}"/>
        

        当然要确保属性触发 PropertyChanged。

        另外,如果您使用 Resharper。您可以在 XAML 中创建 UserControl 的 Design 实例,以便它可以链接绑定,并且不会告诉您该属性永远不会通过这样做来使用:

        <UserControl x:Class="myNameSpace.myUserControl"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
                 xmlns:myNameSpace="clr-namespace:myNameSpace"
                 d:DataContext="{d:DesignInstance myNameSpace.myUserControl}"
                 mc:Ignorable="d" ...>
        

        这部分:

        xmlns:myNameSpace="clr-namespace:myNameSpace"
        d:DataContext="{d:DesignInstance myNameSpace.myUserControl}"
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-08-31
          • 2017-12-24
          • 2023-04-07
          • 1970-01-01
          • 1970-01-01
          • 2011-07-29
          • 1970-01-01
          • 2017-12-18
          相关资源
          最近更新 更多