绑定PropertyChanged困境

时间:2014-02-10 07:56:52

标签: wpf

哦,上帝是星期一,我觉得我对wpf的所有知识都被删除了。

我想当在Property ModeChanged模式下绑定时,只有在Target属性发生变化时才会更新Source,而不是所有时间。

这是一个示例,即使Target属性尚未更改,Binding也会不断更新Source。为什么?

顺便说一下,我在.NET 4.0中

  <StackPanel>
    <TextBox x:Name="tbx1" Text="{Binding Txt, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    <Button Content="Change Text" Click="OnClick" />
  </StackPanel>

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new ViewModel();
    }

    private void OnClick(object sender, RoutedEventArgs e)
    {
        tbx1.Text = "hello";
    }
}

public class ViewModel 
{

    private string txt;

    public string Txt
    {
        get { return txt; }
        set { txt = value; Console.WriteLine("Txt Setter Called!");}
    }
}

每次我点击按钮时,都会调用Txt的设置器。为什么?价值没有改变。

GetHashCode()方法返回相同的结果。

我缺少什么? : - )

4 个答案:

答案 0 :(得分:2)

嗯,这就是它的工作方式,但名称可能有点误导。

在设置目标属性时,源值正在更新,不一定在值已更改时,顾名思义。

您可以使用fx观察相同的行为。一个CheckBox。即使目标值没有改变,反复将IsChecked属性设置为true也会触发源更新。

因此绑定系统在触发更新之前不会比较实际值,只关心目标属性是否已设置。

您的示例扩展为CheckBox

XAML:

<StackPanel>
    <TextBox x:Name="tbx1" Text="{Binding Txt, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    <CheckBox x:Name="chk1" IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
    <Button Content="Change Text" Click="OnClick" />
</StackPanel>

代码隐藏:

public partial class MainWindow
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = new ViewModel();
    }

    private void OnClick(object sender, RoutedEventArgs e)
    {
        tbx1.Text = "hello";
        chk1.IsChecked = true;
    }
}

public class ViewModel
{
    private string txt;
    public string Txt
    {
        get { return txt; }
        set { txt = value; Console.WriteLine("Txt Setter Called!"); }
    }

    private bool isChecked;
    public bool IsChecked
    {
        get { return isChecked; }
        set { isChecked = value; Console.WriteLine("IsChecked Setter Called!"); }
    }
}

答案 1 :(得分:1)

如您所料,没有发生实际的“财产变更”,可以通过以下方式确认:

using System.ComponentModel;

var descriptor = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof(TextBox));

descriptor.AddValueChanged(tbx1, (s, e) => Console.WriteLine("tbx1 changed"));

“tbx1已更改”只会出现一次。

如果源是依赖属性,则它也不会更改。尝试添加另一个文本框并将其用作源而不是viewmodel:

<TextBox Name="tbx2" />
<TextBox Name="tbx1" Text="{Binding ElementName=tbx2, Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />

处理程序:

descriptor.AddValueChanged(tbx2, (s, e) => Console.WriteLine("tbx2 changed"));

同样,只有一次改变。

所以是的,触发器没有准确命名。此行为的原因可能是确保属性集始终触发绑定转换器,因为即使使用相同的输入,ConvertBack 也可以返回不同的值。

无论如何,目标和来源都需要承担一些责任来确定“变化”是什么。毕竟,如果它是一个真正的双向绑定,那么允许以这种方式实现OnClick,效果完全相同:

tbx1.DataContext.Txt = "hello";

因此,请确保您的属性设置器在继续之前始终检查实际更改(作为依赖项属性)。

答案 2 :(得分:0)

将您的BindingMode更改为OneWayOneWayToSource ..您的问题将得到解决......

<TextBox x:Name="tbx1" Text="{Binding Txt, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"/>

答案 3 :(得分:0)

您可以更改setter以检查新值是否与旧值不同。

 public string Txt
{
    get { return txt; }
    set { 
          if (txt == value) return;
          txt = value;               
          Console.WriteLine("Txt Setter Called!");
        }
}