Is there something like UITableView? [closed] - c#

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I've found that exist List Box, but I can't find good example how to use it.
And than can I do something like when I scrolled to bottom my ListBox, I need to add new rows, also I need to do some logic when user has clicked on row.
Is it possible?

Yes it is possible.
To have a ListBox that loads items when scrolling reaches the bottom, you must implement something called LazyList.
I've done something like that myself.
You can use this LazyCollection as DataSource for the ListBox:
public class LazyCollection<T> : IList<T>, IList, INotifyCollectionChanged, INotifyPropertyChanged
{
private const int LOAD_THRESHOLD = 3;
private ObservableCollection<T> _innerCollection;
public LazyCollection(Func<int, IEnumerable<T>> fetch)
: this(fetch, null)
{ }
public LazyCollection(Func<int, IEnumerable<T>> fetch, IEnumerable<T> items)
{
_fetch = fetch;
_innerCollection = new ObservableCollection<T>(items ?? new T[0]);
this.AttachEvents();
this.HasMoreItems = true;
}
private void AttachEvents()
{
_innerCollection.CollectionChanged += (s, e) => OnCollectionChanged(e);
((INotifyPropertyChanged)_innerCollection).PropertyChanged += (s, e) => OnPropertyChanged(e);
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, e);
}
#endregion
#region INotifyCollectionChanged
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (this.CollectionChanged != null)
this.CollectionChanged(this, e);
}
#endregion
#region IList<T>
public int IndexOf(T item)
{
return _innerCollection.IndexOf(item);
}
public void Insert(int index, T item)
{
_innerCollection.Insert(index, item);
}
public void RemoveAt(int index)
{
_innerCollection.RemoveAt(index);
}
public T this[int index]
{
get
{
Debug.WriteLine("LazyCollection - Reading item {0}", index);
return _innerCollection[index];
}
set { _innerCollection[index] = value; }
}
public void Add(T item)
{
_innerCollection.Add(item);
}
public void Clear()
{
_innerCollection.Clear();
}
public bool Contains(T item)
{
return _innerCollection.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
_innerCollection.CopyTo(array, arrayIndex);
}
public int Count
{
get { return _innerCollection.Count; }
}
public bool IsReadOnly
{
get { return ((IList<T>)_innerCollection).IsReadOnly; }
}
public bool Remove(T item)
{
return _innerCollection.Remove(item);
}
public IEnumerator<T> GetEnumerator()
{
Debug.WriteLine("LazyCollection - GetEnumerator");
return _innerCollection.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
Debug.WriteLine("LazyCollection - GetEnumerator explicit");
return ((IEnumerable)_innerCollection).GetEnumerator();
}
#endregion
#region IList
int IList.Add(object value)
{
return ((IList)_innerCollection).Add(value);
}
bool IList.Contains(object value)
{
return ((IList)_innerCollection).Contains(value);
}
int IList.IndexOf(object value)
{
return ((IList)_innerCollection).IndexOf(value);
}
void IList.Insert(int index, object value)
{
((IList)_innerCollection).Insert(index, value);
}
bool IList.IsFixedSize
{
get { return ((IList)_innerCollection).IsFixedSize; }
}
bool IList.IsReadOnly
{
get { return ((IList)_innerCollection).IsReadOnly; }
}
void IList.Remove(object value)
{
((IList)_innerCollection).Remove(value);
}
object IList.this[int index]
{
get
{
if (index > this.Count - LOAD_THRESHOLD)
{
this.TryLoadMoreItems();
}
Debug.WriteLine("LazyCollection - Reading item {0} IList", index);
return ((IList)_innerCollection)[index];
}
set { ((IList)_innerCollection)[index] = value; }
}
void ICollection.CopyTo(Array array, int index)
{
((IList)_innerCollection).CopyTo(array, index);
}
bool ICollection.IsSynchronized
{
get { return ((IList)_innerCollection).IsSynchronized; }
}
object ICollection.SyncRoot
{
get { return ((IList)_innerCollection).SyncRoot; }
}
#endregion
public T[] GetLoadedItems()
{
return _innerCollection.ToArray();
}
public void ResetLoadedItems(IEnumerable<T> list)
{
_innerCollection.Clear();
foreach (var i in list)
_innerCollection.Add(i);
this.HasMoreItems = true;
}
public bool HasMoreItems { get; set; }
private bool _isLoading = false;
private Func<int, IEnumerable<T>> _fetch;
private async Task TryLoadMoreItems()
{
if (_isLoading || !this.HasMoreItems)
return;
try
{
_isLoading = true;
Debug.WriteLine("LazyCollection - Loading more items skip {0}", this.Count);
List<T> items = _fetch != null ? (_fetch(Count)).ToList() : new List<T>();
if (items.Count == 0)
{
Debug.WriteLine("LazyCollection - No items returned, Loading disabled", this.Count);
this.HasMoreItems = false;
}
items.ForEach(x => _innerCollection.Add(x));
Debug.WriteLine("LazyCollection - Items added. Total count: {0}", this.Count);
}
finally
{
_isLoading = false;
}
}
}
Here's how you use it
var functionThatProvidesData = (indexFromWhereToStart)=>
{
//Here you do the part that provides data to your List and Skip the items that
// are already provided and take as many as you decide to take.
return MyDataSource.Skip(indexFromWhereToStart).Take(30 or more);
}
LazyCollection<MyObjectType> myDataSource = new LazyCollection<MyObjectType>(functionThatProvidesData);
lboMyListBox.ItemsSource = myDataSource;
As About your event when clicking an Item there are multiple ways to do it
Tap event on each item in the ItemTemplate
SelectionChanged on the ListBox doesn't work when you click an item that is already selected
XAML code:
<ListBox Margin="50" Name="lboMyListBox" SelectionChanged="lboMyListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="15" Tap="Item_Tap">
<TextBlock Text="{Binding Quantity}" FontSize="40" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Price}" FontSize="25" VerticalAlignment="Bottom" HorizontalAlignment="Right"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note
You must have your event handler in the Code behind for the XAML to be compiled.

Related

Serialize ObservableCollection-like class ObservableList in Unity C#

