tags:

views:

580

answers:

5

My MainView.xaml contains my SmartForm View:

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

the SmartForm view loads an ItemsControl

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

which is an ObservableCollection of 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 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?

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?

+3  A: 

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:

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();

HTH, Kent

Kent Boogaart
A: 

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

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

Lee Roth
A: 

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.

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

Román
A: 

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.

Edward Tanguay
A: 

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.

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.

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

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

MyMediator.NotifyColleagues("ViewModelBChanged",this);

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

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

Jose