【问题标题】:How to bubble up changes in my ViewModel hierarchy?如何冒泡我的 ViewModel 层次结构中的更改?
【发布时间】:2009-06-08 14:04:03
【问题描述】:

我的 MainView.xaml 包含我的 SmartForm 视图:

<Grid Margin="10">
    <views:SmartForm/>
</Grid>

SmartForm 视图加载一个 ItemsControl

<Grid Margin="10">
    <ItemsControl
        ItemsSource="{Binding DataTypeViews}"/>
</Grid>

这是一个 DataTypeViews 的 ObservableCollection:

List<FormField> formFields = new List<FormField>();
formFields.Add(new FormField { IdCode = "firstName", Label = "First Name", Value = "Jim" });
formFields.Add(new FormField { IdCode = "lastName", Label = "Last Name", Value = "Smith" });
formFields.Add(new FormField { IdCode = "address1", Label = "Address 1", Value = "123 North Ashton Rd." });
formFields.Add(new FormField { IdCode = "address2", Label = "Address 2", Value = "Box 23434" });
formFields.Add(new FormField { IdCode = "city", Label = "City", Value = "New Haven" });
formFields.Add(new FormField { IdCode = "state", Label = "State", Value = "NM" });
formFields.Add(new FormField { IdCode = "zip", Label = "Zip Code", Value = "34234" });

foreach (FormField formField in formFields)
{
    DataTypeView dtv = new DataTypeView();
    DataTypeViewModel dtvm = new DataTypeViewModel(formField);
    dtv.DataContext = dtvm;
    DataTypeViews.Add(dtv);
}

每个视图都显示了构建表单的标签和文本框:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="90"/>
        <ColumnDefinition Width="400"/>
    </Grid.ColumnDefinitions>
    <StackPanel Orientation="Horizontal" Grid.Column="0">
        <TextBlock Text="{Binding Label}" FontSize="14"/>
        <TextBlock Text=": " FontSize="14"/>
    </StackPanel>
    <TextBox Grid.Column="1" Text="{Binding Value}" FontSize="12"/>
</Grid>

如何将 DataTypeViewModel 中发生的文本框更改冒泡到 SmartFormViewModel 中?

或者换句话说:如果 ViewModel A 包含 ViewModel B 的集合,并且 ViewModel B 中发生更改,我如何将更改冒泡到 ViewModel A?

【问题讨论】:

    标签: c# wpf mvvm


    【解决方案1】:

    我认为你应该使用中介者模式which you can read about here

    基本上它是一个静态类,它允许 ViewModel(或与此相关的任何类)相互通信并来回传递参数。

    基本上,ViewModel A 开始侦听某种消息类型(例如 ViewModelBChanged),每当发生该事件时,ViewModelB 只会通知正在侦听此消息类型的任何人,它还可以传递它想要的任何信息。

    这是中介者的骨架。

    public static class MyMediator
    {
        public static void Register(Action<object> callback, string message);
    
        public static void NotifyColleagues(string message, object args);
    }
    

    ViewModel A 会这样做(可能在构造函数中):

    MyMediator.Register(ProcessMessage,"ViewModelBChanged")
    

    然后必须像这样声明一个函数:

    void ProcessMessage(object args)
    {
        //Do some important stuff here
    }
    

    并且 ViewModel B 会在想要告诉 ViewModel A 时调用它

    MyMediator.NotifyColleagues("ViewModelBChanged",this);
    

    中介类会负责调用viewModel A的回调函数,然后大家就开心了。

    我个人喜欢将这些字符串消息值放在像这样的静态类中

    static class MediatorMessages
    {
        public static string ViewModelBChanged= "ViewModelBChanged";
    }
    

    这样您就可以执行以下操作(而不是上面的):

     MyMediator.Register(ProcessMessage,MediatorMessages.ViewModelBChanged)
     MyMediator.NotifyColleagues(MediatorMessages.ViewModelBChanged,this);
    

    如果不清楚,只需谷歌 MVVM 调解器并点击您的心内容 :)

    【讨论】:

      【解决方案2】:

      您可以让父虚拟机连接到子虚拟机上的 PropertyChanged 事件。这是一种 PITA 来跟踪已添加/删除等的孩子,因此您可以考虑将您的孩子虚拟机存储在我的 ItemObservableCollection 中:

      public sealed class ItemObservableCollection<T> : ObservableCollection<T>
          where T : INotifyPropertyChanged
      {
          public event EventHandler<ItemPropertyChangedEventArgs<T>> ItemPropertyChanged;
      
          protected override void InsertItem(int index, T item)
          {
              base.InsertItem(index, item);
              item.PropertyChanged += item_PropertyChanged;
          }
      
          protected override void RemoveItem(int index)
          {
              var item= this[index];
              base.RemoveItem(index);
              item.PropertyChanged -= item_PropertyChanged;
          }
      
          protected override void ClearItems()
          {
              foreach (var item in this)
              {
                  item.PropertyChanged -= item_PropertyChanged;
              }
      
              base.ClearItems();
          }
      
          protected override void SetItem(int index, T item)
          {
              var oldItem = this[index];
              oldItem.PropertyChanged -= item_PropertyChanged;
              base.SetItem(index, item);
              item.PropertyChanged -= item_PropertyChanged;
          }
      
          private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
          {
              OnItemPropertyChanged((T)sender, e.PropertyName);
          }
      
          private void OnItemPropertyChanged(T item, string propertyName)
          {
              ItemPropertyChanged.Raise(this, new ItemPropertyChangedEventArgs<T>(item, propertyName));
          }
      }
      

      然后您的父虚拟机可以通过以下方式监听子项的所有更改:

      _formFields.ItemPropertyChanged += (s, e) => Foo();
      

      【讨论】:

        【解决方案3】:

        非 WPF 方式是在 DataTypeViewModel 上创建静态事件。这允许您在适当的时候(在属性设置器或属性更改处理程序中)从 DataTypeViewModel 触发事件。当然,您还必须在 SmartForm 中为事件注册一个监听器(要求 SmartForm 知道 DataTypeViewModel 类型)。

        或者,我认为您可以创建自己的自定义路由事件。

        【讨论】:

          【解决方案4】:

          虽然 Kent 就在上面,但并非所有子视图模型的更改都与属性有关,有些可能比这更语义化。在这种情况下,实现Chain of Responsibility pattern 的变体可能是一个不错的选择。

          总之

          • 使所有子视图模型都知道“主处理程序对象”,该对象具有处理各种更改事件的方法。与该对象的通信可以通过事件或消息进行,具体取决于更改的复杂性。
          • 让这个主处理程序对象注册一组处理程序对象,这些处理程序对象将处理更改事件,每个事件一个。它们可以像原始模式一样链接起来,也可以注册到快速集合(例如字典)中以提高性能。
          • 使此处理程序对象将适当的更改分派给已注册的处理程序。

          “主”处理程序不必是单例,它的注册表可以依赖于父视图模型本身。

          希望够清楚(抱歉没有放代码)

          【讨论】:

            【解决方案5】:

            我通过将 ViewModel 本身传递到其中包含的 ViewModel 中解决了这个问题,here's a demo showing how it's done

            【讨论】:

            • 哦...但这是野蛮的方式!
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-09-01
            • 1970-01-01
            • 2016-08-07
            相关资源
            最近更新 更多