DataContext 用于在 DataTemplate 中将 UserControl 与 ViewModel 绑定

Posted

技术标签:

【中文标题】DataContext 用于在 DataTemplate 中将 UserControl 与 ViewModel 绑定【英文标题】:DataContext for binding a UserControl with ViewModel within DataTemplate 【发布时间】:2011-09-20 20:02:25 【问题描述】:

我想要实现的是:

将 ListView 绑定到 ItemRecords 的 ObservableCollection。 拥有一个 TabControl,其中包含 ListView 中选择进行编辑的所有 ItemRecords 的详细视图。 每个 TabItem 都包含一个 UserControl(“ItemInfo”),它使用 ItemInfoViewModel 作为其 VM(以及,并非巧合的是,DataContext)。 ItemInfo UserControl 需要使用相应 ItemRecord 中的数据进行填充。

为此,我尝试将 ItemRecord(在 ListView 中选择)传递给 ItemInfoViewModel。

最后,问题:在不破坏 MVVM 模式的情况下,您认为最好的方法是什么?

我看到的不太优雅的方式(它实际上并不完全遵循 MVVM 原则)是在 UserControl 中定义一个 DependencyProperty ItemRecord,通过绑定提供它的值,并在构造函数中(在UserControl 的代码隐藏)将 ItemRecord 传递给 VM(我们通过转换 DataContext 获得)。

另一个问题是如何通过绑定实际传递 ItemRecord。 一旦我将 VM 设置为 UserControl 的 DataContext,我就不能只使用Binding 来指定 TabControl 的源集合中的当前项。 目前我正在使用 ElementName 绑定到 TabControl 的 SelectedItem - 但这听起来不太健壮:-(

<localControls:TabControl.ContentTemplate>
    <DataTemplate>
        <ScrollViewer>
            <localControls:ItemInfo ItemRecord="Binding ElementName=Tabs, Path=SelectedItem"/>
        </ScrollViewer>
    </DataTemplate>
</localControls:TabControl.ContentTemplate>

任何好的建议将不胜感激!

亚历克斯

【问题讨论】:

“它实际上并不完全遵循 MVVM 原则”确实如此。难道TextBox的Text属性不遵循MVVM原则吗? UserControl 关心视图的职责。它不应该执行业务逻辑,但它当然可以公开可以绑定到 DataContext 的属性。 @Will:在这一点上我恐怕还没有说清楚。我的意思是,将属性值从 View 的代码隐藏推送到 ViewModel 并不是那么优雅。 不,你说得很清楚。 MVVM != 后面没有代码。这就是我的观点。 MVVM 是关于关注点分离的。视图与视图有关。如果您没有创建实际的控件,那么您就是在 UserControl 中合成控件。一样的区别。如果您没有在自定义控件中公开公共 DP,那么您就是在 UserControl 的代码隐藏中执行此操作。代码隐藏不是反 MVVM,当您将业务关注点与视图关注点混合在一起时,您就打破了这种模式。 【参考方案1】:

我认为你的问题是你不太了解这里的 MVVM 模式;您仍然将其视为不同的控件相互交流。在 MVVM 中,它们不应该存在,每个控件都独立于所有其他控件与 视图模型 进行通信。视图模型控制(并提供)告诉控件如何行为的逻辑。

所以,理想情况下,您应该有类似的东西:

public ObservableCollection<ItemRecord> ListViewRecords 

   get  ... 
   set  ... 


public IEnumerable<ItemRecord> SelectedListViewRecords 

   get  ... 
   set  ... 

ListViewRecords 将绑定到您的ListViewItemsSource 属性(实际属性可能会根据您使用的特定控件而有所不同,我现在已经习惯了 Telerik 套件,所以这是我的头在哪里)。而SelectedListViewRecords 将绑定到ListViewSelectedItems 属性。然后对于您的 TabControl,您将拥有:

public ObservableCollection<MyTabItem> Tabs

   get  ... 
   set  ... 


public MyTabItem SelectedTab

   get  ... 
   set  ... 

同样,您可以将Items 属性绑定到Tabs,并将SelectedItem 绑定到TabControl 上的SelectedTab。现在您的视图模型包含所有逻辑,因此在您的 SelectedListViewRecords 中您可能会执行以下操作:

public IEnumerable<ItemRecord> SelectedListViewRecords 

   get  ... 
   set 
   
       _selectedRecords = value;
       NotifyPropertyChanged("SelectedListViewRecords");

       Tabs.Clear(); // Clear the existing tabs
       // Create a new tab for each newly selected record
       foreach(ItemRecord record in value)
          Tabs.Add(new MyTabItem(record)); 
   

所以这里的想法是,控件只是发送和接收属性更改,它们对底层数据、逻辑等一无所知。它们只是显示绑定的属性告诉它们显示的内容。

【讨论】:

这是一个非常彻底的答案。 +1 好吧,也许我不懂 MVVM,也许我懂。可能只是我表达的不够清楚。答案确实非常彻底,但它是对一些不同问题的答案。当然,我的虚拟机中有这些属性。问题是——在我有限的认知中——我的 UC 拥有自己的虚拟机是合理的,与主机窗口的虚拟机分开。 UC 的 VM 处理“主” VM 不感兴趣的一些专有验证逻辑。所以,我最初的问题仍然存在:将所选项目从主 V/VM 传达给 UC 的 VM 的最佳方式是什么。 我不打算将其作为批评;因此,如果遇到这种情况,我深表歉意。很难直接回答你的问题,因为我不知道你的实现细节,但想法是一样的。视图模型之间的通信取决于视图模型,这可能是通过订阅INotifyPropertyChanged 接口/事件、通过响应式扩展或其他方法。 听起来不错。这将在 2 个 VM 之间引入(弱)依赖关系——但我想只要它被订阅机制抽象出来就可以了。抱歉,我的回复也不是有意如此严厉;-) @Coding Gorilla:再想一想……我不太明白单个 ItemInfo VM 如何区分它属于哪个 Tab,以及它应该管理的 ItemRecord。毕竟,这是一对多的关系(一个主 VM 与多个 ItemInfo VM),所以当主 VM 触发通知时,ItemInfo VM 的实例应该如何知道它是为它准备的——而不是为另一个 ItemInfo VM 实例?看起来我仍然需要通过 Main.xaml 的绑定将一些标识(例如 ItemRecord.ID)传递给 ItemInfo - 在这里我们回到原来的 DataContext 问题......

以上是关于DataContext 用于在 DataTemplate 中将 UserControl 与 ViewModel 绑定的主要内容,如果未能解决你的问题,请参考以下文章

设置DataContext后WPF依赖属性两种方式绑定不起作用[重复]

DataContext 数据在F5刷新频繁,会出现数据读取错误

DataContext 有啥用?

在 MVVM 中的窗口之间传递 DataContext

C# WPF 数据绑定DataContext;Window_Loaded时进行过数据绑定,指定DataContext;触发另一事件?

如何将命令绑定到 DataContext 之外的按钮中?