Можно ли "украсть" обработчик событий из одного элемента управления и передать его другому?
Я хочу сделать что-то вроде этого:
Button btn1 = new Button();
btn1.Click += new EventHandler(btn1_Click);
Button btn2 = new Button();
// Take whatever event got assigned to btn1 and assign it to btn2.
btn2.Click += btn1.Click; // The compiler says no...
Где btn1_Click уже определен в классе:
void btn1_Click(object sender, EventArgs e)
{
//
}
Конечно, это не скомпилируется ("Событие 'System.Windows.Forms.Control.Click' может появляться только в левой части += или -="). Есть ли способ взять обработчик событий из одного элемента управления и назначить его другому во время выполнения? Если это невозможно, возможно ли дублировать обработчик событий и назначить его другому элементу управления во время выполнения?
Пара моментов: я какое-то время гуглил это и не нашел способа сделать это. Большинство попыток подхода включают рефлексию, поэтому, если вы читаете мой вопрос и думаете, что ответ невероятно очевиден, попробуйте сначала скомпилировать код в Visual Studio. Или, если ответ действительно очевиден, пожалуйста, не стесняйтесь дать мне пощечину. Спасибо, я действительно с нетерпением жду возможности увидеть это.
Я знаю, я мог бы просто сделать это:
btn2.Click += new EventHandler(btn1_Click);
Это не то, что я ищу здесь.
Это тоже не то, что я ищу:
EventHandler handy = new EventHandler(btn1_Click);
Button btn1 = new Button();
btn1.Click += handy;
Button btn2 = new Button();
btn2.Click += handy;
4 ответа
Да, это технически возможно. Отражение требуется, потому что многие из членов являются частными и внутренними. Запустите новый проект Windows Forms и добавьте две кнопки. Затем:
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.Reflection;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
button1.Click += new EventHandler(button1_Click);
// Get secret click event key
FieldInfo eventClick = typeof(Control).GetField("EventClick", BindingFlags.NonPublic | BindingFlags.Static);
object secret = eventClick.GetValue(null);
// Retrieve the click event
PropertyInfo eventsProp = typeof(Component).GetProperty("Events", BindingFlags.NonPublic | BindingFlags.Instance);
EventHandlerList events = (EventHandlerList)eventsProp.GetValue(button1, null);
Delegate click = events[secret];
// Remove it from button1, add it to button2
events.RemoveHandler(secret, click);
events = (EventHandlerList)eventsProp.GetValue(button2, null);
events.AddHandler(secret, click);
}
void button1_Click(object sender, EventArgs e) {
MessageBox.Show("Yada");
}
}
}
Если это убедит вас в том, что Microsoft действительно очень старалась помешать вам сделать это, вы поняли код.
Нет, ты не можешь этого сделать. Причина в инкапсуляции - события просто подписываются / отписываются, т.е. они не позволяют вам "заглянуть внутрь", чтобы увидеть, какие обработчики уже подписаны.
Что вы могли бы сделать, это извлечь из Button и создать публичный метод, который вызывает OnClick
, Тогда вам просто нужно сделать btn1
экземпляр этого класса, и подписать обработчик на btn2
какие звонки btn1.RaiseClickEvent()
или как вы называете метод.
Я не уверен, что я действительно рекомендую это все же. Что ты на самом деле пытаешься сделать? Какая картина больше?
РЕДАКТИРОВАТЬ: я вижу, что вы приняли версию, которая выбирает текущий набор событий с отражением, но в случае, если вы заинтересованы в альтернативе, которая вызывает обработчик OnXXX в исходном элементе управления, у меня есть образец здесь. Я изначально скопировал все события, но это действительно приводит к очень странным эффектам. Обратите внимание, что эта версия означает, что если кто-либо подписывается на событие в исходной кнопке после вызова CopyEvents, он все еще "подключен" - то есть не имеет значения, когда вы связываете эти два.
using System;
using System.Drawing;
using System.Reflection;
using System.Windows.Forms;
class Test
{
static void Main()
{
TextBox output = new TextBox
{
Multiline = true,
Height = 350,
Width = 200,
Location = new Point (5, 15)
};
Button original = new Button
{
Text = "Original",
Location = new Point (210, 15)
};
original.Click += Log(output, "Click!");
original.MouseEnter += Log(output, "MouseEnter");
original.MouseLeave += Log(output, "MouseLeave");
Button copyCat = new Button
{
Text = "CopyCat",
Location = new Point (210, 50)
};
CopyEvents(original, copyCat, "Click", "MouseEnter", "MouseLeave");
Form form = new Form
{
Width = 400,
Height = 420,
Controls = { output, original, copyCat }
};
Application.Run(form);
}
private static void CopyEvents(object source, object target, params string[] events)
{
Type sourceType = source.GetType();
Type targetType = target.GetType();
MethodInfo invoker = typeof(MethodAndSource).GetMethod("Invoke");
foreach (String eventName in events)
{
EventInfo sourceEvent = sourceType.GetEvent(eventName);
if (sourceEvent == null)
{
Console.WriteLine("Can't find {0}.{1}", sourceType.Name, eventName);
continue;
}
// Note: we currently assume that all events are compatible with
// EventHandler. This method could do with more error checks...
MethodInfo raiseMethod = sourceType.GetMethod("On"+sourceEvent.Name,
BindingFlags.Instance |
BindingFlags.Public |
BindingFlags.NonPublic);
if (raiseMethod == null)
{
Console.WriteLine("Can't find {0}.On{1}", sourceType.Name, sourceEvent.Name);
continue;
}
EventInfo targetEvent = targetType.GetEvent(sourceEvent.Name);
if (targetEvent == null)
{
Console.WriteLine("Can't find {0}.{1}", targetType.Name, sourceEvent.Name);
continue;
}
MethodAndSource methodAndSource = new MethodAndSource(raiseMethod, source);
Delegate handler = Delegate.CreateDelegate(sourceEvent.EventHandlerType,
methodAndSource,
invoker);
targetEvent.AddEventHandler(target, handler);
}
}
private static EventHandler Log(TextBox output, string text)
{
return (sender, args) => output.Text += text + "\r\n";
}
private class MethodAndSource
{
private readonly MethodInfo method;
private readonly object source;
internal MethodAndSource(MethodInfo method, object source)
{
this.method = method;
this.source = source;
}
public void Invoke(object sender, EventArgs args)
{
method.Invoke(source, new object[] { args });
}
}
}
Я немного покопался с решением @nobugz и придумал эту универсальную версию, которая могла бы использоваться на большинстве объектов общего назначения.
Я обнаружил, что события для, осмелюсь сказать, автоматических событий на самом деле компилируются с полем делегата поддержки с тем же именем:
Так что вот для кражи обработчиков событий для более простых объектов:
class Program
{
static void Main(string[] args)
{
var d = new Dummy();
var d2 = new Dummy();
// Use anonymous methods without saving any references
d.MyEvents += (sender, e) => { Console.WriteLine("One!"); };
d.MyEvents += (sender, e) => { Console.WriteLine("Two!"); };
// Find the backing field and get its value
var theType = d.GetType();
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
var backingField = theType.GetField("MyEvents", bindingFlags);
var backingDelegate = backingField.GetValue(d) as Delegate;
var handlers = backingDelegate.GetInvocationList();
// Bind the handlers to the second instance
foreach (var handler in handlers)
d2.MyEvents += handler as EventHandler;
// See if the handlers are fired
d2.DoRaiseEvent();
Console.ReadKey();
}
}
class Dummy
{
public event EventHandler MyEvents;
public void DoRaiseEvent() { MyEvents(this, new EventArgs()); }
}
Думал, что это может быть полезно для некоторых.
Но учтите, что способы передачи событий в компонентах Windows Forms несколько иные. Они оптимизированы таким образом, что несколько событий не занимают много памяти, просто удерживая нули. Так что нужно еще немного покопаться, но @nobugz уже сделал это:-)
Статья " Делегаты" и события, связанные с объединенными делегатами, могут помочь прояснить многие моменты в ответах.
Вы можете использовать общий обработчик событий для кнопок и графических блоков (согласно комментариям к предыдущему ответу), а затем использовать объект "отправитель", чтобы определить, как обрабатывать событие во время выполнения.