WPF TwoWay Binding and PropertyChanged - c#

So suppose there is an enum
enum SampleEnum
{
Item1,
Item2
}
Then there is a ComboBox
<ComboBox ItemsSource="{Binding SomeItemSource}"
SelectedItem="{Binding
Path=ItemX,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource ResourceKey=SomeConverter}}">
Combo box has a ViewModel as its DataContext
ViewModel : INotifyPropertyChanged, ...
{
...
public SampleEnum ItemX
{
get => model.GetItemX();
set
{
model.SetItemX(value);
RaisePropertyChanged();
}
}
...
}
And RaisePropertyChanged([CallerMemberName] string caller = "") invokes PropertyChanged with property's name.
But.
When I run my code, open my CheckBox, select one item, I get following behaviour: my code enters setter, sets value of the model, raises PropertyChanged, then my getter is invoked, value is retrieved, but it never reaches ComboBox. ComboBox displays the value I chose by hand, not the value returned by accessor.
E.g. if you rewrite get => SampleEnum.Item2 to return always the same value, ComboBox will still display the value I picked by my hand in UI, not the one that is returned by accessor, even though I am 100% sure that getter is invoked, than the value travels to Converter and Convrter also returns proper value.
But if RaisePropertyChanged(nameof(ItemX)) is invoked from any other place, ComboBox immediately retrieves the value from accessor and displays it.
In short, ComboBox ignores PropertyChanged if invoked from setter, but in any other case it works perfectly fine. Specifying directly the name of property (instead of relying on compiler services) or invoking several RasiePropertyChanged in a row in setter does not work.
In general, one should expect that the value selected in combo box and the one returned by getter are the same, but sometimes model can reject provided value and instead return a default one. Not the best behaviour, but it is possible. And in this case the user will be misinformed which item of a ComboBox is actually selected.
I am just wondering what is so special about this accessor that ComboBox ignores it.

tl;dr: What you're trying to do is data validation. That's a solved problem: You can implement validation in your viewmodel with IDataErrorInfo, or in your view with ValidationRules. Either one of those works with WPF instead of against it. Working against WPF is almost invariably losing proposition.
You could also write an ItemContainerStyle for the ComboBox that disables invalid items, or your viewmodel could update the ComboBox item collection to exclude any items that are currently unselectable. I prefer that approach: Rather than "here, you can choose any of these options -- BZZZT, LOL, WRONG CHOICE!", it seems friendlier to present them only with the options that they can choose.
And if you'll know which options are valid after they make their choice, you can almost certainly know beforehand as well.
ComboBox ignores PropertyChanged if invoked from setter, but in any other case it works perfectly fine.
That's correct. The ComboBox is still handling the change it got from the user, and won't be finished doing so until some time after your setter is complete. The ComboBox will ignore any PropertyChanged events on a property it's currently updating. This is by design.
The standard workaround is to call BeginInvoke() with ApplicationIdle priority, and raise PropertyChanged in the delegate. That will have the effect of raising PropertyChanged again after the ComboBox is entirely finished with the current selection-change event. But that's nothing a viewmodel should ever be concerning itself with.
As you say, "Not the best behaviour". It would be preferable to write validation which rejects the wrong value in the first place, rather than writing a strange workaround like the above.

Related

WPF ComboBox (with IsEditable) - Prevent selected item from being deleted when removed from the list of choices

I have a ComboBox with IsEditable set to true. If I type in a custom option and then reset the list of choices (or even remove all choices), the displayed text does not change. Which is exactly what I want. However if I select or type in an option that is on the list and then later remove that option from the list, the selected text gets reset to null or empty string. In this second instance I'd like the selected text to not change (essentially turn into a custom entry).
I tried using the SourceUpdated and setting Handled to true but the event doesn't trigger whether I try replacing it with a new ObservableCollection or calling Clear.
Additionally manually managing the value is problematic because of delayed events triggering on the control itself in response to the change. If I use the following code the control responds to the updated list (and resets the value) after everything has finished executing.
string holdValue = SelectedListString;
ListOptions.Clear();
SelectedListString = holdValue;
This is a "nice to have" so I'm hoping for a simple solution and can't use 3rd party libraries for it. Worst case I guess I can do some hacky change tracking that then prevents the value from being modified when the setter is called on the dependency property SelectedListString, but I was hoping for something simpler.
You can bind your 'SelectedListString' property with 'Text' property of the 'ComboBox' like this
<ComboBox IsEditable="True"
ItemsSource="{Binding ListOptions}"
SelectedItem="{Binding SelectedListString}"
Text="{Binding SelectedListString}"/>
And when you clear the list, you hold on the value manually like you did
string holdValue = SelectedListString;
ListOptions.Clear();
SelectedListString = holdValue;

