首页 > 解决方案 > 在 DataTemplate 中使用时,行为 DependencyProperty 不更新 ViewModel

问题描述

我有一个DependencyPropertyin aBehavior我正在设置 in 的值OnAttached()

然后我DependencyPropertyMode.OneWayToSource

OneWayToSource由于某种原因,绑定的视图模型属性在 a 中完成时不会被绑定更新DataTemplate(视图模型的设置器永远不会被调用)。在其他情况下,它似乎工作正常。

我没有收到任何绑定错误,也看不到任何异常的迹象,等等,我不知道我做错了什么。

WPF 设计器确实显示了一些错误,根据您查看的位置,声明为The member "TestPropertyValue" is not recognized or is not accessible或。The property "TestPropertyValue was not found in type 'TestBehavior'我不确定这些是否是“真正的”错误(正如我所观察到的那样,WPF 设计器似乎并不完全可靠地始终显示真正的问题),如果是,它们是否与此问题或其他问题完全相关.

如果这些设计器错误确实与此问题有关,我只能假设我必须DependencyProperty错误地声明了。如果是这种情况,我无法看到错误在哪里。

我制作了一个复制问题的示例项目。下面的代码应该足够了,并且可以添加到任何名为 的新 WPF 项目中WpfBehaviorDependencyPropertyIssue001

主窗口.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>
测试行为.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));
        }
    }
}
主视图模型.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;
    }
}
项目视图模型.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

标签: c#wpfdata-bindingdependency-propertiesbehavior

解决方案


我已经设法解决了这个问题。

出于某种原因,看起来a 中的绑定需要一个UpdateSourceTriggerof ,该绑定具有一个of 。这样做会导致正确更新视图模型属性。PropertyChangedDataTemplateModeOneWayToSource

我通过实验发现了这一点,但我不确定为什么这种行为不同于DataTemplate.

如果我能找到这种行为的原因(记录与否),我将使用该信息更新此答案。

附加信息

为了让未来的读者更清楚,带有OneWayToSource装订的标签DataTemplate按预期工作。用于此的 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>

然而,TestBehaviorOneWayToSource绑定并没有奏效。用于此的 XAML(来自原始问题)如下所示:DataTemplate

                <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=PropertyChangedTestBehavior绑定导致视图模型属性被正确更新。更新后的 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>

推荐阅读