Lets assume property Name is bind to TextBox in view like this.
private string name
public string Name
{
get {return name;}
set {
name=value;
OnPropertyChanged("Name");
}
}
View
<TextBox Text="{Binding Name, Mode=TwoWay"/>
When we update the text in text box, it will call the setter in Name property which in turn raise PropertyChanged which suppose to update UI again. I am curious how WPF avoid recursion of update and raise event. is it done by considering the sender of that event?
A standard implementation of a property should look like this:
private string name;
public string Name
{
get { return name; }
set
{
if( name != value )
{
name = value;
OnPropertyChanged("Name");
}
}
}
Note the additional if to make sure the event is only raised if the value of the property actually changed.
There is no recursion as far I understand.
1) TextBox updates the value using viewmodel property.
2) Viewmodel raises update, letting UI know that something changed
3) TextBox now updates himself to match the viewmodel value.
may the answer from here help you out.
if you set a property from ui to viewmodel it goes like this.
setter call started
value set
INotifyPropertyChanged started
INotifyPropertyChanged done
setter done
getter called and done
IDataErrorInfo called and done
but if you set the property in your viewmodel it goes like this
setter call started
value set
INotifyPropertyChanged started
getter called and done
IDataErrorInfo called and done
INotifyPropertyChanged done
setter done
Changing property from UI to ViewModel may lead to deadlock kind of
situation which might run into end less recursive calls in two way
scenarios. In order to block this from happening, when WPF is making
change to model, it will continue to track changes via
INotifyPropertyChanged ,but this change will be queued in dispatcher
queue, and it will be executed after its current update is finished.
Since the change in viewmodel is not initiated by WPF, WPF will not
queue the operation, it will immediately execute the change.
Related
I'm struggling with an issue where the following binding seems to have no impact on the UI:
<Toolbar MaxWidth="{Binding AllowedHorizontalSpace}" />
Property and field:
private int allowedHorizontalSpace;
public int AllowedHorizontalSpace {
get { return this.allowedHorizontalSpace; }
set {
this.allowedHorizontalSpace = value;
this.OnPropertyChanged(nameof(this.AllowedHorizontalSpace));
}
}
In the function that listens for monitor size changes:
this.AllowedHorizontalSpace = (int) (monitorWidth * 0.4)
What am I missing? The size of the control just does not want to change! The same error persists for MaxHeight as well.
I have tried manually changing this value using the tool snoop. The change is reflected in the control when I do this.
In comments, it comes out that when the monitor size changes, the setter for AllowedHorizontalSpace is called but not the getter.
The INotifyPropertyChanged implementation looks right from here, and the whole thing works correctly when AllowedHorizontalSpace is set via Snoop, so it is right, and the DataContext must be an instance of your viewmodel.
This kind of thing is commonly caused by there being a redundant viewmodel instance. Often, one is created in the XAML, and another is created in the codebehind constructor. The second instance assigned to DataContext is the one the property will be bound to, but you may be setting the property on the first. In that case the setter would execute on the first, but since no bindings are using that instance as a source, nothing would call the getter.
And you mention that the viewmodel's Initialize() method is unexpectedly being called twice, which is what I'd expect if the above were the case.
The problem is that AllowedHorizontalSpace is not a dependency property yet you are using the dependency property change notification.
The thing to do is to implement INotifyPropertyChanged on the view model. Change the property implementation to the following and everything will start working.
public int AllowedHorizontalSpace
{
get { return this.allowedHorizontalSpace; }
set
{
this.allowedHorizontalSpace = value;
// this.OnPropertyChanged(nameof(this.AllowedHorizontalSpace));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("AllowedHorizontalSpace"));
}
}
I have an class, Loans and I have an instance of it in my ViewModel.
I bind to multiple properties in Loans, like
loan.amount;
loan.name;
Etc. I only raise INotifyPropertyChanged on loan itself, and the rest of the properties don't raise it. IE
ViewModel{
loan { get; set { notifypropertychanged("loan")}
Everything I read says this shouldn't work, but it does...in fact, all of loans properties are binding properly and updating property. I always thought you have to raise notifyproperty changed on each and every property, and can't just do it at the main object. Am I missing something?
This is working the way it should be. When you raise the property change, binding framework goes and get the updated value for the property.
Here when you are raising the property change on property of custom type (loan) in your VM, binding framework will get the updated value of loan and will hit getter of each and every property of 'loan' bound on the UI.
No, that will work fine. You're specifying the full path to the property so anything that changes along that path will cause the binding to refresh. It also works if you do something like this:
<StackPanel DataContext="{Binding loan}">
<TextBlock Text="{Binding amount}" />
</StackPanel>
In this case you're not specifying the full path in the TextBlock binding but its parent is bound to a property that does change notification and again it will inform its children if it sees there's been a change. Where you don't get change notification is if you do something like the above and keep loan the same but change amount.
Bear in mind there are different times when change notification is applied. If your UpdateSourceTrigger is set to "PropertyChanged" and you want updates to be applied the instant a value changes then yes, you have to apply it to each and every child member. If it's LostFocus then they'll all get done in one go once the focus is lost irrespective of whether or not they support change notification.
The change notification only works when setting the loan object, not on its properties.
loan.amount = 123;
This uses the [loan] Getter and then then [amount] Setter.
If you need to listen form the parent object then you could set the loan as a private variable and expose the loan properties from the ViewModel
ViewModel
{
private Loan _loan
public decimal loanAmmont
{
get { return _loan.amount; }
set
{
_loan.amount = value;
notifypropertychanged("loan")
}
}
}
Or add the change notification to the loan properties and register to their change from the parent object
ViewModel
{
private Loan _loan;
public Loan loan;
{
get { return _loan; }
set
{
_loan = value;
_loan.PropertyChanged += NotifiyLoanChanged;
}
}
void NotifyLoanChanged(object sender, EventArgs e)
{
// invoke my events to ViewModel listners
notifypropertychanged(e.PropertyName);
}
}
http://msdn.microsoft.com/en-us/library/ms743695(v=vs.110).aspx
I have a property in my view model which returns a constant under some conditions.
which is implemented similiar to this:
class Tmp : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public String Text
{
get { return "testing"; }
set
{
PropertyChanged(this,new PropertyChangedEventArgs("Text")); }
}
}
so the property Text alwasys returns "testing".
I bound this to a text box like :
<TextBox Text="{Binding Path=Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
When the application starts, the textbox correclty says testing.
now when I type something in the text box the setter gets called, which calls PropertyChanged.
After this something ( probably the GUI ) calls the getter and gets the value "testing".
However the text in the textbox is not changed back to testing.
So I can type "abc" into the text box, and text box displays "abc" even though the model is just storing "testing".
why isnt the text in the text box not reset to "testing" at each keystroke?
Why should the textbox get the text again? It just wrote it into your source property it "knows" that it must be the same, because he is in the middle of telling the property the new value. Otherwise you would create circular references. What you are doing is going completely against guidelines and behavior expected from a property.
Remove the setter, make the binding one way, raise the text property when your constant is changed, and make the textbox readonly. Remember, its not necessary to call Propertychanged in the setter.
To make your initial approach work, you need to break out of the "Textbox changes property but won't listen for incoming raises" state
set
{
sUIDispatcher.BeginInvoke((Action)(() => Raise("Name")));
}
i would like to add, that this is a REALLY bad idea and strongly discourage you to do it like that.
I am having a problem with DomainContext.RejectChanges() and reflecting the rollback in the UI. Here is my scenario.
I have a Model (Entity) generated for use with RIA services (I'll call it Foo)
I have a ViewModel that wraps Foo and extends it (I'll call it FooViewModel)
I have a View that is using Binding to display and update data using the FooViewModel
I have an "outer" ViewModel that holds an ObservableCollection of FooViewModels
The "outer" View has a list box bound to the ObservableCollection
So essentially there is a listbox of FooViewModels on one screen...when you select an item a childwindow is displayed to edit that particular FooViewModel. The FooViewModel is serving both the listbox and the childwindow.
Editing works just fine. A change in the childwindow reflects in the listbox immediately because I am calling RaisePropertyChanged() when the viewmodel properties are updated.
However, If I perform a DomainContext.RejectChanges()...the underlying entity gets rolled back (all changes reverted as expected)...however the FooViewModel isn't aware that this change has occurred and thus the UI isn't updated. If I reselect the item in the listbox on the first screen, the childwindow is displayed with the rolled back changes (which is what I want). The listbox still isn't updated though.
When I reject changes, if I kludge a RaiseProperyChanged() for the field that I changed...the UI listbox does update.
How do I get the UI to update when the underlying entity is rejected?? And how do I do it without tracking what properties of the viewmodel were rolledback? There has to be an easy way to accomplish this that I am just missing.
Something you could try is use the PropertyChanged event on the underlying entity Foo to trigger a RaisePropertyChanged pass on the FooViewModel properties.
so making some assumptions (so this code make sense):
You have a private variables in your FooViewModel
private Foo _foo;
private DomainContext _context;
You have a method on your FooViewModel that is calling RejectChanges() on your domain context.
Like so:
public void RejectChanges()
{
_context.RejectChanges();
}
We have a method that raises the PropertyChanged event on our FooViewModel
Like so:
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs(propertyName);
}
Ok, now we have that established, lets have a look at exactly what happens when you call RejectChanges() on a domain context.
When you call RejectChanges() this bubbles down through the DomainContext to its EntityContainer, then to each EntitySet in that container and then to each Entity in the set.
Once there (and in the EntitySet), it reapplies the original values if there was any, removes the entity if it was added, or adds it if it was deleted. If there was changes to the values, then it applies them back to the properties.
So theoretically, all the RaisePropertyChanged(), that are generated in the entity properties, should be triggered.
NOTE: I haven't actually tested this. If this isn't the case, then none of this works :P
So we can hook into PropertyChanged event of the Foo entity, and raise the PropertyChanged event on our FooViewModel.
so our RejectChanges() method might look like this:
public void RejectChanges()
{
Func<object, PropertyChangedEventArgs> handler = (sender, e) =>
{
RaisePropertyChanged(e.PropertyName);
};
_foo.PropertyChanged += handler;
_context.RejectChanges();
_foo.PropertyChanged -= handler;
}
So we hook up an event handler to our Foo entity, which calls the FooViewModel.RaisePropertyChanged method with the property name that is changing on the Foo entity.
Then we reject changes (which triggers the property changes),
then we unhook the event handler.
Pretty long winded, but I hope this helps :)
I presume that the call to DomainContext.RejectChanges() is happening within the ViewModel as you probably bound that to some command or method called from the parent ViewModel. Since all your bindings to the data is done on the ViewModel properties you will have to raise the property change event on them when you directly manipulate the model outside of those properties. Which you probably doing already.
public void RejectChanges()
{
DomainContext.RejectChanges();
RaisePropertyChangeOnAll();
}
How you implement RaisePropertyChangeOnAll() can be done simply with a list of RaisePropertyChange("...") for each property or you could do it through reflection (if Silverlight permissions allow, not too sure about it) by adding an Attribute on each property you want to raise. Find all the properties that are tagged with it and call RaisePropertyChanged on the MemberInfo.Name value.
[Raiseable]
public string SomeValue
{
...
}
Just an idea but may not be a perfect solution.
Hi I have a viewmodel where i can track the value of a certain item in the constructor. I am opening a dialog window using the MVVM model.
example
private int _myField;
public ClassName(int MyProperty)
{
_myField = MyProperty;
}
public int MyIntProperty
{
get{ return _myField;}
set { _myField = value;}
}
this is all perfect obviously.
but as soon as the window opens the value in the viewmodel changes.
lets say the _myField goes from 1 to 8 with out any interaction. i've walked through the code and there are no other interactions with the field.
also not in the code sample is the bound property.
anyone every came accross this before. it has me stumped.
Edit: included missing property from example
You should either:
1) Implement INotifyPropertyChanged on ClassName. This will allow you to raise the PropertyChanged event when you change MyIntProperty. WPF will listen to this event and update the UI accordingly.
or
2) Make ClassName inherit from DependancyObject and MyIntProperty a dependency property. This will take care of everything for you.