WPF ItemsControl doesn't refresh when I use PropertyChanged - c#

I have list of strings and a property for it, only with get that returns List<string>. So, when I add something to my list and call OnPropertyChanged("NameOfProperty") it does not refresh my ItemsControl in the view, but when I add something in the constructor it works.
MainWindow
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowModel();
}
MainWindowModel C#
private static List<string> messages = new List<string>();
public List<string> Messages
{
get
{
return messages;
}
}
// ...
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// ...
public void foo()
{
messages.Add("Hi");
OnPropertyChanged("Messages");
}
MainWindow XAML
<ScrollViewer Grid.Row="0" Grid.Column="1" Margin="10, 0, 0, 0" Grid.ColumnSpan="2">
<ItemsControl ItemsSource="{Binding Messages, Mode=OneWay}"/>
</ScrollViewer>

The expression
messages.Add("Hi");
does not change the value of the Messages property, and unless the property value has not actually changed, the PropertyChanged event is ignored.
In order to update the UI when a collection is modified (i.e. elements are added, moved or removed), the collection needs to implement the INotifyCollectionChanged interface. The framework provides the ObservableCollection<T> class that implements this interface.
public ObservableCollection<string> Messages { get; }
= new ObservableCollection<string>();
and just
Messages.Add("Hi");

Collection Properties which should notify the framework, that items were added or deleted must implement the interface INotifyCollectionChanged.
A default implementation is found in the ObservableCollection.
So you could change your Messages Property Type to ObservableCollection instead of List.

Changing lists to ObservableCollection should be enough.

Related

ComboBox ItemSourceProperty losts binding when item selected

I spend few hours on this working on bigger project, so I have made simple example. Problem is when you press "Add" button, it adds numbers to ComboBox item source property...Great, but when you open or select any item from comboBox, binding stops working.
I must be missing something.
XAML:
....
<Grid>
<ComboBox x:Name="comboBox" HorizontalAlignment="Left" Margin="82,63,0,0"
VerticalAlignment="Top" Width="120"/>
<Button x:Name="AddButton" Content="Add" HorizontalAlignment="Left"
Margin="82,143,0,0" VerticalAlignment="Top" Width="75"
Click="NewNumberClick"/>
</Grid>
...
C# code:
namespace ComboBoxBinding
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private List<double> _numbers;
Binding comboBoxBinding;
public List<double> Numbers
{
get
{
return _numbers;
}
set
{
_numbers = value;
OnPropertyChanged("Numbers");
}
}
public MainWindow()
{
InitializeComponent();
Numbers = new List<double>(){ 1.0, 2.0, 3.0};
comboBoxBinding = new Binding();
comboBoxBinding.Path = new PropertyPath("Numbers");
comboBoxBinding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(comboBox, ComboBox.ItemsSourceProperty, comboBoxBinding);
DataContext = this;
}
private void NewNumberClick(object sender, RoutedEventArgs e)
{
Random rand = new Random();
double newNumber = 2.0 - rand.NextDouble();
Numbers.Add(newNumber);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new
PropertyChangedEventArgs(propertyName));
}
}
}
Use ObservableCollection instead of List. List don't provide a notification when somethings inside a list changes.
ObservableCollection is a collection that allows code outside the collection be aware of when changes to the collection (add, move, remove) occur. It is used heavily in WPF and Silverlight but its use is not limited to there. Code can add event handlers to see when the collection has changed and then react through the event handler to do some additional processing. This may be changing a UI or performing some other operation.
See: What is the use of ObservableCollection in .net?
your source is a List, it won't notify the UI about member updates. You could use ObservableCollection instead or call OnPropertyChanged each time after you do .Add
More importantly you should use a real DataContext instead of your UI class and you should do the binding in xaml not in code behind

Binding ObservableCollection in viewmodel to listbox

