ListBox bound to ObservableCollection doesn't update - c#

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" />

Related

UserControl ComboBox not updating

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.

Why does the CollectionChanged event for ObserverableCollection not always work when the collection is used as a Property?

More about the question
First, let me clear the air... I'm somewhat new to C#. I bumped into this issue in an application I was working on. This particular class only had ObservableCollections exposed as Properties so my initial thought was that ObservervableCollection has it's own event, so I don't need the PropertyChanged event. The first attempt worked beautifully. Then I started cleaning up my code and found I didn't really need one of the backing vars, so I moved it all into the method...and it quit updating UI. Adding the INotifyPropertyChanged fixed the issue but I was left with a very big "WHY?"
Here is some test code I put together to see what I could figure out. Be advised, it is littered with bad practice, but I am really only trying to figure out when CollectionChanged can be depended on and when PropertyChagned must be added. Should I ever depend on CollectionChanged over PropertyChanged? If not, should we be using another List type, as we wouldn't really need the overhead of the ObservableCollection. It really seems like a waste to use both.
The bulk of the code
private TestClass test = new TestClass();
public MainWindow()
{
InitializeComponent();
this.DataContext = test;
}
internal class TestClass : INotifyPropertyChanged
{
public ObservableCollection<String> test1 = new ObservableCollection<String>();
public ObservableCollection<String> test2 = new ObservableCollection<String>() { "T2-A" };
public TestClass()
{
}
public ObservableCollection<String> Test1 { get => test1; set { } }
public ObservableCollection<String> Test2 { get => test2; set { this.test2 = value; } }
public ObservableCollection<String> Test3 { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void BT1_Click(object sender, RoutedEventArgs e)
{
//Both of these work fine
test.test1.Add("T1-A");
test.Test1.Add("T1-B");
//First line works...Second line breaks it... but there is a setter.
test.Test2.Add("T2-C");
test.Test2 = new ObservableCollection<String>() { "T2-A", "T2-B" };
test.test2.Add("T2-D");
//First Line throws Null exception... So it's not creating a backing var of it's own.
//test.Test3.Add("T3-A");
var t3 = new ObservableCollection<String>() { "T3-B", "T3-C" };
test.Test3 = t3;
test.Test3.Add("T3-D");
//It updates when I trigger the property changed...but what broke CollectionChanged
test.OnPropertyChanged(nameof(test.Test3));
}
Here is the XAML if anyone is interested
<ListBox ItemsSource="{Binding Test1}" HorizontalAlignment="Left" Height="205" VerticalAlignment="Top" Width="161" Margin="10,5,0,0" />
<ListBox ItemsSource="{Binding Test2}" Height="205" Margin="186,5,187,0" VerticalAlignment="Top" />
<ListBox ItemsSource="{Binding Test3}" Height="205" Margin="366,5,8,0" VerticalAlignment="Top" />
<Button x:Name="bT1" Content="Test It" HorizontalAlignment="Left" Height="33" Margin="10,215,0,0" VerticalAlignment="Top" Width="516" Click="BT1_Click" />
My Findings so far
It seems that if you want to use the ObservableCollection CollectionChanged event, you must create your backing var, and never alter the instance of Collection. In otherwords, use Clear() to wipe and rebuild rather than 'var col = new ObservableCollection()'. Is there something I am missing? I would think this would get rather flaky when you start looking at TwoWay data. How would you prevent someone from breaking your code downstream following the 2nd line of test2?
//First line works...Second line breaks it... but there is a setter.
test.Test2.Add("T2-C");
test.Test2 = new ObservableCollection<String>() { "T2-A", "T2-B" };
Well, lets look at the setter
public ObservableCollection<String> Test2 { get => test2; set { this.test2 = value; } }
That does set the value to the new collection, but what about the binding?
In order for the binding to know of the new object you need to raise the NotifyPropertyChanged event. So after you set that value currently the binding will still be pointing to the old collection (which still is listening for collection changes but on the previous value). Adding a notify property changed call to the setter will allow the binding to be updated:
private ObservableCollection<String> test2;
public ObservableCollection<String> Test2
{
get
{
return test2;
}
set
{
test2 = value;
OnPropertyChanged("Test2");
}
}
Add this example to each property and your bindings will update when you update the entire collection to a new object. i.e. property = new ObservableCollection... Then the ListBox will be looking for collection changes on the new object.
In response to your last paragraph, there is another way to go about it:
You can keep one instance of the collection and never change it. i.e.:
public ObservableCollection<String> Test2 { get; set; }
Then in the constructor of the class initialize your collection. (or initialize it in-line, either works, the key is to only initialize it one time)
Test2 = new ObservableCollection<String>();
Then, when you want to create a new list do not overwrite the object. Instead just clear the list and add the new values in:
public void UpdateCollection(List<String> newValues)
{
Test2.Clear(); //notifies the list box with the CollectionChanged event
foreach(var value in newValues)
{
Test2.Add(value); //notifies the list box with the new item in the collection
}
}

Why does Binding to an instance of my class not work when this.DataContext = this

I have a class that defines a preconfigured socket and all the methods needed to access and control a specific piece of equipment remotely. Part of the class includes an instance of an object that holds the current status of various aspects of the equipment. Each item in the object reports updates using INotifyPropertyUpdate. When I plug it into my test program, all of the methods are called and execute properly, but the only way I seem to be able to get updates of the status to show in the UI is when the DataContext is set to the "Current" object inside the instance of the class. If I set the DataContext to the instance of the class, or to the UI, I stop getting updates in the UI. I would like to be able to use the UI as the DataContext and then bind in the XAML using {Binding Path=InstanceOfMyClass.Current.StatusItemA}
The pertinent parts of the classes in question:
public MyClass : Socket, INotifyPropertyChanged // INotifyPropertyChanged is also used to notify changes in other parts of the class
{
public MyClass : base(//socket configuration info here)
{}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private CurrentStatusObject _current = new CurrentStatusObject();
public CurrentStatusObject Current
{
get { return _current; }
set
{
if (_current != value)
{
_current = value;
NotifyPropertyChanged();
}
}
}
// other methods and properties etc.
}
// this is the Current status object
public class CurrentStatusObject : object, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private string _statusItemA;
public string StatusItemA
{
get { return _statusItemA; }
set
{
if (_statusItemA != value)
{
_statusItemA = value;
NotifyPropertyChanged(); // not necessary to pass property name because of [CallerMemberName]
}
}
}
This works:
c#
this.DataContext = this.InstanceOfMyClass.Current;
XAML
<Label Content="{Binding Path=StatusItemA, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
This does not work, but I want it to:
c#
this.DataContext = this;
XAML
<Label Content="{Binding Path=InstanceOfMyClass.Current.StatusItemA, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
Nor does this:
c#
this.DataContext = this.InstanceOfMyClass;
XAML
<Label Content="{Binding Path=Current.StatusItemA, Mode=OneWay, UpdateSourceTrigger}"/>
I didn't see any answers when searching, but sometimes my research skills fail me. Any help would be appreciated. I enjoy learning new ways of coding. This is my first c# or wpf project. All my projects previous to this have been vb.net in WinForms so I'm at a slight handicap with the learning curve. I would like to learn the correct way to reach my goals for this project, which at this point is simply completing the UI.
The CurrentStatusObject notifies changes internally and that does work. The problem is that the changes are only reflected in the User Interface if I set the DataContext for the UI to that one object. I want to be able to set the DataContext to include a wider scope. I would be happy if I could use the instance of MyClass as the DataContext, but that is not working right now.
The question is Why? and How do I get it to work (using correct practices)?
I presume you have a typo...
public _Current Current = new _Current();
But if so this is a field and not a property. If you change it to this then the binding might work
private _Current _current = new _Current();
public _Current Current
{
get
{
return _current;
}
}
B.T.W: it is not standard to use underscore as part of your class name. Removing it should be all you need
You can only bind to Properties, never fields.
InstanceOfMyClass and Current needs to be declared as properties before you can bind to it (to make DataContext = this work).
As an aside, MVVM dictates that you shouldn't be using your View code-behind as the view model. You should have a separate class for that.

Using BindingOperations.EnableCollectionSynchronization

I have two WPF applications "UI", "Debugger" and one ClassLibrary "BL". UI references to Debugger and BL. Debugger references to BL.
I have collection in BL called MyCollection. UI app starts the Debugger app and Debugger binds to a collection MyCollection in BL. When I try changing the MyCollection collection from UI app I am getting exception.
A first chance exception of type 'System.NotSupportedException' occurred in PresentationFramework.dll
Additional information: This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
I was googling around and found this: BindingOperations.EnableCollectionSynchronization
I can't figure out how to use it. I don't want to reference to any UI dlls from my BL project. Can anybody assist me on that?
Thanks for the help!
All the examples I've seen on Stack Overflow for this get it wrong. You must lock the collection when modifying it from another thread.
On dispatcher (UI) thread:
_itemsLock = new object();
Items = new ObservableCollection<Item>();
BindingOperations.EnableCollectionSynchronization(Items, _itemsLock);
Then from another thread:
lock (_itemsLock)
{
// Once locked, you can manipulate the collection safely from another thread
Items.Add(new Item());
Items.RemoveAt(0);
}
More information in this article: http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux
I am not sure if this will help but still you can give it a try.
Add a Property in Debugger which will hold the Collection from BL like
private ObservableCollection<string> _data = new ObservableCollection<string>();
private object _lock = new object();
public ObservableCollection<string> Data { get {return _data;} }
In the constructor just add the below line
BindingOperations.EnableCollectionSynchronization(_data, _lock);
this will above line will take care of thread safety.
Below is the example
ViewModel (Debugger)
internal class ViewModelClass : INotifyPropertyChanged
{
private object _lock = new object ();
private ObservableCollection<string> _data;
public ObservableCollection<string> Data
{
get { return _data; }
private set
{
_data = value;
RaisePropertyChanged ("Data");
}
}
private string _enteredText;
public string EnteredText
{
get { return _enteredText; }
set
{
_enteredText = value;
_data.Add (value); RaisePropertyChanged ("EnteredText");
}
}
private void RaisePropertyChanged (string name)
{
var pc = PropertyChanged;
if (pc != null)
pc (this, new PropertyChangedEventArgs (name));
}
public ViewModelClass ()
{
var _model = new ModelClass ();
Data = _model.Data;
_data.CollectionChanged += (s, e) => RaisePropertyChanged ("Data");
}
public event PropertyChangedEventHandler PropertyChanged;
}
Model(BL)
internal class ModelClass
{
private ObservableCollection<string> _data;
public ObservableCollection<string> Data
{
get { return _data; }
private set { _data = value; }
}
public ModelClass ()
{
_data = new ObservableCollection<string> { "Test1", "Test2", "Test3" };
}
}
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow ()
{
InitializeComponent ();
this.DataContext = new ViewModelClass ();
}
}
MainWindow.xaml
<Window x:Class="CollectionSynchronizationTest.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>
<ComboBox IsEditable="True"
ItemsSource="{Binding Data}"
Text="{Binding EnteredText, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
<Button Content="Test" />
</StackPanel>
When the window loads just enter "SomeValue" in the ComboBox and then after pressing the Tab key you should find the new value in the ComboBox dropdown
A WPF application can display a collection of data using an ItemsControl or one of its subclasses (ListBox, DataGrid, TreeView, ListView, etc.). WPF channels all its access to the collection through a subclass of CollectionView. Both the ItemsControl and the CollectionView have affinity to the thread on which the ItemsControl was created, meaning that using them on a different thread is forbidden and throws an exception. In effect, this restriction applies to the collection as well. You may want to use the collection on multiple threads. For example, you want to update the collection (add or remove items) on a "data-gathering" thread, while displaying the results on a "user interface" thread, so that the UI remains responsive while data-gathering is happening. In such a situation, you are responsible for ensuring synchronized ("thread-safe") access to the collection. This is typically done using either a simple lock mechanism or a more elaborate synchronization mechanism such as semaphores, reset events, etc. While you must synchronize your application's access to the collection, you must also guarantee that access from WPF (specifically from CollectionView) participates in the same synchronization mechanism. You do this by calling the EnableCollectionSynchronization method.
The DOC remark this very nice, I think you should have a look:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.data.bindingoperations.enablecollectionsynchronization?view=netcore-3.1
In this blog you find an easy tutorial how to work with BindingOperations...it is quite easy.
I could not figure out how to use it, either, when I had the same problem.
I ended with my own collection type where I store the dispatcher and use it when necessary.
Note that my naming was very poor, this collection is not threadsafe, far from it.
public class ThreadableObservableCollection<T> : ObservableCollection<T>
{
private readonly Dispatcher _dispatcher;
public ThreadableObservableCollection()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
public void ThreadsafeRemove(T item, Action callback)
{
if (_dispatcher.CheckAccess())
{
Remove(item);
callback();
}
else
{
_dispatcher.Invoke(() =>
{
Remove(item);
callback();
});
}
}
public void ThreadsafeInsert(int pos, T item, Action callback)
{
if (_dispatcher.CheckAccess())
{
Insert(pos, item);
callback();
}
else
{
_dispatcher.Invoke(() =>
{
Insert(pos, item);
callback();
});
}
}
}

List<string> doesn't update the UI when change occurs

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

Categories

Resources