Binding ignores PropertyChanged for string properties
-
lundi 30 mars 2009 19:01I have two TextBoxes: One bound to an int property the other bound to a string property.
<TextBox Text="{Binding Path=MyInt}" />
<TextBox Text="{Binding Path=MyString}" />
When I send a PropertyChanged event for MyInt the TextBox gets updated, but not for the string.
Here the implementation of the two properties:
public int _myInt = 17;
public int MyInt
{
get
{
// this gets called from SendPropertyChanged
return this._myInt;
}
set
{
if ((this._myInt != value)) {
this._myInt = value;
this.SendPropertyChanged("MyInt");
}
}
}
public string _myString = "hello";
public string MyString
{
get
{
// does NOT get called from SendPropertyChanged. Why???
return this._myString.ToString();
}
set
{
if ((this._myString != value)) {
this._myString = value;
this.SendPropertyChanged("MyString");
}
}
}
I am sure the int and the string use the same infrastructure (INotifyPropertyChanged) for the Binding.
1. Why is this behaving differently for int and string?
2. How can I force the string binding to update its value?
Thanks for any hints.
Réponses
-
vendredi 3 avril 2009 23:26
This has to do with what we familiarly refer to as "the $10 bug" (or feature), so named because of the following motivating scenario. Your data item has a property called Balance of type double. You want to bind a TextBox to this property, but you want to display the dollar sign, so you add a converter that changes the double to a string and tacks a dollar sign in front. The double value 20.00 is displayed as "$20.00". When the user types '10' into the TextBox and tabs away, you want the binding to write 10.00 into the Balance property and also update the display to "$10.00".
To get this effect, WPF converts the text "10" to double, writes 10.0 into the Balance property, [*], sends 10.0 through the converter again to obtain "$10.00", and finally assigns "$10.00" to TextBox.Text. This is the $10 feature. In WPF 3.x, this feature is enabled only when (a) the binding updates on LostFocus, and (b) the binding has a converter. The behavior you're seeing in Marcel's app is a consequence of this - the only binding that meets both requirements is the first one, the LostFocus binding to MyInt (to which WPF attaches a default converter).
Yes, it's a "strange Binding phenomenon". We've been getting complaints about it for years. In addition, people have asked for an extra step at position [*], namely to re-fetch the value of the Balance property from the data item. (This would cope with a data item that changed the value during the setter, perhaps because of internal validation.)
In WPF 4.0 this is fixed, including the [*] step. On my machine, all of Marcel's bindings call the appropriate setter. Meanwhile, you can get the effect by adding a dummy converter to your LostFocus bindings. If you need the $10 feature on a binding that is not LostFocus, I'm afraid I don't have a workaround.
Dev Lead, Windows Presentation Foundation, WinFX- Proposé comme réponse Alexander Yudakov samedi 4 avril 2009 11:32
- Marqué comme réponse Tao Liang lundi 6 avril 2009 01:52
Toutes les réponses
-
mardi 31 mars 2009 01:03Hello,
If I understand your case correctly, I think you just send the PropertyChanged event for MyInt property, not for MyString property. The two properties are independent. If you want to change both properties when update MyInt. You can try this:
public int _myInt = 17;
public int MyInt
{
get
{
// this gets called from SendPropertyChanged
return this._myInt;
}
set
{
if ((this._myInt != value)) {
this._myInt = value;
this.SendPropertyChanged("MyInt");
this.SendPropertyChanged("MyString"); // This can make sure MyString updated when you update MyInt.
}
}
}
Yiling, MVP(Visual C++)- Marqué comme réponse Tao Liang jeudi 2 avril 2009 06:54
- Non marqué comme réponse Andreas Steidle vendredi 3 avril 2009 18:02
-
mercredi 1 avril 2009 08:56I can reproduce this and I must agree this is definitely not what I expected!?!
I uploaded my test application here: http://cid-027cdb908d14e584.skydrive.live.com/self.aspx/WPFSamples/INotifyPropertyChangedTest.zip
I tried with different options for UpdateSourceTrigger and I noticed that if you set it to PropertyChanged, the getter to an integer property is also not called.
Using LostFocus (default) or Explicit does call the getter for an integer property.
However, none of the options will result in a call to the getter of a string property.
If someone can please explain this...?
Marcel -
mercredi 1 avril 2009 11:49
Hi Marcel,
I downloaded you test application and take a look. Yes, I can reproduce your output. At first, none of the getter of string will be called. But after I added one empty debug IValueConverter into this application. MyString.get is called. But only MyString.get is called, MyString2 and MyString3 getter are never been called. Maybe we can try to catch some information from the debugging symbol of WPF.
Keeping on this.....
Yiling, MVP(Visual C++) -
vendredi 3 avril 2009 18:02Marcel, Yiling,
thanks for your investigation in this strange Binding phenomenon!
@Yiling: I have allowed myself to unmark your answer as it definitely does not address the core of the problem. I appreciate very much that you announced you will have a 2nd look.
@Marcel: Thanks for the extensive test app. The reason why I came across this is a "string to typeX" converter property in a ViewModel class, i.e. a string property that does some custom parsing/validation before it assigns the user string to a backend DateTime, string, int, etc... But of course the user should see the result of this process upon LostFocus - and that's where we are stuck because the Binding seems to cancel PropertyChanged events from string properties and the user never sees what the system did to its value. :-(
To simulate some processing one could manipulate the value before assigning it:
this._myString = value + "X";
This way you should see in the TextBox itsself if the PropertyChanged has arrived - or not and the probelm becomes very obious.
If I have not misunderstood the notion of MVVM such string-to-X-converter-properties should be a major asset of ViewModel classes. As it seems the WPF Binding engine rules out this IMO important use case. With all the MVVM enthusiasm around I wonder nobody else has come across this yet. Or have I missed something?
Thanks for any further clarifications. -
vendredi 3 avril 2009 23:26
This has to do with what we familiarly refer to as "the $10 bug" (or feature), so named because of the following motivating scenario. Your data item has a property called Balance of type double. You want to bind a TextBox to this property, but you want to display the dollar sign, so you add a converter that changes the double to a string and tacks a dollar sign in front. The double value 20.00 is displayed as "$20.00". When the user types '10' into the TextBox and tabs away, you want the binding to write 10.00 into the Balance property and also update the display to "$10.00".
To get this effect, WPF converts the text "10" to double, writes 10.0 into the Balance property, [*], sends 10.0 through the converter again to obtain "$10.00", and finally assigns "$10.00" to TextBox.Text. This is the $10 feature. In WPF 3.x, this feature is enabled only when (a) the binding updates on LostFocus, and (b) the binding has a converter. The behavior you're seeing in Marcel's app is a consequence of this - the only binding that meets both requirements is the first one, the LostFocus binding to MyInt (to which WPF attaches a default converter).
Yes, it's a "strange Binding phenomenon". We've been getting complaints about it for years. In addition, people have asked for an extra step at position [*], namely to re-fetch the value of the Balance property from the data item. (This would cope with a data item that changed the value during the setter, perhaps because of internal validation.)
In WPF 4.0 this is fixed, including the [*] step. On my machine, all of Marcel's bindings call the appropriate setter. Meanwhile, you can get the effect by adding a dummy converter to your LostFocus bindings. If you need the $10 feature on a binding that is not LostFocus, I'm afraid I don't have a workaround.
Dev Lead, Windows Presentation Foundation, WinFX- Proposé comme réponse Alexander Yudakov samedi 4 avril 2009 11:32
- Marqué comme réponse Tao Liang lundi 6 avril 2009 01:52
-
samedi 4 avril 2009 01:42Thanks Sam for detailed explain.
So that's reason of Why I added one debug converter into LostFocus binding, MyString.get is called. It meets the two requiements mentioned above.
Yiling, MVP(Visual C++) -
dimanche 5 avril 2009 08:02Thanks for the answer, Sam! I guess that after a couple of years working in WPF, there are still plenty of corners I have to explore :-)
Marcel -
jeudi 9 avril 2009 15:56Sam, thanks for your extensive explainations! I am very glad to hear that the "ignore PropertyChanged under condition xyz" will be eliminated in 4.0. I am also looking forward to the [*] feature.
What I do not quite understand is the motivation for "the $10 feature". If EVERY Binding would ALWAYS honour the PropertyChanged event and re-fetch the value from the getter this would also be supported, provided the developer explicitly raises the event in the setter, of course. This would be the natural behaviour one would expect. I guess WPF's data binding was originally more designed around Converters for special display rather than properties and it might have been a concern data could be re-fetched when it's not necessary. To realize this and the behaviour you explain there must be a considerable amount of extra logic in the Binding to decide whether an incoming PropertyChanged should be ignored or not.
Thanks again for your clarifications.