How to fluidly reorder a collection in a listbox? - c#

The situation is like this: I have an Observable Collection that has a bunch of objects in it which I then display to a listbox using a binding. Then periodically, I get a message from an external server that gives me a new (or possibly the same) ordering for these objects. Right now, I just clear the observable collection, and add each item back in using the specified ordering.
This doesn't look too nice. Is there a better way to go about doing this? I'd be really awesome if I could somehow get the listbox to reorder and have a nice reordering animation with it, but that might be asking too much.
I thought about adding the ordering as an attribute to each object in the ObservableCollection and then calling a sort on it. Would this look clean? My assumption is that it would be almost the same effect as clearing it and readding everything.
Thanks for any help!

I created a class that inherits from ObservableCollection. This class has a SetItems method where you need to pass in the newly ordered items. Key in this class is that it will suppress the collectionchanged event and thus will not refresh the listbox each time an item is added. It looks better and loads a lot faster.
public class SuperObservableCollection<T> : ObservableCollection<T>
{
public void SetItems(IEnumerable<T> items)
{
suppressOnCollectionChanged = true;
Clear();
AddRange(items);
}
private bool suppressOnCollectionChanged;
public void AddRange(IEnumerable<T> items)
{
suppressOnCollectionChanged = true;
if (items != null)
{
foreach (var item in items)
Add(item);
}
suppressOnCollectionChanged = false;
NotifyCollectionChanged();
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!suppressOnCollectionChanged)
base.OnCollectionChanged(e);
}
public void NotifyCollectionChanged()
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}

Related

Get item from custom control / C# WPF

I have 2 classes:
class MultiSwitch : ListBox
{
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is MultiSwitchItem;
}
protected override DependencyObject GetContainerForItemOverride( )
{
return new MultiSwitchItem( );
}
}
class MultiSwitchItem : ListBoxItem {...}
Basically, I want to place items depending on items count. So I subscribed to collection changed event and i'm trying to recieve MultiSwitchItem from Items collection. I found out that Items collection doesn't contain MultiSwitchItems. Then I found solution that looks like that:
private void RearrangeItems(
object sender,
NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
foreach (var item in Items)
{
MultiSwitchItem item2 = ItemContainerGenerator.ContainerFromItem(item) as MultiSwitchItem;
...
}
}
But item2 is always null. What am I doing wrong?
EDIT
Ok, I have a little progress with that. This issue appears only when control is initialized (so I think, wrappers for items are created not instantly). So the question now: how to force creating of MultiSwitchItems, or how to iterate over items AFTER wrappers was created?
instead of this
ItemContainerGenerator.ContainerFromItem(item as TextBlock)
do this
ItemContainerGenerator.ContainerFromItem(item)
item wont be a TextBlock, it will be the underlying data item

How to notify the UI that a change has taken place in one or several of a List's members

I have the following problem:
I am adding an element to a favorites list (List<Item>) through a ContextMenu. Each Item has a derived property IsFavorite that changes depending on the favorites list - like so:
public bool IsFavorite
{
get { return ItemController.FavoriteList.Contains( this ); }
}
When I add something (or delete it) from the ContextMenu, the ContextMenu must be immediately updated.
Now, I know this is possible through using an ObservableCollection, but due to a few factors out of my control, I must make due with List objects. Now, is there any way I can get this to refresh?
public void DeleteFromFavorites(Item item)
{
Item itemInMainList = MainList.First(item);
itemInMainList.Refresh();
}
Item.cs:
public bool IsFavorite
{
get { return ItemController.FavoriteList.Contains( this ); }
}
public void Refresh()
{
NotifyPropertyChanged("IsFavorite");
}

DataBinding reverses the order of my ObservableCollection?

