OnPropertyChanged not working as expected with ObjectListView - c#

Here is my model class, the column that I am interested in this question:
public class Cell : INotifyPropertyChanged
{
public string TestImageAspect
{
get { return testImageAspect; }
set
{
testImageAspect = value;
Console.WriteLine("OnPropertyChanged => testImageAspect");
this.OnPropertyChanged("OperationResult");
}
}
private string testImageAspect;
}
ImageList is prepared with required images. In the ObjectListView I set appropriate column's ImageAspectName to the property name:
Then on button click I run the following code to change the
Cell c = ...;
c.TestImageAspect = "success"; // the name exist in ImageList
After above code I see that OnPropertyChanged has been called, however UI is not updating, unless I hover to the row where it has to change, then I see new icon. I am not looking for dirty workaround, since I know few, but rather want to understand whether ObjectListView has to update UI itself. If yes, what am I doing wrong?

The ObjectListView property UseNotifyPropertyChanged has to be set true.
From the official documentation
If you set UseNotifyPropertyChanged, then ObjectListView will listen for changes on your model classes, and automatically update the rows when properties on the model classes changed. Obviously, your model objects have to implement INotifyPropertyChanged.

Could you post the XAML for the binding - that might help debug this. Also, it's bit confusing that your property is called TestImageAspect but you're passing "OperationResult" to OnPropertyChanged. I'm not sure if OnPropertyChanged would work either. The more usual way would be to do:-
public class Cell : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string TestImageAspect
{
get { return testImageAspect; }
set
{
testImageAspect = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("TestImageAspect"));
}
}
}
private string testImageAspect;
}

Related

How to update an ObservableCollection with a List and update only the changed values?

Edit:
Turns out I was merging a list with the latest data from a rest API with the data in my GUI. All I really had to do was clear and refill my observablecollection. This post is basically an xy problem. I did not have the vocabulary to explain the problem I was facing.
I'm building an app with a Data class where I store all my data. In this class I have a List filled with objects. I have a Page with a ObservableCollection and a ListView. Currently when I update the ObservableCollection, I clear it and refill it with all the data from Data-class's List. When I do this, my ListView flickers. My guess is that completely rebuilding the ObservableCollection causes this, in combination with a custom ViewCell that is not the lightests. How could I go about updating only what I want? The list/o.collection can have diffrent sizes. The list/o.collection both store the same object.
What I tried:
List<csharp.Truck> favs = data.getFavoriteTrucks();
trucks.Clear();
foreach (csharp.Truck truck in favs)
{
trucks.Add(truck);
}
}
Works but makes my ListView flicker.
Trying this now, its pretty bad code I think, it does update the list how I want it to but the listview does not get updated for some reason. Maybe I need to trigger a refresh?
List<csharp.Truck> all = data.getTrucks();
//if list sizes are not equal(excess/missing objects)
if (all.Count != trucks.Count)
{
//excess object
if (all.Count < trucks.Count)
{
trucks.Clear();
foreach(csharp.Truck t in all)
{
trucks.Add(t);
}
}
//object missing
if(all.Count > trucks.Count)
{
foreach(csharp.Truck t in all)
{
if (!trucks.Contains(t))
{
trucks.Add(t);
}
}
}
}//objects are now the same
//test if object is present but outdated(favorite property)
foreach(csharp.Truck t in trucks)
{
if (t.isFavorite() != all[all.IndexOf(t)].isFavorite())
{
t.setFavorite(all[all.IndexOf(t)].isFavorite());
}
}
Also let me know if this approach is not good practise in the first place.
If you want to update only some properties, you could implement in your model class INotifyPropertyChanged interface.
public class Model : INotifyPropertyChanged
{
private string _myProperty;
public string MyProperty
{
get { return _myProperty; }
set
{
_myProperty = value;
RaisePropertyChanged();
}
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName]string propertyName = "")
{
Volatile.Read(ref PropertyChanged)?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And then if this property is binded to view, view will know, when you change it:
<Label Text="{Binding MyProperty}" />

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.)

Glitch on Windows Forms when Data Binding on a property called Property

