Change property in PropertyChanged method not updating view - c#

Under certain conditions if the user selects an item in a combobox, it automatically must be changed to another item
ViewModel
public class VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
private string selected;
public string Selected
{
get { return selected; }
set
{
if (selected != value)
{
selected = value;
OnPropertyChanged("Selected");
}
}
}
private ObservableCollection<string> collection;
public ObservableCollection<string> Collection
{
get { return collection; }
set
{
collection = value;
OnPropertyChanged("Collection");
}
}
public VM()
{
this.Collection = new ObservableCollection<string>(new string[] { "A", "B", "C" });
this.Selected = "A";
this.PropertyChanged += VM_PropertyChanged;
}
void VM_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.Selected = "C";
}
}
View
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<Grid>
<ComboBox ItemsSource="{Binding Collection}" SelectedValue="{Binding Selected}"/>
</Grid>
<Label Content="{Binding Selected, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</Window>
So, in this example, no matter what do I select, it should show "C" both, on the combobox and Label, but "C" only shows on Label, it means that the ViewModel is updated but not the view.
It seems the problem here is to try to change the property from the PropertyChanged method.
What could be wrong?

Here's how I would most likely do it, but the BeginInvoke() call that does the magic could just as easily be called from your PropertyChanged handler.
What it's doing is essentially queueing the action to happen after the entire property-set business has fully completed. The DispatcherPriority.ApplicationIdle flag is a key point.
As you've found, it's useless for the PropertyChanged handler and the property setter to raise PropertyChanged while the ComboBox is still in the process of changing its selection. This code lets that whole thing finish, and then immediately changes Selected to something else. At that point, the ComboBox will be at leisure to take notice of your PropertyChanged event and update its own selection.
private string selected;
public string Selected
{
get { return selected; }
set
{
if (selected != value)
{
// Don't let them select "B".
if (value == "B")
{
Dispatcher.CurrentDispatcher.
BeginInvoke(new Action(() => this.Selected = "C"),
DispatcherPriority.ApplicationIdle);
return;
}
selected = value;
OnPropertyChanged("Selected");
}
}
}

