I'm using x:Bind and INotifyPropertyChanged to update UI in UWP application. But it behaves like OneTime binding even though I set it to OneWay.
Bindings.Update() works, but I want to know why INotifyPropertyChanged fails.
XAML
<TextBlock Text="{x:Bind staffVM.Name, Mode=OneWay}"/>
Code-behind:
private StaffViewModel staffVM;
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// I want to change staffVM according to ListView's selection.
staffVM = staffListView.SelectedItem as StaffViewModel;
staffVM.Update(); // If change this to Bindings.Update(), It works.
}
ViewModel:
public class StaffViewModel: INotifyPropertyChanged
{
private Character character;
public string Name => character.name == string.Empty ? null : character.name;
public void Update()
{
RaisePropertyChanged(string.Empty);
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName]string propName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
Firstly, you need to specify the name of the variable that you want to update:
public void Update()
{
RaisePropertyChanged(nameof(Name));
}
Documentation and sample: https://learn.microsoft.com/en-us/uwp/api/windows.ui.xaml.data.inotifypropertychanged.propertychanged
Secondly, by default x:Bind is OneTime
To fix it, add Mode="OneWay"
Mode Specifies the binding mode, as one of these strings: "OneTime", "OneWay", or "TwoWay". The default is "OneTime". Note that this differs from the default for {Binding}, which is "OneWay" in most cases.
Please read documentation
https://learn.microsoft.com/en-us/windows/uwp/xaml-platform/x-bind-markup-extension
The problem here is not on the level of the StaffViewModel class, but on the level of the page. When you do:
staffVM = staffListView.SelectedItem as StaffViewModel;
The UI has no notification about the fact that the staffVM field has changed. So the binding is still pointing to the old instance of StaffViewModel. Hence when you do staffVM.Update(), it does notify about changes, but the UI is not listening to that instance - it is still listening to notifications on the first selected item. Bindings.Update() fixes this because it completely re-evaluates all bindings so it will "get" the new value of staffVM field.
Solution would be to implement INotifyPropertyChanged on the Page and encapsulate the staffVM in a property which raises PropertyChanged event.
Ideally I would however suggest creating a "root" view model, which you will set only once and will not change and which will contain the selected item as its property. This way you don't have to implement INotifyPropertyChanged in the Page and its code-behind will be simpler. As a result you will have something like the following in the code-behind:
public RootViewModel VM {get;} = new RootViewModel();
And in XAML:
<TextBlock Text="{x:Bind VM.SelectedStaff.Name, Mode=OneWay}"/>
Related
Can someone explain me why need to use implementation of INotifyPropertyChanged when using binding in wpf?
I can bind properties without implementation of this interface?
For example i have code
public class StudentData : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
string _firstName = null;
public string StudentFirstName
{
get
{
return _firstName;
}
set
{
_firstName = value;
OnPropertyChanged("StudentFirstName");
}
}
}
And binding in .xaml
<TextBox Text="{Binding Path=StudentFirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1"
Grid.Column="2"
VerticalAlignment="Center" />
this code from .xaml.cs
StudentData _studentData = new StudentData { StudentFirstName = "John", StudentGradePointAverage = 3.5};
public MainWindow()
{
InitializeComponent();
this.DataContext = _studentData;
}
why we need to use INotifyPropertyChanged in this case?
It is not my code.
You need INotifyPropertyChanged if you want a wpf form to be automatically updated when a property changes through code. Also some controllers might want to know if edits have been made in order to enable/disable a save-button, for instance. You also might be displaying the same property on different views; in this case INotifyPropertyChanged helps to immediately update the other view when you edit a property.
If you think that your form behaves well without INotifyPropertyChanged, then you can drop it.
Note that binding works even without INotifyPropertyChanged. See: Why does the binding update without implementing INotifyPropertyChanged?
I would implement the properties like this. In some rare cases it can help to avoid endless circular updates. And it is more efficient by the way.
private string _firstName;
public string StudentFirstName
{
get { return _firstName; }
set
{
if (value != _firstName) {
_firstName = value;
OnPropertyChanged("StudentFirstName");
}
}
}
Starting with C#6.0 (VS 2015), you can implement OnPropertyChanged like this:
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
When you bind to a property of StudentData such as the StudentFirstName then the binding class tests to see if the StudentData instance provides the INotifyPropertyChanged interface. If so then it will hook into the PropertyChanged event. When the event fires and it fires because of the StudentFirstName property then it knows it needs to recover the source value again because it has changed. This is how the binding is able to monitor changes in the source and reflect them in the user interface.
If you do not provide the INotifyPropertyChanged interface then the binding has no idea when the source value changes. In which case the user interface will not update when the property is changed. You will only see the initial value that was defined when the binding was first used.
It does need to be implemented in order for binding to work but that doesn't mean you always have to do it yourself. There are other options like Castle Dynamic Proxy (which wraps your classes in a proxy and injects INPC into all virtual properties) and Fody (which adds it to the IL in a post-processing step). It's also possible to implement yourself while at the same time reducing code bloat, as demonstrated in my answer to this question.
Can someone explain me why need to use implementation of INotifyPropertyChanged when using binding in wpf?
I can bind properties without implementation of this interface?
For example i have code
public class StudentData : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
string _firstName = null;
public string StudentFirstName
{
get
{
return _firstName;
}
set
{
_firstName = value;
OnPropertyChanged("StudentFirstName");
}
}
}
And binding in .xaml
<TextBox Text="{Binding Path=StudentFirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Row="1"
Grid.Column="2"
VerticalAlignment="Center" />
this code from .xaml.cs
StudentData _studentData = new StudentData { StudentFirstName = "John", StudentGradePointAverage = 3.5};
public MainWindow()
{
InitializeComponent();
this.DataContext = _studentData;
}
why we need to use INotifyPropertyChanged in this case?
It is not my code.
You need INotifyPropertyChanged if you want a wpf form to be automatically updated when a property changes through code. Also some controllers might want to know if edits have been made in order to enable/disable a save-button, for instance. You also might be displaying the same property on different views; in this case INotifyPropertyChanged helps to immediately update the other view when you edit a property.
If you think that your form behaves well without INotifyPropertyChanged, then you can drop it.
Note that binding works even without INotifyPropertyChanged. See: Why does the binding update without implementing INotifyPropertyChanged?
I would implement the properties like this. In some rare cases it can help to avoid endless circular updates. And it is more efficient by the way.
private string _firstName;
public string StudentFirstName
{
get { return _firstName; }
set
{
if (value != _firstName) {
_firstName = value;
OnPropertyChanged("StudentFirstName");
}
}
}
Starting with C#6.0 (VS 2015), you can implement OnPropertyChanged like this:
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
When you bind to a property of StudentData such as the StudentFirstName then the binding class tests to see if the StudentData instance provides the INotifyPropertyChanged interface. If so then it will hook into the PropertyChanged event. When the event fires and it fires because of the StudentFirstName property then it knows it needs to recover the source value again because it has changed. This is how the binding is able to monitor changes in the source and reflect them in the user interface.
If you do not provide the INotifyPropertyChanged interface then the binding has no idea when the source value changes. In which case the user interface will not update when the property is changed. You will only see the initial value that was defined when the binding was first used.
It does need to be implemented in order for binding to work but that doesn't mean you always have to do it yourself. There are other options like Castle Dynamic Proxy (which wraps your classes in a proxy and injects INPC into all virtual properties) and Fody (which adds it to the IL in a post-processing step). It's also possible to implement yourself while at the same time reducing code bloat, as demonstrated in my answer to this question.
My label only seems to get the data from the property it is bound to once. I have the Property raising the Property Changed event in the setter, but when the value of the property gets changed, it raises the event properly (I know this because of the break point I set), but the text in the Label on the window doesn't change. I should maybe also note that the window with the label isn't the main window, but a new one that pops up.
ViewModel:
public class PurchaseVerificationViewModel : ViewModelBase
{
private WindowService.WindowService windowService = new WindowService.WindowService();
private string _verificationQuestion = "Question"; //default so i can check if it changed in the window
public string VerificationQuestion
{
get { return _verificationQuestion; }
set
{
if (_verificationQuestion != value)
{
_verificationQuestion = value;
OnPropertyChanged(nameof(VerificationQuestion));
}
}
}
}
Window:
<Window>
<Window.DataContext>
<viewmodels:PurchaseVerificationViewModel/>
</Window.DataContext>
<Grid>
<Label Content="{Binding VerificationQuestion, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
ViewModelBase:
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
There's no problem with your implementation of the INotifyPropertyChanged, since you are correctly invoking the notification that your property has been modified.
So if the problem is not with the one who's raising the notification, might it rather be with what is actively listening to it?
And the problem is that you're defining the DataContext of your Window to the class itself, rather than to the instance which you are utilizing and modifying in the code-behind of your application.
What is actually happening under the hoods, due to the way you defined your DataContext in xaml, is that a new PurchaseVerificationViewModel class is being constructed (is the not the one who are modifying on your logic) and therefore your VerificationQuestion will return it's default value (or rather the private backing field default value, "Question").
In reality the problem is that you have induced your listener to listen to the wrong thing.
Since you want the content of the Label (target) to be update based on a source change, what you have to do, is to set as the DataContextof the Window the specific instance which you are modifying on the logic of your application, and make sure you define it as a property!
public PurchaseVerificationViewModel myViewModel {get;set;}
For instance after InitializeComponent(), on your page constructor, you could initialize the property and set it as the DataContext, like this:
myViewModel = new PurchaseVerificationViewModel();
this.DataContext = myViewModel;
I've encountered weird issue, which I cannot understand. In main page I've only one button which navigates to second page and holds my model:
public class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaiseProperty(string property) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
private int index = 0;
public int Index
{
get { Debug.WriteLine($"Getting value {index}"); return index; }
set { Debug.WriteLine($"Setting value {value}"); index = value; RaiseProperty(nameof(Index)); }
}
}
public sealed partial class MainPage : Page
{
public static Model MyModel = new Model();
public MainPage()
{
this.InitializeComponent();
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
SystemNavigationManager.GetForCurrentView().BackRequested += (s, e) => { if (Frame.CanGoBack) { e.Handled = true; Frame.GoBack(); } };
}
private void Button_Click(object sender, RoutedEventArgs e) => Frame.Navigate(typeof(BlankPage));
}
On second page there is only ComboBox which has two way binding in SelectedIndex:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ComboBox SelectedIndex="{x:Bind MyModel.Index, Mode=TwoWay}">
<x:String>First</x:String>
<x:String>Second</x:String>
<x:String>Third</x:String>
</ComboBox>
</Grid>
public sealed partial class BlankPage : Page
{
public Model MyModel => MainPage.MyModel;
public BlankPage()
{
this.InitializeComponent();
this.Unloaded += (s, e) => Debug.WriteLine("--- page unloaded ---");
DataContext = this;
}
}
Nothing extraordinary. The problem is that I get two different outputs when I use Binding and x:Bind, but the worst is that after every new navigation to same page the property's getter (and setter in x:Bind) is called more and more times:
The old page still resides in memory and is still subscribed to property, that is understandable. If we run GC.Collect() after returning from page, we will begin from start.
But if we use old Binding with one-way and selection changed event:
<ComboBox SelectedIndex="{Binding MyModel.Index, Mode=OneWay}" SelectionChanged="ComboBox_SelectionChanged">
along with the eventhandler:
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.RemovedItems.Count > 0 && e.AddedItems.FirstOrDefault() != null)
MyModel.Index = (sender as ComboBox).Items.IndexOf(e.AddedItems.FirstOrDefault());
}
then it will work 'properly' - only one getter and setter, no matter how many times we navigate to page before.
So my main questions are:
where this difference in one-way - two-way binding comes from?
taking into account that one-way Binding fires getter only once - is the described behavior of two-way desired/intended?
how you deal with this two-way binding in case of multiple getters/setters getting called?
A working sample you can download from here.
Actually when you use OneWay binding with the SectionChanged event, only the setter of the Index property is called after changing the selection. The getter is never reached hence you don't see multiple "Getting value ...".
But why is the getter not called??
Put a breakpoint on this line -
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
You will see that the value of PropertyChanged is null. So the Invoke method is never fired. I suspect this might be a bug in ComboBox with traditional binding set to OneWay. Whenever you change the selection, the binding is broken hence the PropertyChanged is null. If you change to use x:Bind, this problem goes away.
As you are already aware, the GC will only collect abandoned page instances when needed. So there are times when you see Index is referenced in multiple places, no matter which binding mechanism you chose.
One way to guarantee the getter and setter only get called once is to change the the NavigationCacheMode of your second Page to Enabled/Required. Doing so will ensure a single instance of the page.
Even after you navigated from and to a new BlankPage the other pages are still in the memory and still binded in your static model as #KiranPaul commented.
Now if you, as you commented, change in to no-static and still behaves the same, is because you make the same mistake. Even if it isn't static you still use the same variable from MainPage.(I think its not possible though, cause its not static)
So all the pages that are in the memory that havent GC.Collect()-ed will get the PropertyChanged event raised. Because the MyModel is always the same one.
Try this should work. Each time that you navigate to the BlankPage you instantiate a new Model and pass your index. Then when you unload the page you update the value in the MainPage.Model. That way when you leave the BlankPage you will only see a Set and a Get in Output.
public sealed partial class BlankPage : Page
{
public Model MyModel = new Model() { Index = MainPage.MyModel.Index };
public BlankPage()
{
this.InitializeComponent();
this.Unloaded += (s, e) => { MainPage.MyModel.Index = MyModel.Index; Debug.WriteLine("--- page unloaded ---"); };
DataContext = this;
}
}
Or when you leave BlankPage you can either:
call GC.Collect()
unbind the MyModel when you unload the page?
Edit:
With Binding it also does the same if you do it really fast. My guess is that the GC.Collect() is called
So I searched a bit and I found this:
Binding vs. x:Bind, using StaticResource as a default and their differences in DataContext
An answer says:
The {x:Bind} markup extension—new for Windows 10—is an alternative to {Binding}. {x:Bind} lacks some of the features of {Binding}, but it runs in less time and less memory than {Binding} and supports better debugging.
So Binding surely works different, it might calls GC.Collect() or Unbind it self??. Maybe have a look in x:Bind markup
You could try to add tracing to this binding to shed some light.
Also I would advise to swap these lines so that they look like this:
DataContext = this;
this.InitializeComponent();
It could be messing with your binding. As when you call initializeComponent it builds xaml tree but for binding I guess it uses old DataContext and then you immediately change DataContext, forcing rebinding of every property.
I have a problem with two way binding a TextBox content to a property in another class. Searching stackoverflow gave a lot of tips/solutions but none seem to work.
In my XAML code I have:
< TextBox ... Width="336" IsReadOnly="True"
Text="{Binding Path=AssignedClearProgram, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
(... I removed all non important items)
In the accompanies cs code I have:
public CombiWindow(Combi combi)
{
ViewModel = new CombiViewModel(combi);
DataContext = ViewModel;
}
In the CombiViewModel:
[UsedImplicitly]
public string AssignedClearProgram { get; set; }
It seems that the first time I assign AssignedClearProgram, the textbox is filled with the text that I set, however after the window is displayed and AssignedClearProgram gets updated from the code (i.e. the set method is called), the data is not updated in the screen.
Does anybody have a solution to update the textbox when this variable is changed?
Kind regards,
Michel
Your viewmodel class needs to implement INotifyPropertyChanged and you need to raise that interface's event whenever you change the property. Then the binding will spot changes and update the textbox.
Your view model class should implement the INotifyPropertyChanged interface.
Your property would then look like the following:
private string assignedClearProgram;
public string AssignedClearProgram
{
get { return assignedClearProgram; }
set
{
if (assignedClearProgram != value)
{
assignedClearProgram = value;
// Notify property has changed here using PropertyChanged event from INotifyPropertyChanged.
}
}
}
Read this article for an example of how to implement the INotifyPropertyChanged interface and utilize its PropertyChanged event.