INotifyDataErrorInfo. ErrorsChanged how to make wpf show the errors of property like `Address.Country`

There is only 3 things inside of INotifyDataErrorInfo:
HasErrors: a read-only boolean property which tells if the object as a whole have any validation errors;
GetErrors: a method which returns validation errors for a given property;
ErrorsChanged: an event which must be raised when new errors – or the lacks of errors – is detected. You have to raise this event for each property.
In the demo project I create a form which display the properties of an object named ‘Person’. Here is how the validation with INotifyDataErrorInfo is enabled in the Binding:
<TextBox Text="{Binding Name,Mode=TwoWay,ValidatesOnNotifyDataErrors=True}"/>
We have to set the ValidatesOnNotifyDataErrors property to true.
The binding will then register itself for the ErrorsChanged event of the binded Person. Eeach time this event is raised for the binded property, the controls will dress itself to display an error. this is done only if the HasErrors is set to true.
Question:
Is there anyone know more detail aobut the ErrorsChanged event is raised for
the binded property, the controls will dress itself to display an
error?
If I binding Address.Country of Person ,will the ErrorsChanged event be raised for the binded property Address.Country or not? why? is there a way make this binding to show Errors too?
<TextBox Text="{Binding Address.Country,Mode=TwoWay,ValidatesOnNotifyDataErrors=True}"/>
I think I can risk an answer, this question is already one year old.
The Binding will register to the ErrorsChanged event in the Class containing the property. In that case, Address must implement INotifyDataErrorInfo.
And, it's you to raise the ErrorsChanged event when you implement the validation logic. Once you have validated the Address.Country, you store the ValidationResults (or simple strings list) and raise the event. The Binding will get the stored ValidationResults list for the PropertyName he is binded to, by calling the method GetErrors(string propertyName)that you wrote yourself implementing the INotifyDataErrorInfo interface.
If this list is not empty, the Binding will set the Property Validation.HasError to True and the control will raise the Validation.Error event. Some controls have a built-in behavior to change their appearance in error case (a TextBox will have a red frame around its border). If you want to show the errors, you have to retrieve them by writing a style in xaml. Plenty of examples out there.
The HasErrors method is used if you want to know if the Person has any errors in its Properties. It's mostly used in such cases : enabling or disabling a save button. Once again, it's you to implement the logic using the HasErrors Property. It's mostly done by binding it to a control Property in xaml.

ComboBox Binding Breaks on Changes to object in SelectedItem

