2010-01-05 3 views
15

Скажем, в какой-то абстрактный ViewModel базового класса У меня есть набившие оскомину свойство следующим образом:Уведомление об изменении в MVVM иерархий

public Size Size 
{ 
    get { return _size; } 
    set 
    { 
     _size = value; 
     OnPropertyChanged("Size"); 
    } 
} 

Я тогда создать более конкретную ViewModel, унаследовав от предыдущей, которая содержит следующее свойство:

public Rect Rectangle 
{ 
    get { return new Rect(0, 0, _size.Width, _size.Height); } 
} 

Теперь, в каком-то классе View Я связываю к Rectangle собственности вышеупомянутого ViewModel в. Все работает нормально, пока я не изменю размер. Когда Size изменяется, Rectangle не знает об этом, и изменение не распространяется на представление. А так как Rectangle находится в дочернем классе, я не могу просто добавить OnPropertyChanged("Rectangle") в сеттер Size.

Теперь представьте, что у меня много разных свойств, таких как Rectangle, что все зависит от свойств базового класса и что ни одно из этих изменений не распространяется. Мне нужен легкий и элегантный способ связывания уведомлений об изменениях, желательно тот, который не требует большого количества кода и не заставляет меня использовать свойства зависимостей.

Очевидно, что здесь есть много уродливых решений - я ищу что-то чистое и умное. Мне кажется, что это будет очень распространенный сценарий, и мне кажется, что может быть MVVM-friendly способ сделать это.

ответ

9

Недавно я писал об этой точной проблеме. Я включаю атрибут [DependsUpon("Size")] с прямоугольником. Я ДЕЙСТВИТЕЛЬНО люблю этот подход, потому что он сохраняет знание зависимости с кодом, который создает зависимость, а не наоборот.

Посмотрите: http://houseofbilz.com/archive/2009/11/14/adventures-in-mvvm----dependant-properties-with-inotifypropertychanged.aspx

+0

Отличная идея. Я немного изменил его, чтобы создать словарь во время построения (по соображениям производительности), но концепция звуковая. – Charlie

3

Вы можете просто переопределить OnPropertyChanged в производных ViewModel как так:

protected override void OnPropertyChanged(string propertyName) { 
    base.OnPropertyChanged(propertyName); 
    if (propertyName == "Size") { 
     base.OnPropertyChanged("Rectangle"); 
    } 
} 

Еще одна возможность ... Некоторое время назад я собрал довольно хороший ViewModel базовый класс, который поддерживает атрибуты на такие свойства, как:

[DependsOn("Size")] 
public Rect Rectangle { 
    get { new Rect(0,0,Size.Width, Size.Height); } 
} 

Затем базовый класс ViewModel собирает эти атрибуты DependOnAttribute во время выполнения и в методе OnPropertyChanged, он в основном просто ищет, какие другие свойства необходимо признать недействительными, когда свойство chang e происходит.

+0

Переопределение OnPropertyChanged - это, безусловно, решение, но не элегантное. Тем не менее, мне нравится ваше второе предложение. – Charlie

+0

Не все решения требуют элегантности. Переопределение OnPropertyChanged очень просто, и я все еще делаю это во многих случаях, если зависимости не становятся волосатыми, что заставило меня сделать второй подход. – Josh

+0

Вы правы, но помните, что в моем вопросе я специально сказал: «Теперь представьте, что у меня много разных свойств, таких как Rectangle, что все зависит от свойств базового класса и что ни одно из этих изменений не распространяется». Таким образом, предполагается, что иерархия уже становится волосатой. – Charlie

1

Чистый MVVM способом было бы использовать Messenger подписываться/уведомит механизм (как в Джош Смит MvvmFoundation)

Создать одноплодной Курьер объект где-то - главный класс App всегда хорошее место для этого

public partial class App : Application 
{ 
    private static Messenger _messenger; 
    public static Messenger Messenger 
    { 
     get 
     { 
      if (_messenger == null) 
      { 
       _messenger = new Messenger(); 
      } 
      return _messenger; 
     } 
    } 
} 

В Размер сеттера от базового класса, уведомляют изменения:

public Size Size 
{ 
    get { return _size; } 
    set 
    { 
     _size = value; 
     OnPropertyChanged("Size"); 

     App.Messenger.NotifyColleagues("SIZE_CHANGED"); 
    } 
} 

Теперь вы можете позволить своему унаследованный View Модель послушают эти изменения, а также повысить события PropertyChanged в зависимости от обстоятельств ...

public MyViewModel : MyViewModelBase 
{ 
    public MyViewModel() 
    { 
     App.Messenger.Register("SIZE_CHANGED",() => OnPropertyChanged("Rectangle")); 
    } 
} 

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

Надеется, что это помогает :)

+0

Это точно так же, как переопределение OnPropertyChanged для ребенка в родительском доме, не уверен, что это действительно делает его чище. – AwkwardCoder

+0

Это неплохая идея, но я хочу что-то немного более легкое. – Charlie

+0

Преимущество использования подхода Messenger может быть в тех случаях, когда у вас есть глубоко вложенные иерархии, где было бы неудобно и, возможно, неэффективно связывать события изменения свойств. – jpierson

0

может быть, потому что им VB парень, но в коде Прямоугольника это выглядит, как вы обращаетесь к частному _size декларации вместо свойства общественного Размера, который бы не сгореть на OnPropertyChanged события, чтобы предупредить вид.

Также я могу быть вне базы, но должен ли Rectangle быть фактическим объектом, а Size является свойством этого объекта? Возможно, это то, что вы делаете. Некоторые методологии C# по-прежнему мне по-прежнему чужды.

+0

Я использую частную декларацию _size, но это не имеет значения. Это геттер, а не сеттер. Представление должно быть уведомлено о том, что Rectangle изменяется при изменении размера, а не при обращении к Rectangle. – Charlie

4

Я использую PropertyObserver Джоша Смита, который вы можете получить из его библиотеки MVVM Foundation в http://mvvmfoundation.codeplex.com/.

Использование:

_viewmodel_observer = new PropertyObserver<OtherViewModel>(_OtherViewModel) 
    .RegisterHandler(m => m.Size, m => RaisePropertyChanged(Rectangle); 

атрибут подход Брайана это слишком хорошо. Одна вещь, которая мне нравится в PropertyObserver, заключается в том, что я могу выполнить произвольный код; позволяя мне проверить условия, которые могут заставить меня избежать рейза или выполнять другие действия вместе.