2016-02-29 7 views
0

Рассмотрим следующий объект, часть приложения WPF MVVM:Уведомьте ViewModel, что объект в коллекции был выбран

public class MyObject : INotifyPropertyChanged 
{ 
    // INotifyPropertyChanged gubbins 

    private bool _isSelected; 
    public bool IsSelected 
    { 
     get 
     { 
      return _isSelected; 
     } 
     set 
     { 
      _isSelected = value; 
      OnPropertyChanged("IsSelected"); 
     } 
    } 
} 

И его использование в следующем ViewModel:

public class MyViewModel : INotifyPropertyChanged 
{ 
    // INotifyPropertyChanged gubbins 

    private List<MyObject> _myObjects; 
    public List<MyObject> MyObjects 
    { 
     get 
     { 
      return _myObjects; 
     } 
     set 
     { 
      _myObjects = value; 
      OnPropertyChanged("MyObjects"); 
     } 
    } 

    public bool CanDoSomething 
    { 
     get 
     { 
      return MyObjects.Where(d => d.IsSelected).Count() > 0; 
     } 
    } 
} 

В в этой ситуации я могу отслеживать, какой из моих объектов был выбран, и при выборе их будет срабатывать OnPropertyChanged и поэтому может уведомить родительский вид.

Однако CanDoSomething всегда будет ложным, потому что нигде я не могу запустить OnPropertyChanged для создания уведомления. Если я поставлю его в MyObject, он ничего не знает об этом свойстве и ничего не делает. Его нет в ViewModel, потому что ничего не реагирует, когда выбран объект в списке.

Я попытался заменить список на ObservableCollection и пользовательский «TrulyObservableCollection» (см. Notify ObservableCollection when Item changes), но не работает.

Как я могу обойти это, не прибегая к событиям щелчка?

+1

Вы можете подключить 'PropertyChangedEvent' в' MyViewModel' для всех «MyObjects» и когда свойство «IsSelected» будет изменено в Модели, оно уведомит ViewModel, и вы сможете поднять другое измененное событие свойства для «CanDoSomething» 'чтобы показать, что это может измениться. –

+1

Это в основном вопрос о том, как общаться между ViewModels. Один из подходов - передать каждому родительскому элементу ViewModel родительский элемент, поэтому вы можете вызвать метод родительского ViewModel в установщике 'IsSelected'. Другим возможным подходом (должно быть больше) является использование статического события, которое запускается установщиком 'IsSelected', подписывается на это событие в родительском ViewModel и вызывает обработчик' NotifyPropertyChanged (nameof (CanDoSomething)) '. – Sinatr

+1

Хорошая точка, Sinatr. В этой статье Мэтт Гамильтон вводит родительский ViewModel в качестве объекта-хоста. Это приемлемо в случаях, когда дочерний ViewModel действительно не может существовать без родителя (например, в случае с тэгом без родительской временной шкалы). http://matthamilton.net/nested-viewmodels – Christoph

ответ

1

Я чувствую, что если бы у меня было лучшее представление о том, какова была ваша конечная цель, я мог бы рекомендовать лучший подход. Есть кое-что, что происходит, просто чувствуя себя маленьким. Например, возможно, что «CanDoSomething» должно быть частью объекта команды. И мне интересно, можно ли выбирать одновременно по одному MyObject? Если нет, я бы подошел к этому совершенно другим способом.

Так или иначе, вы хотите обновить CanDoSomething в любое время IsSelected property of один из пунктов в MyObjects изменений. Похоже, вы использовали ObservableCollection в какой-то момент, а затем оставили его. Это была ошибка. Вам необходимо обновить CanDoSomething в любое время, когда произойдет одно из двух событий; во-первых, когда элементы добавляются или удаляются из MyObjects, а во-вторых, когда изменяется свойство IsSelected любого из объектов в MyObjects. Для первого события вам потребуется что-то, что реализует INotifyCollectionChanged, то есть ObservableCollection. У вас уже есть второе событие, потому что объекты реализуют INotifyPropertyChanged. Поэтому вам просто нужно объединить эти две вещи.