Under certain conditions if the user selects an item in a combobox, it automatically must be changed to another item
For what it's worth, I think it would be a good idea to revisit that design choice. It is likely to be confusing to users, and there is probably a better way to present that state of affairs to the user, than to ignore input they give the program. There's not enough context in your question to fully understand how you got into this situation in the first place, so I can't offer anything more than to suggest it's likely better to fix the design, than to finagle the code into doing what you want.
That said…
The issue you are running into is that WPF ignores property-changed events for the source of a binding it is currently already updating. In your scenario, the binding is updating the Selected value from its binding, and so changes to that property will be ignored until that binding update is completed.
There are a variety of ways to get the code to work the way you want. Probably the easiest is to simply defer the update of the source property until the handling of the user input has completed. You can do that by using the Dispatcher.InvokeAsync() method:
void VM_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
Dispatcher.CurrentDispatcher.InvokeAsync(() => this.Selected = "C");
}
I'm not a big fan of the above, because of the fact that it takes what ideally should be a view-agnostic object, the view model, and injects knowledge of your specific view API, i.e. use of the Dispatcher object. That said, there are other mechanisms you could use which are similar, and which don't rely on the Dispatcher object (e.g. using an asynchronous timer).
There are a variety of other examples of ways to address this on Stack Overflow. For example, you might look at this answer for inspiration. I don't think it will do exactly what you want "straight out of the box", but the attached property approach might be something you find more appropriate, by moving the logic from view model to view code, and thus a place where it's more appropriate to use Dispatcher. (And arguably, if you are going to do something like this, the logic probably belongs in the view anyway…it's weird enough there, but I see no compelling reason this should be inherent in the view model.)
Another approach can be seen in the question Coerce a WPF TextBox not working anymore in .NET 4.0. I.e. manually force the view's state to be updated after the fact.

Related

How to refresh a window in C#/WPF?

I want to change a value (textBlock) according to an event. Then, I want to refresh my window, but I couldn't. I used invalidateVisual as well as solutions of other posts, but nothing worked.
Thank you in advance
Several solutions (the first and second one does not make use of databinding).
txtMyControl.text = "New value";
If not on the main thread, you could use the dispatcher to update the value.
Application.Current.Dispatcher.BeginInvoke(() => txtMyControl.text == "New Value")
However, the most WPF friendly way to do it is to use the databinding.
Any change made to the value in code will be instantly reflected in the UI.
XAML
<TextBox x:Name="txtExample" Text="{Binding MyTextProperty,Mode=TwoWay}" Height="24" Width="120"/>
In your code, you have to declare a variable that will be persistent.
private ExampleModel _ExampleModel = new ExmampleModel();
When you load your code, you associate that variable to your textbox data context.
txtExample.DataContext = _ExampleModel
Then, you have the class that will contains all the editable properties on screen (textboxes, radio boxes, etc...)
public class ExampleModel : INotifyPropertyChanged
{
private string _MyTextProperty = "test";
public string MyTextProperty {
get { return _MyTextProperty; }
set {
if (string.Compare(_MyTextProperty, value) != 0) {
_MyTextProperty = value;
RaisePropertyChanged("MyTextProperty");
}
}
}
public void RaisePropertyChanged(string PropertyName)
{
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
public event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged;
public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);
}
Whenever you handle your event, you just have to change the value of the property containing the information and the UI will refresh accordingly. Also, since we use a two-way binding, the value from your textbox will always be the same than the one contained by MyTextProperty property in ExampleModel class, which make value retrieval very easy.
ex:
_ExampleModel.MyTextProperty = "New value";
If you were already using databinding, make sure the class used implements INotifyPropertyChanged and that the propertyChanged event is called when the property value change or otherwise it won't update the UI.
The best approach to what you're trying to do would be to use Data Binding.
You need to have a string object that will always hold the value of your textblock. Next you need to bind that object to your textblock and then use the event provided by the INotifyPropertyChanged interface and each time the value changes its representation (the textblock) will change to, no need to refresh the window.
More information here
If your event updates the textblock and the textblock you are using is bound to a string property and that property issues a NotifyPropertyChanged() in it's set method, that will cause the display to refresh as you desire.
There are other ways, but this is the easiest given my understanding of your question.
(this is similar to the other answer, but I tried to word so it is easier to understand/implement.)

wpf itemlist not updating values

So I have three things:
A ListBox in a Window
A DataBase class granting me access to an ObservableList
Contact implements INotifyChanged
In my main Window, I have three Buttons (One for new List Entry, one for editing, one for deleting an item)
I fill the list like this:
lbKontakte.ItemsSource = DB.GetInstance().Kontakte;
whereas Kontakte is a ObservableCollection
I can create a new Entry using
DB.GetInstance().Kontakte.Add(New Kontakt(...));
or remove an entry using
DB.GetInstance().Kontakte.Remove(...);
Boh actions are immediately visible in the ListBox.
If I modify a value however, I'm not using any Code. I have a TextBox which is bound to the Name field of a contact. If I make changes to it, the changes should theoretically be carried out immediately to the bound Contact Object.
However, if I do modify the text, the changes do not become visible in the ListBox. If I pause the code and take a look at the object, I can see its Name Field has correctly been changed.
How come my ListBox is not updated?
PS:
Contact does implement INotifyChanged using following Code:
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
and
public String Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(new PropertyChangedEventArgs("Name")); }
}
Edit: The Textbox is NOT part of the main Window but a Window showed as a dialog if the user clicks the edit button. The Window is then given the selectedItem casted as Kontakt in the Constructor. Bound to the Field like this:
<TextBox Name="txtName" Grid.Column="1" Grid.Row="0" Margin="4" Text="{Binding Path=Name}"></TextBox>
and
public KontaktAddUI(Kontakt kontaktToEdit)
{
InitializeComponent();
this.kontaktToEdit = kontaktToEdit;
this.MainGrid.DataContext = kontaktToEdit;
}
Correct Answer in the Comments, thanks again!
Your problem is ObservableCollection doesn't get notified if your Item Property Changed that is a known issue To fix this problem you need to wire up your INotifyPropertyChanged event to the CollectionChanged event from your ObservableCollection
Here you can see an example how you could do it.

How do I use INotifyPropertyChanged in WinRT?

