Обновление зависимых свойств с использованием MVVM - PullRequest
9 голосов
/ 11 июня 2011

Некоторые свойства на моем viewmodel:

public ObservableCollection<Task> Tasks { get; set; }

public int Count
{
    get { return Tasks.Count; }
}

public int Completed
{
    get { return Tasks.Count(t => t.IsComplete); }
}

Как лучше всего обновить эти свойства при изменении Tasks? 1006 *

Мой текущий метод :

public TaskViewModel()
{
    Tasks = new ObservableCollection<Task>(repository.LoadTasks());
    Tasks.CollectionChanged += (s, e) => 
        {
            OnPropertyChanged("Count");
            OnPropertyChanged("Completed");
        };
}

Есть ли более элегантный способ сделать это?

Ответы [ 2 ]

9 голосов
/ 11 июня 2011

Что касается Count, вам совсем не обязательно это делать. Просто привяжитесь к Tasks.Count, и ваши привязки будут уведомлены об изменении ObservableCollection.

Completed - это отдельная история, потому что это за пределами ObservableCollection. Тем не менее, на уровне абстракции / интерфейса вы действительно хотите, чтобы Completed был свойством этой коллекции Tasks.

Для этого, я думаю, лучшим подходом было бы создание "подчиненной" модели представления для вашего свойства Tasks:

public class TasksViewModel : ObservableCollection<Task>
{
    public int Completed
    {
        get { return this.Count(t => t.IsComplete); }
    }

    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);
        if(e.PropertyName == "Count") NotifyCompletedChanged();
    }

    protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        base.OnCollectionChanged(e);
        NotifyCompletedChanged();
    }

    void NotifyCompletedChanged()
    {
        OnPropertyChanged(_completedChangedArgs);
    }
    readonly PropertyChangedEventArgs _completedChangedArgs = new PropertyChangedEventArgs("Completed");
}

Это дает вам все преимущества ObservableCollection и фактически делает свойство Completed его частью. Мы до сих пор не зафиксировали только случаи, когда количество завершенных элементов действительно изменяется, но мы несколько сократили количество избыточных уведомлений.

Теперь у viewmodel просто есть свойство:

public TasksViewModel Tasks { get; set; }

… и вы можете легко связать Tasks, Tasks.Count и Tasks.Completed.


В качестве альтернативы, если вы предпочитаете создавать эти другие свойства в "основной" модели представления, вы можете воспользоваться этим понятием подкласса ObservableCollection<T>, чтобы создать свойство с некоторым методом, который можно передать в Action<string> делегат, который будет представлять повышение уведомления об изменении свойства в основной модели представления, и некоторый список имен свойств. Затем эта коллекция может эффективно вызывать уведомления об изменении свойств в модели представления:

public class ObservableCollectionWithSubscribers<T> : ObservableCollection<T>
{
    Action<string> _notificationAction = s => { }; // do nothing, by default
    readonly IList<string> _subscribedProperties = new List<string>();

    public void SubscribeToChanges(Action<string> notificationAction, params string[] properties)
    {
        _notificationAction = notificationAction;

        foreach (var property in properties)
            _subscribedProperties.Add(property);
    }


    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);
        NotifySubscribers();
    }

    protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        base.OnCollectionChanged(e);
        NotifySubscribers();
    }

    void NotifySubscribers()
    {
        foreach (var property in _subscribedProperties)
            _notificationAction(property);
    }
}

Вы можете даже оставить тип свойства как ObservableCollection<Task>.

public class ViewModel : INotifyPropertyChanged
{
    public ViewModel()
    {
        var tasks = new ObservableCollectionWithSubscribers<Task>();
        tasks.SubscribeToChanges(Notify, "Completed");
        Tasks = tasks;
    }

    public ObservableCollection<Task> Tasks { get; private set; }

    public int Completed
    {
        get { return Tasks.Count(t => t.IsComplete); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    void Notify(string property)
    {
        var handler = PropertyChanged;
        if(handler != null) handler(this, new PropertyChangedEventArgs(property));
    }
}
4 голосов
/ 11 июня 2011

Выглядит довольно элегантно для меня. Я действительно не знаю, как ты сделал бы это более кратким.

(Как странно написать такой ответ. Если кто-то действительно придумает что-то более изящное, я мог бы удалить это.)

Хорошо, я заметил одну вещь, не связанную с первоначальным вопросом: у вашего свойства Tasks есть открытый сеттер. Сделайте это private set;, или вам нужно будет внедрить set с полем поддержки, чтобы вы могли удалить делегата в предыдущем экземпляре, заменить и подключить новый и выполнить OnPropertyChanged с «Задачами», «Считать» и «Завершено». (И, видя, как Tasks установлен в конструкторе, я думаю, private set; - лучший вариант.)

Не делает уведомление о Count и Completed более элегантным, но исправляет ошибку.

И многие инфраструктуры MVVM получают имя свойства из лямбды, так что вместо OnPropertyChanged("Count") вы можете написать OnPropertyChanged(() => Count), чтобы оно следовало переименованиям, выполненным с помощью инструментов рефакторинга. Я не думаю, что переименование происходит все же часто, но оно избегает некоторых строковых литералов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...