I'm very new to MVVM and bindings and I'm trying to learn to work with it.
I run into the problem of binding my viewmodel to the view in particular binding an observable collection to a listbox.
this is what my viewmodel looks like:
namespace MyProject
{
using Model;
public class NetworkViewModel: INotifyPropertyChanged
{
private ObservableCollection<Person> _networkList1 = new ObservableCollection<Person>();
public ObservableCollection<Person> NetworkList1 //Binds with the listbox
{
get { return _networkList1; }
set { _networkList1 = value; RaisePropertyChanged("_networkList1"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public NetworkViewModel()
{
_networkList1 = new ObservableCollection<Person>()
{
new Person(){FirstName="John", LastName="Doe"},
new Person(){FirstName="Andy" , LastName="Boo"}
};
}
}
in the view I have
namespace MyProject
{
public partial class Networking : Window
{
public Networking()
{
InitializeComponent();
this.DataContext = new NetworkViewModel();
lb1.ItemsSource = _networkList1;
}
}
}
and in the XAML I have
<ListBox x:Name="lb1" HorizontalAlignment="Left" ItemsSource="{Binding NetworkList1}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock >
<Run Text="{Binding Path=FirstName}"/>
<Run Text="{Binding Path=LastName}"/>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
It seems like you might have a typo in your view model.
RaisePropertyChanged("_networkList1");
You want to raise the property changed notification for the public property not the private variable.
RaisePropertyChanged("NetworkList1");
This might be preventing your view from updating properly.
In addition to Gaurav answer, if _networkList1 is a private field in your NetworkViewModel class, how is it possible to get access to it in Networking window? I mean what's the meaning of the following line?
lb1.ItemsSource = _networkList1;
when you define a Property (NetworkList1), you have to use it in order to get advantages of its features (e.g. to get RaisePropertyChanged working). Otherwise what's the point, you could have just defined a field (_networklist1). So changing
_networkList1 = new ObservableCollection<Person>()
to
NetworkList1 = new ObservableCollection<Person>()
results in actually setting NetworkList1 and therefore RaisePropertyChanged("NetworkList1") to be fired. (however if you want to just show data in a your listbox this is unnecessary)
and if i'm getting it right, changing this:
public partial class Networking : Window
{
public Networking()
{
InitializeComponent();
this.DataContext = new NetworkViewModel();
lb1.ItemsSource = _networkList1;
}
}
to
public partial class Networking : Window
{
public NetworkViewModel MyViewModel { get; set; }
public Networking()
{
InitializeComponent();
MyViewModel = new NetworkViewModel();
this.DataContext = MyViewModel;
}
}
should get your binding to work.
*Note that when you set DataContext to NetworkViewModel, then the binding in
<ListBox x:Name="lb1" HorizontalAlignment="Left" ItemsSource="{Binding NetworkList1}">
works, because NetworkList1 is a Property of NetworkViewModel.
Do not call RaisePropertyChanged() method on ObservableCollection<T>, for god's sake. This is a common mistake in a majority of cases (however, there are cases, where you need to reset ObservableCollection<T> using new keyword, but they are kinda rare).
This is a special type of collection which notifies UI internally about all the changes of its content (like add, remove etc.). What you need is to set the collection using new keyword once in a lifetime of your ViewModel, and then manipulate your items via Add(T item), Remove(T item), Clear() methods etc.
and UI will get notified about it and updated automatically.

ListBox bound to List is not updated

This is the simplest example of a program that demonstrates the problem. I need to bind a List to ListBox. Yes, the data structure must be a list, not an ObservableCollection (but if it's needed, I can build a wrapper or something).
In this program there is a listbox and a button which removes the first item in the list. But when the item is removed, the ListBox contents are not updated even though oneway binding is configured. I need it to get updated on item removal.
class Data: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<string> items;
public List<string> Items { get { return items; } }
public Data(List<string> items)
{
this.items = items;
}
public void RemoveFirstItem()
{
items.Remove(items[0]);
RaisePropertyChanged("Items");
}
private void RaisePropertyChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
}
XAML code:
<Window x:Class="ListBoxTest.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">
<Grid>
<ListBox Name="TheListBox" Margin="0,0,0.138,46.231" ItemsSource="{Binding Path=Items, Mode=OneWay}"/>
<Button Content="Button" HorizontalAlignment="Left" Margin="212,292,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
</Window>
Window C# code:
public partial class MainWindow : Window
{
private Data data;
public MainWindow()
{
InitializeComponent();
data = new Data(new List<string>{ "1", "2", "3", "4" });
this.DataContext = data;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
data.RemoveFirstItem();
}
}
Yes, the data structure must be a list, not an ObservableCollection (but if it's needed, I can build a wrapper or something)
It's pretty simple, The ListBox UI(Xaml) updating is all based on a few interfaces such as INotifyPropertyChanged, INotifyCollectionChanged and few more
These interfaces instruct the UI when an item they hold gets added / removed / refreshed ...
A List does not implement these interfaces(it implements things such as IEnumerable, ICollection...). So having your ListBox.ItemsSource bound to a collection of type List isn't going to get you the benefits of using an ObservableCollection with updates in the UI.
Sure, if you're forced to use a List and wouldn't mind how you're achieving the result, Every-time you remove an item from your List, reset the Window's DataContext (set the DataContext to null before the remove and again to data after the remove). However this is just insane if someone forced you to have to do that and you should prolly have a chat with them and explain why it's insane.
As for your wrapper idea, sure you can build your own custom collection that say implements IEnumerable, INotifyCollectionChanged, INotifyPropertyChanged and internally hold a List or whatever, but your ListBox.ItemSource should be of this type wrapper to get the updating behavior.
You haven't mentioned why exactly you are forced to not use an ObservableCollection<T>, however if you can you prolly should just use it than invent your own custom collection for simple stuff.

Bind a control to a single value in a collection/array in WPF

In WPF I have a collection of bool? values and I want to bind each of these to a separate checkbox programmatically. I want the bindings to be TwoWay so that changing the value of the individual item in the collection in code updates the check box and vice versa.
I have spent ages trying to figure out how to do this and I am completely stuck. With the following code the checkbox only gets the right value when the window is loaded and that's it. Changing the check box doesn't even update the value in the collection. (UPDATE: this appears to be a bug in .NET4 as the collection does get updated in an identical .NET3.5 project. UPDATE: Microsoft have confirmed the bug and that it will be fixed in the .NET4 release.)
Many thanks in advance for your help!
C#:
namespace MyNamespace
{
public partial class MyWindow : Window, INotifyPropertyChanged
{
public MyWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public List<bool?> myCollection = new List<bool?>
{ true, false, true, false, true, false };
public List<bool?> MyCollection
{
get { return myCollection; }
set { myCollection = value; }
}
}
}
XAML:
<CheckBox IsChecked="{Binding Path=MyCollection[0], Mode=TwoWay}">
There are a few things that need changing here to get this to work. Firstly you'll need to wrap your boolean value in an object that implements the INotifyPropertyChanged interface in order to get the change notification that you are looking for. Currently you are binding to boolean values in your collection which do not implement the interface. To do this you could create a wrapper class like so :
public class Wrapper: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private bool val = false;
public bool Val
{
get { return val; }
set
{
val = value;
this.OnPropertyChanged("Val");
}
}
public Wrapper(bool val)
{
this.val = val;
}
}
You'll then want to create these objects in your form instead of a list of booleans. You may also want to use an observable collection instead of a list so that notification of items being added and removed are sent. This is shown below:
public Window1()
{
InitializeComponent();
this.DataContext = this;
}
private ObservableCollection<Wrapper> myCollection = new ObservableCollection<Wrapper>()
{new Wrapper(true), new Wrapper(false), new Wrapper(true)};
public ObservableCollection<Wrapper> MyCollection
{
get { return myCollection; }
}
The next thing to do is to display a list of check boxes in your ui. To do this WPF provides itemscontrols. ListBox is an itemscontrol so we can use this as a starting point. Set the itemssource of a listbox to be MyCollection. We then need to define how each Wrapper object is going to be displayed in the list box and this can be done with a datatemplate which is created in the windows resources. This is shown below :
<Window.Resources>
<DataTemplate x:Key="myCollectionItems">
<CheckBox IsChecked="{Binding Path=Val, Mode=TwoWay}"></CheckBox>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=MyCollection}" ItemTemplate="{StaticResource myCollectionItems}"></ListBox>
</Grid>
This should get you up and running with a simple demo of checkboxes that have values bound to a list of booleans.
What makes you think it's not working? It's working for me :)
Here's my test XAML:
<UniformGrid>
<CheckBox IsChecked="{Binding Path=MyCollection[0], Mode=TwoWay}"/>
<ListBox ItemsSource="{Binding MyCollection}"/>
<Button Content="Test" Click="Button_Click"/>
</UniformGrid>
Here's my code behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
}
(the rest is the same as yours)
I placed a breakpoint on Button_Click and checked MyCollection[0] it was updated according to the IsChecked value of the CheckBox.
Try changing your collection type from List<bool?> to ObservableCollection<bool?> perhaps that is the reason you think it's not working for you (the fact that changes to the collection are not reflected anywhere else in your view).
Change your List<bool?> to an ObservableCollection<bool?>. A List does not raise the change notifications that WPF needs to update the UI. An ObservableCollection does. This handles the case where the list entry is changed and the CheckBox needs to update accordingly.
In the other direction, it works for me even with a List<bool?> -- i.e. toggling the checkbox modifies the value in the collection. Your binding syntax is certainly correct.

Refreshing UI with databind in WPF

I have a 3 layer deep treeview,
-MAIN
->:SUB1
>:SUB2
>:SUB2
-X:SUB1
X:SUB2
SUB1
SUB1
where, > and X represent graphics denoting the status of that specific item (determined from backend).
I'm using an Observable Dictionary to bind to this tree (and it has an ICollectionChanged event). The structure is like this:
ObservableDictionary<string,CustomClass> mainitems;
public class CustomClass{
ObservableDictionary<string, InnerClass> sub1item;
// Bunch of properties and methods in this class
// INotify not implemented
}
public class InnerClass{
// Bunch of properties and methods in this class
// INotify not implemented
public SomeEnum Status{
get{ return this.status; }
}
}
The graphics, mentioned above, are binded using a custom converter which converts the Status enum to a path so that it can be binded (ie. <img source="{Binding Path=something, Converter={StaticResource someconverter}, Mode=OneWay" /> ).
QUESTION:
My problem is, when I update the CustomClass's sub1item dictionary with new statuses, it doesn't update it in the UI. I think implementing INotify stuff might work but I don't know where I need to update it and exactly how to do so.
Edit:
My XAML template for the treeview is as follows:
<TreeView Name="tvInstance" ItemsSource="{Binding}" TreeViewItem.Selected="tviSelected" IsTextSearchEnabled="True">
<TreeView.ItemContainerStyle>
<Style>
<Setter Property="TreeViewItem.IsExpanded" Value="{Binding Path=Value.Expanded, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Value.CustomClass}" ItemContainerStyle="{x:Null}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=Key}"/>
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Value.AnotherClass}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=Value.Status, Converter={StaticResource convertstatus} }"
Width="10" Height="10"/>
<Label Content="{Binding Path=Key}" />
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=Value, Converter={StaticResource convertstatus} }"
Width="10" Height="10"/>
<Label Content="{Binding Path=Key}" />
</StackPanel>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
EDIT: After adding all INotifyProperty events in my mainclass, my CustomClass, and my InnerClass, it still doesn't work. I'm using the Dr. WPF version of ObservableDictionary (and using a dictionary is crucial to my application since I need to do lots of lookups). Help!
Epilogue
The answers in this page are correct in that INotifyPropertyChanged needs to be implemented on properties I want updated in the UI.
I found that binding the dictionary was too much trouble so I kept both an ObservableCollection and a Dictionary. I used the dictionary for lookup and the collection for binding (since both use the same reference to the object, removing was easy with the collection and the only O(n) operation).
With regards to updating in the UI, please refer to the other posts on this page.
This may be a little long, here would be my best guess:
public class CustomClass : INotifyPropertyChanged
{
public CustomClass()
{
sub1item = new ObservableDictionary<string, InnerClass>();
// This next line may not be necessary... Changes might propogate up.
sub1item.CollectionChanged += () => NotifyPropertyChange("Sub1Item");
}
private ObservableDictionary<string, InnerClass> sub1item;
public ObservableDictionary<string, InnerClass> Sub1Item
{
get { return sub1item; }
private set { sub1item = value; NotifyPropertyChange("Sub1Item"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
public class InnerClass : INotifyPropertyChanged
{
public SomeEnum Status
{
get { return this.status; }
private set { this.status = value; NotifyPropertyChange("Status"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Just make sure you update your status by calling Status = something, and not directly through this.status
Edit: If you're just looking to ONLY update the single object that got the updated status, I'm not sure that this will do it. I suspect this will signal that Sub1Item changed, but mainitems will likely not know about the individual object. It depends on your implementation.
If you created a DataTemplate for CustomClass, which had a binding to Sub1Item, then your binding will properly update for only the updated status
<DataTemplate DataType="{x:Type myClrNamespace:InnerClass}">
<Grid>
<TextBlock Text={Binding Path=Status}/>
</Grid>
</DataTemplate>
...
<ListBox x:Name="listStatus"/>
Then in the C# somewhere, you could have: listStatus = mainlist[0].Sub1Item; After seeing your example of your TreeView ItemTemplate though, I'm not sure anymore.
Observable collections implement INofityCollectionChanged which is used by WPF to refresh the collection of view items.
However, for the status to be updated you need your data to implement INotifyPropertyChanged.
Each class you want to appear within the view must implement it, so WPF will know when its properties change and which of its properties has changed.
The implementation is simple...
// Should implement INotifyPropertyChanged if the dictionary itself
// can be changed and not only its items
public class CustomClass {
ObservableDictionary sub1item;
// Bunch of properties and methods in this class
// INotify not implemented
}
public class InnerClass : INotifyProperyChanged {
// Bunch of properties and methods in this class
// INotify not implemented
public SomeEnum Status{
get{ return this.status; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// where ever this.status is changed directly,
// call NotifyPropertyChanged("Status")
// (at end of that method)
//
// if this.status is changed from outside class (if public),
// then add a public method NotifyStatusChanged() which calls
// NotifyPropertyChanged("Status")
//
// If Status property has a set{} then if new value != this.status,
// call NotifyPropertyChanged("Status") at end of setter
}
You need to use an event, have your class implement INotifyPropertyChanged, it would look something like this:
public class InnerClass: INotifyPropertyChanged
{
private string _propertyName;
//Implemented from INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public string PropertyName
{
get { return _propertyName; }
set
{
_propertyName = value;
OnPropertyChanged("Name or Property Data");
}
}
//Just using string as an example, send whatever data you'd like
protected void PropertyChanged(string name)
{
//Check to make sure the event is wired.
if(PropertyChanged != null)
{
//Fire event
PropertyChanged(this, name);
}
}
}
Basically, have these events fire for your sub items and pass up to your CustomClass object. Then, if need be, have the CustomClass handle these events, and fire another event up to your main object telling it to update the UI.
ObservableDictionary(Of TKey, TValue) - VB.NET
General feature list:
ObservableDictionary(Of TKey, TValue)
AddRange getting notified only once.
Generic EventArgs(Of TKey, TValue)
NotifyDictionaryChanging(Of TKey, TValue) - a subclass of CancelEventArgs that allows cancelling operation.
working example for class of type "Task"
public class Task: INotifyPropertyChanged
{
//Implemented from INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private string text;
public string Text
{
get { return text; }
set {
text = value;
NotifyPropertyChanged("Text");
}
}
}
On a side note its worth remembering that you need to use an ObservableCollection rathan than List to get a dynamically updating ItemSource when databinding to a collection of types. List does not notify.

Categories

Resources