WPF binding update notification for nested property when parent property changes - c#

I have a ViewModel with a complex property type and want to bind my view to a nested property of this object.
My ViewModel is implementing INotifyPropertyChanged (or do be extact BaseViewModel is implementing it). The class of the parent property is not implementing INotifyPropertyChanged.
The class Car is not implementing INotifyPropertyChanged. But I'm not changing the property Manufacturer, I change the MyCarProperty property, and so I expect that the OnNotifyPropertyChanged event will trigger the value update?
When I'm updating the value of the parent property, the nested property is not updating. Can you tell me how can I implement this functionality?
ViewModel
public class ViewModel : BaseViewModel
{
private Car _myCarProperty;
public Car MyCarProperty
{
get { return _myCarProperty; }
set
{
if (value == _myCarProperty) return;
_myCarProperty = value;
OnPropertyChanged();
}
}
}
Binding in the View
<TextBlock Text="{Binding Path=MyCarProperty.Manufacturer}" />
When I change the value of MyCarProperty the View does not update.
Thanks for any help!
Edit: OnPropertyChanged() implementation
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged

"The class Car is not implementing INotifyPropertyChanged. But I'm not
changing the property Manufacturer, I change the MyCarProperty
property, and so I expect that the OnNotifyPropertyChanged event will
trigger the value update?"
Nope, it won't trigger the value update a level down. Bindings don't listen to property changes for an entire path, they listen only to the object that they're bound to.
I see a couple options off the top of my head (in order of my preference when I run into this):
Bind to the car, not the sub property, and create a data template that displays what you want out of it.
Manually kick the binding by calling UpdateTarget on it's BindingExpression when you need to.
I know it looks like there's a lot more to learn on the data template route, but I promise you that data templates will prove vastly more powerful, scalable, maintainable, and useful than manually kicking bindings as you work more in WPF. (Also, once you understand them, I think they're actually less work than manually kicking bindings).
Good luck!

The accepted answer explains how to handle the case where a sub-property on a Binding Source is changed and you wish to update the view - which is not what the question is asking. WPF will in fact respond to changes from many levels down, so long as you are notifying changes for any properties being changed within the specified path.
As for this:
"The class Car is not implementing INotifyPropertyChanged. But I'm not changing the property Manufacturer, I change the MyCarProperty property, and so I expect that the OnNotifyPropertyChanged event will trigger the value update?"
WPF handles this already.
In your example, ViewModel is the Binding Source. When you set MyCarProperty (firing the NotifyPropertyChanged event), WPF will re-evaluate the Binding Target Value using the Binding Path for the new Binding Source object - updating your view with the new Manufacturer.
I have tested this with a simple WPF app - it also holds true for very deeply nested Paths:
https://pastebin.com/K2Ct4F0F
<!-- When MyViewModel notifies that "MyCarProperty" has changed, -->
<!-- this binding updates the view by traversing the given Path -->
<TextBlock Text="{Binding Path=MyCarProperty.Model.SuperNested[any][thing][you][want][to][try][and][access].Name}" />

I'm not an WPF expert, but I think it's because you've chosen the wrong path.
<TextBlock Text="{Binding Path=MyCarProperty, Value=Manufacturer}" />
update:
<TextBlock Text="{Binding Source=MyCarProperty, Path=Manufacturer}" />

Related

Is there a way to get UpdateSourceTrigger=PropertyChanged for compiled bindings?

