Custom IEnumerable as ItemsSource for ListBox - c#

I have a class that defines a custom GetEnumerator() function(by implementing IEnumerable<>). I use it to iterate in a contiguous manner over several ObservableCollection<LogEvent> that are in every TestStep. I have a private ObservableCollection<TestStep> that contains all the needed data.
I would like to use an instance of this class as the ItemsSource of a ListBox. However, the ListBox never gets updated when the underlying data(ObservableCollection<LogEvent>) is updated. Here's a sample of that class:
public class FlatLogViewModel : IEnumerable<LogEvent>
{
public FlatLogViewModel(ObservableCollection<TestStep> subSteps)
{
m_subSteps = subSteps;
}
public IEnumerator<LogEvent> GetEnumerator()
{
foreach (TestStep step in SubSteps)
{
// step.LogEvents is an ObservableCollection<LogEvent>
foreach (LogEvent logEvent in step.LogEvents)
yield return logEvent;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
private ObservableCollection<TestStep> m_subSteps;
}
I'm not sure if I should/can implement INotifyCollectionChanged here. How can I know if ObservableCollection has been modified?
My question is: how can I get the ListBox to display the changes happening in LogEvents(which is of type ObservableCollection<LogEvent>)?

When theObservableCollection changes, how does the ListBox know? You have to implement like you mentioned INotifyCollectionChanged and then update the ItemSource with the new enumerable data inside the event handler.
ObservableCollection is an INotifyCollectionChanged. Use casting
var collectionChanged = yourObCollection as INotifyCollectionChanged;
if( collectionChanged !=null)
{
collectionChanged.CollectionChanged += YourEventHandler;
}
inside the handler do your own logic to update the item source

Have you considered binding to the original collection but running it through a converter in order to pull out the LogEvents?
The converter should be able to simply return subSteps.SelectMany(s => s.LogEvents).

Related

Data Binding with a Sorted List

My UI has a ListBox which is bound to a Collection. Right now this happens to be an ObservableCollection
My objective is to add objects to this Collection via the UI, and have the ListBox dynamically update, all while maintaining a sorted Collection.
I am aware that there is some SortedView that I can use in WPF. But that is not what I want - I need the actual Collection to remain sorted because my business logic requires a sorted collection.
One way that I thought of, is to create my own Collection class which uses a SortedList internally, and implements the INotifyCollectionChanged interface and produces NotifyCollectionChangedEventArgs event when the internal list changes. Sounds like a lot of work!
Is there a simple solution that I've missed?
Depending on your exact needs, the simplest approach is to keep your ObservableCollection, but wrap in in a new property of type ICollectionView:
public class MyViewModel {
private CollectionViewSource _collectionViewSource;
public ICollectionView MyCollectionView => _collectionViewSource.View;
public MyViewModel(ObservableCollection<MyDataItem> dataItems) {
_collectionViewSource = new CollectionViewSource() { Items = dataItems };
//Add sorting here using _collectionViewSource.SortDescriptions.Add(...)
}
You can use the wrapper property to extract a sorted list as needed.
Okay so I ended up inheriting from ObservableCollection, and overriding the Add() method.
This did the trick for me. Now my list is always sorted, and the ObservableCollection is the one that Notifies the UI of changes.
public class MyCollection : ObservableCollection<Int32>
{
public new void Add(Int32 x)
{
base.Add(x);
var oldList = new ObservableCollection<Int32>(this.OrderBy(c=>c));
Clear();
foreach(var i in oldList)
{
base.Add(i);
}
}
}
I'm a beginner with C#, any feedback on the code is appreciated.

ObservableCollection<T> isn't getting notified of change in property of <T>

I have a an ObservableCollection of Component, a class with another ObservableCollection, a String and a ComponentVersion. The SelectedComponentVersion is being updated via my view correctly but I'm unable to get the property of Components to fire it's setter/OnPropertyChanged.
private ObservableCollection<Component> components
public ObservableCollection<Component> Components
{
get { return foo; }
set { foo = value; OnPropertyChanged(); }
}
Here is the class of Component.
public class Component : ViewModelBase
{
private string componentName;
private ObservableCollection<ComponentVersion> componentVersions;
private ComponentVersion selectedComponent;
public string ComponentName
{
get { return componentName; }
set { componentName = value; OnPropertyChanged(); }
}
public ObservableCollection<ComponentVersion> ComponentVersions
{
get { return componentVersions; }
set { componentVersions = value; OnPropertyChanged(); }
}
public ComponentVersion SelectedComponent
{
get { return selectedComponent; }
set { selectedComponent = value; OnPropertyChanged(); }
}
public Component(string componentName, List<ComponentVersion> componentVersion)
{
ComponentName = componentName;
ComponentVersions = componentVersion.ToObservableCollection();
}
}
I then have a binding from a listview onto the SelectedComponent property inside of Component.
I have read countless stack overflows about setting up CollectionChanged and have tried to implement it with no luck.
Components.CollectionChanged += stuff;
private void stuff(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
throw new System.NotImplementedException();
}
but this is never hit as tested with breakpoints.
Am I missing something entirely, losing my mind or daft! Someone please give me a point in the right direction, if any of this makes any sense at all.
P.S another solution I though of would be to place an invisible button inside the listview and have that send a command to tell the vm that a selected item has been updated.
In cases like this where I want to do something when the property on an item inside the collection changes, I usually hook up a PropertyChanged event in the CollectionChanged event
Here's a code example :
public MyViewModel()
{
// Setup Collection, with a CollectionChanged event
Components = new ObservableCollection<Component>();
Components.CollectionChanged += Components_CollectionChanged;
}
// In the CollectionChanged event (items getting added or removed from collection),
// hook up the PropertyChanged event
void Components_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach(MyType item in e.NewItems)
item.PropertyChanged += Component_PropertyChanged;
if (e.OldItems != null)
foreach(MyType item in e.OldItems)
item.PropertyChanged -= Component_PropertyChanged;
}
// In the PropertyChanged event, run some code if SelectedComponent property changed
void Component_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "SelectedComponent")
DoWork();
}
Note that we are using two separate events here.
CollectionChanged event runs when the collection itself changes. This means it gets set to a new collection, or item(s) get added or removed from the collection.
This captures newly added items and hooks up the PropertyChanged
handler, or detaches the PropertyChanged handler for items being
removed from the collection.
PropertyChanged event runs when a property changes and fires the event. You'll use this event to run code when the SelectedComponent property on any item in the collection changes
Well, components hasn't changed. I'm not sure if you set up your handler correctly, but even if you did, a CollectionChanged event is only fired, if the collection changed (item added or removed).
Lets say you have a collection of cars and all are red.
You pick one car and set it's color to blue.
The collection has not changed. It's still the very same cars. No car is missing, no car was added.
You probably want to attach yourself to the handlers of all cars instead of the handler of the collection.
So to sum it up:
ObservableCollection<T> isn't getting notified of change in property of <T>
That's true and it's by design.
When adding items to the collection you want to "hook up" the events you are interested in for those items, then you can act as needed. As mentioned above, the collection isn't changing it is the individual items that are changing.
One thing you could do is extend the ObservableCollection class and override the functionality so that when the collection is changed, an item is either added or removed, you then go through the items in the collection and "hook up" the events you're interested in. One thing to note is you may have to go through the collection and remove the event handlers and "hook up" them again in order to stop getting multiple event handlers being set for the items in the list.
ObservableCollection<T> does not send notifications about property changes of the elements. However, the System.ComponentModel namespace contains another collection, which supports that: you can consider to use BindingList<T> instead, which is also supported by WPF (elements must implement INotifyPropertyChanged).
Please note though that BindingList<T> scales poorly and its performance starts to decline above hundreds and thousands of elements as it always searches for the changed element sequentially in order to return the element index in its ListChanged event.

