S...">

How to bubble changes in my ViewModel hierarchy?

My MainView.xaml contains my SmartForm view:

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

SmartForm view loads an ItemsControl

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

which is an ObservableCollection for DataTypeViews:

 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 a label and a text field that creates the 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 can I bubble textbox changes that occur in a DataTypeViewModel into a SmartFormViewModel?

Or, in other words: If ViewModel A contains a collection of ViewModel B and the change occurs in ViewModel B, how can I bubble this change before ViewModel A?

+4
source share
5 answers

You can simply connect the parent virtual machine to the PropertyChanged event on the child virtual machines. This is a kind of PITA to keep track of children that have been added / removed and so on, so that you can instead consider storing your child virtual machines in my 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 virtual machine can simply listen to all changes to the child elements with:

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

I think you should use a reseller template which you can read here .

This is basically a static class that allows ViewModels (or any class, for that matter) to interact with each other and pass arguments back and forth.

Basically, ViewModel A starts listening to a specific type of message (e.g. ViewModelBChanged), and whenever this event occurs, ViewModelB just notifies everyone who listens to this type of message, it can also transmit any information it wants.

Here is the skeleton of the pick.

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

ViewModel A will do this (possibly in the constructor):

 MyMediator.Register(ProcessMessage,"ViewModelBChanged") 

and then you need to declare a function like this:

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

and ViewModel B will call this when it wants to tell ViewModel A

 MyMediator.NotifyColleagues("ViewModelBChanged",this); 

The mediation class will be responsible for calling the viewModel A callback function. And then everyone is happy.

Personally, I like to put these string values ​​in a static class, like this

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

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

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

If this is unclear, just google MVVM broker and click on your content :)

+8
source

A way other than WPF is to create a static event in the DataTypeViewModel. This allows you to activate the event from the DataTypeViewModel if necessary (in the property adjuster or property change handler). Of course, you will also have to register the listener in the event in SmartForm (requiring SmartForm to learn about the DataTypeViewModel type).

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

0
source

Despite the fact that Kent is higher, not all changes in child viewing models are related to properties, some may be a little more semantic than that. In such cases, the implementation of the Chain of Responsibility pattern variation may be appropriate.

In short

  • Make all child view models aware of the "main handler object", which has a method for handling all kinds of change events. Communication with this object can be through events or messages, depending on the complexity of the changes.
  • If this master handler object registers a collection of handler objects that will handle change events, one for each of them. They can be chained, as in the original template, or they can be registered in a quick collection (for example, in a dictionary) to improve performance.
  • Make this handler object dispatch the appropriate change to the registered handler.

The "master" handler should not be Singleton, its registry may depend on the parent view model itself.

Hope this is clear enough (sorry for not setting the code)

0
source

I solved this by simply passing the ViewModel itself to the ViewModels contained in it, here is a demo showing how this is done .

0
source

Source: https://habr.com/ru/post/1285907/


All Articles