Search code examples
wpfcustom-controlsrouted-events

Mixing events from CustomControls, in an adorner, with a Viewmodel


I've been going back and forth over the pros and cons of the two following approaches to Events from custom controls. My debate basically revolves around how much "logic" should be placed within a custom (not user) control and to best get events into a viewmodel.

The "control", DataGridAnnotationControl, resides within an adorner to my data grid. The goal here is to respond to the user selecting an item from a combobox displayed within the custom control.

The first example, Example #1, uses a pretty standard custom event in the DataGridAnnotationControl which is then mapped by way of the adorner to the target AppointmentEditor (viewmodel). My biggest complaint with this is the obvious dependency to the (AppointmentEditor) from the adorner to achieve proper event routing.

♦ Example #1:
            ♦ CustomControl  DataGridAnnotationControl
                     public override void OnApplyTemplate()
                     {
                     ......
                                _cboLastName.SelectionChanged += _cboLastName_SelectionChanged;
                     }

                     private void _cboLastName_SelectionChanged(object sender, SelectionChangedEventArgs e)
                    {
                        RaiseSelectionChanged();
                    }

                    public event Action SelectionChanged;
                    public void RaiseSelectionChanged()
                    {
                        SelectionChanged?.Invoke();
                    }


            ♦ Adorner  DataGridAnnotationAdorner
                    public DataGridAnnotationAdorner(DataGrid adornedDataGrid)
                        : base(adornedDataGrid)
                    {
                    ......
                        Control = new DataGridAnnotationControl();
                        this.SelectionChanged += ((AppointmentEditor)adornedDataGrid.DataContext).SelectionChanged;    <--This requires a reference to Patient_Registration.Editors. THIS IS FORCING 
                                                                                                                        A DEPENDENCY ON THE PATIENT_REGISTRATION PROJECT.
                    }

                     public event Action SelectionChanged
                    {
                        add { Control.SelectionChanged += value; }
                        remove { Control.SelectionChanged -= value; }
                    }

            ♦ AppointmentEditor
                    public void SelectionChanged()
                    {
                        throw new NotImplementedException();
                    }

Example #2 This example uses pretty standard event routing up to the mainwindow from which an event aggregator is being used to hit the AppointmentEditor as a subscriber to the event. My biggest complaint here is all the additional code needed (over Example #1). In addition, it seems like a complicating factor to climb the visual tree just to jump into the one viewmodel designed to support this customcontrol.

Example #2:
            ♦  CustomControl  DataGridAnnotationControl
                     public override void OnApplyTemplate()
                     {
                     .....
                      _cboLastName.SelectionChanged += _cboLastName_SelectionChanged;
                     }


                     private void _cboLastName_SelectionChanged(object sender, SelectionChangedEventArgs e)
                    {
                        RaisePatientNameSelectionChangedEvent();
                    }


                    public static readonly RoutedEvent PatientNameSelectionChangedEvent = EventManager.RegisterRoutedEvent(
                          "PatientNameSelectionChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DataGridAnnotationControl));

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

                    protected virtual void RaisePatientNameSelectionChangedEvent()
                    {
                        RoutedEventArgs args = new RoutedEventArgs(DataGridAnnotationControl.PatientNameSelectionChangedEvent);
                        RaiseEvent(args);
                    }


            ♦    public partial class MainWindow : Window
                    {
                         public MainWindow(IMainWindowViewModel mainWindowViewModel, EventAggregator eventAggregator)
                        {
                            InitializeComponent();

                            EventAggregator = eventAggregator;

                            DataContext = mainWindowViewModel;

                            ....
                            AddHandler(DataGridAnnotationControl.PatientNameSelectionChangedEvent, new RoutedEventHandler(PatientNameSelectionChangedHandler));
                        }


                        private void PatientNameSelectionChangedHandler(object sender, RoutedEventArgs e)
                        {
                            EventAggregator.PublishEvent( new PatientNameSelected() );
                        }

                    }

            ♦    public class AppointmentEditor : INotifyPropertyChanged, ISubscriber<PatientNameSelected>  

                        public void OnEventHandlerAsync(PatientNameSelected e)
                        {
                            throw new NotImplementedException();
                        }

Is there a preferred way of doing this?

TIA


Solution

  • Ideally, your custom control should have no knowledge of your view-models.

    Using MVVM, you would bind an event in your custom control to a command in your view-model.

    I author and maintain tons of custom controls that are used by a lot of other teams. I always expose an associated ICommand with any event to make it easy for MVVM users to use my controls in the easiest way possible.