I have the following custom observable collection (The code is taken in parts from Dean Chalk's blog http://www.deanchalk.me.uk/post/Thread-Safe-Dispatcher-Safe-Observable-Collection-for-WPF.aspx and slightly altered):
public class ThreadSaveObservableCollection <T> : IList<T>, INotifyCollectionChanged {
private IList<T> collection;
private Dispatcher uiDispatcher;
private ReaderWriterLock rwLock;
public ThreadSaveObservableCollection () {
collection = new List<T>();
rwLock = new ReaderWriterLock();
uiDispatcher = Dispatcher.CurrentDispatcher;
}
public void Insert (int index, T item) {
if (Thread.CurrentThread == uiDispatcher.Thread) {
insert_(index, item);
} else {
uiDispatcher.BeginInvoke(new Action<int, T>(insert_), DispatcherPriority.Normal, new object[] {index, item});
}
}
private void insert_ (int index, T item) {
rwLock.AcquireWriterLock(Timeout.Infinite);
collection.Insert(index, item);
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
rwLock.ReleaseWriterLock();
}
public IEnumerator<T> GetEnumerator () {
rwLock.AcquireReaderLock(Timeout.Infinite);
IEnumerator<T> enumerator = collection.GetEnumerator();
rwLock.ReleaseReaderLock();
return enumerator;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator () {
rwLock.AcquireReaderLock(Timeout.Infinite);
IEnumerator<T> enumerator = collection.GetEnumerator();
rwLock.ReleaseReaderLock();
return enumerator;
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
... // the remaining methods of the IList<> interface
}
Further I have a ViewModel which holds an instance of this class:
public class ViewModel {
private ThreadSaveObservableCollection<string> Collection {get; set;}
public ViewModel () {
Collection = new ThreadSaveObservableCollection<string>();
}
public void Insert (string item) {
Collection.Insert(0, item);
}
}
I apply data binding in code-behind because I create the corresponding WPF control (an ordinary List control) with name "LogList" dynamically:
wpfContainer.LogList.ItemsSource = viewModel.Collection;
Everything works quite fine except the fact that the order of items in the wpf list control is reversed with respect to the items in the Collection object of the ViewModel.
With the statement Collection.Insert(0, intem) I expect to add the new item at the top of the list but what I get is the same result as I would use Collection.Add(item).
When I step into the code during runtime I can verify that the items inside my Collection are in the correct order but on the surface inside the wpf list control the order is altered i.e. reversed.
What am I making wrong ?
I guess the problem must be found somewhere around the data binding because it's the 'wire' that connects my ObservableCollection with the wpf control and it seems that a correct order is getting into the wire and an incorrect is leaving it.
Maybe it has something to do with the GetEnumerator() methods of the IList interface since the ItemSource property of the wpf control is awaiting an Enumerator ?
I have no clue and I am really stuck ...
Thank you in advance for any help ...
Can you try to do this:
http://msdn.microsoft.com/en-us/library/ms653208.aspx
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<object>() { item }, 0));
I think that this event is the problem.
Couple notes about your code:
Naming: ThreadSafe, not ThreadSave
Race conditions: you're acquiring a lock to call .GetEnumerator. Then releasing the lock, and returning that enumerator. That is not safe, and will throw an exception at runtime if the thread conditions are right. What you should do here is create a copy of the list while under lock, then return an enumerator to that copy.
ReaderWriterLock has some known performance, scalability, and error-prone usage (e.g. re-entrance) concerns. Use ReaderWriterLocksSlim instead.
The whole idea here is to marshal all operations to the UI thread. If everything happens on the UI thread, there's no need for any locking at all.
Finally, rather than re-invent the wheel, I suggest using one of the existing thread-safe ObservableCollections.

Cancel collection changed event on an observable collection

How can i cancel the collection changed event on an observable collection?
When my collection changes it invokes methods on a third party dll.These methods may or may not fail.
If they fail, i want dont want the item to be added to or removed from the collection. Looking at the name, it looks like the collection changed event is fired after something has been added or deleted, but how could i achieve my functionality?
Too late but it might help someone else :
class ExtendedObservableCollection<T> : ObservableCollection<T>
{
private bool _suppressNotification = false;
public bool AllowNotifications { get { return _suppressNotification; } }
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!_suppressNotification)
base.OnCollectionChanged(e);
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (!_suppressNotification)
base.OnPropertyChanged(e);
}
public void ActivateNotifications()
{
_suppressNotification = false;
}
public void DesactivateNotifications()
{
_suppressNotification = true;
}
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.Add));
}
}
I'll share the homely hack I came up with for my WPF/MVVM solution. In the methods I call for e.Action == NotifyCollectionChangedAction.Remove or e.Action == NotifyCollectionChangedAction.Add I check for failure and take any actions required to undo the change and set a boolean member _updateObservable to true. Since I can't modify the ObservableCollection during the change event, I have to defer it. Setting the boolean seemed like the easiest thing.
Then in the view model I have a property used for binding the selected item on the observable. I added to that property get method if (_updateObservable) UpdateObservable(); It appears the selected item bound property always fires its getter even if the item added or deleted does not directly affect the selected item. UpdateObservable() adds back or removes any items required from the collection and sets the flag to false.
You could simply override that particular method using the new command .
If you can manage to handle the event in your code, NotifyCollectionChangedEventArgs.NewItems returns an IList of the new items involved in the change. You could then remove these items from the collection if the methods in the third party dll failed.

