How to bind a custom routed event to the view model in a MVVM pattern?

ComptonAlvaro 166 Reputation points
2022-04-01T15:03:56.203+00:00

I have a user control that has a datagrid. This user control is used in a main view and I want to know in the main view model when a user doble click an item in the datagrid of the user control.

I would like to use the most strict MVVM pattern.

I think that a solution it could be to use routed events, in this way.

The code behind in my user control, to declare the routed event.

public static readonly RoutedEvent ItemDobleClickEvent = EventManager.RegisterRoutedEvent(
    "ItemDobleClick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyUSerControl));

// Provide CLR accessors for the event
public event RoutedEventHandler ItemDobleClick
{
    add { AddHandler(CItemDobleClickEvent, value); }
    remove { RemoveHandler(ItemDobleClickEvent, value); }
}


void RaiseItemDobleClickEvent(MyType? paramItem)
{
    // Create a RoutedEventArgs instance.
    RoutedEventArgs routedEventArgs = new(routedEvent: ItemDobleClickEvent);

    // Raise the event, which will bubble up through the element tree.
    RaiseEvent(routedEventArgs);
}

This is the main view, that uses the user control:

<local:ucComponentesBaseView x:Name="MyControl" Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
                                ItemDobleClick="{Binding ItemDobleClickCommand}"/>

This is the main view model:

private RelayCommand? _itemDobleClickCommand;
public RelayCommand ItemDobleClickCommand
{
    get { return _itemDobleClickCommand ?? (_itemDobleClickCommand = new RelayCommand(param => ItemDobleClickCommandHandler(), param => true)); }
}


private void ItemDobleClickCommandHandler()
{
    //TODO
}

But I get the following error: "InvalidCastException: Unable to cast object of type 'System.Reflection.RuntimeEventInfo' to type 'System.Reflection.MethodInfo'.".

I don't know if really the routed events are the best way to solve it or if there is another options. If there is better options, I would thank to know, but I would like to know why this solution fails in the binding.

Thanks.

Developer technologies | Windows Presentation Foundation
Developer technologies | XAML
Developer technologies | XAML
A language based on Extensible Markup Language (XML) that enables developers to specify a hierarchy of objects with a set of properties and logic.
0 comments No comments
{count} votes

Answer accepted by question author
  1. Peter Fleischer (former MVP) 19,341 Reputation points
    2022-04-03T10:52:43.343+00:00

    Hi,
    what the best solution is, you have to derive from the specific task. See my demo:

    ----- MainWindow
    
    <Window x:Class="WpfApp1.Window011"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1"
            xmlns:vm="clr-namespace:WpfApp011"
            xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
            mc:Ignorable="d"
            Title="Window011" Height="450" Width="800">
      <Window.DataContext>
        <vm:ViewModel/>
      </Window.DataContext>
      <StackPanel>
        <local:Window011UC1 x:Name="MyControl" Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
                                                            ItemDobleClickCommand="{Binding ItemDobleClickCommand}" />
        <local:Window011UC2>
          <i:Interaction.Triggers>
            <i:EventTrigger EventName="ItemDobleClick">
              <i:InvokeCommandAction Command="{Binding ItemDobleClickCommand}"/>
            </i:EventTrigger>
          </i:Interaction.Triggers>
        </local:Window011UC2>
      </StackPanel>
    </Window>
    
    ----- ViewModel
    
    using System;
    using System.Windows;
    using System.Windows.Input;
    
    namespace WpfApp011
    {
      public class ViewModel
      {
        private RelayCommand? _itemDobleClickCommand;
        public RelayCommand ItemDobleClickCommand
        {
          get { return _itemDobleClickCommand ?? (_itemDobleClickCommand = new RelayCommand(param => ItemDobleClickCommandHandler(), param => true)); }
        }
    
    
        private void ItemDobleClickCommandHandler()
        {
          MessageBox.Show("ItemDobleClick");
        }
      }
    
      public class RelayCommand : ICommand
      {
        private readonly Predicate<object> _canExecute;
        private readonly Action<object> _action;
        public RelayCommand(Action<object> action) { _action = action; _canExecute = null; }
        public RelayCommand(Action<object> action, Predicate<object> canExecute) { _action = action; _canExecute = canExecute; }
        public void Execute(object o) => _action(o);
        public bool CanExecute(object o) => _canExecute == null ? true : _canExecute(o);
        public event EventHandler CanExecuteChanged
        {
          add { CommandManager.RequerySuggested += value; }
          remove { CommandManager.RequerySuggested -= value; }
        }
      }
    }
    
    ----- UserControl with ICommand property 
    
    <UserControl x:Class="WpfApp1.Window011UC1"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:WpfApp1"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
      <Grid>
        <Label Content="DoubleClick UC1" MouseDoubleClick="RaiseItemDobleClickEvent"/>
      </Grid>
    </UserControl>
    
    ----- CodeBehind of UserControl
    
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    
    namespace WpfApp1
    {
      /// <summary>
      /// Interaction logic for Window011UC1.xaml
      /// </summary>
      public partial class Window011UC1 : UserControl
      {
        public Window011UC1()
        {
          InitializeComponent();
        }
    
        void RaiseItemDobleClickEvent(object sender, MouseButtonEventArgs e)
        {
          ItemDobleClickCommand.Execute(null);
        }
    
        public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("ItemDobleClickCommand", typeof(ICommand), typeof(Window011UC1));
        // Declare a get accessor method.
        public static ICommand GetItemDobleClickCommand(UIElement target) => (ICommand)target.GetValue(CommandProperty);
        // Declare a set accessor method.
        public static void SetItemDobleClickCommand(UIElement target, ICommand value) => target.SetValue(CommandProperty, value);
    
    
        public ICommand ItemDobleClickCommand
        {
          get { return (ICommand)GetValue(CommandProperty); }
          set { SetValue(CommandProperty, value); }
        }
    
        public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("ItemDobleClickCommandParameter", typeof(object), typeof(Window011UC1));
        public object ItemDobleClickCommandParameter
        {
          get { return (object)GetValue(CommandParameterProperty); }
          set { SetValue(CommandParameterProperty, value); }
        }
      }
    }
    
    ----- UserControl with Event
    
    <UserControl x:Class="WpfApp1.Window011UC2"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:WpfApp1"
                 mc:Ignorable="d" 
                 d:DesignHeight="450" d:DesignWidth="800">
      <Grid>
        <Label Content="DoubleClick UC2" MouseDoubleClick="RaiseItemDobleClickEvent"/>
      </Grid>
    </UserControl>
    
    ----- CodeBehind of UserControl
    
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    
    namespace WpfApp1
    {
      /// <summary>
      /// Interaction logic for Window011UC2.xaml
      /// </summary>
      public partial class Window011UC2 : UserControl
      {
        public Window011UC2()
        {
          InitializeComponent();
        }
    
        public static readonly RoutedEvent ItemDobleClickEvent = EventManager.RegisterRoutedEvent(
        "ItemDobleClick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Window011UC2));
    
        // Provide CLR accessors for the event
        public event RoutedEventHandler ItemDobleClick
        {
          add { AddHandler(ItemDobleClickEvent, value); }
          remove { RemoveHandler(ItemDobleClickEvent, value); }
        }
    
    
        void RaiseItemDobleClickEvent(object sender, MouseButtonEventArgs e)
        {
          // Create a RoutedEventArgs instance.
          RoutedEventArgs routedEventArgs = new(routedEvent: ItemDobleClickEvent);
    
          // Raise the event, which will bubble up through the element tree.
          RaiseEvent(routedEventArgs);
        }
      }
    

1 additional answer

Sort by: Most helpful
  1. Peter Fleischer (former MVP) 19,341 Reputation points
    2022-04-02T04:55:39.92+00:00

    HI,
    if you want to use RoutedEvent in UserControl you must use attached behavior or Interactivity.dll in ViewModel. For your purposes you can use DependencyProperty in UserControl and Binding to RelayCommand in MainWindow:

        void RaiseItemDobleClickEvent(object paramItem)
        {
          ItemDobleClickCommand.Execute(paramItem);
        }
    
        public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("ItemDobleClickCommand", typeof(ICommand), typeof(MyUSerControl));
        // Declare a get accessor method.
        public static ICommand GetItemDobleClickCommand(UIElement target) => (ICommand)target.GetValue(CommandProperty);
        // Declare a set accessor method.
        public static void SetItemDobleClickCommand(UIElement target, ICommand value) => target.SetValue(CommandProperty, value);
    
    
        public ICommand ItemDobleClickCommand
        {
          get { return (ICommand)GetValue(CommandProperty); }
          set { SetValue(CommandProperty, value); }
        }
    
    ...
    
     <local:ucComponentesBaseView x:Name="MyControl" Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
                                     ItemDobleClickCommand="{Binding ItemDobleClickCommand}"/>
    

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.