I'm working on a UWP application and I realized that the default UpdateSourceTrigger mode for the TextBox control, which is LostFocus, can't be changed when using a compiled binding.
This means that whenever I want the binding to update for a TextBox, I have to use all this repeated boilerplate:
<TextBox
Text="{x:Bind ViewModel.Title, Mode=TwoWay}"
TextChanged="TextBox_OnTextChanged"/>
private void TextBox_OnTextChanged(object sender, TextChangedEventArgs e)
{
ViewModel.Title = ((TextBox)sender).Text;
}
Now, this is not too bad, but having to remember to create the TextChanged handler every single time a TextBox is used is annoying and error prone.
This would work fine with a classic binding:
<TextBox Text="{Binding Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
But of course, here there would be the additional overhead of usinc classic bindings (involving runtime reflections, etc.).
Is there a way to get the same behaviour of UpdateSourceTrigger=PropertyChanged as well? I'd be completely fine with, say, writing a custom attached property that sets things up, as long as I can do everything I need directly from XAML, with no code behind involved.
Thanks!
UPDATE: (in response to Nico Zhu - MSFT's answer)
For my testing, it works well.
It doesn't for me, at all, As I said multiple times already, using UpdateSourceTrigger with x:Bind is just not possible. It doesn't compile, the property is shown in red in the XAML editor, it just isn't there. I really don't know where are you trying that, if you say it's working for you. I'm currently targeting 17763 as minimum and I can 100% guarantee that that does not work.
Compiled Binding is used with the {x:Bind} syntax as opposed to the {Binding} syntax of Classic Binding.
I'm well aware of the difference, I've already mentioned this multiple times, both in my original question here (with code snippets too) as well as in my comments.
It still uses the notifying interfaces (like INotifyPropertyChanged) to watch for changes
As I said, I'm aware of this too. But again, as from this question, this isn't the problem here at all. The issue is not with updates from the viewmodel to the bound property, but from the bound property (TextBox.Text in this case) to the viewmodel.
{x:Bind} is by default OneTime compared to {Binding} which is OneWay. so you need to declare bind Mode OneWay or TwoWay for {x:Bind}.
I'm sorry, but I have to say at this point I'm starting to wonder if you've actually read my initial question at all. I'm aware of this, and in fact you can see in both my original code snippets that I had already used the explicit Mode=TwoWay property in both my bindings.
And once again, this was not what the question was about, at all.
To reiterate: the issue here is that the TextBox.Text property defaults to the LostFocus trigger, and that the UpdateSourceTrigger property is not available for compiled bindings. So I'd like to know if there's a way to achieve the same, with a compiled binding, in XAML-only, without having to manually create a TextChanged handler every single time (and if not, if you plan to eventually add the UpdateSourceTrigger property to compiled bindings too).
Side note: I didn't mean to sound disrespectful here, and I hope we've now solved the existing misunderstandings with my question.
UPDATE #2: turns out the issue was causing by the ReSharper plugin, which was marking the UpdateSourceTrigger property as error in compiled bindings.
I've opened an issue for that here: https://youtrack.jetbrains.com/issue/RSRP-474438
Please check UpdateSourceTrigger documentation.
The default UpdateSourceTrigger value is Default. And
using default behavior from the dependency property that uses the binding. In Windows Runtime, this evaluates the same as a value with PropertyChanged. If you used Text="{x:Bind ViewModel.Title, Mode=TwoWay}", the Title will be changed when text changes. we have not need modify the viewmode in TextChanged even handler.
The premise is that we need implement INotifyPropertyChanged like the follow.
public class HostViewModel : INotifyPropertyChanged
{
private string nextButtonText;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public HostViewModel()
{
this.NextButtonText = "Next";
}
public string NextButtonText
{
get { return this.nextButtonText; }
set
{
this.nextButtonText = value;
this.OnPropertyChanged();
}
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
For more detail please refer Data binding in depth document.
Update
<TextBox Text="{x:Bind Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> doesn't compile at all, as I said the UpdateSourceTrigger property isn't available at all when using a compiled binding.
For my testing, it works well. Compiled Binding is used with the {x:Bind} syntax as opposed to the {Binding} syntax of Classic Binding. It still uses the notifying interfaces (like INotifyPropertyChanged) to watch for changes but {x:Bind} is by default OneTime compared to {Binding} which is OneWay. so you need to declare bind Mode OneWay or TwoWay for {x:Bind}.
Xaml
<StackPanel Orientation="Vertical">
<TextBox Text="{x:Bind Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{x:Bind Title, Mode=OneWay}" /> <!--declare bind mode-->
</StackPanel>
Code behind
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _title;
public string Title
{
get
{
return _title;
}
set
{
_title = value;
OnPropertyChanged();
}
}

How to debug UWP xaml bindings?

I have a DataGrid that is binded to an ObservableCollection in my universal windows platform app.
The datagrid is not shown up when page is loaded. I have another datagrid in the same page that is almost the same but is binded to another collection almost the same as the first one(which has binding problems).
Is there any way to debug the XAML file ?
Sample Code:
<GridView Name="HourGridView" Grid.Row="4"
ItemsSource="{x:Bind ForeCastsByDates}"
Foreground="Chartreuse" >
<GridView.ItemTemplate>
<DataTemplate x:DataType="data:ForeCast">
.......
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
The collection which is not binded:
private ObservableCollection<ForeCast> ForeCastsByDates;
The collection that binded well:
private ObservableCollection<ForeCast> ForeCasts;
The ForeCastsByDates is a part of ForeCasts:
ForeCastsByDates = new ObservableCollection<ForeCast>( ForeCasts.GroupBy(x => x.Date).Select(x => x.First()));
If I am not wrong, it seems that you are actually trying to bind to a class field not a property.
Data binding requires properties to work properly. To achieve that, you will have to make a private backing field and a public property that can then be accessed with data binding.
private ObservableCollection<ForeCast> _foreCastsByDates;
public ObservableCollection<ForeCast> ForeCastsByDates
{
get
{
return _foreCastsByDates;
}
set
{
_foreCastsByDates = value;
//notify about changes
OnPropertyChanged();
}
}
You may have noticed the property uses a OnPropertyChanged() method in the setter. To actually notify the user interface about changes of the property, you need to implement the INotifyPropertyChanged interface on your Page:
public partial MainPage : Page, INotifyPropertyChanged
{
// your code...
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The OnPropertyChanged method fires the PropertyChanged event which notifies the listeners that a property has changed. In this case we need to notify about the changes to the ForeCastsByDates property. Using the CallerMemberNameAttribute used next to the OnPropertyChanged method parameter, the parameter is automatically set to the name of the caller (in this case the ForeCastsByDates property.
Finally, the {x:Bind} syntax defaults to OneTime mode, which means it is updated only once and does not listen to property changed. To ensure all later updates to the property are reflected, use
{x:Bind ForecastsByDates, Mode=OneWay}
Important thing to mention is that you have to make changes to the ForecastsByDates property itself to notify the UI (the property setter has to be executed to call the OnPropertyChanged method). If you do just _foreCastsByDates = something, the field will change, but the UI will not know about it and the change will not be reflected.

How to validate a dependency property on a UserControl?

My view model implements IDataErrorInfo and contains a Message property that is validated.
I have created a UserControl with a Text DependencyProperty that is bound to Message. There are several controls on my UserControl that are bound to Text (which therefore show Message).
How can I show validation errors on the controls in my UserControl that are not bound to Message directly?
After quite some time, I have managed to figure out a solution that I thought I should share in case others find it useful:
Basically I have added a PropertyChangedCallback on my Text DependencyProperty. In this call-back I get the binding between Text and the property on the view model and check it for validation errors. If a ValidationError is found, I go through all the controls in my UserControl that are bound to Text, and give their binding the same error using Validation.MarkInvalid.
EDIT:
Copying the validation errors like this works fine if I put the code below in a button click event handler. If however the code is in the PropertyChangedCallback for Text then nothing happens. Does anyone have a solution?
// Get the binding from the Text property to the view model.
BindingExpression textBindingExpression = BindingOperations.GetBindingExpression(this,
MyUserControl.TextProperty);
// If there is a validation error, then give it to the control bindings.
if (textBindingExpression != null && textBindingExpression.ValidationError != null) {
Validation.MarkInvalid(this.MyTextBox.GetBindingExpression(TextBox.TextProperty),
textBindingExpression.ValidationError);
Validation.MarkInvalid(this.MyTextBlock.GetBindingExpression(TextBlock.TextProperty),
textBindingExpression.ValidationError);
}
Here is the solution I came up with which allows a UserControl with Dependency Properties to "wrap" the validation from the View Model it is bound to.
Firstly I followed the pattern in this post to create the desired DataContext hierarchy.
XAML:
<!-- Some boilerplate attributes snipped -->
<UserControl x:Class="App.Views.UserControls.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:App.Views.UserControls"
Validation.ErrorTemplate="{x:Null}">
<Grid x:Name="LayoutRoot"
DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:MyUserControl}}">
<TextBox Grid.Row="0" Grid.Column="0" Text="{Binding Text, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</UserControl>
This way, the DataContext of the control is the view model inherited from the parent, which is where the validation is done. This is then overridden on the control's root child element to be the control itself, which allows binding to the Dependency Properties in code-behind. Also note that the control's ErrorTemplate has been nulled out - this is to prevent the default red box appearing.
The inherited view model can now be accessed from the control's code behind quite simply:
private INotifyDataErrorInfo ViewModelErrors => DataContext as INotifyDataErrorInfo;
Now implement INotifyDataErrorInfo in the user control and wrap the view model:
public bool HasErrors => ViewModelErrors.HasErrors;
public IEnumerable GetErrors(string propertyName)
{
return ViewModelErrors.GetErrors(propertyName);
}
The tricky part comes when you need to know which model property your control dependency property is bound to. This would be easier if you could look up registered dependency properties by name and interrogate the binding, but I couldn't find a way to do that without reflection. Therefore I used the PropertyChangedCallback of the dependency property to manually build a list of mappings. The parameters to the callback contain all the required information.
// Maps User Control properties to their View Model properties.
private readonly Dictionary<string, string> _propertyMappings = new Dictionary<string, string>();
// This should work for any property.
private static void OnDependencyPropertyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var userControl = (MyUserControl)d;
var dependencyPropertyName = e.Property.Name;
// Create this mapping one time only.
if (!userControl._propertyMappings.ContainsKey(dependencyPropertyName))
{
// Get the binding from the property to the view model.
var binding = BindingOperations.GetBindingExpression(d, e.Property);
if (binding != null)
{
// Create a mapping of user control property to view model property.
// This will let us look up the error from the view model.
var boundPropertyName = binding.ResolvedSourcePropertyName;
userControl._propertyMappings[dependencyPropertyName] = boundPropertyName;
}
}
}
Then incorporate this in to GetErrors:
public IEnumerable GetErrors(string propertyName)
{
if (ViewModelErrors != null && _propertyMappings.ContainsKey(propertyName))
{
return ViewModelErrors.GetErrors(_propertyMappings[propertyName]);
}
else
{
return Enumerable.Empty<string>();
}
}
That should be enough. Validation is done in the model and the results pulled down to the user control. No need to duplicate.

How to juggle multiple DataContexts on one XAML window?

I have an class, let's refer to it as SomeClass. SomeClass implements INotifyPropertyChanged and this is coded as follows:
public class SomeClass
{
.
.
.
private bool _isDirty;
public bool IsDirty
{
get { return this._isDirty; }
set
{
this._isDirty = value;
this.NotifyPropertyChanged("IsDirty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I have a form that uses an instance of SomeClass, called instanceOfSomeClass
This property all fires correctly but onto the main issue which is where I have a Save button bound to that property viz.
<Button Content="Save" Height="23" Name="btnSave" IsEnabled="{Binding Path=IsDirty}" Width="60" Margin="10, 10" HorizontalAlignment="Right" Click="btnSave_Click" />
A combo box SelectionChanged event is supposed to change that property is defined as follows:
<ComboBox Name="cboListOfUsers" ItemsSource="{Binding}" SelectionChanged="cboSomeCombo_SelectionChanged"/>
(I have removed parts of the combo box definition that are not pertinent to the question such as styles etc)
Critically the DataContext of the combo box is not set to the instanceOfSomeClass, rather a List of a custom class.
The SelectionChanged event fires and I have code that looks like this:
instanceOfSomeClass.IsDirty = true;
instanceOfSomeClass.User = (ApplicationUser) cboSomeCombo.SelectedItem;
This runs and although it does change the property and raise the appropriate notification it doesn't enable the command button. I surmise that this is because the DataContext for the combo is different to the DataContext for the command button
I've tried changing the DataContext in the SelectionChanged event but this just results in nothing being selected in the combo (the Save button is enabled though!)
Any help would be greatly appreciated
I surmise that this is because the DataContext for the combo is
different to the DataContext for the command button
no i don't think so. You could try that by not binding the ItemsSource directly to the DataContext instead using a member on the datacontext or using RelativeSource, ElementName, directly specifying the source or another binding syntax. I greatly suggest to use a collection from a property and not set the collection as the datacontext (personally i think thats really bad style, {Binding} should only be used very rarely and i use it only when ContentControls are involved).
Check the Datacontext on the button, use snoop for that it helps greatly by finding bugs like these. Make sure the property is REALLY raised, i can't count how many times we didn't step in the actual NotifyPropertyChanged where the bug was.
Make sure your button doesn't use a command sowhere because commands change the IsEnabled property in some ways.
Make sure nobody is overwriting the IsEnabled property, like Triggers, Animations etc.
Check the output for binding errors or warnings, enable them if you use vs10.
I will update my answer if you can provide more info, was just to much for a comment.

XAML inline data binding doesn't work; code behind binding works

Greetings folks!
I'm running into a problem with WPF databinding that I hope you can help out with. I'm new to WPF but an expereienced developer (VB 3.0-6.0, C#).
Here's the scenario:
I have a C# project called MasterPartsData which contains a number of classes which reprsent different types of parts (capacitor, diode, etc). They inherit from a base class called clsPart.
I have another C# WPF project which contains WPF UserControls (as well as a MainWindow) to visually represent the values stored in an individual MasterPartsData (MPD) object. I've created a private field in the usercontrol to hold the object with a getter and setter.
If I create a binding explicitly in the setter for the populated object:
_capacitor = value;
Binding binding = new Binding();
binding.Source = _capacitor;
binding.Path = new PropertyPath("C0uf");
this.txtC0uf.SetBinding(TextBox.TextProperty, binding);
(with _capacitor being the private object variable and C0uf being the property name)
the value correctly displays.
However I don't wish to have to explicitly create each binding in the code behind. My preference is to create the bindings inline in XAML, perhaps with a DataContext pointing to the object.
Unfortunately every different permutation I've tried fails to work; the text box doesn't show data.
I have a couple of suspicions:
1) The binding is correct, but the text box needs to be refreshed.
2) The binding is confused between the private variable and the properties.
3) Maybe the fact that the class is defined in a different project is causing issues.
4) I'm going mad and should check myself into an asylum before someone gets hurt. :)
Any help you can provide would be most appreciated. I'm more than happy to add more information, but didn't want to clutter the question with pages and pages of source.
With respect to your suspicions:
1) I think the default binding behavior of a TextBox is TwoWay, with a LostFocus update trigger, meaning that your UI focus will have to change to another control before the binding will update, if changes are made in the UI.
If changes are made in the code you need to raise the NotifyPropertyChanged event in order for the binding system to see it.
2) This is probably not the case, but it leaves the impression that you're trying to set bindings on your UserControl properties, which is not the way data binding was designed to be used in this particular kind of use case. What you want is to bind data from non-UI classes to dependency properties on your UserControls.
3) This will never matter, as long as your UI project has a reference to your classes.
4) This is a common reaction people have when beginning to use XAML and WPF. It's like instead of being handed a box of Legos, you just got handed an injection molding machine with insufficient instructions, isn't it?
Overall, this is a situation where you might need to examine your design; elements of the "Model-View-ViewModel" pattern will come in handy. If you're unfamiliar with this, it's a development pattern in which you introduce a "ViewModel" class, perhaps you can call it MasterPartsVM which contains an implementation of INotifyPropertyChanged.
The DataContext of your UserControl would be set to this MasterPartsVM class.
A brief code example, using some generic names. Given a ViewModel class with a small backing class that looks like this:
class PartViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public PartClass Data { get; set; }
public String SomeVMProperty
{
get { return Data.SomeProperty; }
set
{
if (Data.SomeProperty != value)
Data.SomeProperty = value;
this.PropertyChanged(this, new PropertyChangedEventArgs("SomeVMProperty"));
}
}
}
class PartClass
{
public string SomeProperty { get; set; }
}
The XAML of a basic UserControl would look like this:
<UserControl x:Class="WpfApplication1.PartUserControl"
... >
<Grid>
<TextBox Text="{Binding SomeVMProperty}" Margin="68,77,104,176" />
</Grid>
</UserControl>
To connect your data class to this UserControl, you set the UserControl's DataContext property. If you do this in code, it's a matter of having a reference to your user control and the ViewModel, and then setting the property:
MyUserControlInstance.DataContext = new PartViewModel(); // or some existing PartViewModel
That combination of code should work to produce a textbox whose Text property changes every time the SomeVMProperty property is changed.
In a basic binding scenario, if your class looks like this
public class MasterPartsData
{
private string _c0uf;
public string C0uf
{
get { return _c0uf;}
set { _c0uf = value;}
}
public MasterPartsData()
{
C0uf = "Hello World!";
}
}
your XAML would look like this
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" >
<Window.DataContext>
<local:MasterPartsData />
</Window.DataContext>
<Grid>
<TextBlock Text="{Binding Path=C0uf}" />
</Grid>
</Window>
Note, there are many different approaches to setting the DataContext, you don't necessarily just have to do it in the XAML
Also, typically your MasterDataParts class would implement INotifyPropertyChanged

Categories