Since there is no support for serializing ObservableCollection of C# in Unity as of yet, I am using a script which extends List<> and creates a Serializable class called ObservableList as mentioned in this unity forum answer ObservableList. Following is the same code:
[Serializable]
public class ObservedList<T> : List<T>
{
public event Action<int> Changed = delegate { };
public event Action Updated = delegate { };
public new void Add(T item)
{
base.Add(item);
Updated();
}
public new void Remove(T item)
{
base.Remove(item);
Updated();
}
public new void AddRange(IEnumerable<T> collection)
{
base.AddRange(collection);
Updated();
}
public new void RemoveRange(int index, int count)
{
base.RemoveRange(index, count);
Updated();
}
public new void Clear()
{
base.Clear();
Updated();
}
public new void Insert(int index, T item)
{
base.Insert(index, item);
Updated();
}
public new void InsertRange(int index, IEnumerable<T> collection)
{
base.InsertRange(index, collection);
Updated();
}
public new void RemoveAll(Predicate<T> match)
{
base.RemoveAll(match);
Updated();
}
public new T this[int index]
{
get
{
return base[index];
}
set
{
base[index] = value;
Changed(index);
}
}
}
Still it is not being serialized and I cannot see it in Unity editor. Any help would be greatly appreciated!
EDIT #1
Intended Use case:
public class Initialize : MonoBehaviour
{
public ObservedList<int> percentageSlider = new ObservedList<int>();
void Start()
{
percentageSlider.Changed += ValueUpdateHandler;
}
void Update()
{
}
private void ValueUpdateHandler(int index)
{
// Some index specific action
Debug.Log($"{index} was updated");
}
}
I am attaching this script as a component to a GameObject so that I can input the size of this list, play around with the values (just like I can do with List) and perform some action which only gets fired when some value inside the ObservableList is updated.
What I want to see
What I am seeing
So here is what I did
Instead of inheriting from the List<T> and trying to fix the Inspector somehow with editor scripting you could simply use a "backend" List<T> for which Unity already provides a serialization and let your class implement IList<T> like e.g.
[Serializable]
public class ObservedList<T> : IList<T>
{
public delegate void ChangedDelegate(int index, T oldValue, T newValue);
[SerializeField] private List<T> _list = new List<T>();
// NOTE: I changed the signature to provide a bit more information
// now it returns index, oldValue, newValue
public event ChangedDelegate Changed;
public event Action Updated;
public IEnumerator<T> GetEnumerator()
{
return _list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(T item)
{
_list.Add(item);
Updated?.Invoke();
}
public void Clear()
{
_list.Clear();
Updated?.Invoke();
}
public bool Contains(T item)
{
return _list.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
_list.CopyTo(array, arrayIndex);
}
public bool Remove(T item)
{
var output = _list.Remove(item);
Updated?.Invoke();
return output;
}
public int Count => _list.Count;
public bool IsReadOnly => false;
public int IndexOf(T item)
{
return _list.IndexOf(item);
}
public void Insert(int index, T item)
{
_list.Insert(index, item);
Updated?.Invoke();
}
public void RemoveAt(int index)
{
_list.RemoveAt(index);
Updated?.Invoke();
}
public void AddRange(IEnumerable<T> collection)
{
_list.AddRange( collection);
Updated?.Invoke();
}
public void RemoveAll(Predicate<T> predicate)
{
_list.RemoveAll(predicate);
Updated?.Invoke();
}
public void InsertRange(int index, IEnumerable<T> collection)
{
_list.InsertRange(index, collection);
Updated?.Invoke();
}
public void RemoveRange(int index, int count)
{
_list.RemoveRange(index, count);
Updated?.Invoke();
}
public T this[int index]
{
get { return _list[index]; }
set
{
var oldValue = _list[index];
_list[index] = value;
Changed?.Invoke(index, oldValue, value);
// I would also call the generic one here
Updated?.Invoke();
}
}
}
In the usage from code absolutely nothing changes (except as mentioned the one signature of the event):
public class Example : MonoBehaviour
{
public ObservedList<int> percentageSlider = new ObservedList<int>();
private void Start()
{
percentageSlider.Changed += ValueUpdateHandler;
// Just as an example
percentageSlider[3] = 42;
}
private void ValueUpdateHandler(int index, int oldValue, int newValue)
{
// Some index specific action
Debug.Log($"Element at index {index} was updated from {oldValue} to {newValue}");
}
}
But the Inspector now looks like this
Then if you really need to because you hate how it looks like in the editor ^^ You could use a bit of a dirty hack and overwrite it like
Have a non-generic base class because custom editors don't work with generics
public abstract class ObservedList { }
Then inherit from that
[Serializable]
public class ObservedList<T> : ObservedList, IList<T>
{
...
}
Then we can implement a custom drawer actually providing the base type but it will be applied to the inheritor
[CustomPropertyDrawer(typeof(ObservedList), true)]
public class ObservedListDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
// NOTE: Now here is the dirty hack
// even though the ObservedList itself doesn't have that property
// we can still look for the field called "_list" which only the
// ObservedList<T> has
var list = property.FindPropertyRelative("_list");
EditorGUI.PropertyField(position, list, label, true);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
var list = property.FindPropertyRelative("_list");
return EditorGUI.GetPropertyHeight(list, label, true);
}
}
Now it looks like this
Unity 2019 and older
In Unity 2019 and older the serialization of generics wasn't supported at all.
In Script Serialization 2020 this was added:
Generic field types can also be directly serialized, without the need to declare a non-generic subclass.
this is not the case in Script Serialization 2019 and older.
So here you would need to have explicit implementations like
[Serializable]
public class ObservedListInt : ObservedList<int>{ }
public class Example : MonoBehaviour
{
public ObservedListInt percentageSlider = new ObservedListInt();
private void Start()
{
percentageSlider.Changed += ValueUpdateHandler;
// Just as an example
percentageSlider[3] = 42;
}
private void ValueUpdateHandler(int index, int oldValue, int newValue)
{
// Some index specific action
Debug.Log($"Element at index {index} was updated from {oldValue} to {newValue}");
}
}
Without the custom drawer
With the custom drawer
If you want the actions to be serialized then you'll have to use UnityEvents (which hook into Unity's serialized event system).
Make sure you're using the [SerializedField] attribute before all the types you want serialized.
The main problem is likely stemming from inheritance, which does not play well with Unity's serialization system. In general, I would never inherit from List anyways (here are some reasons why) Instead I would make your observable type a wrapper for List with some added features (List would be a member within Observable).
Edit 1
Added a tested code example.
Instead of inheriting, an ObservedList<T> acts as wrapper for List<T>.
You'll be able to subscribe to the Changed and Updated events, but they're not serialized. If you want to access any other list functionality, you'll just have to add a public method in the ObservedList<t> class which acts as its wrapper. Let me know if you have any other issues.
[Serializable]
public class ObservedList<T>
{
public event Action<int> Changed;
public event Action Updated;
[SerializeField] private List<T> _value;
public T this[int index]
{
get
{
return _value[index];
}
set
{
//you might want to add a check here to only call changed
//if _value[index] != value
_value[index] = value;
Changed?.Invoke(index);
}
}
public void Add(T item)
{
_value.Add(item);
Updated?.Invoke();
}
public void Remove(T item)
{
_value.Remove(item);
Updated?.Invoke();
}
public void AddRange(IEnumerable<T> collection)
{
_value.AddRange(collection);
Updated?.Invoke();
}
public void RemoveRange(int index, int count)
{
_value.RemoveRange(index, count);
Updated?.Invoke();
}
public void Clear()
{
_value.Clear();
Updated?.Invoke();
}
public void Insert(int index, T item)
{
_value.Insert(index, item);
Updated?.Invoke();
}
public void InsertRange(int index, IEnumerable<T> collection)
{
_value.InsertRange(index, collection);
Updated?.Invoke();
}
public void RemoveAll(Predicate<T> match)
{
_value.RemoveAll(match);
Updated?.Invoke();
}
}
I slightly modified #derHugo answer. I wanted to implement INotifyCollectionChanged interface like ObservableCollection does.
This code is not tested yet, but I'll try to edit this answer ASAP if any bug would be found.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using UnityEngine;
public abstract class ObservableList
{
}
[Serializable]
public class ObservableList<T> : ObservableList, IList<T>, INotifyCollectionChanged
{
public event NotifyCollectionChangedEventHandler CollectionChanged;
[SerializeField] private List<T> _list = new List<T>();
public IEnumerator<T> GetEnumerator()
{
return _list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void Add(T item)
{
_list.Add(item);
OnCollectionChanged(NotifyCollectionChangedAction.Add, item, _list.Count - 1);
}
public void Clear()
{
_list.Clear();
OnCollectionReset();
}
public bool Contains(T item)
{
return _list.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
_list.CopyTo(array, arrayIndex);
}
public bool Remove(T item)
{
var itemIndex = _list.IndexOf(item);
var doExist = itemIndex != -1;
var didRemove = _list.Remove(item);
if (doExist && didRemove)
{
OnCollectionChanged(NotifyCollectionChangedAction.Remove, item, itemIndex);
}
return didRemove;
}
public int Count => _list.Count;
public bool IsReadOnly => false;
public int IndexOf(T item)
{
return _list.IndexOf(item);
}
public void Insert(int index, T item)
{
_list.Insert(index, item);
OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
}
public void RemoveAt(int index)
{
_list.RemoveAt(index);
var item = _list[index];
OnCollectionChanged(NotifyCollectionChangedAction.Remove, item, index);
}
public void AddRange(IEnumerable<T> collection)
{
// _list.AddRange(collection);
InsertRange(_list.Count, collection);
}
public void RemoveAll(Predicate<T> predicate)
{
// _list.RemoveAll(predicate);
int index = 0;
while (index < _list.Count)
{
if (predicate(_list[index]))
{
RemoveAt(index);
}
else
{
index++;
}
}
}
public void InsertRange(int index, IEnumerable<T> collection)
{
// _list.InsertRange(index, collection);
foreach (var item in collection)
{
Insert(index, item);
index++;
}
}
public void RemoveRange(int index, int count)
{
// _list.RemoveRange(index, count);
if (index < 0 || _list.Count < index + count - 1)
{
throw new ArgumentOutOfRangeException();
}
for (var i = 0; i < count; i++)
{
RemoveAt(index);
}
}
public T this[int index]
{
get => _list[index];
set
{
var oldValue = _list[index];
_list[index] = value;
OnCollectionChanged(NotifyCollectionChangedAction.Replace, oldValue, value, index);
}
}
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
CollectionChanged?.Invoke(this, e);
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index) =>
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index));
private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index, int oldIndex)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index, oldIndex));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index));
}
private void OnCollectionReset() =>
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
/// <summary>
/// Not-LINQ lambda foreach
/// </summary>
public void ForEach(Action<T> action)
{
foreach (var cur in _list)
{
action(cur);
}
}
}

