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

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();
}
}

Related

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.

WPF binding update notification for nested property when parent property changes

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}" />

Metro style windows 7 WPF app -toggleSwitch-

I'm currently fiddling around with the look of one of my older wpf apps using MahApps metro library. I'm stuck with Controls:ToggleSwitch where I can bind almost everything but commands.
When I try to bind a command as below,
<Controls:ToggleSwitch Header="Start Playing" OnLabel="Stop" OffLabel="Play"
IsChecked="{Binding ToggleRecordCommand}"
CommandParameter="{Binding}" />
I get an error like;
Error 62 A TwoWay or OneWayToSource binding cannot work on the read-only property 'ToggleRecordCommand' of type 'RecorderApp.View.MainWindowViewModel'.
Also it tells me there is no CommandParameter. How am I going to bind actions to this?
First of all, as Brendan said, IsChecked property has to be binded with a general Property which has INotifyPropertyChanged, NOT an ICommand type.
In order to bind with Command, the easiest workaround is to use Click(or Checked) event with xaml.cs Code-behind works.
In XAML, as below.
<ToggleButton x:Name="recordButton"
Checked="OnRecordButton_Checked"
IsChecked={Binding IsRecording} />
In Code-behind, as below.
private void OnRecordButton_Checked(object sender, RoutedEventArgs e)
{
if (recordButton.IsChecked.GetValueOrDefault())
{
// Do your own logic to execute command. with-or-without command parameter.
viewModel.ToggleRecordCommand.Execute(null);
}
}
And, In ViewModel (assumption), as below.
// Property for toggle button GUI update
public bool IsRecording{
get{ return _isRecording;}
set{
_isRecording = value;
NotifyPropertyChanged("IsRecording");
}
}
public ICommand ToggleRecordCommand{
// Your command logic.
}
IsChecked is a bool? property and will likely not work if you pass it an ICommand. Source code
If you'd like to see this supported, please raise an issue on the project site and we can discuss it further.

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.

Custom UserControl "IsEnabled" data binding not working

I have a kinda awful problem with my WPF application right now...
I have a custom UserControl used to edit details of a component. It should start by being not enabled, and become enabled as soon as the user chose a component to edit.
The problem is: the IsEnabled property does not even change.
Here is my code:
<my:UcComponentEditor Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
IsEnabled="{Binding EditorEnabled}"
DataContext="{Binding VmComponent}" />
EditorEnabled is a property in my ViewModel (VmComponent), and is by default false, becomes true when the user chose a component or created one
Just for the record, in my ViewModel:
private Boolean _editorEnabled = false;
public Boolean EditorEnabled
{
get { return _editorEnabled; }
set
{
_editorEnabled = value;
OnPropertyChanged("EditorEnabled");
}
}
When I try to launch my app, the UserControl is starting... enabled.
I added breakpoints everywhere, the EditorEnabled is false from the beginning.
I also did a horribly stupid thing to try to figure out what's happening: I created a converter (so useful -- converting a boolean to boolean -- eh), put a breakpoint on it, and... The code is never reached.
<my:UcComponentEditor Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
IsEnabled="{Binding EditorEnabled, Converter={StaticResource BoolConverter}}"
DataContext="{Binding VmComponent}" />
That probably means that the property isEnabled is never set, since the converter is never reached.
Do you see any kind of problem there? I started working in WPF about one week ago and therefore I may have missed something essential...
Thank you very much for your time :-)
You should add a DependencyProperty for the binding to work properly. See here for more information.
Code-behind:
public static readonly DependencyProperty EditorEnabledDependencyProperty = DependencyProperty.Register("EditorEnabled", typeof(bool), typeof(UcComponentEditor), new PropertyMetadata(false));
public bool EditorEnabled
{
get { return (bool)base.GetValue(UcComponentEditor.EditorEnabledDependencyProperty); }
set { base.SetValue(UcComponentEditor.EditorEnabledDependencyProperty, value); }
}
The issue I think is that there is a binding on the DataContext property of the user control. Which means the EditorEnabled property should be a property in the VmComponent object. At least that's what my problem was.
To get around it, I specified a proper source to the binding of IsEnabled. Once I did that the control started working as expected.
Hope that helps.
Encapsulating your control in a DockPanel (for example) will remove the need for a DependencyProperty.
You can then simply do your binding with the dockpanel instead of the custom control. Setting the variable bound to IsEnabled on the Dockpanel will automatically enable or disable the items contained in the Dockpanel.

Categories