Are There Any CollectionViewSource Alternative with Generic?

For my WPF application, I need CollectionViewSource to enable selection, filtering, sorting, and grouping in a collection. But CollectionViewSource is not a type safe collection like IList, the property View.CurrentItem is an object for example. We need to cast the items if we use them.
Are there any CollectionViewSource alternatives that support Generic?
Or maybe anybody know the reason why CollectionViewSource is not a generic?
=============================
I made a generic CollectionViewSource based on standard CollectionViewSource.
Any comment whether it is a better alternative for collection class that is instantiated outside XAML? Or there is another better alternative?
Edit 1: Add Generic CollectionViewSource
namespace Data
{
using System.Collections.Generic;
using System.Linq;
using System.Windows.Data;
public class CollectionViewSource<T> : CollectionViewSource
{
public T CurrentItem => this.View != null ? (T)this.View.CurrentItem : default(T);
public new IEnumerable<T> Source
{
get
{
return (IEnumerable<T>)base.Source;
}
set
{
base.Source = value;
}
}
public IEnumerable<T> ViewItems => this.View != null ? Enumerable.Cast<T>(this.View) : (IEnumerable<T>)null;
public CollectionViewSource(IEnumerable<T> source)
{
this.Source = source;
}
public bool Contains(T item)
{
return this.View != null && this.View.Contains(item);
}
public IEnumerable<T> Groups()
{
return this.View.Groups.Cast<T>();
}
public void MoveCurrentTo(T item)
{
this.View?.MoveCurrentTo(item);
}
}
}
You can actually just bind to your ObservableCollection (or any collection) and then call CollectionViewSource.GetDefaultView for that collection instance, then apply a filter, and your DataGrid (or other items controls) will get filtered. This way you can have your cake and eat it too :-)
The reason for this, I suspect, is because WPF list controls never actually bind to normal .NET collections, but always call CollectionViewSource.GetDefaultView behind the scenes, and that seems to return the same instance as the one you already created, if you created one.
Codebehind:
MySourceCollection = new[]
{
new ViewModel(1, "first"),
new ViewModel(2, "second"),
new ViewModel(3, "third"),
new ViewModel(4, "fourth")
};
MyListView = CollectionViewSource.GetDefaultView(MySourceCollection);
MyListView.Filter = o => ((ViewModel)o).Number >= 3;
XAML:
<DataGrid ItemsSource="{Binding MySourceCollection}" />
Result:
I don't know whether this is recommended, but I don't see any problem yet. Just remember that if you reinitialize your source list, you have to call CollectionViewSource.GetDefaultView again and reapply your filters.
The reason why its not generic is that the type safety should be in your underlying collection not your view.
The CollectionViewSource is purely for formatting the display of the data, so like a combo and list controls are not typed neither is CollectionViewSource and for exactly the same reason, because they need to work with anything that is given to them
as an example you have a Students Collection, you want to display this in a combo but your also want to be able to select "NEW STUDENT" new student isn't a student so can't be added to the student collection but is a perfectly valid combo item so while the underlying collection has to be Type safe, enforcing the same on the combo is limiting and not protective, out side of your view your code really shouldn't care if values are sorted or not that's usually just a human thing
as for your generic CollectionViewSource, it depends how your are using it if its a good idea not however the type safety should be superflous because your underlying collection should already be doing this.
I would suggest having an ObservableCollection<T> as the source of your CollectionViewSource and then just forgetting about Type safing the display