I'm a total newbie, just learning the basics of DataContext and the MVVM model. I've now got a grid bound to a view model object which implements INotifyPropertyChanged, however it appears that UpdateSourceTrigger (which all the WPF tutorials tell me to use) is not available for WinRT / Metro Style apps!
How do I implement INotifyPropertyChanged then?
I'm at the end of my tether here. I've spend nearly the whole day on the most basic of app examples, simply trying to get a grid to update after I click something. The only way I've managed to do this so far is to create an entirely new instance of the view model and reassign the DataContext which I know is wrong
UPDATE:
I have made some progress, but things have gotten very weird. I have a view model, with a generic list of items. The items list is wired up with a PropertyChangedEventHandler. If I replace the entire collection with a new one, the listview updates.
model.Items = new List<DataItem>{ new DataItem{ Title = "new item" }};
This results in a one item list with the above item. However, if I try adding an item, nothing happens
model.Items.Add(new DataItem{ Title = "added item" });
I also tried creating a method which added an item and specifically fired PropertyChanged, but that also doesn't work
Here's where it gets weird. Next I tried this code.
model.Items.Add(new DataItem { Title = "added item" });
model.Items = new List<DataItem> { new DataItem { Title = "new item" }};
This results in a two item list:
- new item
- added item
How can this be? The code says, "add one item" then "replace the whole list" but it executes in the reverse order?
UPDATE 2:
I've switched to ObservableCollection as suggested, which has actually solved the original problem. I can now add an item and it shows up on the list.
However, the new weird behaviour is still in effect. Items added before the collection is reset are appended to the end of the new collection. Why is my code executing in reverse order?
You need to implement the interface and send out the notification once the given property you care about changes.
public event PropertyChangedEventHandler PropertyChanged;
public string CustomerName
{
get
{
return this.customerNameValue;
}
set
{
if (value != this.customerNameValue)
{
this.customerNameValue = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("CustomerName"));
}
}
}
}
Keep in mind that for a collection, you should use an ObservableCollection as it will take care of the INotifyCollectionChanged being fired when an item is added or removed.
I would suggest to scale your sample back as far as possible. Don't start with a DataGrid but rather a simple TextBoxand Button, where the Button forces a change in your ViewModel which will then reflect on the UI.
Code taken from here.
It's best to implement a parent class which implements it like this:
public class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected void RaisePropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
And then in your subclass (i.e. ViewModel) in your property do something like this:
public class MyViewModel : NotifyPropertyChangedBase
{
private string _name;
public string Name {
get{ return _name; }
set{
_name = value;
RaisePropertyChanged("Name");
}
}
}

MVVM property depends on a graph of objects

