WPF Databinding Magic - c#

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.

Related

How to create TwoWay binding between two DependencyProperties in code? [duplicate]

I have heard a lot about two-way bindings in WPF, but I'm not entirely clear on how to accomplish it or what it actually means.
I have a ListView with a bunch of items in it. When the user selects a new item, a TextBox in the application will change its text to display some property of the selected item.
But when the user changes the text in the text box I want the ListView item to be updated immediately as well. Is there any "two-way binding" magical WPF way of accomplishing this?
Mark's answer shows how to accomplish what you want, but you also asked more generally about "how to accomplish [two-way binding] and what it actually means."
One-way binding means that the binding target (e.g. control) will display data from the binding source (e.g. business object), and will update itself as the business object changes, but that changes to the control will not be propagated back to the business object. E.g. if the Person.Name changes from "bob" to "kate", the TextBlock.Text bound to the Name will change from "bob" to "kate" too.
Two-way binding simply means that not only are changes in the business object reflected in the UI, but changes made by the user in the UI are propagated back to the business object too. So now when the user edits the TextBox.Text bound to the Name, say changing "kate" to "edmund", WPF will set the Person.Name property to "edmund" as well.
To accomplish this, just set Mode=TwoWay on the Binding declaration. Some properties bind two-way by default: TextBox.Text, for example, binds TwoWay by default, which is why Mark's code doesn't need the Mode declaration. In addition, as Mark notes, by default WPF only propagates changes back to the business object when the control loses focus. If you have two UI elements bound to the same property, this can mean they appear out of sync, in which case you can use the UpdateSourceTrigger to force WPF to propagate whenever the property changes.
MSDN covers this in detail with some good clear diagrams: see Data Binding Overview in the WPF SDK.
If you haven't you'll need to implement INotifyPropertyChanged for your class that you're binding to.
Also, when you say you want the ListBox item to be updated immediately, you mean that you want it to change as you type in the TextBox. By default the TextBox.Text property updates its source when it loses focus, but you can change this by setting the binding UpdateSourceTrigger to PropertyChanged:
{Binding Source={...}, Path=Whatever, UpdateSourceTrigger=PropertyChanged}
What is the Type of the items in the ListView? To get the two way binding going the need you implement INotifyPropertyChanged...
This might help WPF event property changed?

Using ViewModels in ObservableCollections in Prism

As far as I know, the default way to use a ObservableCollection that is bound to a listview is with model classes as elements (ObservableCollection<MyModel>). So when a listview element is selected, we use NavigateAsync and pass the model, which then can be used by the ViewModel to "fill itself".
The problem with this approach is, that it's not possible to use ViewModel properties for binding in the listview.
For example:
I have a View, ViewModel and Model "PickList", which contains a collection of "PickLine" objects - each having a View, ViewModel and Model themselves. The PickLine object contains a property "PickedQuantity" and a property "OpenQuantity". Now in my PickList view, I don't want to bind these two to separate items (e.g. two labels), but I want to have one label to display both I a format like for example "PickedQuantity / OpenQuantity". I know this example can be solved by using multi binding or something like this. But that's not the meaning of it all.
My PickLine ViewModel already has a property "QuantityString", that I want to bind to the label of a listview element via DataTemplate. But how can I do this. Is it even possible?
Make a property that combines the two other properties and bind to that. E.g.:
public string FullQuantity {get {return $"{PickedQuantity} / {OpenQuantity}";}}
Then in the setter for PickedQuantity and OpenQuantity, you will want to call whatever PropertyChanged method you have set up to notify the bindings of a property change and pass in the FullQuantity property name so elements that are bound to FullQuantity get updated when either PickedQuantity or OpenQuantity are changed.
This way, you are only binding one label's text to one property and that label would get updated when either of the two quantity properties are changed.
Note: I am unfamiliar with Prism, but this approach should work regardless of the Mvvm framework in use.
Your PickListViewModel should expose a collection property whose items are of type PickLineViewModel (not PickLine).
Whether you need an ObservableCollection<PickLineViewModel> depends on where changes can happen - in service / model that initially created the PickLines or in the GUI or both. In any way, you have to make sure the changes are propagated from one side (the collection of view models) to the other (the collection of models). Google wrapping observable collection as a starter (hint: avoid two-way sync if possible). These blog posts are old but still relevant and make a good reading. A trivial wrapping is described in this answer.

WPF TwoWay Binding and PropertyChanged

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.

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.

Bind ObservableCollection as source for different CollectionViewSource

I have an ObservableCollection<MyClass> named myCollection that should be binded to two CollectionViewSources (AllItems and SelectedItems).
AllItems's source property is binded to the myCollection. SelectedItems's source property should be bind to myCollection items which IsSelected == true.
public class MyClass : INotifyPropertyChanged
{
//fields and interface implementations
public string Name {//proper code};
public bool? IsSelected {//proper code};
}
// some where else in the MainWindow
AllItems.Source = myCollection;
SelectedItems.Source = myCollection.Where(input=>input.IsSelected==true);
Problem: Every thing is OK when the Window is loaded. But when the IsSelected value for some items in the myCollection is changed obviously it has no effect on the SelectedItems. So to overcome this problem I update the source property of the SelectedItems every time an IsSelected property is changed.
Question: How can I do these kind of binding so that there is no need to manually update the source property of the SelectedItems?
Thnaks.
First of all, you should unconditionally remove your manual filtering setup and replace it with something more appropriate. The choice of what to replace with depends mainly on what .NET version you are targeting.
If targeting .NET 4.5 then a simple solution is and instead enable live filtering on the collection view.
For earlier versions of .NET you will have to do some manual work no matter what, but it is better to just call Refresh on the collection view that re-bind your control. To do this, you should defer the filtering to the collection view itself by setting the Filter event handler from XAML.
After the changes in MyClass you should raise PropertyChanged Event.
If you already do this than you should look at your SelectedItems.Source in Debug mode. Maybe its the correct value already there but its not shown to you.
I mean if SelectedItems.Source belongs to some visible Elements - GUI - you should refresh it on the screen. Cause the other way the value is there but will not be shown till your Element on the Screen is repainted.

Categories