Reputation: 8791
Oh lord is monday again and I feel like all my knowledge about wpf has been deleted just like that.
I thought when Binding in Mode PropertyChanged that the Source will be only updated when the Target property was changed and not all the time.
Here is an example where Binding keeps updating the Source even though the Target property hasn't been changed. Why?
Btw, I am in .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!");}
}
}
Everytime I click on Button the setter of Txt is being called. Why? The value was not changed.
GetHashCode() method returns same results.
What am I missing??? :-)
Upvotes: 0
Views: 137
Reputation: 7724
As you expect, there is no actual "property change" happening, which can be confirmed with:
using System.ComponentModel;
var descriptor = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof(TextBox));
descriptor.AddValueChanged(tbx1, (s, e) => Console.WriteLine("tbx1 changed"));
"tbx1 changed" will only appear once.
If the source is a dependency property, it doesn't change either. Try adding another textbox and using it as the source instead of the viewmodel:
<TextBox Name="tbx2" />
<TextBox Name="tbx1" Text="{Binding ElementName=tbx2, Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Handler:
descriptor.AddValueChanged(tbx2, (s, e) => Console.WriteLine("tbx2 changed"));
Again, only one change.
So yes, the trigger is not accurately named. A reason for this behavior may be to ensure that a property set always triggers a binding converter, because a ConvertBack could return a different value even with the same input.
In any event, both target and source need to take some responsibility in determining what a "change" is. After all, if it's a true two-way binding, then we should be allowed to implement OnClick
this way with exactly the same effect:
tbx1.DataContext.Txt = "hello";
So just make sure your property setters always check for an actual change before proceeding (as dependency properties do).
Upvotes: 1
Reputation: 8907
Well, that's the way it's supposed to work, but the name might be a bit misleading.
The source value is being updated when the target property has been set, not necessarily when the value has changed, as the name suggests.
You can observe the same behavior by using fx. a CheckBox
. Setting the IsChecked
property to true over and over will also trigger a source update even though the target value does not change.
So the binding system does not compare the actual values before triggering an update, it just cares about whether the target property was set or not.
Your example extended with a 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>
Code-behind:
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!"); }
}
}
Upvotes: 2
Reputation: 5566
You could change you setter to check if the new value is different from the old.
public string Txt
{
get { return txt; }
set {
if (txt == value) return;
txt = value;
Console.WriteLine("Txt Setter Called!");
}
}
Upvotes: 0
Reputation: 2665
Change your BindingMode
to OneWay
or OneWayToSource
.. your problem will get solve...
<TextBox x:Name="tbx1" Text="{Binding Txt, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"/>
Upvotes: 0