I am working with WPF+MVVM.
I have a VM which contains a Customer property. The Customer has an ObservableCollection of Orders. Each Order has an ObservableCollection of Items. Each Items has a Price.
Now, I have the following property on my VM:
public double TotalPrice
{
return Customer.Orders.Sum(x => x.Items.Sum(y => y.Price));
}
The problem is whenever a change occurs at any point in this graph of objects - the UI should be notified that TotalPrice had changed - but it doesn't...
For example if the Customer will be altered from A to B, or an order will be added, or an item will be deleted, or an item's price will be altered etc.
Does anyone has an elegant solution for this?
Thanks.
Have you supported INotifyPropertyChanged / INotifyCollectionChanged interfaces in ViewModels? You should be able trigger any property manually, for instance in setter of any property you can trigger OnPropertyChanged("TotalPrice") so UI bindings for TotalPrice would be updated as well.
To handle dependent objects changes you can provide events or something like that so ViewModel would be able to subscribe and handle underlying object changes, for instance you have some service which is in chanrge of reloading of the Orders from a database, so as soo as new changes come you would update UI as well. In this case OrdersService should expose event OrdersUpdated and ViewModel can subscribe for this event and in trigger PropertyChanged events for affected properties.
Let's consider some case as an example, for instance Order price has been changed. Who is in charge of this changes? Is this done via UI by an user?
You can find here an interesting post written by me few days ago and it talks exactly about this problem (and its solution...)
You might implement "accumulator" properties which store the sum of values in a collection of objects. Listen for changes to those values and update the accumulator appropriately.
(BTW - I forgot to mention that this one is really just for situations where the value is expensive to calculate. Otherwise Sll's answer is definitely the way to go.)
Something like this, with the boring stuff left out:
class BasketOfGoods : INotifyPropertyChanged
{
ObservableCollection<Good> contents = new ObservableCollection<Good>();
public decimal Total
{
get { /* getter code */ }
set { /*setter code */ }
}
public BasketOfGoods()
{
contents.CollectionChanged += contents_CollectionChanged;
}
void contents_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
foreach (var newGood in e.NewItems) ((Good)newGood).PropertyChanged += BasketOfGoods_PropertyChanged;
}
void BasketOfGoods_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Price") Total = contents.Select(x => x.Price).Sum();
}
}
class Good : INotifyPropertyChanged
{
public decimal Price
{
{
get { /* getter code */ }
set { /*setter code */ }
}
}
I think my latest WPF endeavor MadProps handles this scenario pretty well. Take a look at this example master-detail scenario. As long as there a path from the Item being edited to the VM (for example, VM.TheCustomer.SelectedOrder.SelectedItem or simply VM.SelectedItem), the PropChanged event will propogate up to the VM and you can write:
public readonly IProp<Customer> TheCustomer;
public readonly IProp<double> TotalPrice;
protected override void OnPropChanged(PropChangedEventArgs args)
{
if (args.Prop.IsAny(TheCustomer, Item.Schema.Price))
{
TotalPrice.Value = TheCustomer.Value.Orders
.Sum(order => order.Items.Sum(item => item.Price.Value));
}
}
Regarding adding and removing Items or Orders, I would just put an explicit update in the ICommand code, something like VM.UpdateTotalPrice();. Managing subscriptions and unsubscriptions to ObservableCollections and the items in them can be tricky.

WPF: Replacing databound collection contents without Clear/Add

When using WPF databinding, I obviously can't do something along the lines of MyCollection = new CollectionType<Whatever>( WhateverQuery() ); since the bindings have a reference to the old collection. My workaround so far has been MyCollection.Clear(); followed by a foreach doing MyCollection.Add(item); - which is pretty bad for both performance and aesthetics.
ICollectionView, although pretty neat, doesn't solve the problem either since it's SourceCollection property is read-only; bummer, since that would have been a nice and easy solution.
How are other people handling this problem? It should be mentioned that I'm doing MVVM and thus can't rummage through individual controls bindings. I suppose I could make a wrapper around ObservableCollection sporting a ReplaceSourceCollection() method, but before going that route I'd like to know if there's some other best practice.
EDIT:
For WinForms, I would bind controls against a BindingSource, allowing me to simply update it's DataSource property and call the ResetBindings() method - presto, underlying collection efficiently changed. I would have expected WPF databinding to support a similar scenario out of the box?
Example (pseudo-ish) code: WPF control (ListBox, DataGrid, whatever you fancy) is bound to the Users property. I realize that collections should be read-only to avoid the problems demonstrated by ReloadUsersBad(), but then the bad code for this example obviously wouldn't compile :)
public class UserEditorViewModel
{
public ObservableCollection<UserViewModel> Users { get; set; }
public IEnumerable<UserViewModel> LoadUsersFromWhateverSource() { /* ... */ }
public void ReloadUsersBad()
{
// bad: the collection is updated, but the WPF control is bound to the old reference.
Users = new ObservableCollection<User>( LoadUsersFromWhateverSource() );
}
public void ReloadUsersWorksButIsInefficient()
{
// works: collection object is kept, and items are replaced; inefficient, though.
Users.Clear();
foreach(var user in LoadUsersFromWhateverSource())
Users.Add(user);
}
// ...whatever other stuff.
}
If the object MyCollection is of implements INotifyPropertyChanged, you can simply replace the collection.
An example:
public class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Whatever> _myCollection;
private void NotifyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public ObservableCollection<Whatever> MyCollection
{
get
{
return _myCollection;
}
set
{
if (!ReferenceEquals(_myCollection, value))
{
_myCollection = value;
NotifyChanged("MyCollection");
}
}
}
}
With this, when you assign a collection, WPF detects this and everything gets updated.
This is how I'd solve this.
The link below explains how to implement an AddRange method.
http://web.archive.org/web/20150715112054/http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx
It looks like you're stuck with implementing a sub-class that handles this case correctly.
Apparently, certain controls don't support batched collection change notifications. At least they didn't when that article was written. Though now you should have a bit more information if you want to investigate further.

Categories