2

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:

  1. Make a wrapper for the list which looks like my observable collection.
  2. 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();
    } 
}
snipervld
  • 69
  • 1
  • 10

3 Answers3

2

Here is a variation of your code, the points to note are:

  • Model: refactored to be more object oriented and closer to real world situations, it is now a parent-child relationship between objects
  • View Model: now implements INotifyPropertyChanged to notify framework elements about changes causing then to update its binding properties
  • View Model: Added property SelectedChild, which stores the selected item. This item will be the one removed when hitting Remove button.
  • DelegateCommand is not generic as no parameter will be passed to the command. Instead it will use SelectedChild property
  • View: Now has not knowledge about Model. Note SelectedItem property pointing to SelectedChild from view model

The code:

    public class Model
    {
        public Model() 
        {
            ChildList = new HashSet<Child>();
        }
        public ICollection<Child> ChildList { get; set; }
    }

    public class Child
    {
        public string Name { get; set; }
    }


    //View Model, now implements INotifyPropertyChanged    
    public class VM: INotifyPropertyChanged{
        private Model _model; 

        public VM()
        {
            var model = new Model();
            model.ChildList.Add(new Child { Name = "Child 1" });
            model.ChildList.Add(new Child { Name = "Child 2" });
            model.ChildList.Add(new Child { Name = "Child 3" });
            model.ChildList.Add(new Child { Name = "Child 4" });

            _model = model;
            Command = new DelegateCommand(CommandExecuted);
        }

        public ObservableCollection<Child> ChildCollection
        {
            get
            {
                return new ObservableCollection<Child>(_model.ChildList);
            }
        }

        public DelegateCommand Command { get; set; }

        private void CommandExecuted()
        {
            _model.ChildList.Remove(SelectedChild);
            OnPropertyChanged("ChildCollection");
        }

        public Child SelectedChild { get; set; }   

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
       {
           var eventHandler = this.PropertyChanged;
           if (eventHandler != null)
           {
               eventHandler(this, new PropertyChangedEventArgs(propertyName));
           }
       }
    }


    public class DelegateCommand : ICommand
    {
        private readonly Action _execute;    
        public DelegateCommand(Action execute)
        {
            _execute = execute;
        }

        public void Execute(object parameter)
        {
            _execute();
        }

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged;    
        }

    //View    
<ListBox ItemsSource="{Binding Path = ChildCollection}" SelectedItem="{Binding SelectedChild}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Label Content="{Binding Name}"/>
         </DataTemplate>
     </ListBox.ItemTemplate>
</ListBox>

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new VM();
        }
    }
E-Bat
  • 4,792
  • 1
  • 33
  • 58
  • Do not hit the performance a large number of items in the list? From msdn: ObservableCollection Constructor (IEnumerable) "Initializes a new instance of the ObservableCollection class that contains elements copied from the specified collection." – snipervld Jun 17 '15 at 11:03
  • For large collection you would not want to expose ObserbableCollection directly, instead can use ICollectionView with CollectionViewSource to page results. ( see http://stackoverflow.com/questions/5305883/mvvm-paging-sorting ). Also you can use UI virtualization (from msdn: which means the item container generation and associated layout computation for an item is deferred until the item is visible.), Listbox supports UI virtualization ( https://msdn.microsoft.com/en-us/library/cc716879.aspx ) – E-Bat Jun 17 '15 at 13:22
2

You do realize that

  • ObservableCollection<T> is a subtype of Collection<T>, which [explicitly] implements IList<T>, and that

  • List<T> likewise implements IList<T>?

That means an ObservableCollection<T> can be pretty much interchanged with any other implementation of IList<T>: use the ObservableCollection<T> in both places, casting it to an IList<T> where and if necessary.

Nicholas Carey
  • 71,308
  • 16
  • 93
  • 135
0

Could you please specify why you need a list in model and an ObservableCollection in ViewModel?

Is that possible for you to use ObservableCollection in both model and view model?

In that case you can ask the viewmodel observable collection to return the models observable collection.

Sample:

class Model
{
    //model's property. Instead of your list
    public ObservableCollection<string> ModelCol { get; set; }
}

class ViewModel
{
    Model model;

    public ViewModel ()
    {
        model = new Model();
    }

    //view model property returning model property. If you want, you can do
    //the customization here.
    public ObservableCollection<string> ViewModelCol {
    get 
    {
        return model.ModelCol; 
    }
    set 
    {
        //return collection from your model.
        model.ModelCol = value; 
    }
}

It is okay to use ObservableCollection in Model also. If it wont cause any problems to your requirement, you can use the same concept.

Adeeb Arangodan
  • 152
  • 1
  • 11
  • Not agree, Model must be as POCO as they can be. I would rather use an interface, probably ICollection – E-Bat Jun 16 '15 at 20:08
  • I see that people mostly use lists instead of observablecollection's in model. – snipervld Jun 16 '15 at 20:09
  • in dot Net 4.0 ObservableCollection was moved from the WindowsBase to the System Dll which is a clear indication by MS that ObservableCollections are appropriate for your model. – Adeeb Arangodan Jun 17 '15 at 09:33
  • Please check the link https://social.msdn.microsoft.com/Forums/vstudio/en-US/49a6d4fb-a676-4b39-bc2e-8efb30c653a1/do-you-use-model-or-the-viewmodel-to-populate-an-observablecollection?forum=wpf – Adeeb Arangodan Jun 17 '15 at 09:34
  • @Adeeb Arangodan. Thank you. I'll remember about the `ObservableCollection` in the model for the future – snipervld Jun 17 '15 at 21:38