views:

158

answers:

1

I am making a configuration editor for another application and am using reflection to pull editable fields from the configuration class. The following class is the base class for my various "DataTypeViewModels" and shows how I get and set the appropriate properties.

public abstract class DataTypeViewModel<T> : ViewModelBase
{
    Func<T> getFunction;

    Action<T> setAction;

    public const string ValuePropertyName = "Value";

    public string Label { get; set; }

    public T Value
    {
        get
        {
            return getFunction.Invoke();
        }

        set
        {
            if (getFunction.Invoke().Equals(value))
            {
                return;
            }

            setAction.Invoke(value);

            // Update bindings, no broadcast
            RaisePropertyChanged(ValuePropertyName);
        }
    }

     /// <summary>
    /// Initializes a new instance of the StringViewModel class.
    /// </summary>
    public DataTypeViewModel(string sectionName, string label)
    {
        if (IsInDesignMode)
        {
            // Code runs in Blend --> create design time data.
        }
        else
        {
            Label = label;

            getFunction = new Func<T>(() =>
                {
                    return (T)Settings.Instance.GetType().GetProperty(sectionName).PropertyType.
                        GetProperty(label).GetValue(Settings.Instance.GetType().GetProperty(sectionName).GetValue(Settings.Instance, null), null);
                });

            setAction = new Action<T>(value =>
                {
                    Settings.Instance.GetType().GetProperty(sectionName).PropertyType.GetProperty(label).
                        SetValue(Settings.Instance.GetType().GetProperty(sectionName).GetValue(Settings.Instance, null), value, null);
                });
        }
    }
}

This part works the way I want it to, the next part is a sample DataTypeViewModel on a list of strings.

public class StringListViewModel : DataTypeViewModel<ICollection<string>>
{
    /// <summary>
    /// The <see cref="RemoveItemCommand" /> property's name.
    /// </summary>
    public const string RemoveItemCommandPropertyName = "RemoveItemCommand";

    private RelayCommand<string> _removeItemCommand = null;

    public ObservableCollection<string> ObservableValue { get; set; }

    /// <summary>
    /// Gets the RemoveItemCommand property.
    /// TODO Update documentation:
    /// Changes to that property's value raise the PropertyChanged event. 
    /// This property's value is broadcasted by the Messenger's default instance when it changes.
    /// </summary>
    public RelayCommand<string> RemoveItemCommand
    {
        get
        {
            return _removeItemCommand;
        }

        set
        {
            if (_removeItemCommand == value)
            {
                return;
            }

            var oldValue = _removeItemCommand;
            _removeItemCommand = value;

            // Update bindings, no broadcast
            RaisePropertyChanged(RemoveItemCommandPropertyName);
        }
    }

    /// <summary>
    /// The <see cref="AddItemCommand" /> property's name.
    /// </summary>
    public const string AddItemCommandPropertyName = "AddItemCommand";

    private RelayCommand<string> _addItemCommand = null;

    /// <summary>
    /// Gets the AddItemCommand property.
    /// TODO Update documentation:
    /// Changes to that property's value raise the PropertyChanged event. 
    /// This property's value is broadcasted by the Messenger's default instance when it changes.
    /// </summary>
    public RelayCommand<string> AddItemCommand
    {
        get
        {
            return _addItemCommand;
        }

        set
        {
            if (_addItemCommand == value)
            {
                return;
            }

            var oldValue = _addItemCommand;
            _addItemCommand = value;

            // Update bindings, no broadcast

            RaisePropertyChanged(AddItemCommandPropertyName);
        }
    }

    /// <summary>
    /// Initializes a new instance of the StringListViewModel class.
    /// </summary>
    public StringListViewModel(string sectionName, string label) : base(sectionName, label)
    {
        ObservableValue = new ObservableCollection<string>(Value);
        AddItemCommand = new RelayCommand<string>(param =>
            {
                if (param != string.Empty)
                {
                    Value.Add(param);
                    ObservableValue.Add(param);
                }
            });

        RemoveItemCommand = new RelayCommand<string>(param =>
            {
                if (param != null)
                {
                    Value.Remove(param);
                    ObservableValue.Remove(param);
                }
            });
    }
}

As you can see in the constructor, I currently have "Value" mirrored into a new ObservableCollection called "ObservableValue", which is then bound to by a ListView in the XAML. It works well this way, but cloning the List seems like such a hacky way to do this. While bound to Value, I've tried adding:

RaisePropertyChanged("Value");

to the AddItemCommand and RemoveItemCommand, but this doesn't work, the ListView won't get updated. What is the proper way to do this?

+1  A: 

Implement INotifyCollectionChanged it's like NotifyPropertyChanged but is used by ObservableCollection to notify on inserts/removes/resets...

  public class MyCustomCollection : INotifyCollectionChanged
    {
        public event NotifyCollectionChangedEventHandler CollectionChanged;

        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (CollectionChanged != null)
            {
                CollectionChanged(this, e);
            }
        }

        public void Add(Object o)
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, o));
        }

        public void Remove(Object o)
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, 0));
        }

        public void Clear()
        {
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        public void Move(Object o, Int32 newIndex)
        {
            Int32 oldIndex = 0; // can get the old index position using collection.IndexOf(o);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move,
                o, newIndex, oldIndex));
        }

        public Object this[Int32 index]
        {
            get
            {
                return null; // return collection[index];
            }
            set
            {
                Object oldValue = null;  // get old value using collection[index];
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,
                    value, oldValue));
            }
        }
    }

...i copied this from here

Aran Mulholland
I tried this and NotifyPropertyChanged and both did not work as expected.
MGSoto
have a look at the edit above, is this what you did?
Aran Mulholland
I added the add and remove commands in their appropriate calls, however I am not creating a custom collection, I'm using a property from the base class that is a List templated from the sub class.
MGSoto
so basically your issue is you have a collection that does not implement collection changed events. and you want to be notified when it changes. If a collection does not implement ICollectionChanged, you can't call the events on it. If you dont have the ability to change the base collection then a hack is your best solution.
Aran Mulholland
Hack was the way to go!
MGSoto