DependencyProperty of type ObservableCollection doesn't bind

I am trying to make a custom control with a DependencyProperty.
But I cannot bind my ObservableCollection to the control.
When I use an Enumerable I have no problem. But I need to add items to the collection so my only option is ObservableCollection.
Creating Authorizations:
AuthorizationsDest = new ObservableCollection<Authorization>();
AuthorizationsDest.Add(new Authorization() { Key = "Test1", Description = "Test1", ObjectState = ObjectState.UnModified });
}
The custom control in xaml
<customControls:ListBoxEditLookup ItemsSource="{Binding Authorizations}" DisplayMember="Description" DestinationList="{Binding AuthorizationsDest}" />
The DependencyProperty:
[Description("Binded destination list"), Category("Data")]
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("DestinationList", typeof(ObservableCollection<HrdEntity>), typeof(ListBoxEditLookup), new UIPropertyMetadata(null));
public ObservableCollection<HrdEntity> DestinationList
{
get
{
return GetValue(ItemsProperty) as ObservableCollection<HrdEntity>;
}
set { SetValue(ItemsProperty, value); }
}
Based on the comment responses to your question, I think we have arrived at the realization that using a specific concrete collection type on your dependency property is causing issues, and you should consider using an interface such as IEnumerable instead. Read on for a more detailed explanation.
It is generally a good idea to use the IEnumerable interface as the type for collection dependency properties in a custom control. It is the base interface that every collection implements as it allows foreach loops to be run on them. When the dependency property is set, you can inspect the value to see if it implements other interfaces that you care about within your control.
For example, if your control wants to do things like add, remove and insert items and/or index into the collection, check to see if it implements IList. If you want to observe the collection for changes, check to see if it implements INotifyCollectionChanged.
Consider maintaining private references to the collection that are typed as the interfaces you need to access. For example:
private IList mItemsAsList;
private INotifyCollectionChanged mItemsAsObservable;
// Call when the value of ItemsProperty changes
private void OnItemsChanged(IEnumerable newValue)
{
if (mItemsAsObservable != null)
{
mItemsAsObservable.CollectionChanged -= Items_CollectionChanged;
}
mItemsAsList = newValue as IList;
mItemsAsObservable = newValue as INotifyCollectionChanged;
if (mItemsAsObservable != null)
{
mItemsAsObservable.CollectionChanged += Items_CollectionChanged;
}
}
private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Do stuff in response to collection being changed
}
If there are certain things that are required by your control (not optional), you can always throw an ArgumentException in the property changed callback if those requirements are not met. For example, if you must be able to add new items to the collection:
mItemsAsList = newValue as IList;
if (newValue != null && (mItemsAsList == null || mItemsAsList.IsReadOnly || mItemsAsList.IsFixedSize))
{
throw new ArgumentException("The supplied collection must implement IList, not be readonly, and have a variable size.", "newValue");
}
Once you have specialized references to the collection, you can limit your functionality based on which interfaces are implemented. For example, let's say you want to add a new item:
private void AddItem(object item)
{
// Make sure to check IsFixedSize because some collections, such as Array,
// implement IList but throw an exception if you try to call Add on them.
if (mItemsAsList != null && !mItemsAsList.IsReadOnly && !mItemsAsList.IsFixedSize)
{
mItemsAsList.Add(item);
}
}

