We have an AsyncObservableCollection class which is inheriting from ObservableCollection in our wpf application.
This class is having :
public Dispatcher Dispatcher
{
get
{
if (_dispatcher == null)
{
if (Application.Current != null)
{
_dispatcher = Application.Current.Dispatcher;
}
else
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
}
return _dispatcher;
}
set
{
_dispatcher = value;
}
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!Dispatcher.CheckAccess())
{
Dispatcher.BeginInvoke(
new Action<NotifyCollectionChangedEventArgs>(OnCollectionChanged), e);
return;
}
base.OnCollectionChanged(e);
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (!Dispatcher.CheckAccess())
{
Dispatcher.BeginInvoke(new Action<PropertyChangedEventArgs>(OnPropertyChanged), e);
return;
}
base.OnPropertyChanged(e);
}
and there is another class RangeObservableCollection which is inheriting from this AsyncObservableCollectionClass, having only this inside it
private bool _suppressNotification;
public void ReplaceRange(IEnumerable<T> list)
{
_suppressNotification = true;
Clear();
if (list != null)
{
foreach (T item in list)
{
Add(item);
}
}
_suppressNotification = false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!_suppressNotification)
{
base.OnCollectionChanged(e);
}
}
now I tried to use BindingOperations.EnableCollectionSynchronization.
Then inside our ListViewModelBase.cs
created a lock object like this
private readonly static object _storiesCollectionLock = new object();
and inside the constructor of our ListViewModelBase
we are creating the RangeObservableCollection and binding BindingOperations.CollectionRegistering
protected ListViewModelBase()
{
_storiesCollection = new RangeObservableCollection<StoryInfoViewModel>();
BindingOperations.CollectionRegistering += BindingOperations_CollectionRegistering;
}
private void BindingOperations_CollectionRegistering(object sender, CollectionRegisteringEventArgs e)
{
if (e.Collection == _storiesCollection)
{
Dispatcher.BeginInvoke(new Action(() =>
{
BindingOperations.EnableCollectionSynchronization(_storiesCollection, _storiesCollectionLock);
}));
}
}
Then wherever in our project we are using this _storiesCollection, I am wrapping it in a lock which is using this _storiesCollectionLock object. Locking even the ListCollectionView which is getting populated with this observable collection.
Have created properties in the ListViewModelBase, for the _storiesCollection and the _storiesCollectionLock object.
public RangeObservableCollection<StoryInfoViewModel> StoriesCollection
{
get
{
lock (StoriesCollectionLock())
{
return _storiesCollection;
}
}
}
public static object StoriesCollectionLock()
{
return _storiesCollectionLock;
}
Then in a separate StoryListViewModel inside my add method when i am trying to insert in this StoriesCollection on a worker thread
lock (StoriesCollectionLock())
{
StoriesCollection.Insert(0, storyVM);
}
I am getting exceptions inside OnCollectionChanged in AsyncObservableCollection class
System.InvalidOperationException: 'Added item does not appear at given index '0'.'
also getting exception Message = "Cannot change ObservableCollection during a CollectionChanged event.", below is the stack trace for this
at System.Collections.ObjectModel.ObservableCollection1.CheckReentrancy() at System.Collections.ObjectModel.ObservableCollection1.InsertItem(Int32 index, T item)
at System.Collections.ObjectModel.Collection`1.Insert(Int32 index, T item)
and
System.InvalidOperationException: ''1510' index in collection change event is not valid for collection of size '1500'.'
Can someone please help, what am i doing wrong or missing here?
Or is it a known issue with EnableCollectionSynchronization?
Related
So I have this object:
public class SomeObject: INotifyPropertyChanged
{
public decimal AlertLevel {
get {
return alertLevel;
}
set {
if(alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
private void OnPropertyChanged(string propertyName) {
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Suppose I am changing this object on a thread that is NOT the GUI thread. How can I have this object raise the PropertyChanged event on the same thread as the GUI when I don't have a reference to any GUI component in this class?
Normally the event subscriber should be responsible for marshalling the calls to the UI thread if necessary.
But if the class in question is UI specific (a.k.a view model), as soon it is created on the UI thread, you can capture the SynchronizationContext and use it for raising the event like this:
public class SomeObject : INotifyPropertyChanged
{
private SynchronizationContext syncContext;
public SomeObject()
{
syncContext = SynchronizationContext.Current;
}
private decimal alertLevel;
public decimal AlertLevel
{
get { return alertLevel; }
set
{
if (alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
if (syncContext != null)
syncContext.Post(_ => handler(this, new PropertyChangedEventArgs(propertyName)), null);
else
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Alternatively you can pass SynchronizationContext via constructor.
Yet another way is to keep the object intact, but data bind to it via intermediate synchronized binding source as described here Update elements in BindingSource via separate task.
for WPF - Add the following references:
PresentationFramework.dll
WindowsBase.dll
In your background thread - wrap the code that needs access to UI into a dispatcher.Invoke()
using System.Windows;
using System.Windows.Threading;
...
//this is needed because Application.Current will be NULL for a WinForms application, since this is a WPF construct so you need this ugly hack
if (System.Windows.Application.Current == null)
new System.Windows.Application();
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
//Do Your magic here
}), DispatcherPriority.Render);
for WinForms use
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Render, new Action(() => {
//Do Your magic here
}));
An even better idea, without using any WPF references:
public class GUIThreadDispatcher {
private static volatile GUIThreadDispatcher itsSingleton;
private WeakReference itsDispatcher;
private GUIThreadDispatcher() { }
public static GUIThreadDispatcher Instance
{
get
{
if (itsSingleton == null)
itsSingleton = new GUIThreadDispatcher();
return itsSingleton;
}
}
public void Init(Control ctrl) {
itsDispatcher = new WeakReference(ctrl);
}
public void Invoke(Action method) {
ExecuteAction((Control ctrl) => DoInGuiThread(ctrl, method, forceBeginInvoke: false));
}
public void BeginInvoke(Action method) {
ExecuteAction((Control ctrl) => DoInGuiThread(ctrl, method, forceBeginInvoke: true));
}
private void ExecuteAction(Action<Control> action) {
if (itsDispatcher.IsAlive) {
var ctrl = itsDispatcher.Target as Control;
if (ctrl != null) {
action(ctrl);
}
}
}
public static void DoInGuiThread(Control ctrl, Action action, bool forceBeginInvoke = false) {
if (ctrl.InvokeRequired) {
if (forceBeginInvoke)
ctrl.BeginInvoke(action);
else
ctrl.Invoke(action);
}
else {
action();
}
}
}
}
And initialize like this:
private void MainForm_Load(object sender, EventArgs e) {
//setup the ability to use the GUI Thread when needed via a static reference
GUIThreadDispatcher.Instance.Init(this);
...
}
And use like this:
public class SomeObject: INotifyPropertyChanged
{
public decimal AlertLevel {
get {
return alertLevel;
}
set {
if(alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
private void OnPropertyChanged(string propertyName) {
GUIThreadDispatcher.Instance.BeginInvoke(() => {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
});
}}
Found an even better answer without having to use a WeakReference to the form control and NO WPF References based on https://lostechies.com/gabrielschenker/2009/01/23/synchronizing-calls-to-the-ui-in-a-multi-threaded-application/ and Ivan's answer above:
public class GUIThreadDispatcher {
private static volatile GUIThreadDispatcher itsSingleton;
private SynchronizationContext itsSyncContext;
private GUIThreadDispatcher() {}
/// <summary>
/// This needs to be called on the GUI Thread somewhere
/// </summary>
public void Init() {
itsSyncContext = AsyncOperationManager.SynchronizationContext;
}
public static GUIThreadDispatcher Instance
{
get
{
if (itsSingleton == null)
itsSingleton = new GUIThreadDispatcher();
return itsSingleton;
}
}
public void Invoke(Action method) {
itsSyncContext.Send((state) => { method(); }, null);
}
public void BeginInvoke(Action method) {
itsSyncContext.Post((state) => { method(); }, null);
}
}
}
And initialize like this:
private void MainForm_Load(object sender, EventArgs e) {
//setup the ability to use the GUI Thread when needed via a static reference
GUIThreadDispatcher.Instance.Init();
...
}
And use like this:
public class SomeObject: INotifyPropertyChanged
{
public decimal AlertLevel {
get {
return alertLevel;
}
set {
if(alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
private void OnPropertyChanged(string propertyName) {
GUIThreadDispatcher.Instance.BeginInvoke(() => {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
});
}}
This turned out to be a clean implementation (relatively). Just had to include a reference to WindowsBase.dll which turns out to be a WPF library so eh..., not extremely pleased with it but it's a solution...:
public class GUIThreadDispatcher {
private static volatile GUIThreadDispatcher itsSingleton;
private Dispatcher itsDispatcher;
private GUIThreadDispatcher() { }
public static GUIThreadDispatcher Instance
{
get
{
if (itsSingleton == null)
itsSingleton = new GUIThreadDispatcher();
return itsSingleton;
}
}
public void Init() {
itsDispatcher = Dispatcher.CurrentDispatcher;
}
public object Invoke(Action method, DispatcherPriority priority = DispatcherPriority.Render, params object[] args) {
return itsDispatcher.Invoke(method, priority, args);
}
public DispatcherOperation BeginInvoke(Action method, DispatcherPriority priority = DispatcherPriority.Render, params object[] args) {
return itsDispatcher.BeginInvoke(method, priority, args);
}
Then initialize it like this:
static class Program {
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main() {
GUIThreadDispatcher.Instance.Init(); //setup the ability to use the GUI Thread when needed via a static reference
Application.Run(new MainForm());
}
}
And then use it like this:
public class SomeObject: INotifyPropertyChanged
{
public decimal AlertLevel {
get {
return alertLevel;
}
set {
if(alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
private void OnPropertyChanged(string propertyName) {
GUIThreadDispatcher.Instance.BeginInvoke(() => {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
});
}}
Hi,
I'm struggling a bit using the ListBox.DataSource and the INotifyPropertyChanged Interface. I checked several posts about this issue already but I cannot figure out, how to update the view of a ListBox if an element of the bound BindingList is changed.
I basically want to change the color of an IndexItem after the content has been parsed.
Here the relevant calls in my form:
btn_indexAddItem.Click += new EventHandler(btn_indexAddItem_Click);
lst_index.DataSource = Indexer.Items;
lst_index.DisplayMember = "Url";
lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);
private void btn_indexAddItem_Click(object sender, EventArgs e)
{
Indexer.AddSingleURL(txt_indexAddItem.Text);
}
private void lst_index_DrawItem(object sender, DrawItemEventArgs e)
{
IndexItem item = lst_index.Items[e.Index] as IndexItem;
if (item != null)
{
e.DrawBackground();
SolidBrush brush = new SolidBrush((item.hasContent) ? SystemColors.WindowText : SystemColors.ControlDark);
e.Graphics.DrawString(item.Url, lst_index.Font, brush, 0, e.Index * lst_index.ItemHeight);
e.DrawFocusRectangle();
}
}
Indexer.cs:
class Indexer
{
public BindingList<IndexItem> Items { get; }
private object SyncItems = new object();
public Indexer()
{
Items = new BindingList<IndexItem>();
}
public void AddSingleURL(string url)
{
IndexItem item = new IndexItem(url);
if (!Items.Contains(item))
{
lock (SyncItems)
{
Items.Add(item);
}
new Thread(new ThreadStart(() =>
{
// time consuming parsing
Thread.Sleep(5000);
string content = item.Url;
lock (SyncItems)
{
Items[Items.IndexOf(item)].Content = content;
}
}
)).Start();
}
}
}
IndexItem.cs
class IndexItem : IEquatable<IndexItem>, INotifyPropertyChanged
{
public int Key { get; }
public string Url { get; }
public bool hasContent { get { return (_content != null); } }
private string _content;
public string Content {
get
{
return (hasContent) ? _content : "empty";
}
set
{
_content = value;
ContentChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void ContentChanged()
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public IndexItem(string url)
{
this.Key = url.GetHashCode();
this.Url = url;
}
public override bool Equals(object obj)
{
return Equals(obj as IndexItem);
}
public override int GetHashCode()
{
return Key;
}
public bool Equals(IndexItem other)
{
if (other == null) return false;
return (this.Key.Equals(other.Key)) ||
((hasContent || other.hasContent) && (this._content.Equals(other._content)));
}
public override string ToString()
{
return Url;
}
}
Any ideas what went wrong and how to fix it? I'll appreciate any hint...
It seems to me that the control should redraw when it raises the ListChanged event for that item. This will force it to do so:
lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);
Indexer.Items.ListChanged += Items_ListChanged;
private void Items_ListChanged(object sender, ListChangedEventArgs e)
{
lst_index.Invalidate(); // Force the control to redraw when any elements change
}
So why doesn't it do that already? Well, it seems that the listbox only calls DrawItem if both DisplayMember changed, and if the INotifyPropertyChanged event was raised from the UI thread. So this also works:
lock (SyncItems)
{
// Hacky way to do an Invoke
Application.OpenForms[0].Invoke((Action)(() =>
{
Items[Items.IndexOf(item)].Url += " "; // Force listbox to call DrawItem by changing the DisplayMember
Items[Items.IndexOf(item)].Content = content;
}));
}
Note that calling PropertyChanged on the Url is not sufficient. The value must actually change. This tells me that the listbox is caching those values. :-(
(Tested with VS2015 REL)
Ok from below which one is best approach or any other better ways ?
Both are working (credits not belongs to me)
public static MTObservableCollection<string> ocEventsCollection = new MTObservableCollection<string>();
public static void AddMsgToEvents(string srMessage)
{
lock (ocEventsCollection)
ocEventsCollection.Insert(0, srMessage);
}
public class MTObservableCollection<T> : ObservableCollection<T>
{
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler CollectionChanged = this.CollectionChanged;
if (CollectionChanged != null)
foreach (NotifyCollectionChangedEventHandler nh in CollectionChanged.GetInvocationList())
{
DispatcherObject dispObj = nh.Target as DispatcherObject;
if (dispObj != null)
{
Dispatcher dispatcher = dispObj.Dispatcher;
if (dispatcher != null && !dispatcher.CheckAccess())
{
dispatcher.BeginInvoke(
(Action)(() => nh.Invoke(this,
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
DispatcherPriority.DataBind);
continue;
}
}
nh.Invoke(this, e);
}
}
}
And the second approach
public static ObservableCollection<string> ocEventsCollection = new ObservableCollection<string>();
public static void AddMsgToEvents(string srMessage)
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
ocEventsCollection.Insert(0, srMessage);
}));
}
Suppose, I have objects:
public interface ITest
{
string Data { get; set; }
}
public class Test1 : ITest, INotifyPropertyChanged
{
private string _data;
public string Data
{
get { return _data; }
set
{
if (_data == value) return;
_data = value;
OnPropertyChanged("Data");
}
}
protected void OnPropertyChanged(string propertyName)
{
var h = PropertyChanged;
if (null != h) h(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
and its holder:
private BindingList<ITest> _listTest1;
public BindingList<ITest> ListTest1 { get { return _listTest1 ?? (_listTest1 = new BindingList<ITest>() { RaiseListChangedEvents = true }); }
}
Also, I subscribe to ListChangedEvent
public MainWindow()
{
InitializeComponent();
ListTest1.ListChanged += new ListChangedEventHandler(ListTest1_ListChanged);
}
void ListTest1_ListChanged(object sender, ListChangedEventArgs e)
{
MessageBox.Show("ListChanged1: " + e.ListChangedType);
}
And 2 test handlers:
For adding object
private void AddITestHandler(object sender, RoutedEventArgs e)
{
ListTest1.Add(new Test1 { Data = Guid.NewGuid().ToString() });
}
and for changing
private void ChangeITestHandler(object sender, RoutedEventArgs e)
{
if (ListTest1.Count == 0) return;
ListTest1[0].Data = Guid.NewGuid().ToString();
//if (ListTest1[0] is INotifyPropertyChanged)
// MessageBox.Show("really pch");
}
ItemAdded occurs, but ItemChanged not. Inside seeting proprty "Data" I found that no subscribers for my event PropertyChanged:
protected void OnPropertyChanged(string propertyName)
{
var h = PropertyChanged; // h is null! why??
if (null != h) h(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
Digging deeper i took reflector and discover BindingList:
protected override void InsertItem(int index, T item)
{
this.EndNew(this.addNewPos);
base.InsertItem(index, item);
if (this.raiseItemChangedEvents)
{
this.HookPropertyChanged(item);
}
this.FireListChanged(ListChangedType.ItemAdded, index);
}
private void HookPropertyChanged(T item)
{
INotifyPropertyChanged changed = item as INotifyPropertyChanged;
if (changed != null) // Its seems like null reference! really??
{
if (this.propertyChangedEventHandler == null)
{
this.propertyChangedEventHandler = new PropertyChangedEventHandler(this.Child_PropertyChanged);
}
changed.PropertyChanged += this.propertyChangedEventHandler;
}
}
Where am I wrong? Or this is known bug and i need to find some workaround?
Thanks!
BindingList<T> doesn't check if each particular item implements INotifyPropertyChanged. Instead, it checks it once for the Generic Type Parameter. So if your BindingList<T> is declared as follows:
private BindingList<ITest> _listTest1;
Then ITest should be inherited fromINotifyPropertyChanged in order to get BindingList raise ItemChanged events.
I think we may not have the full picture from your code here, because if I take the ITest interface and Test1 class verbatim (edit Oops - not exactly - because, as Nikolay says, it's failing for you because you're using ITest as the generic type parameter for the BindingList<T> which I don't here) from your code and write this test:
[TestClass]
public class UnitTest1
{
int counter = 0;
[TestMethod]
public void TestMethod1()
{
BindingList<Test1> list = new BindingList<Test1>();
list.RaiseListChangedEvents = true;
int evtCount = 0;
list.ListChanged += (object sender, ListChangedEventArgs e) =>
{
Console.WriteLine("Changed, type: {0}", e.ListChangedType);
++evtCount;
};
list.Add(new Test1() { Data = "yo yo" });
Assert.AreEqual(1, evtCount);
list[0].Data = "ya ya";
Assert.AreEqual(2, evtCount);
}
}
The test passes correctly - with evtCount ending up at 2, as it should be.
I found in constructor some interesting things:
public BindingList()
{
// ...
this.Initialize();
}
private void Initialize()
{
this.allowNew = this.ItemTypeHasDefaultConstructor;
if (typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(T))) // yes! all you're right
{
this.raiseItemChangedEvents = true;
foreach (T local in base.Items)
{
this.HookPropertyChanged(local);
}
}
}
Quick fix 4 this behavior:
public class BindingListFixed<T> : BindingList<T>
{
[NonSerialized]
private readonly bool _fix;
public BindingListFixed()
{
_fix = !typeof (INotifyPropertyChanged).IsAssignableFrom(typeof (T));
}
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
if (RaiseListChangedEvents && _fix)
{
var c = item as INotifyPropertyChanged;
if (null!=c)
c.PropertyChanged += FixPropertyChanged;
}
}
protected override void RemoveItem(int index)
{
var item = base[index] as INotifyPropertyChanged;
base.RemoveItem(index);
if (RaiseListChangedEvents && _fix && null!=item)
{
item.PropertyChanged -= FixPropertyChanged;
}
}
void FixPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (!RaiseListChangedEvents) return;
if (_itemTypeProperties == null)
{
_itemTypeProperties = TypeDescriptor.GetProperties(typeof(T));
}
var propDesc = _itemTypeProperties.Find(e.PropertyName, true);
OnListChanged(new ListChangedEventArgs(ListChangedType.ItemChanged, IndexOf((T)sender), propDesc));
}
[NonSerialized]
private PropertyDescriptorCollection _itemTypeProperties;
}
Thanks for replies!
The type of elements that you parameterize BindingList<> with (ITest in your case) must be inherited from INotifyPropertyChanged. Options:
Change you inheritance tree ITest: INotifyPropertyChanged
Pass concrete class to the generic BindingList
I have ObservableCollection playList. This playlist item displays in Listbox.
All Item update their data in a special thread. I need that only after updating all data, element in listbox updates its info. Actually Is there a way to notify that all data in item changed?
I have 1 solution but it looks bad and moreover in some case it leads to an error.
private void AsyncMIReady(MediaItem mediaItem)
{
if (PlayList.Contains(mediaItem))
{
CurSynchronizationContext.Post(delegate(object someState)
{
UpdateItemInPlayList(mediaItem);
}
, null);
}
}
public void UpdateItemInPlayList(MediaItem mediaItem)
{
int i = PlayList.IndexOf(mediaItem);
PlayList.RemoveAt(i);
PlayList.Insert(i, mediaItem);
}
I found this to be useful. It involves sub-classing ObservableCollection and suspending notifications on AddRange.
public class RangeObservableCollection<T> : ObservableCollection<T>
{
private bool _suppressNotification = false;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!_suppressNotification)
base.OnCollectionChanged(e);
}
public void AddRange(IEnumerable<T> list)
{
if (list == null)
throw new ArgumentNullException("list");
_suppressNotification = true;
foreach (T item in list)
{
Add(item);
}
_suppressNotification = false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}