Issue with ObservableStack<T> implementation and XAML Binding

Where have I gone wrong with my implementation of a ObservableStack<T>? The XAML is failing to bind to it some how and so the information contained in the stack is not showing in the UI. If I only change the type of the property in the ViewModel from my ObservableStack<T> to ObservableCollection<T> then the UI elements appear. This makes me think it's the implementation.
I want to use this so my elements appear in the UI in Stack order and not collection order.
Here is my implementation:
public class ObservableStack<T> : INotifyPropertyChanged, INotifyCollectionChanged, ICollection , IEnumerable<T>, IEnumerable , IReadOnlyCollection<T>
{
ObservableCollection<T> _coll = new ObservableCollection<T>();
public ObservableStack()
{
_coll.CollectionChanged += _coll_CollectionChanged;
}
public ObservableStack(IEnumerable<T> items) : base()
{
foreach (var item in items)
{
Push(item);
}
}
private void _coll_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnCollectionChanged(e);
}
public void Push(T item)
{
_coll.Insert(0, item);
}
public void Pop()
{
_coll.RemoveAt(0);
}
public T Peek
{
get { return _coll[0]; }
}
public int Count
{
get
{
return ((ICollection)_coll).Count;
}
}
public bool IsSynchronized
{
get
{
return ((ICollection)_coll).IsSynchronized;
}
}
public object SyncRoot
{
get
{
return ((ICollection)_coll).SyncRoot;
}
}
public void CopyTo(Array array, int index)
{
((ICollection)_coll).CopyTo(array, index);
}
public IEnumerator GetEnumerator()
{
return ((ICollection)_coll).GetEnumerator();
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return ((IEnumerable<T>)_coll).GetEnumerator();
}
#region INotifyPropertyChanged
protected void OnPropertyChanged([CallerMemberName] string caller = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(caller));
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region INotifyCollectionChanged
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
CollectionChanged?.Invoke(this, e);
}
#endregion
}
You forgot to attach the internal CollectionChanged handler in the second constructor.
So call the other constructor (instead of the base class constructor) like this:
public ObservableStack(IEnumerable<T> items)
: this() // instead of base()
{
foreach (var item in items)
{
Push(item);
}
}
The solution I've found is that I needed to implement IList on my ObservableStack. After that everything worked as expected. Curiously, just implementing IList<T> didn't work. It needed the non generic interface. Thanks again to those who offered help.