В следующем примере я принял ваш код и внесли некоторые изменения. Для начала я изменил MyObjects назад на ObservableCollection<MyObject>. У него нет сеттера, потому что я обнаружил, что обычно нет веских оснований менять наблюдаемую коллекцию; просто добавьте и удалите объекты по мере необходимости. Затем в конструкторе viewmodel я регистрирую событие CollectionChangedMyObjects. В этом обработчике я захватываю элементы, которые добавляются в коллекцию и подключают их событие PropertyChanged к обработчику событий OnIsSelectedChanged, и я отвязываю событие PropertyChanged от OnIsSelectedChanged для любых объектов, которые были удалены из коллекции. Поскольку элементы были добавлены или удалены, мы понятия не имеем, что может быть из состояния IsSelected объектов в MyObjects, поэтому это хорошая возможность обновить CanDoSomething, а в нижней части обработчика событий. Наконец, OnIsSelectedChanged - это другая половина волшебства. Каждый объект в MyObjects будет иметь свое событие PropertyChanged, подключенное к этому обработчику событий. Всякий раз, когда свойство IsSelected на любом из этих объектов изменяется, обработчик события будет обновлять CanDoSomething.

public class MyViewModel : INotifyPropertyChanged 
{ 
    // INotifyPropertyChanged gubbins 
    public MyViewModel() 
    { 
     this._myObjects.CollectionChanged += (o, e) => 
     { 
      if (e.NewItems != null) 
      { 
       foreach (var obj in e.NewItems.OfType<MyObject>()) 
       { 
        obj.PropertyChanged += this.OnIsSelectedChanged; 
       } 
      } 

      if (e.OldItems != null) 
      { 
       foreach (var obj in e.OldItems.OfType<MyObject>()) 
       { 
        obj.PropertyChanged -= this.OnIsSelectedChanged; 
       } 
      } 

      if (e.PropertyName == "IsSelected") 
      { 
       this.CanDoSomething = this.MyObjects.Any(x => x.IsSelected); 
      } 
     }; 
    } 

    private readonly ObservableCollection<MyObject> _myObjects = 
     new ObservableCollection<MyObject>(); 
    public ObservableCollection<MyObject> MyObjects 
    { 
     get 
     { 
      return _myObjects; 
     } 
    } 

    private void OnIsSelectedChanged(object o, PropertyChangedEventArgs e) 
    { 
     if (e.PropertyName == "IsSelected") 
     { 
      this.CanDoSomething = this.MyObjects.Any(x => x.IsSelected); 
     } 
    } 

    private bool _canDoSomething; 
    public bool CanDoSomething 
    { 
     get { return this._canDoSomething; } 
     private set 
     { 
      if (_canDoSomething != value) 
      { 
       _canDoSomething = value; 
       OnPropertyChanged("CanDoSomething"); 
      } 
     } 
    } 
} 
1

Сначала нужно создать класс, который определяет это вложенное свойство:

public static class ItemClickCommand 
{ 
    public static readonly DependencyProperty CommandProperty = 
    DependencyProperty.RegisterAttached("Command", typeof(ICommand), 
    typeof(ItemClickCommand), new PropertyMetadata(null, OnCommandPropertyChanged)); 

    public static void SetCommand(DependencyObject d, ICommand value) 
    { 
     d.SetValue(CommandProperty, value); 
    } 

    public static ICommand GetCommand(DependencyObject d) 
    { 
     return (ICommand)d.GetValue(CommandProperty); 
    } 

    private static void OnCommandPropertyChanged(DependencyObject d, 
     DependencyPropertyChangedEventArgs e) 
    { 
     var control = d as ListViewBase; 
     if (control != null) 
      control.ItemClick += OnItemClick; 
    } 

    private static void OnItemClick(object sender, ItemClickEventArgs e) 
    { 
     var control = sender as ListViewBase; 
     var command = GetCommand(control); 

     if (command != null && command.CanExecute(e.ClickedItem)) 
      command.Execute(e.ClickedItem); 
    } 
} 

Тогда просто связать это вложенное свойство к команде делегата в вашей модели представления: helper:ItemClickCommand.Command="{Binding MyItemClickCommand}"

Вы можете найти более подробную информацию в это сообщение в блоге: https://marcominerva.wordpress.com/2013/03/07/how-to-bind-the-itemclick-event-to-a-command-and-pass-the-clicked-item-to-it/

Сообщите мне, если он будет работать

 Смежные вопросы

  • Нет связанных вопросов^_^