I have a ComboBox that has an ItemSource bound to a an ObservableCollection and its SelectedItem property is bound to a property of T. Both properties live on my view model. Everything works great except for one issue that is causing me to tear my hair out. When I set a property on the object bound to the SelectedItem, the ComboBox binding stops working. VS 2012 output window does not report any binding errors and no exceptions are thrown.
XAML
<ComboBox ItemSource="{Binding Path=Connections, Mode=OneWay}"
SelectedItem="{Binding Path=SelectedConnection, Mode=TwoWay}"
DisplayMemberPath="Name"/>
<TextBlock Text="{Binding Path=ConnectionName, Mode=TwoWay}" />
<TextBlock Text="{Binding Path=ConnectionGroup, Mode=TwoWay}" />
View Model
private void SaveChanges()
{
this.SelectedConnection.Name = this.ConnectionName;
this.SelectedConnection.Group = this.ConnectionGroup;
// -- Save the changes
}
Now all of that works just fine. But as soon as SaveChanges() completes the ComboBox starts acting funny. It looks like everything is fine, but if you attempt to change the selection nothing happens; the same item is selected and the setter for "SelectedConnection" does not fire. I tried systematically commenting out code in SaveChanges() and if I leave only a single line that is setting any property on SelectedConnection I see this failure happen.
I also added an event handler in the code behind of the control to handle the SelectionChanged event on the ComboBox. After the break happens, any time I try to change the value via the UI or in Code the event handler fires twice in immediate succession. The first has the event args' AddedItems set with a single value which is the value I just selected and would expect. However the event fires again immediately with only a single item in the RemovedItems collection of the event args. That collection also contains the item I just selected.
I have tried setting the SelectedConnection property to null and then after the method finishes, setting back to the object in question. No luck.
I also saw an old bug post about moving the ItemSource property after the SelectedItem property on my XAML mark up but that had no effect either.
I'm trying to avoid creating a shadow list and trying to keep everything in sync as that was going to be my next try but that is just getting ugly to do something so simple. Any ideas would be greatly appreciated.
If it matters, this is Visual Studio 2012 building to the 4.5 framework.
EDIT
I've now also tried just completely removing the modified object from the ObservableCollection after setting its properties. It still shows as if it was the selected value (its name is immediately visible in the ComboBox) but it no longer shows up in the drop down list. It also will still not allow itself to be "unselected" even though it doesn't exist in ItemSource any more.
** UPDATE **
I changed the method in question to completely null out the ObservableCollection that is bound to the ItemSource and then restore it once the process is over with like so:
private void SaveChanges()
{
var selected = this.SelectedConnection;
var name = this.ConnectionName; // -- bound to fields on the view
var group = this.ConnectionGroup;
this.Connections = null;
this.RaisePropertyChanged("Connections"); // -- SelectionChanged fires as expected
selected.Name = name;
selected.Group = group;
this._repository.SaveChanges();
this.Connections = new ObservableCollection<Connection>(this._repository.AllConnections);
this.RaisePropertyChanged("Connections"); // -- SelectionChanged event doesn't fire
this.SelectedConnection = selected; // -- SelectionChanged event doesn't fire
}
After that is done the ComboBox once again allows me to change the value as you would expect. However the selected item does not actually appear as selected (even though it is correctly set in the View Model). So yeah, it's a really ugly workaround and it creates its own issue as well.
Have just had this same issue, and I think I tracked down what was doing on.
It only appeared after I made some recent changes (suggested by the code analysis) to add equals/hashcode/operator overloads for a class that I had as IComparable, and was used in an ObservableCollection for a combobox. I made the hashcode based on a descriptive field of the object. However that field could be user-modified, and after the user modified the value, the combobox broke, being unable to unselect that item anymore.
Given that there are no unchanging fields in this object, there's no way to create a fixed hash code based on the object's properties. However if I remove the hashcode override entirely (or set it to something fixed, like type.GetHashCode), everything works again.
So, check to see if your hashcode is based on whatever value you were changing.
I gave up on doing it the way I had originally envisioned. Instead of directly modifying the object in the ItemSource collection, I made the ItemSource collection to be a list of Key/Value pairs and extracted the object I needed from the source list based on the key of the selected pair. I was able to edit that object without issue (as expected). It works now, I just added a layer of code in there that I was hoping to avoid. The UI also allowed me to update the Value side of the pair to reflect changes to the underlying model's "Name" field without giving me the same issues it was previously. I'll just count it as a small win and move on.

TextBox TextChanged Event Problems