WPF DataGrid MultiSelect

I have read several posts on this topic but many are from a previous versions of VS or framework. What I am trying to do is selected multiple rows from a dataGrid and return those rows into a bound observable collection.
I have tried creating a property(of type) and adding it to an observable collection and it works with single records but the code never fires with multiple records.
Is there a clean way to do this in VS2013 using an MVVM patern?
Any thoughts would be appreciated.
<DataGrid x:Name="MainDataGrid" Height="390" Width="720"
VerticalAlignment="Center" CanUserAddRows="False" CanUserDeleteRows="False" AutoGenerateColumns="False"
ItemsSource="{Binding Path=DisplayInDataGrid}"
SelectedItem="{Binding Path=DataGridItemSelected}"
SelectionMode="Extended"
private ObservableCollection<ScannedItem> _dataGridItemsSelected;
public ObservableCollection<ScannedItem> DataGridItemsSelected
{
get { return _dataGridItemsSelected; }
set
{
_dataGridItemsSelected = value;
OnPropertyChanged("DataGridItemsSelected");
}
}
private ScannedItem _dataGridItemSelected;
public ScannedItem DataGridItemSelected
{
get { return _dataGridItemSelected;}
set
{
_dataGridItemSelected = value;
OnPropertyChanged("DataGridItemSelected");
EnableButtons();
LoadSelectedCollection(DataGridItemSelected);
}
}
void LoadSelectedCollection(ScannedItem si)
{
if (DataGridItemsSelected == null)
{
DataGridItemsSelected = new ObservableCollection<ScannedItem>();
}
DataGridItemsSelected.Add(si);
}
I have implemented a two-way data binding for MultiSelector.SelectedItems property using attached behavior pattern.
The following image shows how it works:
There are two DataGrids bound to the same model and they share selected items. Left DataGrid is active so selected items are blue and right DataGrid is inactive so selected items are gray.
Following is the sample code how to use it:
MainWindow.xaml
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding People}" local:MultiSelectorExtension.SelectedItems="{Binding SelectedPeople}" CanUserAddRows="True"/>
<DataGrid Grid.Column="1" ItemsSource="{Binding People}" local:MultiSelectorExtension.SelectedItems="{Binding SelectedPeople}" CanUserAddRows="True"/>
<StackPanel Grid.Row="1" Grid.ColumnSpan="2">
<Button DockPanel.Dock="Top" Content="Select All" Command="{Binding SelectAllCommand}"/>
<Button DockPanel.Dock="Top" Content="Unselect All" Command="{Binding UnselectAllCommand}"/>
<Button DockPanel.Dock="Top" Content="Select Next Range" Command="{Binding SelectNextRangeCommand}"/>
</StackPanel>
</Grid>
</Window>
Model.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows.Input;
namespace WpfApplication
{
abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, e);
}
protected void Set<T>(ref T field, T value, string propertyName)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
}
sealed class DelegateCommand : ICommand
{
private readonly Action action;
public DelegateCommand(Action action)
{
if (action == null)
throw new ArgumentNullException("action");
this.action = action;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute()
{
this.action();
}
bool ICommand.CanExecute(object parameter)
{
return true;
}
void ICommand.Execute(object parameter)
{
this.Execute();
}
}
class Person : ObservableObject
{
private string name, surname;
public Person()
{
}
public Person(string name, string surname)
{
this.name = name;
this.surname = surname;
}
public string Name
{
get { return this.name; }
set { this.Set(ref this.name, value, "Name"); }
}
public string Surname
{
get { return this.surname; }
set { this.Set(ref this.surname, value, "Surname"); }
}
public override string ToString()
{
return this.name + ' ' + this.surname;
}
}
class MainWindowModel : ObservableObject
{
public ObservableCollection<Person> People { get; private set; }
public SelectedItemCollection<Person> SelectedPeople { get; private set; }
public DelegateCommand SelectAllCommand { get; private set; }
public DelegateCommand UnselectAllCommand { get; private set; }
public DelegateCommand SelectNextRangeCommand { get; private set; }
public MainWindowModel()
{
this.People = new ObservableCollection<Person>(Enumerable.Range(1, 1000).Select(i => new Person("Name " + i, "Surname " + i)));
this.SelectedPeople = new SelectedItemCollection<Person>();
for (int i = 0; i < this.People.Count; i += 2)
this.SelectedPeople.Add(this.People[i]);
this.SelectAllCommand = new DelegateCommand(() => this.SelectedPeople.Reset(this.People));
this.UnselectAllCommand = new DelegateCommand(() => this.SelectedPeople.Clear());
this.SelectNextRangeCommand = new DelegateCommand(() =>
{
var index = this.SelectedPeople.Count > 0 ? this.People.IndexOf(this.SelectedPeople[this.SelectedPeople.Count - 1]) + 1 : 0;
int count = 10;
this.SelectedPeople.Reset(Enumerable.Range(index, count).Where(i => i < this.People.Count).Select(i => this.People[i]));
});
this.SelectedPeople.CollectionChanged += this.OnSelectedPeopleCollectionChanged;
}
private void OnSelectedPeopleCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Debug.WriteLine("Action = {0}, NewItems.Count = {1}, NewStartingIndex = {2}, OldItems.Count = {3}, OldStartingIndex = {4}, Total.Count = {5}", e.Action, e.NewItems != null ? e.NewItems.Count : 0, e.NewStartingIndex, e.OldItems != null ? e.OldItems.Count : 0, e.OldStartingIndex, this.SelectedPeople.Count);
}
}
class SelectedItemCollection<T> : ObservableCollection<T>
{
public void Reset(IEnumerable<T> items)
{
int oldCount = this.Count;
this.Items.Clear();
foreach (var item in items)
this.Items.Add(item);
if (!(oldCount == 0 && this.Count == 0))
{
this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
if (this.Count != oldCount)
this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
}
}
}
}
Following is the implementation, which unfortunately is not documented. Implementation uses various tricks (via reflection) to reduce the number of changes to underlying collections as much as possible (to suspend excessive collection changed notifications).
It is worth noting that if selected items collection in model has Select(IEnumerable) or Select(IEnumerable) method, that method will be used for performing bulk updates (updates which affect more than one item) which offers better performance if, for example, all items in the DataGrid are selected or un-selected.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Markup;
namespace WpfApplication
{
static class MultiSelectorExtension
{
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached("SelectedItems", typeof(IList), typeof(MultiSelectorExtension), new PropertyMetadata(new PropertyChangedCallback(OnSelectedItemsChanged)));
private static readonly DependencyProperty SelectedItemsBinderProperty = DependencyProperty.RegisterAttached("SelectedItemsBinder", typeof(SelectedItemsBinder), typeof(MultiSelectorExtension));
[AttachedPropertyBrowsableForType(typeof(MultiSelector))]
[DependsOn("ItemsSource")]
public static IList GetSelectedItems(this MultiSelector multiSelector)
{
if (multiSelector == null)
throw new ArgumentNullException("multiSelector");
return (IList)multiSelector.GetValue(SelectedItemsProperty);
}
public static void SetSelectedItems(this MultiSelector multiSelector, IList selectedItems)
{
if (multiSelector == null)
throw new ArgumentNullException("multiSelector");
multiSelector.SetValue(SelectedItemsProperty, selectedItems);
}
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var multiSelector = d as MultiSelector;
if (multiSelector == null)
return;
var binder = (SelectedItemsBinder)multiSelector.GetValue(SelectedItemsBinderProperty);
var selectedItems = e.NewValue as IList;
if (selectedItems != null)
{
if (binder == null)
binder = new SelectedItemsBinder(multiSelector);
binder.SelectedItems = selectedItems;
}
else if (binder != null)
binder.Dispose();
}
private sealed class SelectedItemsBinder : IDisposable
{
private static readonly IList emptyList = new object[0];
private static readonly Action<MultiSelector> multiSelectorBeginUpdateSelectedItems, multiSelectorEndUpdateSelectedItems;
private readonly MultiSelector multiSelector;
private IList selectedItems;
private IResetter selectedItemsResetter;
private bool suspendMultiSelectorUpdate, suspendSelectedItemsUpdate;
static SelectedItemsBinder()
{
GetMultiSelectorBeginEndUpdateSelectedItems(out multiSelectorBeginUpdateSelectedItems, out multiSelectorEndUpdateSelectedItems);
}
public SelectedItemsBinder(MultiSelector multiSelector)
{
this.multiSelector = multiSelector;
this.multiSelector.SelectionChanged += this.OnMultiSelectorSelectionChanged;
this.multiSelector.Unloaded += this.OnMultiSelectorUnloaded;
this.multiSelector.SetValue(SelectedItemsBinderProperty, this);
}
public IList SelectedItems
{
get { return this.selectedItems; }
set
{
this.SetSelectedItemsChangedHandler(false);
this.selectedItems = value;
this.selectedItemsResetter = GetResetter(this.selectedItems.GetType());
this.SetSelectedItemsChangedHandler(true);
if (this.multiSelector.IsLoaded)
this.OnSelectedItemsCollectionChanged(this.selectedItems, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
else
{
RoutedEventHandler multiSelectorLoadedHandler = null;
this.multiSelector.Loaded += multiSelectorLoadedHandler = new RoutedEventHandler((sender, e) =>
{
this.OnSelectedItemsCollectionChanged(this.selectedItems, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
this.multiSelector.Loaded -= multiSelectorLoadedHandler;
});
}
}
}
private int ItemsSourceCount
{
get
{
var collection = this.multiSelector.ItemsSource as ICollection;
return collection != null ? collection.Count : -1;
}
}
public void Dispose()
{
this.multiSelector.ClearValue(SelectedItemsBinderProperty);
this.multiSelector.Unloaded -= this.OnMultiSelectorUnloaded;
this.multiSelector.SelectionChanged -= this.OnMultiSelectorSelectionChanged;
this.SetSelectedItemsChangedHandler(false);
}
private void OnSelectedItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (this.suspendMultiSelectorUpdate || e.Action == NotifyCollectionChangedAction.Move)
return;
this.suspendSelectedItemsUpdate = true;
if (this.selectedItems.Count == 0)
this.multiSelector.UnselectAll();
else if (this.selectedItems.Count == this.ItemsSourceCount)
this.multiSelector.SelectAll();
else if (e.Action != NotifyCollectionChangedAction.Reset && (e.NewItems == null || e.NewItems.Count <= 1) && (e.OldItems == null || e.OldItems.Count <= 1))
UpdateList(this.multiSelector.SelectedItems, e.NewItems ?? emptyList, e.OldItems ?? emptyList);
else
{
if (multiSelectorBeginUpdateSelectedItems != null)
{
multiSelectorBeginUpdateSelectedItems(this.multiSelector);
this.multiSelector.SelectedItems.Clear();
UpdateList(this.multiSelector.SelectedItems, this.selectedItems, emptyList);
multiSelectorEndUpdateSelectedItems(this.multiSelector);
}
else
{
this.multiSelector.UnselectAll();
UpdateList(this.multiSelector.SelectedItems, this.selectedItems, emptyList);
}
}
this.suspendSelectedItemsUpdate = false;
}
private void OnMultiSelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (this.suspendSelectedItemsUpdate)
return;
this.suspendMultiSelectorUpdate = true;
if (e.AddedItems.Count <= 1 && e.RemovedItems.Count <= 1)
UpdateList(this.selectedItems, e.AddedItems, e.RemovedItems);
else
{
if (this.selectedItemsResetter != null)
this.selectedItemsResetter.Reset(this.selectedItems, this.multiSelector.SelectedItems.Cast<object>().Where(item => item != CollectionView.NewItemPlaceholder));
else
UpdateList(this.selectedItems, e.AddedItems, e.RemovedItems);
}
this.suspendMultiSelectorUpdate = false;
}
private void OnMultiSelectorUnloaded(object sender, RoutedEventArgs e)
{
this.Dispose();
}
private void SetSelectedItemsChangedHandler(bool add)
{
var notifyCollectionChanged = this.selectedItems as INotifyCollectionChanged;
if (notifyCollectionChanged != null)
{
if (add)
notifyCollectionChanged.CollectionChanged += this.OnSelectedItemsCollectionChanged;
else
notifyCollectionChanged.CollectionChanged -= this.OnSelectedItemsCollectionChanged;
}
}
private static void UpdateList(IList list, IList newItems, IList oldItems)
{
int addedCount = 0;
for (int i = 0; i < oldItems.Count; ++i)
{
var index = list.IndexOf(oldItems[i]);
if (index >= 0)
{
object newItem;
if (i < newItems.Count && (newItem = newItems[i]) != CollectionView.NewItemPlaceholder)
{
list[index] = newItem;
++addedCount;
}
else
list.RemoveAt(index);
}
}
for (int i = addedCount; i < newItems.Count; ++i)
{
var newItem = newItems[i];
if (newItem != CollectionView.NewItemPlaceholder)
list.Add(newItem);
}
}
private static void GetMultiSelectorBeginEndUpdateSelectedItems(out Action<MultiSelector> beginUpdateSelectedItems, out Action<MultiSelector> endUpdateSelectedItems)
{
try
{
beginUpdateSelectedItems = (Action<MultiSelector>)Delegate.CreateDelegate(typeof(Action<MultiSelector>), typeof(MultiSelector).GetMethod("BeginUpdateSelectedItems", BindingFlags.NonPublic | BindingFlags.Instance));
endUpdateSelectedItems = (Action<MultiSelector>)Delegate.CreateDelegate(typeof(Action<MultiSelector>), typeof(MultiSelector).GetMethod("EndUpdateSelectedItems", BindingFlags.NonPublic | BindingFlags.Instance));
}
catch
{
beginUpdateSelectedItems = endUpdateSelectedItems = null;
}
}
private static IResetter GetResetter(Type listType)
{
try
{
MethodInfo genericReset = null, nonGenericReset = null;
Type genericResetItemType = null;
foreach (var method in listType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
if (method.Name != "Reset")
continue;
if (method.ReturnType != typeof(void))
continue;
var parameters = method.GetParameters();
if (parameters.Length != 1)
continue;
var parameterType = parameters[0].ParameterType;
if (parameterType.IsGenericType && parameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
genericResetItemType = parameterType.GetGenericArguments()[0];
genericReset = method;
break;
}
else if (parameterType == typeof(IEnumerable))
nonGenericReset = method;
}
if (genericReset != null)
return (IResetter)Activator.CreateInstance(typeof(GenericResetter<,>).MakeGenericType(genericReset.DeclaringType, genericResetItemType), genericReset);
else if (nonGenericReset != null)
return (IResetter)Activator.CreateInstance(typeof(NonGenericResetter<>).MakeGenericType(nonGenericReset.DeclaringType), nonGenericReset);
else
return null;
}
catch
{
return null;
}
}
private interface IResetter
{
void Reset(IList list, IEnumerable items);
}
private sealed class NonGenericResetter<TTarget> : IResetter
{
private readonly Action<TTarget, IEnumerable> reset;
public NonGenericResetter(MethodInfo method)
{
this.reset = (Action<TTarget, IEnumerable>)Delegate.CreateDelegate(typeof(Action<TTarget, IEnumerable>), method);
}
public void Reset(IList list, IEnumerable items)
{
this.reset((TTarget)list, items);
}
}
private sealed class GenericResetter<TTarget, T> : IResetter
{
private readonly Action<TTarget, IEnumerable<T>> reset;
public GenericResetter(MethodInfo method)
{
this.reset = (Action<TTarget, IEnumerable<T>>)Delegate.CreateDelegate(typeof(Action<TTarget, IEnumerable<T>>), method);
}
public void Reset(IList list, IEnumerable items)
{
this.reset((TTarget)list, items.Cast<T>());
}
}
}
}
}
Create a command that fires on the DataGrid's SelectionChanged event, passing in the DataGrid's SelectedItems.
In your ViewModel, have a List of selected objects.
Your SelectionChangedCommand execution method would then update that collection of selected objects.
For example:
In my XAML:
<DataGrid ItemsSource="{Binding Datasets, NotifyOnTargetUpdated=True}" Name="dsDatagrid" SelectionMode="Extended" MouseDoubleClick="ViewDataset">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding SelectionChangedCommand}" CommandParameter="{Binding ElementName=dsDatagrid, Path=SelectedItems}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
In my ViewModel:
private List<ObservableDataset> selectedDatasets;
private void SelectionChangedExecuted(object datasets)
{
this.selectedDatasets = new List<ObservableDataset>((datasets as IList).Cast<ObservableDataset>());
}
EDIT: I'm using MVVMLight.

