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

5

我的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?

5个回答

8

我认为你应该采用中介者模式(这里可以阅读相关内容)

基本上,它是一个静态类,允许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中介者并点击您的心愿内容即可 :)

7

您可以让父VM连接到子VM的PropertyChanged事件。跟踪已添加/删除的子项有点麻烦,因此您可以考虑将子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));
    }
}

然后你的父级 VM 只需使用以下代码监听子项的所有更改:
_formFields.ItemPropertyChanged += (s, e) => Foo();

0

非 WPF 的方法是在 DataTypeViewModel 上创建一个静态事件。这样,当适当时(在属性设置器或属性更改处理程序中),您可以从 DataTypeViewModel 触发事件。当然,您还需要在 SmartForm 中注册事件侦听器(需要 SmartForm 知道 DataTypeViewModel 类型)。

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


0

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

简而言之

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

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

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


0

哦...但这是野蛮的方式! - alerya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接