3

I'm trying to copy every event handlers from one control to another (same type). I found several examples to do it with Winform but nothing for WPF...

<Window x:Class="WpfApp1.MainWindow"
    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"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">

    <StackPanel x:Name="panel">
        <Button x:Name="btn1" 
                Content="Button 01"/>
    </StackPanel>
</Window>


using System;
using System.Windows;
using System.Windows.Controls;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            btn1.Click += (s, e) => Console.WriteLine("Button pressed");

            AddButton();
        }

        private void AddButton()
        {
            Button btn2 = new Button() { Content = "Button 02" };
            panel.Children.Add(btn2);

            // Copy all event handler from btn1 to btn2 ??
        }
    }
}

The thing is that I don't have access to any handlers method name, as i'm suppose to copy the event handlers from another class...

Sugz
  • 97
  • 14
  • What do you mean by saying "copy every event handlers from one control to another (same type)"? Do you want that the `btn1` click handler handles the click event of `btn2` too? – Il Vic May 31 '17 at 11:22
  • `event`s are a C# concept, independend of the UI framework. If you found code for doing this with the `System.Windows.Forms.Button` class, why shouldn't that work with the `System.Windows.Controls.Button` class? – René Vogt May 31 '17 at 11:26
  • What I need is something that would produce something like that at the end: `btn2.Click += (s, e) => Console.WriteLine("Button pressed");` or any other event handlers that might have been assigned to btn1, and not just for the ClickEvent – Sugz May 31 '17 at 11:30
  • 1
    You still did not answer the question: what is wrong with the "several ex[a]mple[s] to do it with Winform"? There is no difference in the way events are subscribed between wpf and winforms. – René Vogt May 31 '17 at 11:33
  • I tried this post: [link](https://www.codeproject.com/Articles/308536/How-to-copy-event-handlers-from-one-control-to-ano) which seems to be exactly what I wanted but haven't been able to make it work for WPF... – Sugz May 31 '17 at 11:37
  • Also tried this one [link](https://stackoverflow.com/questions/293007/is-it-possible-to-steal-an-event-handler-from-one-control-and-give-it-to-anoth), but didn't manage to make it work either... – Sugz May 31 '17 at 11:49
  • Ah I see, so I may have been wrong. It seems it's not possible to access the registered handlers by reflecting the public event properties, and the winforms solution depends on the specific internal implementation of the backing `EventHandler` delegate field used for the public event properties. This implementation is indeed different in `System.Windows.Cotnrols`. And it's not trivial to implement. But maybe there is a better design for you which does not require these reflection hacks. If you add some context about _why_ you want to do that, someone might suggest a better overall approach. – René Vogt May 31 '17 at 11:50
  • I'll try to keep it short, but the goal here is to send a control with properties and events handlers that will be use as a model for a `FrameworkElementFactory`. I managed to copy the properties, but not the event handlers... You can find the whole thing here: [link](https://github.com/Sugz/SugzTools/blob/master/CSharp/SugzTools/Controls/SgzDataGrid/SgzDataGrid.cs). In last method: `public bool AddColumn(...)`. And yes it's quite weird, but at the end it's supposed to be used with maxscript (3ds max scripting language...) – Sugz May 31 '17 at 11:57
  • So it seems you have to go down that road: check the reference source and use reflection to access the internal delegate fields for the events (just like the winform solution does). Or hope someone comes up with a link to an existing solution. Anyway, the result will depend on an internal implementation that you don't control and maybe changed in future framework versions. – René Vogt May 31 '17 at 12:03
  • Then I'll continue to break my head on this until someone can help me :). Thanks – Sugz May 31 '17 at 12:09

1 Answers1

4

I finally found something that works here:

Here is the solution I'm using:

using System;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using BF = System.Reflection.BindingFlags;

namespace WpfApp1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            btn1.Click += (s, e) => Console.WriteLine($"{((Button)s).Content}a pressed");
            btn1.Click += Btn1_Click;
            btn1.MouseEnter += (s, e) => Console.WriteLine($"{((Button)s).Content} mouse entered");

            AddButton();
        }

        private void Btn1_Click(object sender, RoutedEventArgs e)
        {
            Console.WriteLine($"{((Button)sender).Content}b pressed");
        }


        private void AddButton()
        {
            Button btn2 = new Button() { Content = "Button 02" };
            panel.Children.Add(btn2);

            // Copy all event handler from btn1 to btn2 ??
            FieldInfo[] fields = btn1.GetType().GetFields(BF.Static | BF.NonPublic | BF.Instance | BF.Public | BF.FlattenHierarchy);
            foreach (FieldInfo field in fields.Where(x => x.FieldType == typeof(RoutedEvent)))
            {
                RoutedEventHandlerInfo[] routedEventHandlerInfos = GetRoutedEventHandlers(btn1, (RoutedEvent)field.GetValue(btn1));
                if (routedEventHandlerInfos != null)
                {
                    foreach (RoutedEventHandlerInfo routedEventHandlerInfo in routedEventHandlerInfos)
                        btn2.AddHandler((RoutedEvent)field.GetValue(btn1), routedEventHandlerInfo.Handler);
                }
            }
        }


        /// <summary>
        /// Get a list of RoutedEventHandlers
        /// Credit: Douglas : https://stackoverflow.com/a/12618521/3971575
        /// </summary>
        /// <param name="element"></param>
        /// <param name="routedEvent"></param>
        /// <returns></returns>
        public RoutedEventHandlerInfo[] GetRoutedEventHandlers(UIElement element, RoutedEvent routedEvent)
        {
            // Get the EventHandlersStore instance which holds event handlers for the specified element.
            // The EventHandlersStore class is declared as internal.
            PropertyInfo eventHandlersStoreProperty = typeof(UIElement).GetProperty("EventHandlersStore", BF.Instance | BF.NonPublic);
            object eventHandlersStore = eventHandlersStoreProperty.GetValue(element, null);

            // If no event handlers are subscribed, eventHandlersStore will be null.
            // Credit: https://stackoverflow.com/a/16392387/1149773
            if (eventHandlersStore == null)
                return null;

            // Invoke the GetRoutedEventHandlers method on the EventHandlersStore instance 
            // for getting an array of the subscribed event handlers.
            MethodInfo getRoutedEventHandlers = eventHandlersStore.GetType().GetMethod("GetRoutedEventHandlers", BF.Instance | BF.Public | BF.NonPublic);

            return (RoutedEventHandlerInfo[])getRoutedEventHandlers.Invoke(eventHandlersStore, new object[] { routedEvent });
        }

    }
}

With this, I'm assigning both handlers for the Click event and the one for MouseEntered event to the second button.

Il Vic
  • 5,576
  • 4
  • 26
  • 37
Sugz
  • 97
  • 14