WPF: Replacing databound collection contents without Clear/Add

When using WPF databinding, I obviously can't do something along the lines of MyCollection = new CollectionType<Whatever>( WhateverQuery() ); since the bindings have a reference to the old collection. My workaround so far has been MyCollection.Clear(); followed by a foreach doing MyCollection.Add(item); - which is pretty bad for both performance and aesthetics.
ICollectionView, although pretty neat, doesn't solve the problem either since it's SourceCollection property is read-only; bummer, since that would have been a nice and easy solution.
How are other people handling this problem? It should be mentioned that I'm doing MVVM and thus can't rummage through individual controls bindings. I suppose I could make a wrapper around ObservableCollection sporting a ReplaceSourceCollection() method, but before going that route I'd like to know if there's some other best practice.
EDIT:
For WinForms, I would bind controls against a BindingSource, allowing me to simply update it's DataSource property and call the ResetBindings() method - presto, underlying collection efficiently changed. I would have expected WPF databinding to support a similar scenario out of the box?
Example (pseudo-ish) code: WPF control (ListBox, DataGrid, whatever you fancy) is bound to the Users property. I realize that collections should be read-only to avoid the problems demonstrated by ReloadUsersBad(), but then the bad code for this example obviously wouldn't compile :)
public class UserEditorViewModel
{
public ObservableCollection<UserViewModel> Users { get; set; }
public IEnumerable<UserViewModel> LoadUsersFromWhateverSource() { /* ... */ }
public void ReloadUsersBad()
{
// bad: the collection is updated, but the WPF control is bound to the old reference.
Users = new ObservableCollection<User>( LoadUsersFromWhateverSource() );
}
public void ReloadUsersWorksButIsInefficient()
{
// works: collection object is kept, and items are replaced; inefficient, though.
Users.Clear();
foreach(var user in LoadUsersFromWhateverSource())
Users.Add(user);
}
// ...whatever other stuff.
}
If the object MyCollection is of implements INotifyPropertyChanged, you can simply replace the collection.
An example:
public class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Whatever> _myCollection;
private void NotifyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public ObservableCollection<Whatever> MyCollection
{
get
{
return _myCollection;
}
set
{
if (!ReferenceEquals(_myCollection, value))
{
_myCollection = value;
NotifyChanged("MyCollection");
}
}
}
}
With this, when you assign a collection, WPF detects this and everything gets updated.
This is how I'd solve this.
The link below explains how to implement an AddRange method.
http://web.archive.org/web/20150715112054/http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx
It looks like you're stuck with implementing a sub-class that handles this case correctly.
Apparently, certain controls don't support batched collection change notifications. At least they didn't when that article was written. Though now you should have a bit more information if you want to investigate further.

Categories