In my application model I have a list of strings. In my view model I have an ObservableCollection<string>
which is initialized with a list from my model. I want to synchronize the list with my observable collection so when we change it, it also changes the list.
I came up with two ways to achieve this:
- Make a wrapper for the list which looks like my observable collection.
- Initialize a new observable collection with a list from the model and attach an event handler for the
CollectionChanged
event.
As for the first way:
public class ObservableWrapper<T> : IList<T>, IEnumerator<T>,
INotifyCollectionChanged, INotifyPropertyChanged
{
IList<T> _list;
IEnumerator<T> enumer;
public ObservableWrapper(IList<T> list)
{
_list = list;
}
public T this[int index]
{
get
{
return _list[index];
}
set
{
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, _list[index]));
_list[index] = value;
}
}
public int Count => _list.Count;
public bool IsReadOnly => false;
public T Current => enumer.Current;
object IEnumerator.Current => Current;
public event NotifyCollectionChangedEventHandler CollectionChanged;
public event PropertyChangedEventHandler PropertyChanged;
public void Add(T item)
{
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _list.Count));
_list.Add(item);
}
public void Clear()
{
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
_list.Clear();
}
public bool Contains(T item) => _list.Contains(item);
public void CopyTo(T[] array, int arrayIndex)
{
_list.CopyTo(array, arrayIndex);
}
public void Dispose() { }
public IEnumerator<T> GetEnumerator()
{
enumer = _list.GetEnumerator();
return enumer;
}
public int IndexOf(T item) => _list.IndexOf(item);
public void Insert(int index, T item)
{
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
_list.Insert(index, item);
}
public bool MoveNext() => enumer.MoveNext();
public bool Remove(T item) => _list.Remove(item);
public void RemoveAt(int index)
{
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, _list[index], index));
_list.RemoveAt(index);
}
public void Reset()
{
enumer.Reset();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
For second:
var list = new List<string>{ "aba", "abacaba" };
var obscol = new ObservableCollection<string>(list);
obscol.CollectionChanged += (s,e) =>
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
list.Add((string)e.NewItems[0]);
}
// etc
}
obscol.Add("test");
//now list has "aba", "abacaba", "test"
I think second way is bad because the new observable collection creates and copies all items from the list into this new observable collection. But in the first case list elements are not being copied.
What way should I prefer?
EDIT:
In my application model I have a list of emails (list of strings for simplify).
View has listbox which binds to observable collection with items from list.
When user presses some button, email deletes from observable collection and also from the list from application model.
EDIT 2:
Model
public class Model
{
public List<string> List { get; set; }
}
View-model
public class VM
{
public Model _model;
public ObservableWrapper<string> WrapperList { get; set; }
public VM(Model model)
{
_model = model;
WrapperList = new ObservableWrapper<string>(_model.List);
Command = new DelegateCommand<object>(CommandExecuted);
}
public DelegateCommand<object> Command { get; }
private void CommandExecuted(object obj)
{
WrapperList.RemoveAt(0); //0 for example and simplify
}
}
public class DelegateCommand<T> : System.Windows.Input.ICommand
{
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public DelegateCommand(Action<T> execute) : this(execute, null) { }
public DelegateCommand(Action<T> execute, Predicate<T> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute?.Invoke((T)parameter) ?? true;
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
View / xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding WrapperList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Command="{Binding Command}"
Grid.Row="1"/>
</Grid>
View / code
public partial class MainWindow : Window
{
public MainWindow()
{
var model = new Model();
model.List = new List<string> { "11", "22","33","44" };
DataContext = new VM(model);
InitializeComponent();
}
}