Is INotifyPropertyChanged needed for binding ObservableCollection?

When I'm binding, say a Label to a string, I define the string this way:
private string _lbl;
public string Lbl
{
get
{
return = _lbl;
}
set
{
_lbl=value;
OnPropertyChanged("Lbl");
}
}
With the INotifyPropertyChanged interface implemented in my class.
Should I define the same way an ObservableCollection or I just could leave it this way?
public ObservableCollection<File> myFiles {get; set;}
As a general rule, I tend to define ObservableCollections like this:
private ObservableCollection<Item> _items;
public ObservableCollection<Item> Items
{
get { return _items ?? (_items = new ObservableCollection<Item>()); }
}
This is called "Lazy initialization", where the ObservableCollection is only instantiated where it is first accessed.
This is a good way to ensure your Collection Will Never Be Null
Notice that it does not have a setter, because an ObservableCollection is not something that you usually assign to. Instead, if you want to completely replace items, do:
Items.Clear();
//... Add all the new items
This avoids WPF having to rebind all the CollectionChanged events and stuff in order to listen to and react to items added / removed from the collection. You only have 1 instance of the collection, forever. Whatever items you place on it, the collection remains the same.
This is not to be confused with the PropertyChange notification inside the ITEMS of the collection. WPF handles these concepts separately, because property changes are notified by ITEMS, but Collection changes (Item added or removed) are notified by the Collection itself.
If the myFiles property can change, then yes, you should raise the PropertyChanged event. If not (that is, if it's got no setter, or it has a private setter that is only set once, e.g. in the constructor), then you don't need to raise the event. The collection itself will raise its own PropertyChanged and CollectionChanged events, but the object that contains the collection must raise PropertyChanged if the property that contains the collection changes.

Categories

Resources