目录
效果:
一、简介
在 WinForms 开发中,动态生成控件并绑定事件是常见的用法,在类似的需求中,一些程序员往往忽略了代码中事件 “-=” 的重要性,在清除这些动态生成的控件时,如果未移除控件对应的订阅事件,可能会导致事件重复触发或内存泄漏问题,对于内存泄漏问题,在国内很多 C# 中文的视频教程中,几乎很少有讲到,这可能也是部分程序员忽略的事实。
为了解决这个问题,我写了一个小 Demo,也许不一定的最好的解决方式,就算是一个参考案例吧。
二、实现效果
新建一个 Winform 项目,添加一个类 DynamicControlEventManager,代码:
using System;
using System.Collections.Generic;
using System.Windows.Forms;
public class DynamicControlEventManager
{
// 存储控件与事件及其处理程序的映射
//string 为事件名
//List<Delegate> 为事件名订阅的委托
private readonly Dictionary<Control, Dictionary<string, List<Delegate>>> ControlEventMap;
public DynamicControlEventManager()
{
ControlEventMap = new Dictionary<Control, Dictionary<string, List<Delegate>>>();
}
/// <summary>
/// 添加控件并为其绑定事件
/// </summary>
/// <param name="control">要添加的控件</param>
/// <param name="eventName">事件名称(如 "Click")</param>
/// <param name="handler">事件处理程序</param>
public void AddControlWithEvent(Control control, string eventName, Delegate handler)
{
if (control == null || string.IsNullOrEmpty(eventName) || handler == null)
throw new ArgumentException("控件、事件名称、处理程序 都不能为空!");
// 动态绑定事件
var eventInfo = control.GetType().GetEvent(eventName);
if (eventInfo == null)
throw new ArgumentException($"控件 {control.GetType().Name} 上不存在名为 {eventName} 的事件!");
eventInfo.AddEventHandler(control, handler);
// 记录控件与事件及其处理程序的映射
if (!ControlEventMap.ContainsKey(control))
{
ControlEventMap[control] = new Dictionary<string, List<Delegate>>();
}
if (!ControlEventMap[control].ContainsKey(eventName))
{
ControlEventMap[control][eventName] = new List<Delegate>();
}
ControlEventMap[control][eventName].Add(handler);
}
/// <summary>
/// 检查控件是否绑定了特定事件
/// </summary>
/// <param name="control">目标控件</param>
/// <param name="eventName">事件名称</param>
/// <returns>是否绑定</returns>
public bool HasEvent(Control control, string eventName)
{
return ControlEventMap.TryGetValue(control, out var eventHandlers) &&
eventHandlers.ContainsKey(eventName);
}
/// <summary>
/// 获取指定控件绑定的所有事件
/// </summary>
/// <param name="control">目标控件</param>
/// <returns>绑定的事件名称列表</returns>
public IEnumerable<string> GetControlEvents(Control control)
{
if (ControlEventMap.TryGetValue(control, out var eventHandlers))
{
return eventHandlers.Keys;
}
return Array.Empty<string>();
}
/// <summary>
/// 移除控件的指定事件
/// </summary>
/// <param name="control">目标控件</param>
/// <param name="eventName">事件名称(如 "Click")</param>
public void RemoveControlEvent(Control control, string eventName)
{
if (control == null || string.IsNullOrEmpty(eventName))
throw new ArgumentException("控件或事件名称不能为空!");
if (ControlEventMap.TryGetValue(control, out var eventHandlers) &&
eventHandlers.TryGetValue(eventName, out var handlers))
{
var eventInfo = control.GetType().GetEvent(eventName);
if (eventInfo == null)
throw new ArgumentException($"控件 {control.GetType().Name} 上不存在名为 {eventName} 的事件!");
foreach (var handler in handlers)
{
eventInfo.RemoveEventHandler(control, handler);
}
// 从映射中移除记录
eventHandlers.Remove(eventName);
if (eventHandlers.Count == 0)
{
ControlEventMap.Remove(control);
}
}
}
/// <summary>
/// 移除控件所有的事件
/// </summary>
/// <param name="control">目标控件</param>
public void RemoveControlAllEvent(Control control)
{
if (control == null)
{
throw new ArgumentNullException(nameof(control), "参数 control 不能为空");
}
if (!ControlEventMap.TryGetValue(control, out var eventHandlers))
{
Console.WriteLine($"没有找到当前控件,控件类型:{control.GetType().Name},控件名:{control.Name}");
return;
}
// 遍历并移除所有事件处理程序
foreach (var kvp in eventHandlers)
{
string eventName = kvp.Key;
List<Delegate> handlers = kvp.Value;
// 获取事件信息
var eventInfo = control.GetType().GetEvent(eventName);
if (eventInfo == null)
{
Console.WriteLine($"警告:控件 {control.GetType().Name} 上不存在名为 {eventName} 的事件,跳过...");
continue;
}
// 移除事件绑定
foreach (var handler in handlers)
{
eventInfo.RemoveEventHandler(control, handler);
}
Console.WriteLine($"已移除控件 {control.Name} 的事件:{eventName}");
}
// 最后从字典中移除该控件的记录
ControlEventMap.Remove(control);
Console.WriteLine($"已移除控件 {control.Name} 的所有事件记录");
}
/// <summary>
/// 清除控件绑定的所有事件
/// </summary>
public void ClearAllEvents()
{
foreach (var control in ControlEventMap.Keys)
{
foreach (var eventName in ControlEventMap[control].Keys)
{
var handlers = ControlEventMap[control][eventName];
var eventInfo = control.GetType().GetEvent(eventName);
if (eventInfo != null)
{
foreach (var handler in handlers)
{
eventInfo.RemoveEventHandler(control, handler);
}
}
}
}
ControlEventMap.Clear();
}
}
Winform 界面也没别的,就一个按钮
代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace 事件处理
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private DynamicControlEventManager EventManager;
private void Form1_Load(object sender, EventArgs e)
{
EventManager = new DynamicControlEventManager();
TestDynamicControls();
}
ComboBox comboBox;
Button button;
private void TestDynamicControls()
{
comboBox = new ComboBox
{
Location = new Point(10, 10),
Items = { "test1", "test2" },
SelectedIndex = 0
};
button = new Button
{
Location = new Point(50, 50),
Text = "测试"
};
this.Controls.Add(comboBox);
this.Controls.Add(button);
// 添加控件及事件
EventManager.AddControlWithEvent(comboBox, "SelectedIndexChanged", new EventHandler((s, e) =>
{
Console.WriteLine($"ComboBox SelectedIndexChanged, text: {comboBox.Text}");
}));
EventManager.AddControlWithEvent(comboBox, "MouseHover", new EventHandler((s, e) =>
{
Console.WriteLine("鼠标经过 MouseHover");
}));
EventManager.AddControlWithEvent(button, "Click", new EventHandler((s, e) =>
{
Console.WriteLine("Button Clicked");
}));
Console.WriteLine("控件事件已绑定!");
}
private void button1_Click(object sender, EventArgs e)
{
// 移除事件
EventManager.ClearAllEvents();
Console.WriteLine("所有事件已移除!");
}
}
}
效果:
1.事件的添加
在上面的案例中,已经演示了事件的添加,这里只需要写入对应的字符串就行了,比如点击事件,我们看控件对应的面版:
所以,代码是这样写:
EventManager.AddControlWithEvent(button, "Click", new EventHandler((s, e) =>
{
Console.WriteLine("Button Clicked");
}));
同样的,我随意添加了一个 MouseHover,在对应的事件中,也可以找到同样的名字
代码:
EventManager.AddControlWithEvent(comboBox, "MouseHover", new EventHandler((s, e) =>
{
Console.WriteLine("鼠标经过 MouseHover");
}));
ComboBox 控件的 SelectedIndexChanged 事件:
对应的代码:
EventManager.AddControlWithEvent(comboBox, "SelectedIndexChanged", new EventHandler((s, e) =>
{
Console.WriteLine($"ComboBox SelectedIndexChanged, text: {comboBox.Text}");
}));
2.事件的移除
事件的移除这里有三种方式:
1)移除控件的指定事件
使用方法 RemoveControlEvent 来完成这个需求。
将上面代码的 button1 按钮点击事件稍微做一点更改:
private void button1_Click(object sender, EventArgs e)
{
EventManager.RemoveControlEvent(button, "Click");
Console.WriteLine("已经移除指定的事件");
}
效果:
2)移除控件所有的绑定事件
使用方法 RemoveControlAllEvent 来完成这个需求,代码:
EventManager.RemoveControlAllEvent(comboBox);
只要指定一个控件,那么该控件使用 DynamicControlEventManager 类绑定的事件都会被移除
3)移除所有控件的绑定事件
使用方法 ClearAllEvents 来完成这个需求。
这个在第二节中的最开始的案例已经展示,这里就不展示了。
3.是否绑定了指定事件
可以使用 HasEvent 来进行判断,代码:
bool isHas = EventManager.HasEvent(button, "Click");
Console.WriteLine("是否绑定了 Click:{0}", isHas);
效果:
4.获取控件绑定的所有事件
比如,我想知道某一个控件使用当前 DynamicControlEventManager 类,绑定了那些事件呢?代码:
List<string> eventList = EventManager.GetControlEvents(comboBox).ToList();
Console.WriteLine(string.Join(",", eventList));
效果:
可以看到,这里绑定了两个事件
结束
如果这个帖子对你有所帮助,欢迎 关注 + 点赞 + 留言
end