I'm using a basic TextBox that is bound to an object. Basically, what I want to do is call a method every time the text in the box is edited and the user de-selects the box or when the underlying bound data is edited. What I am using right now is the TextChanged event, but this has a few problems:
It is called when the TextBox is first created, and I don't want this.
It is called every time a new character is added, and I only want it called when the underlying bound data is changed (which seems to be whenever focus shifts from the box).
How can I accomplish this?
EDIT: I've tried several other TextBox properties like Get/LostFocus but they never seem to fire.
Also, I don't want to put this method call in the Setter of the Property, because the underlying data is something that is logically separate from the UI of this project and I don't want any method calls that relate to doing computations for the UI.
The event LostFocus fires when the focus is shifted from the current element. I tried it and its working fine.
As jods says, the best way to bind your TextBox's Text to ViewModel's property. The Code are:
View:
<TextBox x:Name="TextBox1" Text="{Binding Path=Text1,Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
ViewModel:
public string Text1
{
get { return _text1; }
set
{
_text1 = value;
RaisePropertyChanged("Text1");
}
}
View code behind:
private void ViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Text1")
{
//Call UI related method...
}
}
In this way, it satisfy your two conditions:
1. Every time when you edit TextBox and lose the focus, Setter of Text1 will be called and ViewModel will raise PropertyChanged event.
2. When underlying Text1 is changed. Text1 will also raise the event so View can know it.
Also it can avoid your two concerns:
1. In the first time binding, only getter of Text1 is called. No event is raised.
2. Setter of Text1 is only called after TextBox is lost focus.
every time the text in the box is edited and the user de-selects the box
Hmmm AFAIK it's a standard behaviour of TextBox if you bind text like that: Text={Binding Property}
when the underlying bound data is edited
You can provide this functionality inside setter of your property.
Best design is to listen for changes in the underlying bound property. You can do that without changing the setter if you use a DependencyProperty or if your object implements INotifyPropertyChanged.
When the underlying property changes (LostFocus by default, or each char at a time) is a binding option.
If you don't want to follow my advice of listenning for changes in your (view-)model, you could subscribe to GotFocus and LostFocus events. Save the current value when you get focus, compare with current value when you lose it. If it's different -> do what it is you want to do.
I am not sure what you are finally trying to achieve but I am going to take a guess at this. If you are following an MVVM pattern then, then it seems like you can achieve what you want by using the updateSourceTrigger property of the binding. If you are not using MVVM then you might what to take a look at using MVVM

WPF Databinding Magic

I have ObservableCollection<Foo> that is bound to an ItemsControl (basically displaying a list).
Foo mostly looks like this (there are other members but it doesn't implement any interfaces or events):
class Foo
{
public string Name { get; set; }
//...
}
When the user clicks on an item I open a dialog where the user can edit Foo's properties (bound to a small viewmodel with a Foo property for the selected item), the Xaml looks like this:
<TextBox Text="{Binding Foo.Name,Mode=TwoWay}"
Grid.Column="1" Grid.Row="0" Margin="2" />
The really strange thing is, when the user edits the name, the value in the list changes! (not while typing but after the focus leaves the field)
How does it do that? I haven't implemented the INotifyPropertyChanged interface on the Foo object!
So far I checked that it doesn't just refresh the whole list - only the selected item. But I don't know where I could set a breakpoint to check who's calling.
Update: thanks to casperOne for the link to the solution! I'll add a summary here in case it goes 404:
[..] actually you are encountering a another hidden aspect of WPF, that's it WPF's data binding engine will data bind to PropertyDescriptor instance which wraps the source property if the source object is a plain CLR object and doesn't implement INotifyPropertyChanged interface. And the data binding engine will try to subscribe to the property changed event through PropertyDescriptor.AddValueChanged() method. And when the target data bound element change the property values, data binding engine will call PropertyDescriptor.SetValue() method to transfer the changed value back to the source property, and it will simultaneously raise ValueChanged event to notify other subscribers (in this instance, the other subscribers will be the TextBlocks within the ListBox.
And if you are implementing INotifyPropertyChanged, you are fully responsible to implement the change notification in every setter of the properties which needs to be data bound to the UI. Otherwise, the change will be not synchronized as you'd expect.
This is a total guess, but I'm thinking that because you have two-way binding enabled, WPF is now aware of when it makes changes, and will update other items it knows is bound to the same instance.
So because you have changed the value of the Name property through the textbox, and WPF knows when you have changed that value, it does its best to update whatever else it knows is bound to it.
It uses reflection to set the value of that property. INotifyPropertyChanged is only needed if the TextBox needs to be informed of a change in the Name property of the Foo class.
Because they're databound to the same object. If you change the binding to
{Binding Foo.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
then they'll be in synch when the user types in the textbox.

Categories