ISet<T> that notifies on add and remove?

I'd like an ISet<T> with two additional events ItemAdded and ItemRemoved.
One option I considered was deriving MyHashSet<T> from HashSet<T> but since Add and Remove are not virtual, it would require the use of new. Maybe this is a valid use of the keyword?
Another option I thought would be to implement ISet<T> and delegate everything to a private instance of HashSet<T>. This feels like a bulky solution.
Is there a pattern or framework class that gets me the same result but doesn't require less than elegant/ideal coding?
Based on the comments I received (thanks) here's what I've got:
public class NotifyingHashSet<T>
{
private HashSet<T> hashSet = new HashSet<T>();
public bool Add(T item)
{
bool added = hashSet.Add(item);
if(added && ItemAdded != null)
{
ItemAdded(this, new NotifyingHashSetEvent<T>(item));
}
return added;
}
public bool Remove(T item)
{
bool removed = hashSet.Remove(item);
if(removed && ItemRemoved != null)
{
ItemRemoved(this, new NotifyingHashSetEvent<T>(item));
}
return removed;
}
public event EventHandler<NotifyingHashSetEvent<T>> ItemAdded;
public event EventHandler<NotifyingHashSetEvent<T>> ItemRemoved;
}
public class NotifyingHashSetEvent<T> : EventArgs
{
public NotifyingHashSetEvent(T item)
{
Item = item;
}
public T Item { get; set; }
}
I would recommend inheriting rather than composing in this case.
This will ensure that you get all that HashSet offers like:
Other collection methods such as Contains and other Set operations such as IsSubsetOf
Collection initializers
You could assign it to base type, HashSet<int> foo = new NotifyingHashSet<int>()
My implementation looks like this:
public class NotifyingHashSet<T> : HashSet<T>
{
public new void Add(T item)
{
OnItemAdded(new NotifyHashSetChanged<T>(item));
base.Add(item);
}
public new void Remove(T item)
{
OnItemRemoved(new NotifyHashSetChanged<T>(item));
base.Remove(item);
}
public event EventHandler<NotifyHashSetChanged<T>> ItemAdded;
public event EventHandler<NotifyHashSetChanged<T>> ItemRemoved;
protected virtual void OnItemRemoved(NotifyHashSetChanged<T> e)
{
if (ItemRemoved != null) ItemRemoved(this, e);
}
protected virtual void OnItemAdded(NotifyHashSetChanged<T> e)
{
if (ItemAdded != null) ItemAdded(this, e);
}
}
public class NotifyHashSetChanged<T> : EventArgs
{
private readonly T _item;
public NotifyHashSetChanged(T item)
{
_item = item;
}
public T ChangedItem
{
get { return _item; }
}
}
Some tests to check stuff:
[TestClass]
public class NotifyingHashSetTests
{
[TestMethod]
public void ShouldAddToNotifyingHashSet()
{
var notifyingHashSet = new NotifyingHashSet<int>();
notifyingHashSet.ItemAdded += (sender, changed) => Assert.AreEqual(changed.ChangedItem, 1);
notifyingHashSet.Add(1);
}
[TestMethod]
public void ShouldRemoveFromNotifyingHashSet()
{
//can use collection initializer
var notifyingHashSet = new NotifyingHashSet<int> { 1 };
notifyingHashSet.ItemRemoved += (sender, changed) => Assert.AreEqual(changed.ChangedItem, 1);
notifyingHashSet.Remove(1);
}
[TestMethod]
public void ShouldContainValueInNotifyingHashSet()
{
//can use collection initializer
var notifyingHashSet = new NotifyingHashSet<int> { 1 };
Assert.IsTrue(notifyingHashSet.Contains(1));
}
[TestMethod]
public void ShouldAssignToHashSet()
{
HashSet<int> notifyingHashSet = new NotifyingHashSet<int> { 1 };
Assert.IsTrue(notifyingHashSet.IsSubsetOf(new List<int>{ 1,2 }));
}
}
Your own answer demonstrates how you can wrap a HashSet<T> and Srikanth's answer demonstrates how you can derive from HashSet<T>. However, when you derive from HashSet<T> you have to make sure the new class also correctly implements the Add and Remove methods of the ICollection<T> interface. So I have modified Srikanth's answer to properly create an ISet<T> implementation with notifications that derives from HashSet<T> by using explicit interface implementation of the relevant methods of ICollection<T>:
public class NotifyingHashSet<T> : HashSet<T>, ICollection<T> {
new public void Add(T item) {
((ICollection<T>) this).Add(item);
}
new public Boolean Remove(T item) {
return ((ICollection<T>) this).Remove(item);
}
void ICollection<T>.Add(T item) {
var added = base.Add(item);
if (added)
OnItemAdded(new NotifyHashSetEventArgs<T>(item));
}
Boolean ICollection<T>.Remove(T item) {
var removed = base.Remove(item);
if (removed)
OnItemRemoved(new NotifyHashSetEventArgs<T>(item));
return removed;
}
public event EventHandler<NotifyHashSetEventArgs<T>> ItemAdded;
public event EventHandler<NotifyHashSetEventArgs<T>> ItemRemoved;
protected virtual void OnItemRemoved(NotifyHashSetEventArgs<T> e) {
var handler = ItemRemoved;
if (handler != null)
ItemRemoved(this, e);
}
protected virtual void OnItemAdded(NotifyHashSetEventArgs<T> e) {
var handler = ItemAdded;
if (handler != null)
ItemAdded(this, e);
}
}
public class NotifyHashSetEventArgs<T> : EventArgs {
public NotifyHashSetEventArgs(T item) {
Item = item;
}
public T Item { get; private set; }
}
This class also behaves the same way as your class by only firing events when an element actually is added or removed from the set. E.g., adding the same element twice in succession will only fire one event.

