0

I have a MVVM app with a ListView composed of EditableTextblocks in a DataTemplate (like this).

Here is my model :

public class MyModel
{
    private string _data;
    public string Data
    {
        get { return _data; }
        set { _data = value; }
    }
}

My viewmodel exposes an ObservableCollection of MyModel:

public class MyViewModel
{
    [...]
    public ObservableCollection<Mymodel> models = new ObservableCollection<MyModel>();
}

and in the view bound to a ListView:

<ListView ItemsSource={Binding models}>
    <!-- code removed for more visibility -->
    <DataTemplate>
         <controls:EditableTextblock Text="{Binding Data, Mode=TwoWay}" />
    </DataTemplate>
    <!-- ... -->
</ListView>

Would you have any leads that when in an item in the list I update the value of a Data member, there is a check to see if a value already exists in the collection?

For example, if I update a field to "value 1", it checks if in the models collection there is a member Data that already has this value.

And if it found one, it adds for example a "0" at the end of the member Data.

dymanoid
  • 14,771
  • 4
  • 36
  • 64
ArthurCPPCLI
  • 1,072
  • 7
  • 18
  • By the way, your changes in the model won't be updated in the view. Your `MyModel` class doesn't implement `INotifyPropertyChanged`, but you're binding to a property of that class. – dymanoid Oct 26 '17 at 14:09
  • @dymanoid That was probably just an oversight when OP typed up the original question. – Bradley Uffner Oct 26 '17 at 14:13

2 Answers2

1

For something like this, I typically wrap my models in their own view-model class, and add the validation there. The wrapper takes the instance of the original model to wrap, plus a reference to the parent view-model, so that it can check for duplicates or do other operations with the parent.

The validation could also be done without a reference to the parent if you use some kind of messaging system, like MVVMLight's Messenger class to communicate between view-models.

The main reason I wrap them this way, is because I like to keep the models "pure", without any change notification, WPF, or business logic in them, beyond what is directly required for their domain. This allows me to keep the models as simple data classes, and move any business or view-specific logic to someplace more appropriate.


Your existing classes (note, I changed the collection to be the wrapper class):

public class MyViewModel : BaseViewModel //whatever base class you use to notify of property changes.
{
    [...]
    public ObservableCollection<MyModelVm> models = new ObservableCollection<MyModelVm>();
}

public class MyModel
{
    private string _data;
    public string Data
    {
        get { return _data; }
        set { _data = value; }
    }
}

The new wrapper view-model:

public class MyModelVm : BaseViewModel //whatever base class you use to notify of property changes.
{
    public MyModelVm(MyModel model, MyViewModel parentViewModel)
    {
        Model = model;
        ParentViewModel = parentViewModel;
    }

    public MyModel Model { get; }
    public MyViewModel ParentViewModel { get; }

    public string Data
    {
        get { return Model.Data; }
        set
        {
            if (ParentViewModel.models.Any(x => x != this && x.Data == this.Data))
            {
                //Duplicate entered
            }
            else
            {
                //Not a duplicate, go ahead and allow the change.
                Model.Data = value;
                //don't forget to notify of property change!
            }
        }
    }
}
Bradley Uffner
  • 16,641
  • 3
  • 39
  • 76
1

Provided that the MyModel class implements the INotifyPropertyChanged and raises the PropertyChanged event when the Data property is set, you could handle this in the view model:

public class MyViewModel
{
    public ObservableCollection<MyModel> models = new ObservableCollection<MyModel>();

    public MyViewModel()
    {
        models.CollectionChanged += Models_CollectionChanged;
    }

    private void Models_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null)
        {
            foreach (object model in e.NewItems)
            {
                (model as INotifyPropertyChanged).PropertyChanged
                    += new PropertyChangedEventHandler(Model_PropertyChanged);
            }
        }

        if (e.OldItems != null)
        {
            foreach (object model in e.OldItems)
            {
                (model as INotifyPropertyChanged).PropertyChanged
                    -= new PropertyChangedEventHandler(Model_PropertyChanged);
            }
        }
    }

    private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        MyModel updatedModel = sender as MyModel;
        MyModel duplicate = models.FirstOrDefault(x => x != updatedModel && x.Data == updatedModel.Data);
        if(duplicate != null)
        {
            updatedModel.Data += "0";
        }
    }
}

public class MyModel : INotifyPropertyChanged
{
    private string _data;
    public string Data
    {
        get { return _data; }
        set { _data = value; NotifyPropertyChanged(); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}
mm8
  • 163,881
  • 10
  • 57
  • 88
  • I wrote up an [extension method](https://stackoverflow.com/a/46606925/526724) that makes implementing this kind of pattern easier a while ago for someone else. – Bradley Uffner Oct 26 '17 at 15:13