如何在ViewModel层次结构中冒泡更改?

时间:2022-10-30 12:06:15

My MainView.xaml contains my SmartForm View:

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

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

the SmartForm view loads an ItemsControl

SmartForm视图加载ItemsControl

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

which is an ObservableCollection of DataTypeViews:

这是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);
}

and each view shows the label and textbox which builds a form:

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

<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>

How do I bubble the Textbox changes that happen in DataTypeViewModel up into SmartFormViewModel?

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

Or in other words: If ViewModel A contains a collection of ViewModel B, and a change happens in a ViewModel B, how can I bubble that change up to ViewModel A?

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

5 个解决方案

#1


5  

You can just have the parent VM connect to the PropertyChanged event on the child VMs. It's kind of a PITA to keep track of the children who have been added/removed etcetera so you might instead consider storing your child VMs in my ItemObservableCollection:

您可以让父虚拟机连接到子虚拟机上的PropertyChanged事件。这是一种PITA,用于跟踪已添加/删除的子项等,因此您可以考虑将子VM存储在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));
    }
}

Then your parent VM can just listen for all changes to child items with:

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

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

#2


8  

I think you should employ the mediator pattern which you can read about here.

我认为你应该使用你可以在这里阅读的中介模式。

Basically it is a static class that allows ViewModels(or any class for that matter) to communicate with each other and pass arguments back and forth.

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

Basically ViewModel A starts to listening for a certain message type(e.g. ViewModelBChanged) and whenever that event happens ViewModelB just notifies anyone who's listening to for this message type, it can also pass any information it wants.

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

Here's the skeleton of a mediator.

这是调解员的骨架。

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

    public static void NotifyColleagues(string message, object args);
}

ViewModel A would do this(probably in the constructor):

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

MyMediator.Register(ProcessMessage,"ViewModelBChanged")

and then would have to declare a function like this:

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

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

and ViewModel B would call this whenever it want to tell ViewModel A

只要想要告诉ViewModel A,ViewModel B就会调用它

MyMediator.NotifyColleagues("ViewModelBChanged",this);

The mediator class would be in charge of invoking the callback function of viewModel A. And then everyone is happy.

中介类将负责调用viewModel A的回调函数。然后每个人都很高兴。

Personally I like putting these string message values in a static class like this

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

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

So that you could do the following(instead of the above):

这样你就可以做到以下(而不是上面的):

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

If this is unclear just google MVVM mediator and click to your hearts content :)

如果这不清楚只是谷歌MVVM中介并点击你的心脏内容:)

#3


0  

The non-WPF way is to create a static event on DataTypeViewModel. This allows you to fire the event from DataTypeViewModel when appropriate (in a property setter or property changed handler). Of course, you'll also have to register a listener to the event in SmartForm (requiring SmartForm to know about the DataTypeViewModel type).

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

Alternatively I think you could create your own custom routed event.

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

#4


0  

Although Kent is right above, not all changes in child view models have to do with properties, some might be a little bit more semantic than that. In such cases, implementing a variation of the Chain of Responsibility pattern might be a good fit.

虽然肯特就在上面,但并非所有子视图模型的变化都与属性有关,有些可能比这更具语义性。在这种情况下,实施责任链模式的变体可能是一个很好的选择。

In short

  • Make all child view models aware of a "master handler object" that has a method to handle all sorts of change events. Communication with that object could be via events or messages, depending on the complexity of the changes.
  • 使所有子视图模型都知道“主处理程序对象”,该对象具有处理各种更改事件的方法。与该对象的通信可以通过事件或消息进行,具体取决于更改的复杂程度。

  • Have this master handler object register a collection of handler objects which will handle the change events, one for each one. They can be chained as in the original pattern or they can be registered in a fast collection (e.g. a Dictionary) for performance.
  • 让这个主处理程序对象注册一个处理变量事件的处理程序对象的集合,每个变量事件对应一个。它们可以像原始模式一样链接,或者它们可以在快速集合(例如字典)中注册以获得性能。

  • Make this handler object dispatch the appropriate change to the registered handler.
  • 使此处理程序对象向已注册的处理程序分派适当的更改。

The "master" handler doesn't have to be a Singleton, its registry can depend on the parent view model itself.

“master”处理程序不必是Singleton,其注册表可以依赖于父视图模型本身。