I was experimenting with Data Binding in Windows Forms and found a glitch that I can't explain. I post the question here in hopes that someone in the community can come up with an answer that makes sense.
I tried to come up with a clever way of binding read-only values that depend on operations on other values, and update it automatically when the dependent values change.
I created a form with 3 textboxes, where I want the sum of the first 2 to appear in the 3rd textbox.
The following code should work, but doesn't, at least not properly:
public class Model : INotifyPropertyChanged
{
private int m_valueA;
private int m_valueB;
public int ValueA
{
get { return m_valueA; }
set { m_valueA = value; RaisePropertyChanged("ValueA"); }
}
public int ValueB
{
get { return m_valueB; }
set { m_valueB = value; RaisePropertyChanged("ValueB"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class DynamicBindingProperty<T> : INotifyPropertyChanged
{
private Func<T> m_function;
private HashSet<string> m_properties;
public DynamicBindingProperty(Func<T> function, INotifyPropertyChanged container, IEnumerable<string> properties)
{
m_function = function;
m_properties = new HashSet<string>(properties);
container.PropertyChanged += DynamicBindingProperty_PropertyChanged;
}
public T Property { get { return m_function(); } }
void DynamicBindingProperty_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!m_properties.Contains(e.PropertyName)) return;
if (PropertyChanged == null) return;
PropertyChanged(this, new PropertyChangedEventArgs("Property"));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
InitializeDataBinding();
}
private void InitializeDataBinding()
{
Model model = new Model();
DynamicBindingProperty<int> tmp = new DynamicBindingProperty<int>(() => model.ValueA + model.ValueB, model, new[] {"ValueA", "ValueB"});
textBox1.DataBindings.Add("Text", model, "ValueA");
textBox2.DataBindings.Add("Text", model, "ValueB");
textBox3.DataBindings.Add("Text", tmp, "Property");
tmp.PropertyChanged += (sender, args) => Console.WriteLine(args.PropertyName);
}
}
After experimenting for a while, I tried renaming DynamicBindingProperty<T>.Property to something else (e.g. DynamicProperty), and everything worked as expected!. Now, I was expecting something to break by renaming Model.ValueA to Property, but it didn't, and still worked flawlessly.
What is going on here?
I did some debugging and it looks like a bug (or requirement "the property must not be named Property" I am not aware of). If you replace
PropertyChanged(this, new PropertyChangedEventArgs("Property"));
with
PropertyChanged(this, new PropertyChangedEventArgs(null));
it still does not work - null or an empty string means any property may have changed. This indicates that problem is not in the handling of the change notification but that the binding has not been correctly established.
If you add a second property Property2 to DynamicBindingProperty<T> that does the same as Property and bind it to a fourth text box, then both text boxes will get update correctly if you perform a change notification with an empty string, null or "Property2". If you perform the change notification with "Property" both text boxes will not get update correctly. This indicates that the binding to Property is not completely broken and also that the change notification is somewhat broken.
Sadly I was unable to pin down the exact location where things go wrong, but if you invest enough time stepping through optimized framework source code you can probably figure it out. The earliest difference between the case with property name Property and the case with property name Property2 I could identify when processing a change notification was in OnValueChanged() in the internal class System.ComponentModel.ReflectPropertyDescriptor. In one case the base implementation gets called while it gets skipped in the other case - at least if the debugger didn't trick me, but this is hard to tell in optimized code.

Binding ListBox.SelectedItem to Property

This might be a duplicate question, but I'm unable to find a good answer. All the answers like Binding WinForms ListBox to object properties don't work on my WinForm. I'll explain.
I have a list of Firms that I show in a ListBox. I would like when the SelectedItem changes, that it updates a property on my model. So that I can read the Firms properties.
// the classes
public class Firm
{
public string Name { get; set; }
public int Id { get; set; }
// more properties ...
}
public class MyModel : INotifyPropertyChanged
{
private Firm _firm = new Firm();
public Firm Firm
{
get { return _firm; }
set
{
if (Equals(value, _firm)) return;
_firm = value;
OnPropertyChanged();
}
}
// more properties and OnPropertyChanged() ...
}
// the form
private MyModel Model;
public void MyForm(List<Firm> firms)
{
lstFirm.DataBindings.Add("SelectedItem", Model, "Firm",
true, DataSourceUpdateMode.OnPropertyChanged);
lstFirm.DisplayMember = "Name";
lstFirm.ValueMember = "Id";
lstFirm.DataSource = firms;
}
public void lstFirm_SelectedIndexChanged(object sender, EventArgs e)
{
// Do something with Model.Firm
}
The problem is that Model.Firm null is. Does anybody have an idea what I need to do to make a databinding between the ListBox and the Model? I bind other stuff on my WinForm (such as TextBoxes to String properties) and those work nicely.
From what I can see, your code never sets Model.Firm... Where's the constructor for MyModel? If you don't provide one, Model.Firm will stay null unless you explicitly set it. Here's an example constructor:
public MyModel(Firm firm)
{
_firm = firm;
}
Also, Equals() doesn't do what you think it does. Instead of if (Equals(value, _firm)) return;, use this: if (value == _firm) return;
Ok, so after a weekend of testing, I figured it out.
I was debuging in the SelectedIndexChanged event and didn't see the change in my Model.Firm just yet. But as the SelectedItemChanged event is only internal, I couldn't use that and that's where the databinding on SelectedItem applies the values to databound items.
Now the reason why the change isn't visible yet, is because the SelectedItemChanged is only fired after the SelectedIndexChanged is executed. So internally in the ListBox control, it probably looks like
this.SelectedIndex = value;
this.SelectedItem = FindItem(value);
this.SelectedIndexChanged(/*values*/);
this.SelectedItemChanged(/*values*/); // Apply databinding changes
So it's quite normal that you don't see the changes, before the change has occured. And I didn't know this, so I was kinda stumped why the SelectedItem (who was displaying the changed value) wasn't copied over to the databound model property.
So I didn't have to change anything major to get it all working. :)

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

Categories