在 DataTemplate 中使用时,行为 DependencyProperty 不更新 ViewModel
Posted
技术标签:
【中文标题】在 DataTemplate 中使用时,行为 DependencyProperty 不更新 ViewModel【英文标题】:Behavior DependencyProperty not updating ViewModel when used within DataTemplate 【发布时间】:2021-09-20 03:43:23 【问题描述】:我在Behavior
中有一个DependencyProperty
,我正在为OnAttached()
设置值。
然后我将视图模型属性绑定到这个DependencyProperty
,其中Mode
是OneWayToSource
。
由于某种原因,绑定的视图模型属性在DataTemplate
内完成时不会被OneWayToSource
绑定更新(永远不会调用视图模型的设置器)。在其他情况下,它似乎工作正常。
我没有收到任何绑定错误,也看不到任何异常等迹象,我不知道我做错了什么。
WPF 设计器确实显示了一些错误,声称 The member "TestPropertyValue" is not recognized or is not accessible
或 The property "TestPropertyValue was not found in type 'TestBehavior'
,具体取决于您查看的位置。我不确定这些是否是“真正的”错误(正如我所观察到的那样,WPF 设计器似乎并不完全可靠地始终显示真正的问题),如果是,它们是否与此问题或其他问题完全相关.
如果这些设计器错误确实与此问题有关,我只能假设我必须错误地声明了 DependencyProperty
。如果是这样的话,我看不出错误在哪里。
我制作了一个复制问题的示例项目。以下代码应该足够了,并且可以添加到名称为 WpfBehaviorDependencyPropertyIssue001
的任何新 WPF 项目中。
MainWindow.xaml
<Window x:Class="WpfBehaviorDependencyPropertyIssue001.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:tb="clr-namespace:WpfBehaviorDependencyPropertyIssue001.Behaviors"
xmlns:vm="clr-namespace:WpfBehaviorDependencyPropertyIssue001.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MainViewModel />
</Window.DataContext>
<StackPanel>
<Label Content="Binding TestPropertyValue, ElementName=OuterTestA" Background="Cyan">
<b:Interaction.Behaviors>
<tb:TestBehavior x:Name="OuterTestA" TestPropertyValue="Binding MainTestValueA, Mode=OneWayToSource" />
</b:Interaction.Behaviors>
</Label>
<Label Content="Binding MainTestValueA, Mode=OneWay" Background="Orange" />
<Label Content="Binding MainTestValueB, Mode=OneWay" Background="MediumPurple" />
<DataGrid ItemsSource="Binding Items" RowDetailsVisibilityMode="Visible">
<b:Interaction.Behaviors>
<tb:TestBehavior x:Name="OuterTestB" TestPropertyValue="Binding MainTestValueB, Mode=OneWayToSource" />
</b:Interaction.Behaviors>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<StackPanel>
<Label Content="Binding TestPropertyValue, ElementName=InnerTest" Background="Cyan">
<b:Interaction.Behaviors>
<tb:TestBehavior x:Name="InnerTest" TestPropertyValue="Binding ItemTestViewModelValue, Mode=OneWayToSource" />
</b:Interaction.Behaviors>
</Label>
<Label Content="Binding ItemTestViewModelValue, Mode=OneWay" Background="Lime" />
</StackPanel>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</StackPanel>
</Window>
TestBehavior.cs
using Microsoft.Xaml.Behaviors;
using System.Windows;
namespace WpfBehaviorDependencyPropertyIssue001.Behaviors
public class TestBehavior : Behavior<UIElement>
public static DependencyProperty TestPropertyValueProperty get; = DependencyProperty.Register("TestPropertyValue", typeof(string), typeof(TestBehavior));
// Remember, these two are just for the XAML designer (or I guess if we manually invoked them for some reason).
public static string GetTestPropertyValue(DependencyObject dependencyObject) => (string)dependencyObject.GetValue(TestPropertyValueProperty);
public static void SetTestPropertyValue(DependencyObject dependencyObject, string value) => dependencyObject.SetValue(TestPropertyValueProperty, value);
protected override void OnAttached()
base.OnAttached();
SetValue(TestPropertyValueProperty, "Example");
ViewModelBase.cs
using System.ComponentModel;
namespace WpfBehaviorDependencyPropertyIssue001.ViewModels
public class ViewModelBase : INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
MainViewModel.cs
using System.Collections.ObjectModel;
namespace WpfBehaviorDependencyPropertyIssue001.ViewModels
public class MainViewModel : ViewModelBase
public ObservableCollection<ItemViewModel> Items
get => _Items;
set
_Items = value;
OnPropertyChanged(nameof(Items));
private ObservableCollection<ItemViewModel> _Items;
public MainViewModel()
Items = new ObservableCollection<ItemViewModel>()
new ItemViewModel() ItemName="Item 1"
;
public string MainTestValueA
get => _MainTestValueA;
set
System.Diagnostics.Debug.WriteLine($"Setting nameof(MainTestValueA) to (value != null ? $"\"value\"" : "null")");
_MainTestValueA = value;
OnPropertyChanged(nameof(MainTestValueA));
private string _MainTestValueA;
public string MainTestValueB
get => _MainTestValueB;
set
System.Diagnostics.Debug.WriteLine($"Setting nameof(MainTestValueB) to (value != null ? $"\"value\"" : "null")");
_MainTestValueB = value;
OnPropertyChanged(nameof(MainTestValueB));
private string _MainTestValueB;
ItemViewModel.cs
namespace WpfBehaviorDependencyPropertyIssue001.ViewModels
public class ItemViewModel : ViewModelBase
public string ItemName
get => _ItemName;
set
_ItemName = value;
OnPropertyChanged(nameof(ItemName));
private string _ItemName;
public string ItemTestViewModelValue
get => _ItemTestViewModelValue;
set
System.Diagnostics.Debug.WriteLine($"Setting nameof(ItemTestViewModelValue) to (value != null ? $"\"value\"" : "null")");
_ItemTestViewModelValue = value;
OnPropertyChanged(nameof(ItemTestViewModelValue));
private string _ItemTestViewModelValue;
预期调试输出消息(不包括标准 WPF 消息):
Setting MainTestValueA to null
Setting MainTestValueA to "Example"
Setting MainTestValueB to null
Setting MainTestValueB to "Example"
Setting ItemTestViewModelValue to null
Setting ItemTestViewModelValue to "Example"
实际调试输出消息(不包括标准 WPF 消息):
Setting MainTestValueA to null
Setting MainTestValueA to "Example"
Setting MainTestValueB to null
Setting MainTestValueB to "Example"
Setting ItemTestViewModelValue to null
【问题讨论】:
我认为附加的道具应该使用“RegisterAttached”注册。同样在 ctor 中,您将值“Example”设置为 TestBehaviour 对象本身的实例,而在静态 Get/Set 方法中,您从传入的依赖对象获取/设置值,它们是控件/标签你的xml。所以这些值被映射到不同的目标...... @lidqyTestPropertyValue
不是附加属性。它是TestBehavior
的依赖属性。标签本身的绑定是为了证明问题。要关注的关键点是TestBehavior
元素本身的绑定。此外,此示例中未使用行为的AssociatedObject
,这意味着依赖属性值不与标签一起存储。
@lidqy 另外,这不是作为附加属性开始的原因是因为这最终将用于我 am 使用的更大项目中AssociatedObject
为Behavior
。在重现我遇到的问题时,这些功能是多余的。最后,“Example”的值不是在构造函数中设置的,而是在行为的OnAttached()
方法中设置的。
【参考方案1】:
我完全测试了你的代码,它运行良好。
您的 Debug 运行良好,因为在创建 MainViewModel
的实例时会同时调用所有成员。
MainTestValueA
使用 null 值调用,然后调用 OnPropertyChanged
并使用 TestPropertyValue
属性调用标签控件的 bind
,并使用初始化 example
的 OnAttached
方法并将其打印在输出上。
MainTestValueB
的步骤相同
并且对ItemTestViewModelValue
重复相同的步骤,但因为它位于DataGridView
内部,clr
不允许从 View 访问。
当然,这是我的结论。
【讨论】:
1/2 您提供的更改并不能解决问题,而且我承认,在问题的上下文中没有多大意义(尽管我确实尝试过)。首先,您添加了一个本地TestValueProperty
属性,但没有删除静态 GetTestPropertyValue
和 SetTestPropertyValue
方法。这是在 XAML 中识别依赖属性的两种不同方法。两者兼有是没有意义的,但它们之间没有功能上的区别。 (续)
2/2 其次,在它自己的 PropertyChanged 回调中为属性调用 SetValue()
似乎很奇怪。你这背后的原因是什么?如果 WPF 没有将引发事件限制为 仅,则当依赖项属性的有效值发生更改时,这将导致无限循环。您实际上是在说“如果依赖属性值已更改,请再次将其更改为我们刚刚更改为相同的值”。第三,我明确希望属性为OneWayToSource
,not TwoWay
。我不希望视图模型更新依赖属性值,只读取它。
响应您的编辑,我知道我在问题中提供的代码与DataTemplate
中的Behavior
绑定不同,没有错误等,它只是无法设置视图模型属性的值。您可能会在回答的最后提到这一点,您说“但是因为它在 DataGridView
clr
内部不允许从 View 访问。”,但是恐怕我不确定你是什么正想说。普通数据绑定通常在RowDetailsTemplate
的DataGrid
中工作正常。你能详细说明你的意思吗?
我跟踪了程序,当我到达 ItemTestViewModelValue 时,调试器返回到值为“Example”的项目,并且因为项目绑定到 DataGridView 我做了这个 clr 没有输入定义(公共字符串 itemTestViewModelValue )。
如果我理解你所说的正确,你已经发现了我的问题所问的问题。我很清楚它不会调用 ItemTestViewModelValue
设置器,我努力通过添加调试消息来使这一事实显而易见在 Visual Studio 中。我还在问题本身中包含了输出消息,以尝试使其尽可能清晰。我正在尝试找出为什么会发生这种行为以及是否可以解决此问题。【参考方案2】:
我已经设法解决了这个问题。
由于某种原因,对于具有 Mode
的 OneWayToSource
的 DataTemplate
内的绑定,看起来需要 UpdateSourceTrigger
或 PropertyChanged
。
这样做会导致正确更新视图模型属性。
我通过实验发现了这一点,但我不确定为什么此行为与在 DataTemplate
之外进行的绑定不同,尽管此行为可能记录在某处。
如果我能找到这种行为的原因(记录与否),我将使用该信息更新此答案。
附加信息
为了让未来的读者更清楚,DataTemplate
的OneWayToSource
绑定outside 的标签按预期工作。这个(来自原始问题)的 XAML 如下所示:
<Label Content="Binding TestPropertyValue, ElementName=OuterTestA" Background="Cyan">
<b:Interaction.Behaviors>
<tb:TestBehavior x:Name="OuterTestA" TestPropertyValue="Binding MainTestValueA, Mode=OneWayToSource" />
</b:Interaction.Behaviors>
</Label>
但是,TestBehavior
与 OneWayToSource
绑定在 DataTemplate
不起作用。这个(来自原始问题)的 XAML 如下所示:
<DataTemplate>
<StackPanel>
<Label Content="Binding TestPropertyValue, ElementName=InnerTest" Background="Cyan">
<b:Interaction.Behaviors>
<tb:TestBehavior x:Name="InnerTest" TestPropertyValue="Binding ItemTestViewModelValue, Mode=OneWayToSource" />
</b:Interaction.Behaviors>
</Label>
<Label Content="Binding ItemTestViewModelValue, Mode=OneWay" Background="Lime" />
</StackPanel>
</DataTemplate>
将UpdateSourceTrigger=PropertyChanged
添加到TestBehavior
绑定会导致正确更新视图模型属性。更新后的 XAML 如下所示:
<DataTemplate>
<StackPanel>
<Label Content="Binding TestPropertyValue, ElementName=InnerTest" Background="Cyan">
<b:Interaction.Behaviors>
<tb:TestBehavior x:Name="InnerTest" TestPropertyValue="Binding ItemTestViewModelValue, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged" />
</b:Interaction.Behaviors>
</Label>
<Label Content="Binding ItemTestViewModelValue, Mode=OneWay" Background="Lime" />
</StackPanel>
</DataTemplate>
【讨论】:
以上是关于在 DataTemplate 中使用时,行为 DependencyProperty 不更新 ViewModel的主要内容,如果未能解决你的问题,请参考以下文章
使用 DataTemplate 时,ListView 仅显示列表中的最后一项
如何使用类似表格的 DataTemplate 在 UWP ListView 中动态缩放列宽
如何设置控件属性(在DataTemplate和UserControl中)的绑定以使用ItemSource的给定属性?
发生事件时在 DataTemplate 中运行 Storyboard