I've created the UserControl DefaultComboBox:
<UserControl x:Class="MyProject.ComboBoxes.DefaultComboBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<ComboBox x:Name="ComboBoxDefault"
ItemsSource="{Binding DefaultItems, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</UserControl>
The CodeBehind of the ComboBox UserControl:
public partial class DefaultComboBox : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<String> _defaultItems = new ObservableCollection<String>();
public ObservableCollection<string> DefaultItems
{
get { return _defaultItems; }
set
{
_defaultItems = value;
NotifyPropertyChanged(DefaultItems);
}
}
// Constructor
public DefaultComboBox()
{
UpdateList(ExternalSource.InitialItemList);
NotifyPropertyChanged("DefaultItems");
InitializeComponent();
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// Some DependencProperties like Filter
// Update Method
private void UpdateList(List<String> newList)
{
DefaultItems = new ObservableCollection<string>(newList);
NotifyPropertyChanged("DefaultItems");
}
}
And here is an example of using the Control:
<comboBoxes:DefaultComboBox x:Name="DefaultComboBoxUserView"
Filter="{Binding FilterString}"/>
The problem:
If I start my WPF application for the first time and the constructor of DefaultComboBox is called, the UpdateList method works and the ComboBox contains the expected items.
If I use the UpdateList method at runtime, the setter of DefaultItems is called and the items have been correctly updated, but when I click in the GUI on the combo box drop-down, the old items are still there and nothing has been updated.
You're overriding the value of _defaultItems. That's not what Observable in ObservableCollection does. You should keep the collection instance the same at all times and only Add() and Remove() from it.
One way of entirely replacing the old collection with the new one would be:
// Update Method
private void UpdateList(List<String> newList)
{
DefaultItems.Clear();
DefaultItems.AddRange(newItems);
}
Note that this is inefficient and ObservableCollection will update view each time an item is added. There are ways around that, like suspending the notifications until AddRange is done.
Related
I have a ListBox on my UI that is bound to a property of ObservableCollection. I set a new instance of the ObservableCollection into the property in the view model's constructor and I can add items to it with a button on the form. These are visible in the list.
All is good.
However, if I reinitialize the property with new in the button callback, it breaks the binding and the UI no longer shows what is in the collection.
I assumed the binding would continue to look up the values of the property, but its presumably linked to a reference which is destroyed by the new.
Have I got this right? Can anyone expand on how this is linked up? Is there a way to rebind it when my view model has no knowledge of the view?
Make sure you are raising a PropertyChangedEvent after you reintialize your collection. Raising this event will allow the view to handle changes to the property with the model having no knowledge of the view.
class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
private ObservableCollection<string> _list = new ObservableCollection<string>();
public ObservableCollection<string> List
{
get { return _list; }
set
{
_list = value;
NotifyPropertyChanged("List");
}
}
public Model()
{
List.Add("why");
List.Add("not");
List.Add("these?");
}
}
I think based on your description that what you need to do is refactor the property that exposes your ObservableCollection so that it raises a PropertyChanged event also when it is assigned a new value. Example:
public ObservableCollection<int> Integers
{
get { return this.integers; }
set {
if (this.integers != value)
{
this.integers = value;
RaisePropertyChanged("Integers");
}
}
}
Supposing you've implemented INotifyPropertyChanged on your ViewModel, you can raise the property changed event on your ObservableCollection whenever you assign a new value to it.
public ObservableCollection<string> MyList { get; set; }
public void SomeMethod()
{
MyList = new ObservableCollection<string>();
RaisePropertyChanged("MyList");
}
Updates to the ObservableCollection are handled by hooking in to the CollectionChanged event so when you create a new ObservableCollection your observer is still looking at the old collection.
Two simple suggestions would be either to implement INotifyPropertyChanged on the class that contains the ObservableCollection and raising the PropertyChanged event in the setter of the collection property (don't forget to unhook from the old one first in your observer if it's your own code).
private ObservableCollection<string> _myCollection = new ObservableCollection<string>();
public ObservableCollection<string> MyCollection
{
get { return _myCollection; }
set
{
if(_myCollection == value)
return;
_myCollection = value;
RaisePropertyChanged("MyCollection");
}
}
A second, and the option I generally prefer is to just clear and repopulate the collection with your new data when it arrives.
public void HandleCollectionData(IEnumerable<string> incomingData)
{
MyCollection.Clear();
foreach(var item in incomingData)
{
MyCollection.Add(item);
}
}
<ListBox x:Name="MainList" HorizontalAlignment="Left" Height="468" Margin="10,10,0,0" VerticalAlignment="Top" Width="100" ItemsSource="{Binding Items,Mode=TwoWay}" DisplayMemberPath="Name"/>
[Serializable()]
public class MYcontainer : INotifyPropertyChanged,ISerializable
{
private List<MYClass> _items = new List<MYClass>();
public List<MYClass> Items
{
get{ return _items;}
set { this._items =value;
OnPropertyChanged("Items");
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
When I add an item to "Items" the UI doesn't update, the binding is working fine, since if I closed the window and opened it again, the new items appear correctly.
What am I doing wrong? I know if I used ObservableCollection it will work fine, but shouldn't it work with List<>? I already have in another window a string[] property and it update fine.
If you don't want to ues ObservableCollection you will have to implement INotifyCollectionChanged.
public partial class MainWindow : Window, INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
public MainWindow()
{
InitializeComponent();
}
public void NotifyCollectionChanged(NotifyCollectionChangedAction action)
{
if (CollectionChanged != null)
{
CollectionChanged(this, new NotifyCollectionChangedEventArgs(action));
}
}
}
However ObservableCollection does all this for you, adding all the same logic to your List<T> would just create a custom ObservableCollection, I see no point in this when MS has alraedy made this for you
It will currently only update if you replace the entire list with a new List<MyClass>. Replacing 1 item won't trigger the OnPropertyChanged event.
Use an ObservableCollection<MyClass> instead of a List<MyClass>. It's built specifically to handle this issue and notifies WPF whenever the items in the collection change.
It's very comparable to list in other respects so the changes to your code should be minimal (Both List and ObservableCollection implement the ICollection<T> interface, so most of the methods are shared).
I am having trouble getting a ListBox binding to work as expected. I'm currently attempting to bind a ListBox to a singleton exposed ObservableCollection of items. The items are a separate class themselves. Currently, I am binding like this:
<Window x:Class="toxySharp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:classes="clr-namespace:MyApplication.Classes"
Title="MainWindow" Height="325" Width="400"
DataContext="{Binding Source={x:Static local:SingletonClass.Instance}}">
<Grid x:Name="LayoutRoot">
<ListBox x:Name="lstMyList" ItemsSource="{Binding Path=Objects, Mode=TwoWay}" DisplayMemberPath="Name" />
</Grid>
</Window>
My singleton is a basic implementation like this:
public class SomeObject : INotifyPropertyChanged
{
private Int32 m_vId;
private String m_vName;
public SomeObject() { }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public Int32 Id
{
get { return this.m_vId; }
set { this.m_vId = value; NotifyPropertyChanged("Id"); }
}
public String Name
{
get { return this.m_vName; }
set { this.m_vName = value; NotifyPropertyChanged("Name"); }
}
}
public class SingletonClass : INotifyPropertyChanged
{
private static SingletonClass m_vInstance;
private ObservableCollection<SomeObject> m_vObjects;
private SingletonClass()
{
this.m_vObjects = new ObservableCollection<SomeObject>();
for (int x = 0; x < 255; x++)
this.m_vObjects.Add(new SomeObject() { Id = x, Name = String.Format("{0} - new object", x) });
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public static SingletonClass Instance
{
get
{
if (m_vInstance == null)
m_vInstance = new SingletonClass();
return m_vInstance;
}
}
public ObservableCollection<SomeObject> Objects
{
get { return this.m_vObjects; }
set { this.m_vObjects = value; NotifyPropertyChanged("Objects"); }
}
}
Currently, the binding works on startup. The application will bind and properly show the names of each object. For example this is a test app doing the same implementation:
In my main actual application I have async methods being called (Socket stuff BeginConnect, BeginSend, etc.) that use callbacks that can update the collection. (It's a list of players so when certain packets are received the list is updated with their data.)
My problem is when the collection is updated inside one of the async callbacks it doesn't update on the list. The collection data is updated properly, setting a break in the main code anywhere shows the collection being updated but the listbox never updates to reflect the changes. So it just stays saying the same thing no matter what.
Did I overlook something?
I've tried using a CollectionViewSource as well to allow filtering and that has the same problem.
== EDIT ==
I've found the problem which lies in the singleton with how the collection is initialized. Instead of using the internal copy member when initializing the collection, I needed to use the exposed property to allow it to update the UI.
So using the following fixed it:
private SingletonClass()
{
this.Objects = new ObservableCollection<SomeObject>();
for (int x = 0; x < 255; x++)
this.Objects.Add(new SomeObject() { Id = x, Name = String.Format("{0} - new object", x) });
}
However now that the list binding works I want to be able to filter this based on another property inside the object class. (In the example SomeObject). I have a boolean stating if the object is active. Trying to bind to a CollectionViewSource leads me back to the not updating problems. So is there a way to filter this manually and keep the ui updated?
My problem is when the collection is updated inside one of the async
callbacks it doesn't update on the list.
Well thats the problem isnt it! Observable collections are not thread safe. You need to make them that way.
No TwoWay binding mode or UpdateSourceTrigger=PropertyChanged will help in this case as the problem lies with multi threading in your code...
Use this custom implementation of thread safe and faster observable collection for your ease...
Fast performing and thread safe observable collection
As far as INotifyPropertyChanged interface is concerned, its 'PropertyChangedevent is automatically dispatched to the UI thread. So any multithreaded context updating the properties of a class that implementsINotifyPropertyChanged` will update the GUI.
Let me know if this helps,
UpdateSourceTrigger=PropertyChanged is missing
<ListBox x:Name="lstMyList" ItemsSource="{Binding Path=Objects,UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" DisplayMemberPath="Name" />
I try to "filter" an ObservableCollection and update the bound DataGrid.
ObservableCollection<Record> recordObservableCollection;
recordObservableCollection = new ObservableCollection<Record>(GetData()); //GetData() returns IEnumerable<Record>
dataGrid1.ItemsSource = recordObservableCollection;
Then I try to filter this collection:
recordObservableCollection = new ObservableCollection<Record>(recordObservableCollection.Where(filter));//filter is Func<Data.Record, bool>
recordObservableCollection is updated fine.
But the DataGrid is not updated.
Your field or variable called recordObservableCollection has one value initially and a different value after filtering. Because you used new ObservableCollection<Record>(...) twice you created two separate observable collection instances.
The problem is that the DataGrid is still referring to the first instance. Even though you have changed recordObservableCollection, that only affects its value. The value of DataGrid.ItemsSource is still what it was before the filtering.
To fix this problem, you need to re-assign the new collection's value to the ItemSource property. Simply repeat what you did the first time, but after the filtering:
dataGrid1.ItemsSource = recordObservableCollection;
and now ItemSource will be set to the new value of recordObservableCollection.
ObservableCollection will get update because ObservableCollection (System.Collections.ObjectModel) throws an event every time the collection get changed but you have to set the filter collection as itemsource again other wise it wont update the UI...
The best way to do this use a public property that you'll bind in control as item source and in that property you will define NotifyPropertyChanged in setter . Every time you'll change the collection using this property the control will also be updated ...
Let Suppose you have your data grid in test.xaml
--> First fo all work for INotifyPropertyChanged add an abstract class in your project inherit it from INotifyPropertyChanged interface and define OnPropertyChanged method
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
--> After add a class in your project named it testViewModel which will inherit your ViewModelBase Class..
--> Now in testViewModel you'll make a property for your grid binding like this
Private ObservableCollection<Record> _recordObservableCollection
public ObservableCollection<Record> recordObservableCollection
{
get
{
if(_recordObservableCollection == null)
{
_recordObservableCollection = new ObservableCollection<Record>(GetData());
recordObservableCollection = new ObservableCollection<Record>(recordObservableCollection.Where(filter));
}
return _recordObservableCollection;
}
Set
{
_recordObservableCollection = Value;
OnPropertyChanged("recordObservableCollection"); //Your property name
}
}
here now if u update your collection using property on any other property, method or command UI will be updated beacsue in setter you have defined OnPropertyChanged...
Now comes back to test.xaml here you have to do two things
Set dataContext of test.xaml either in code behing or in in xaml
(In Code behind just after InitializeComponent() make an intance of viewmodel class and assign it as DataContext like this
public test()
{
InitializeComponent();
testViewModel vm = new testViewModel();
this.DataContext = vm;
}
Bind property you defined in testViewModel to grid
<Grid Name="MyGrid" DataContext="{Binding recordObservableCollection}">
</Grid>
Expose the ObservableCollection<Record> as a public property
also
Using ObservableCollection only affect binding when you add/remove items from your list. By using ObservableCollection you do not need to reset binding to the list or DataGrid when your collection changed (not the item inside collection changed). But they do not have any effect when your data object properties changed. For that you need to implement INotifyPropertyChanged interface for your DataObject.
In WPF, I have a ListView bound to an ObservableCollection in the code-behind. I have working code that adds and removes items from the list by updating the collection.
I have an 'Edit' button which opens a dialog and allows the user to edit the values for the selected ListView item. However, when I change the item, the list view is not updated. I'm assuming this is because I'm not actually adding/removing items from the collection but just modifying one of its items.
How do I tell the list view that it needs to synchronize the binding source?
You need to implement INotifyPropertyChanged on the item class, like so:
class ItemClass : INotifyPropertyChanged
{
public int BoundValue
{
get { return m_BoundValue; }
set
{
if (m_BoundValue != value)
{
m_BoundValue = value;
OnPropertyChanged("BoundValue")
}
}
}
void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
int m_BoundValue;
}
Do you have set the binding mode to TwoWay? If not, try to do that.