Hope it's clear enough (sorry for not putting code)

希望它足够清楚(抱歉不放代码)

#5


0  

I solved this by just passing the ViewModel itself down into the ViewModels contained in it, here's a demo showing how it's done.

我通过将ViewModel本身传递到其中包含的ViewModel中来解决这个问题,这是一个演示如何完成的演示。

#1


5  

You can just have the parent VM connect to the PropertyChanged event on the child VMs. It's kind of a PITA to keep track of the children who have been added/removed etcetera so you might instead consider storing your child VMs in my ItemObservableCollection:

您可以让父虚拟机连接到子虚拟机上的PropertyChanged事件。这是一种PITA,用于跟踪已添加/删除的子项等,因此您可以考虑将子VM存储在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));
    }
}

Then your parent VM can just listen for all changes to child items with:

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

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

#2


8  

I think you should employ the mediator pattern which you can read about here.

我认为你应该使用你可以在这里阅读的中介模式。

Basically it is a static class that allows ViewModels(or any class for that matter) to communicate with each other and pass arguments back and forth.

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

Basically ViewModel A starts to listening for a certain message type(e.g. ViewModelBChanged) and whenever that event happens ViewModelB just notifies anyone who's listening to for this message type, it can also pass any information it wants.

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

Here's the skeleton of a mediator.

这是调解员的骨架。

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

    public static void NotifyColleagues(string message, object args);
}

ViewModel A would do this(probably in the constructor):

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

MyMediator.Register(ProcessMessage,"ViewModelBChanged")

and then would have to declare a function like this:

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

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

and ViewModel B would call this whenever it want to tell ViewModel A

只要想要告诉ViewModel A,ViewModel B就会调用它

MyMediator.NotifyColleagues("ViewModelBChanged",this);

The mediator class would be in charge of invoking the callback function of viewModel A. And then everyone is happy.

中介类将负责调用viewModel A的回调函数。然后每个人都很高兴。

Personally I like putting these string message values in a static class like this

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

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

So that you could do the following(instead of the above):

这样你就可以做到以下(而不是上面的):

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

If this is unclear just google MVVM mediator and click to your hearts content :)

如果这不清楚只是谷歌MVVM中介并点击你的心脏内容:)

#3


0  

The non-WPF way is to create a static event on DataTypeViewModel. This allows you to fire the event from DataTypeViewModel when appropriate (in a property setter or property changed handler). Of course, you'll also have to register a listener to the event in SmartForm (requiring SmartForm to know about the DataTypeViewModel type).

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

Alternatively I think you could create your own custom routed event.

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

#4


0  

Although Kent is right above, not all changes in child view models have to do with properties, some might be a little bit more semantic than that. In such cases, implementing a variation of the Chain of Responsibility pattern might be a good fit.

虽然肯特就在上面,但并非所有子视图模型的变化都与属性有关,有些可能比这更具语义性。在这种情况下,实施责任链模式的变体可能是一个很好的选择。

In short

  • Make all child view models aware of a "master handler object" that has a method to handle all sorts of change events. Communication with that object could be via events or messages, depending on the complexity of the changes.
  • 使所有子视图模型都知道“主处理程序对象”,该对象具有处理各种更改事件的方法。与该对象的通信可以通过事件或消息进行,具体取决于更改的复杂程度。

  • Have this master handler object register a collection of handler objects which will handle the change events, one for each one. They can be chained as in the original pattern or they can be registered in a fast collection (e.g. a Dictionary) for performance.
  • 让这个主处理程序对象注册一个处理变量事件的处理程序对象的集合,每个变量事件对应一个。它们可以像原始模式一样链接,或者它们可以在快速集合(例如字典)中注册以获得性能。

  • Make this handler object dispatch the appropriate change to the registered handler.
  • 使此处理程序对象向已注册的处理程序分派适当的更改。

The "master" handler doesn't have to be a Singleton, its registry can depend on the parent view model itself.

“master”处理程序不必是Singleton,其注册表可以依赖于父视图模型本身。

Hope it's clear enough (sorry for not putting code)

希望它足够清楚(抱歉不放代码)

#5


0  

I solved this by just passing the ViewModel itself down into the ViewModels contained in it, here's a demo showing how it's done.

我通过将ViewModel本身传递到其中包含的ViewModel中来解决这个问题,这是一个演示如何完成的演示。