How to handle add to list event?

I have a list like this:
List<Controls> list = new List<Controls>
How to handle adding new position to this list?
When I do:
myObject.myList.Add(new Control());
I would like to do something like this in my object:
myList.AddingEvent += HandleAddingEvent
And then in my HandleAddingEvent delegate handling adding position to this list. How should I handle adding new position event? How can I make this event available?
I believe What you're looking for is already part of the API in the ObservableCollection(T) class. Example:
ObservableCollection<int> myList = new ObservableCollection<int>();
myList.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(
delegate(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
MessageBox.Show("Added value");
}
}
);
myList.Add(1);
You could inherit from List and add your own handler, something like
using System;
using System.Collections.Generic;
namespace test
{
class Program
{
class MyList<T> : List<T>
{
public event EventHandler OnAdd;
public new void Add(T item) // "new" to avoid compiler-warnings, because we're hiding a method from base-class
{
if (null != OnAdd)
{
OnAdd(this, null);
}
base.Add(item);
}
}
static void Main(string[] args)
{
MyList<int> l = new MyList<int>();
l.OnAdd += new EventHandler(l_OnAdd);
l.Add(1);
}
static void l_OnAdd(object sender, EventArgs e)
{
Console.WriteLine("Element added...");
}
}
}
Warning
Be aware that you have to re-implement all methods which add objects to your list. AddRange() will not fire this event, in this implementation.
We did not overload the method. We hid the original one. If you Add() an object while this class is boxed in List<T>, the event will not be fired!
MyList<int> l = new MyList<int>();
l.OnAdd += new EventHandler(l_OnAdd);
l.Add(1); // Will work
List<int> baseList = l;
baseList.Add(2); // Will NOT work!!!
What you need is a class that has events for any type of modification that occurs in the collection. The best class for this is BindingList<T>. It has events for every type of mutation which you can then use to modify your event list.
http://msdn.microsoft.com/en-us/library/ms132679.aspx
You can't do this with List<T>. However, you can do it with ObservableCollection<T>. See ObservableCollection<T> Class.
To be clear: If you only need to observe the standard-functionalities you should use ObservableCollection(T) or other existing classes. Never rebuild something you already got.
..But.. If you need special events and have to go deeper, you should not derive from List! If you derive from List you can not overloead Add() in order to see every add.
Example:
public class MyList<T> : List<T>
{
public void Add(T item) // Will show us compiler-warning, because we hide the base-mothod which still is accessible!
{
throw new Exception();
}
}
public static void Main(string[] args)
{
MyList<int> myList = new MyList<int>(); // Create a List which throws exception when calling "Add()"
List<int> list = myList; // implicit Cast to Base-class, but still the same object
list.Add(1); // Will NOT throw the Exception!
myList.Add(1); // Will throw the Exception!
}
It's not allowed to override Add(), because you could mees up the functionalities of the base class (Liskov substitution principle).
But as always we need to make it work. But if you want to build your own list, you should to it by implementing the an interface: IList<T>.
Example which implements a before- and after-add event:
public class MyList<T> : IList<T>
{
private List<T> _list = new List<T>();
public event EventHandler BeforeAdd;
public event EventHandler AfterAdd;
public void Add(T item)
{
// Here we can do what ever we want, buffering multiple events etc..
BeforeAdd?.Invoke(this, null);
_list.Add(item);
AfterAdd?.Invoke(this, null);
}
#region Forwarding to List<T>
public T this[int index] { get => _list[index]; set => _list[index] = value; }
public int Count => _list.Count;
public bool IsReadOnly => false;
public void Clear() => _list.Clear();
public bool Contains(T item) => _list.Contains(item);
public void CopyTo(T[] array, int arrayIndex) => _list.CopyTo(array, arrayIndex);
public IEnumerator<T> GetEnumerator() => _list.GetEnumerator();
public int IndexOf(T item) => _list.IndexOf(item);
public void Insert(int index, T item) => _list.Insert(index, item);
public bool Remove(T item) => _list.Remove(item);
public void RemoveAt(int index) => _list.RemoveAt(index);
IEnumerator IEnumerable.GetEnumerator() => _list.GetEnumerator();
#endregion
}
Now we've got all methods we want and didn't have to implement much. The main change in our code is, that our variables will be IList<T> instead of List<T>, ObservableCollection<T> or what ever.
And now the big wow: All of those implement IList<T>:
IList<int> list1 = new ObservableCollection<int>();
IList<int> list2 = new List<int>();
IList<int> list3 = new int[10];
IList<int> list4 = new MyList<int>();
Which brings us to the next point: Use Interfaces instead of classes. Your code should never depend on implementation-details!
One simple solution is to introduce an Add method for the list in your project and handle the event there. It doesn't answer the need for an event handler but can be useful for some small projects.
AddToList(item) // or
AddTo(list,item)
////////////////////////
void AddTo(list,item)
{
list.Add(item);
// event handling
}
instead of
list.Add(item);
You cannot do this with standard collections out of the box - they just don't support change notifications. You could build your own class by inheriting or aggregating a existing collection type or you could use BindingList<T> that implements IBindingList and supports change notifications via the ListChanged event.
To piggy-back off Ahmad's use of Extension Methods, you can create your own class where the list is private with a public get method and a public add method.
public class MyList
{
private List<SomeClass> PrivateSomeClassList;
public List<SomeClass> SomeClassList
{
get
{
return PrivateSomeClassList;
}
}
public void Add(SomeClass obj)
{
// do whatever you want
PrivateSomeClassList.Add(obj);
}
}
However, this class only provides access to List<> methods that you manually expose...hence may not be useful in cases where you need a lot of that functionality.
No need for adding an event just add the method.
public class mylist:List<string>
{
public void Add(string p)
{
// Do cool stuff here
base.Add(p);
}
}
//List overrider class
public class ListWithEvents<T> : List<T>
{
public delegate void AfterChangeHandler();
public AfterChangeHandler OnChangeEvent;
public Boolean HasAddedItems = false;
public Boolean HasRemovedItems = false;
public bool HasChanges
{
get => HasAddedItems || HasRemovedItems;
set
{
HasAddedItems = value;
HasRemovedItems = value;
}
}
public new void Add(T item)
{
base.Add(item);
HasAddedItems = true;
if(OnChangeEvent != null) OnChangeEvent();
OnChange();
}
public new void AddRange(IEnumerable<T> collection)
{
base.AddRange(collection);
HasAddedItems = true;
if (OnChangeEvent != null) OnChangeEvent();
OnChange();
}
public new void Insert(int index,T item)
{
base.Insert(index, item);
HasAddedItems = true;
if (OnChangeEvent != null) OnChangeEvent();
OnChange();
}
public new void InsertRange(int index, IEnumerable<T> collection)
{
base.InsertRange(index, collection);
HasAddedItems = true;
if (OnChangeEvent != null) OnChangeEvent();
OnChange();
}
public new void Remove(T item)
{
base.Remove(item);
HasRemovedItems = true;
if (OnChangeEvent != null) OnChangeEvent();
OnChange();
}
public new void RemoveAt(int index)
{
HasRemovedItems = true;
if (OnChangeEvent != null) OnChangeEvent();
OnChange();
}
public new void RemoveRange(int index,int count)
{
HasRemovedItems = true;
if (OnChangeEvent != null) OnChangeEvent();
OnChange();
}
public new void Clear()
{
base.Clear();
HasRemovedItems = true;
if (OnChangeEvent != null) OnChangeEvent();
OnChange();
}
public virtual void OnChange()
{
}
}

Categories