Search code examples
c#wpfmvvmprismrouted-events

Bubbling RoutedEvent or RoutedCommand to a ViewModel


I've got a collection of ViewModels that are rendered as tabs using a style to pull out the relevant content to display on the tab:

public class TabViewModel : DependencyObject
{
      public object Content
      {
          get { return (object)GetValue(ContentProperty); }
          set
          {
              SetValue(ContentProperty, value);
          }
      }

}

Here's the TabControl:

<TabControl 
     ItemsSource={Binding MyCollectionOfTabViewModels}" 
     ItemContainerStyle="{StaticResource TabItemStyle}" />

And here's the style

<Style TargetType="TabItem" x:Key="TabItemStyle">
     <Setter Property="Content" Value="{Binding Content}"/>
</Style>

We are creating an instance of a usercontrol and setting the "Content" property of the TabViewModel to that so that the usercontrol gets displayed in the TabItem's Content area.

MyCollectionOfViewModels.Add(new TabViewModel() 
{ 
     Content = new MyUserControl();
});

My question is, I would like to allow a MyUserControl (or any of its sub controls) added to the TabViewModel's Content property to be allowed to raise an event that the TabViewModel handles.

Anyone know how I would do that?

We've experimented using RoutedEvents and RoutedCommands, but haven't been able to get anything to work 100% and have it be compatible with MVVM. I really think that this could be done with a RoutedEvent or RoutedCommand, but I don't seem to be able to get this to work.

Note: I've removed some of the relevant Prism-specific code, but if you are wondering why we do something so silly, it is because we are trying to stay control agnostic by using Prism's RegionManager.


Solution

  • You could add a State property to your TabViewModel, and check the DependencyPropertyChanged events.

    So imagine the following enum:

    public enum TabViewModelState
    {
        True,
        False,
        FileNotFound
    }
    

    Then add a State property to your TabViewModel of this enum:

    public static readonly DependencyProperty StateProperty =
        DependencyProperty.Register("State", typeof(TabViewModelState), typeof(TabViewModel), new PropertyMetadata(OnStateChanged));
    
    private static void OnStateChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        TabViewModel viewModel= (TabViewModel)obj;
    
        //Do stuff with your viewModel
    }
    

    Use a two-way binding to this property in your control:

    <CheckBox Checked="{Binding Path=State, Converter={StaticResource StateToBooleanConverter}, Mode=TwoWay}" />
    

    And last but not least implement the converter that will convert to and from the original value needed for the control.. (in my example boolean <--> TabViewModelState):

    public class StateToBooleanConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            TabViewModelState state = (TabViewModelState) value;
            return state == TabViewModelState.True;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            bool result = (bool) value;
            return result ? TabViewModelState.True : TabViewModelState.False;
        }
    }
    

    So now you have a State property that is managed by the UI, and throws changed events when you need to respond..

    Hope this helps!