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));
}
}
Related
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?
I am busy creating a user control that has some basic charting/graph functions. In essence I want to have an "Items" dependency property to which the user of the control can bind. The control will then display all the items and updates made to the source.
What I have done so far was to create an "Items" DP in my user control, code behind.
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items",
typeof(ObservableCollection<Polyline>),
typeof(RPGraph),
new FrameworkPropertyMetadata(
new ObservableCollection<Polyline>(),
new PropertyChangedCallback(OnItemsChanged)));
public ObservableCollection<Polyline> Items
{
get { return (ObservableCollection<Polyline>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static void OnItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
My first stumbling block was that "OnItemsChanged" didn't get called when my collection changed. After a couple of hours I found a stackoverflow post explaining why (ObservableCollection dependency property does not update when item in collection is deleted). Following this advice solved one part of my problem. Now I could Add new items (Polylines) to the ObservableCollection list. But what if I added an extra point or modified a point in the Polyline. Armed with the knowledge of the previous problem I found the Points.Changed event. I then subscribed to it and placed the update code in there.
This finally works, but man there must be a better or more elegant way of achieving this (as stated at the top), which I think all boils down to not using ObservableCollection? Any advice?
Below is the working OnItemChanged method (excuse the draft code, I just wanted to get it working :-) :
public static void OnItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var thisControl = d as RPGraph;
foreach (Polyline poly in thisControl.Items)
thisControl.manager.Items.Add(poly.Points.ToArray());
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged)e.OldValue;
// Unsubscribe from CollectionChanged on the old collection
coll.CollectionChanged -= Items_CollectionChanged;
}
if (e.NewValue != null)
{
var coll = (ObservableCollection<Polyline>)e.NewValue;
// Subscribe to CollectionChanged on the new collection
coll.CollectionChanged += (o, t) => {
ObservableCollection<Polyline> items = o as ObservableCollection<Polyline>;
thisControl.manager.Items.Add(items[t.NewStartingIndex].Points.ToArray());
foreach (Polyline poly in items)
{
poly.Points.Changed += (n, m) => {
for (int i = 0; i < thisControl.manager.Items.Count; i++)
thisControl.manager.Items[i] = thisControl.Items[i].Points.ToArray();
thisControl.manager.DrawGraph(thisControl.graphView);
};
}
thisControl.manager.DrawGraph(thisControl.graphView);
};
}
thisControl.manager.DrawGraph(thisControl.graphView);
}
You are completely right, an ObservableCollection does not notify when any of its items changes its property value.
You could extend the functionality of ObservableCollection adding notifications for these cases.
It may look like this:
public sealed class ObservableNotifiableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
public event ItemPropertyChangedEventHandler ItemPropertyChanged;
public event EventHandler CollectionCleared;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
base.OnCollectionChanged(args);
if (args.NewItems != null)
{
foreach (INotifyPropertyChanged item in args.NewItems)
{
item.PropertyChanged += this.OnItemPropertyChanged;
}
}
if (args.OldItems != null)
{
foreach (INotifyPropertyChanged item in args.OldItems)
{
item.PropertyChanged -= this.OnItemPropertyChanged;
}
}
}
protected override void ClearItems()
{
foreach (INotifyPropertyChanged item in this.Items)
{
item.PropertyChanged -= this.OnItemPropertyChanged;
}
base.ClearItems();
this.OnCollectionCleared();
}
private void OnCollectionCleared()
{
EventHandler eventHandler = this.CollectionCleared;
if (eventHandler != null)
{
eventHandler(this, EventArgs.Empty);
}
}
private void OnItemPropertyChanged(object sender, PropertyChangedEventArgs args)
{
ItemPropertyChangedEventHandler eventHandler = this.ItemPropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new ItemPropertyChangedEventArgs(sender, args.PropertyName));
}
}
}
Then you can subscribe to the ItemPropertyChanged event and do your stuff.
I want to show the data in a listview, the data stored in a Queue and data type is user defined type(including two string and one int type).
How can i show the data inside after every process of enqueue and dequeue, what could be the command lines and syntax?
Queue<Customer> aCustomerQueue =new Queue<Customer>();// its the queue
class Customer// its the user defined type variable class
{
public string name;
public string complain;
public int serialNo;
}
Stored data will be user input.
You can try this code.
int count;
Queue<Customer> customers = new Queue<Customer>();
ListViewItem item;
private void enqueueButton_Click(object sender, EventArgs e)
{
Customer customer = new Customer();
count++;
customer.serialNo += count;
customer.name = nameTextBox.Text;
customer.complain = complainTextBox.Text;
customers.Enqueue(customer);
foreach (Customer custm in customers)
{
item = new ListViewItem(custm.serialNo.ToString());
item.SubItems.Add(custm.name);
item.SubItems.Add(custm.complain);
}
customerQueueListView.Items.Add(item);
}
for dequeue
private void dequeueButton_Click(object sender, EventArgs e)
{
if (customers.Count != 0)
{
customers.Dequeue();
customerQueueListView.Items[0].Remove();
}
}
Take a look at binding-to-queuestring-ui-never-updates.
The proposed implementation is a good way to go (code taken from the linked question):
public class ObservableQueue<T> : INotifyCollectionChanged, IEnumerable<T>
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
private readonly Queue<T> queue = new Queue<T>();
public void Enqueue(T item)
{
queue.Enqueue(item);
if (CollectionChanged != null)
CollectionChanged(this,
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Add, item));
}
public T Dequeue()
{
var item = queue.Dequeue();
if (CollectionChanged != null)
CollectionChanged(this,
new NotifyCollectionChangedEventArgs(
NotifyCollectionChangedAction.Remove, item));
return item;
}
public IEnumerator<T> GetEnumerator()
{
return queue.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
How can I do that? I need a list (of type ObservableCollection) where the latest item is first.
Try using
collection.Insert(0, item);
This would add item to the beginning of the collection (while Add adds to the end). More info here.
You should use a stack instead.
This is based on Observable Stack and Queue
Create an observable Stack, where stack is always last in first out (LIFO).
from Sascha Holl
public class ObservableStack<T> : Stack<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
public ObservableStack()
{
}
public ObservableStack(IEnumerable<T> collection)
{
foreach (var item in collection)
base.Push(item);
}
public ObservableStack(List<T> list)
{
foreach (var item in list)
base.Push(item);
}
public new virtual void Clear()
{
base.Clear();
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public new virtual T Pop()
{
var item = base.Pop();
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
return item;
}
public new virtual void Push(T item)
{
base.Push(item);
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
public virtual event NotifyCollectionChangedEventHandler CollectionChanged;
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
this.RaiseCollectionChanged(e);
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
this.RaisePropertyChanged(e);
}
protected virtual event PropertyChangedEventHandler PropertyChanged;
private void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (this.CollectionChanged != null)
this.CollectionChanged(this, e);
}
private void RaisePropertyChanged(PropertyChangedEventArgs e)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, e);
}
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add { this.PropertyChanged += value; }
remove { this.PropertyChanged -= value; }
}
}
This calls INotifyCollectionChanged, does the same as a ObservableCollection, but in a stack manner.
you can try this
collection.insert(0,collection.ElementAt(collection.Count - 1));
I have problem in ListView MultiSelection.
Here is my code :
private ICommand _excludeCurveCommand;
public ICommand ExcludeCurveCommand
{
get
{
if (_excludeCurveCommand == null)
_excludeCurveCommand = new DelegateCommand<object>(param => this.ExcludeCurveExecuted(param));
return _excludeCurveCommand;
}
}
/// <summary>
/// Executed event for ContextMenu --> Remove click
/// </summary>
/// <param name="param">param is FolderItemViewModel bound as CommandParameter</param>
private void ExcludeCurveExecuted(object param)
{
ICollection<object> curves = param as ICollection<object>;
int count = curves.Count;
}
< Button Content="Exclude" Command="{Binding ExcludeCurveCommand}" CommandParameter="{Binding SelectedItems,ElementName=lstView}" .../>
< ListView Name="lstView" ItemsSource="{Binding MyList}" SelectionMode="Extended" ..../>
Here i used shift and selected from first four items. But in ExcludeCurveExecuted method i getting some random counts always. What is the issue in this.
EDIT:
* I don't wantto go with IsSelected flag in ViewModel
To me it seems that you have have ListView UI virtualization enabled, as it enabled by default. That means, that ListView draws only items visible on UI.
Here is MSDN link
To be sure what is going on interface (like selection in your case) I'm afraid you don't have any other option then
or disable UI virtualization (not good)
or implement IsSelected flag on ModelView
Good luck.
You can use a Behavior to Sync the Selected Items to a Collection in Your View Model....
You a ve to add a reference to System.Windows.Interactivity to your project as well as an alias to it in your Xaml
Beahvior
public class MultiSelectionBehavior : Behavior<ListView>
{
protected override void OnAttached()
{
base.OnAttached();
if (SelectedItems != null)
{
AssociatedObject.SelectedItems.Clear();
foreach (var item in SelectedItems)
{
AssociatedObject.SelectedItems.Add(item);
}
}
}
public IList SelectedItems
{
get { return (IList)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IList), typeof(MultiSelectionBehavior), new UIPropertyMetadata(null, SelectedItemsChanged));
private static void SelectedItemsChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var behavior = o as MultiSelectionBehavior;
if (behavior == null)
return;
var oldValue = e.OldValue as INotifyCollectionChanged;
var newValue = e.NewValue as INotifyCollectionChanged;
if (oldValue != null)
{
oldValue.CollectionChanged -= behavior.SourceCollectionChanged;
behavior.AssociatedObject.SelectionChanged -= behavior.ListViewSelectionChanged;
}
if (newValue != null)
{
behavior.AssociatedObject.SelectedItems.Clear();
foreach (var item in (IEnumerable)newValue)
{
behavior.AssociatedObject.SelectedItems.Add(item);
}
behavior.AssociatedObject.SelectionChanged += behavior.ListViewSelectionChanged;
newValue.CollectionChanged += behavior.SourceCollectionChanged;
}
}
private bool _isUpdatingTarget;
private bool _isUpdatingSource;
void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_isUpdatingSource)
return;
try
{
_isUpdatingTarget = true;
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
AssociatedObject.SelectedItems.Remove(item);
}
}
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
AssociatedObject.SelectedItems.Add(item);
}
}
}
finally
{
_isUpdatingTarget = false;
}
}
private void ListViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_isUpdatingTarget)
return;
var selectedItems = this.SelectedItems;
if (selectedItems == null)
return;
try
{
_isUpdatingSource = true;
foreach (var item in e.RemovedItems)
{
selectedItems.Remove(item);
}
foreach (var item in e.AddedItems)
{
selectedItems.Add(item);
}
}
finally
{
_isUpdatingSource = false;
}
}
}
Xaml
<ListView ItemsSource="{Binding ItemsList}"
DisplayMemberPath="Name"
SelectionMode="Extended">
<i:Interaction.Behaviors>
<local:MultiSelectionBehavior SelectedItems="{Binding MySelectedItems}" />
</i:Interaction.Behaviors>
</ListView>
MySelectedItems is the collection in your ViewModel
Dont forget it initialize ... MySelectedItems in your ViewModel....
Your ViewModel
private void ExcludeCurveExecuted(object param)
{
int count = MySelectedItems.Count;
}