C# Winform 控件事件的移除处理

目录

一、简介

二、实现效果

1.事件的添加

2.事件的移除

1)移除控件的指定事件

2)移除控件所有的绑定事件

3)移除所有控件的绑定事件

3.是否绑定了指定事件

4.获取控件绑定的所有事件

结束


效果:

一、简介

在 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

熊思宇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值