c#笔记5 详解事件的内置类型EventHandler、windows事件在winform中的运用

news2024/11/15 19:47:58

为什么要研究这一问题?

事件和委托可以说是息息相关。 前面先解释了什么是委托,怎么定义一个委托以及怎么使用匿名方法来内联地新建委托。

事实上事件这一机制在c#的程序开发中展很重要的地位,尤其是接触了winform软件开发的同学们应该都知道界面上的各种操作和事件比如点击事件,文本改变事件等等,都是我们建立用户界面,乃至于实现具体功能的重要手段。于是我们有必要研究这一机制在c#中的默认类型。

EventHandler类

这一类型是.net类库中预定义的一个委托类型,建议我们使用该类型作为事件的委托类型。事实上我们的桌面端winform开发中的事件就是此类。

以我最近写的例子来说,添加了点击事件相当于在按钮的点击事件中添加新的引用,也就是说,按钮是发布者类,其含有一个事件(委托)click,我们写的程序中写的button2_Click函数就作为其响应的方法,注意,这里的订阅操作是默认在设计代码中就完成了,如果你不在设计界面修改点击事件,需要自己操作你的方法对点击事件的引用和订阅。同样的,这里的click事件也只能由按钮本身触发,而不能手动在代码写一个click()之类的,事实上它的原型应该需要的参数和我们的EventHandler一样;

图1:按钮的点击方法对点击事件的订阅 

图2:click方法的定义以及两个访问器

图3:事件本质是对委托的封装

 add和remove是特殊的访问器。

图4:与EventHandler类相匹配的方法

图5:EventHandler类的定义

如此可以了解该类与委托和事件的关系。他是经过了访问器修改的委托类型,是定义事件的推荐类型。

EventHandler类如何使用

该类具有两个实例化参数,对于一个委托而言,这意味着其对应引用的方法需要与该模式匹配。

第一个参数object sender

这就是委托的触发者,或者说事件的触发者,在事件机制下,这一对象只能是发布者类自己的对象。比如按钮的点击事件就只能是按钮本身触发。

触发时传给该参数的值通常为this,注意这里的参数并不是我们写方法时要传入的,而是事件触发时传入的。

比如我们的click事件原型来自于control类的:

    protected virtual void OnClick(EventArgs e)
    {
        ((EventHandler)base.Events[EventClick])?.Invoke(this, e);
    }

button->buttonbase->control

定义来源如上所示。

也就是说这里的sender是触发时,由发布者类或者说触发者提供给我们的信息:发布者本身的引用。我们凭借这一个引用可以获取触发者的各种信息和方法。

在实际使用中我们一般不需要特别修改这个逻辑,或者说我们如果自定义一个事件,也要类似这样定义这一参数。

第二个参数EventArgs e

这一参数就是真正触发事件时用于传参的结构了,虽然只是一个参数,但是通过建立新的类来继承EventArgs,之后再实例化我们自己的参数类,就可以用于传递我们想要的参数结构了,但是需要注意的是:这不意味着我们必须要传参,这个类内置empty字段,允许我们使用此字段作为参数传递。

public class MyButton
{
    public event EventHandler Click;

    public void OnClick()
    {
        // 触发 Click 事件,不传递任何数据
        Click?.Invoke(this, EventArgs.Empty);
    }
}

传参的例子:

// TimerEventArgs.cs
public class TimerEventArgs : EventArgs
{
    public int ElapsedSeconds { get; private set; }

    public TimerEventArgs(int elapsedSeconds)
    {
        ElapsedSeconds = elapsedSeconds;
    }
}

// Timer.cs
public class Timer
{
    public event EventHandler<TimerEventArgs> SecondElapsed;

    private int seconds;

    public Timer()
    {
        // 假设这里有一些逻辑来启动计时器
    }

    // 这个方法模拟计时器每秒触发一次事件
    public void Tick()
    {
        seconds++;
        OnSecondElapsed(seconds);
    }

    protected virtual void OnSecondElapsed(int elapsedSeconds)
    {
        SecondElapsed?.Invoke(this, new TimerEventArgs(elapsedSeconds));
    }
}

 可以看到我们定义了一个 TimerEventArgs类来传递参数,包含一个只读字段和构造函数,在触发者类中给出初始化参数的方法以及触发我们的SecondElapsed事件时进行传递,在下文的主函数中,我们实例化发布者类,然后订阅了我们定义的private static void Timer_SecondElapsed函数(这一操作也可以说是我们的方法订阅了这个事件),或者说我们的方法添加到了委托中。之后就是让我们的发布者类触发十次该事件。

public class Program
{
    public static void Main()
    {
        Timer timer = new Timer();
        timer.SecondElapsed += Timer_SecondElapsed;

        // 模拟计时器运行
        for (int i = 0; i < 10; i++)
        {
            timer.Tick();
            Thread.Sleep(1000); // 等待一秒钟
        }
    }

    private static void Timer_SecondElapsed(object sender, TimerEventArgs e)
    {
        Console.WriteLine($"Timer elapsed {e.ElapsedSeconds} seconds.");
    }
}

可以看到,在我们的事件响应方法中使用到了我们传入的参数,并读取了那个代表已经过去时间的只读字段。

注意咯:如果我们在事件处理中不是定义了这样:

 private static void Timer_SecondElapsed(object sender, TimerEventArgs e)

而是这样:

 private static void Timer_SecondElapsed(object sender, EventArgs e)

那么传入参数之后还需要进行强转哦:

private static void Timer_SecondElapsed(object sender, EventArgs e)
{
    Console.WriteLine($"Timer elapsed {(TimerEventArgs)e.ElapsedSeconds} seconds.");
}

以我粗浅的见识来看,两个方法都可以。

Windows事件

这一话题我曾经非常感兴趣,这里的Windows事件与广义的操作系统中的机制不同,我们谈论的是我们触发事件的条件与我们在Windows操作系统中的操作有关时,我们就可以理解为我们这里说的Windows事件。

比如我们在图形界面中点击了一个按钮,我们都知道这样一个点击事件会被触发,但是怎么触发的呢,一定有什么东西接收了鼠标的操作,最后由什么东西传递给了我们程序,然后程序调用了这个委托事件,然后我们写的代码才得以执行。

在Windows Forms应用程序中,按钮(Button)控件的Click事件最初是由Windows操作系统通过消息队列触发的。当用户点击按钮时,操作系统会生成一个消息,并将其放入应用程序的消息队列中。这个消息通常具有特定的消息代码(如WM_LBUTTONDOWN、WM_LBUTTONUP等),表示鼠标按钮被按下和释放。

Windows Forms控件的WndProc方法负责处理这些消息。WndProc是控件基类(通常是Control类)的一个方法,它负责处理发送到控件的所有Windows消息。当控件接收到与按钮点击相关的消息时,它会根据消息的内容执行相应的操作,并可能触发相应的Click事件。

 

图6:button的触发事件

下面为接收操作系统消息之后触发事件的代码:

 protected override void WndProc(ref Message m)
 {
     int msg = m.Msg;
     if (msg == 245)
     {
         if (this is IButtonControl)
         {
             ((IButtonControl)this).PerformClick();
         }
         else
         {
             OnClick(EventArgs.Empty);
         }

         return;
     }

     if (OwnerDraw)
     {
         switch (m.Msg)
         {
             case 8:
             case 31:
             case 533:
                 if (!GetFlag(8) && GetFlag(4))
                 {
                     SetFlag(4, value: false);
                     if (GetFlag(2))
                     {
                         SetFlag(2, value: false);
                         Invalidate(DownChangeRectangle);
                     }
                 }

                 base.WndProc(ref m);
                 break;
             case 514:
             case 517:
             case 520:
                 try
                 {
                     SetFlag(8, value: true);
                     base.WndProc(ref m);
                     break;
                 }
                 finally
                 {
                     SetFlag(8, value: false);
                 }
             default:
                 base.WndProc(ref m);
                 break;
             case 243:
                 break;
         }

         return;
     }

     int msg2 = m.Msg;
     if (msg2 == 8465)
     {
         if (NativeMethods.Util.HIWORD(m.WParam) == 0 && !base.ValidationCancelled)
         {
             OnClick(EventArgs.Empty);
         }
     }
     else
     {
         base.WndProc(ref m);
     }
 }

 最后能够溯源到control类的同名函数:

具体的逻辑需要一层层剖析,但是不可否认,这就是我们能够通过外设操作计算机程序的关键。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2102463.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chapter12-异常(Exception)——(注解)——day14

444-异常处理入门 445-异常基本介绍 446-异常体系图 虚线代表 实现接口&#xff0c;实线代表继承 447-五大运行时异常 448-异常课堂练习 449-异常处理机制 450-tryCatch异常处理 1&#xff09;如果异常发送&#xff0c;则异常发生后面的代码不会执行&#xff0c;直接进入到Catc…

接口报错403 Forbidden 【已解决】

接口报错403 Forbidden 【已解决】 在Web开发中&#xff0c;接口请求错误是开发者经常遇到的问题之一。其中&#xff0c;403 Forbidden错误尤为常见&#xff0c;它表明服务器理解了客户端的请求&#xff0c;但是拒绝执行此请求。本文将深入探讨接口请求403 Forbidden错误&#…

iMazing 3官方中文版软件新功能全面解析,最好用的ios设备管理软件

iMazing 3是一款专为iOS设备设计的全面管理软件&#xff0c;想要更换设备的用户&#xff0c;iMazing 3的数据迁移功能能确保无缝切换。iMazing 3不仅提供了强大的备份和恢复功能&#xff0c;确保用户数据安全无虞&#xff0c;还实现了设备与电脑间的高效文件传输。无论是照片、…

浅析SVG无功补偿器在新能源发电中的应用

引言 随着新能源技术的不断进步&#xff0c;光伏发电已经崛起为未来能源发展的一个关键领域&#xff0c;各地纷纷建立起越来越多的光伏电站。然而&#xff0c;光伏发电领域仍面临诸多挑战&#xff0c;包括电网电压不稳定、发电过剩以及电压波动等问题。在这样的背景下&#xf…

《零散知识点 · Kafka 知识拓展》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

【教学类-56-04】数感训练——数字04(中2班寻找自己的学号数字,1号-29号,出现5-10\10-15\15-20次)

背景需求&#xff1a; 今天有个客户购买“学号版的数感训练” 我找到文件夹&#xff0c;发现里面没有1-40号的学号数感&#xff0c;只有上学期为重4班制作的1-31号&#xff08;其中缺了1和7号&#xff09;的数感训练模版 于是用代码重新生成 【教学类-56-03】数感训练——数字…

HarmonyOS开发实战( Beta5版)Swiper高性能开发指南

背景 在应用开发中&#xff0c;Swiper 组件常用于翻页场景&#xff0c;比如&#xff1a;桌面、图库等应用。Swiper 组件滑动切换页面时&#xff0c;基于按需加载原则通常会在下一个页面将要显示时才对该页面进行加载和布局绘制&#xff0c;这个过程包括&#xff1a; 如果该页面…

学习算法需要数学知识吗?

目录 算法与数学:看似不可分割的关系常见算法中的数学元素案例分析:不需要高深数学知识的算法1. 二分查找2. 深度优先搜索 (DFS)3. 动态规划:斐波那契数列 如何在有限的数学背景下学习算法1. 专注于算法的逻辑和过程2. 可视化算法流程3. 从简单的实现开始,逐步优化4. 学习算法设…

centos7使用ifconfig查看IP,终端无ens33信息解决办法

1.问题描述 大概有十几天没用虚拟机&#xff0c;最后一次用忘记关闭虚拟机系统了&#xff1b;突然&#xff0c;发现我用远程连接工具&#xff0c;连接不上&#xff0c;去到虚拟机内部查看IP发现终端竟然没有输出enss33地址信息&#xff0c;额&#xff0c;就像下面这样。 2.解决…

android so的加载流程(Android 13~14)

序言 分析环境: Android 13~14 其实大佬 << 安卓so加载流程源码分析 >> 已经写得非常好了,我就没必要再写了 建议读者看看这篇文字,比较新,质量很高<< 安卓so加载流程源码分析 >> 为什么要分析 android so的加载流程 ??? 我想明白 so是怎么打…

无人机之反制系统篇

无人机的反制系统是一个复杂而精细的系统&#xff0c;旨在应对无人机的不当使用或潜在威胁。该系统通常由多个关键部分组成&#xff0c;包括搜索系统、光电跟踪系统、射频干扰系统及显控单元等&#xff0c;这些部分共同协作以实现对无人机的有效反制。以下是对无人机反制系统的…

SpringBoot开发——初步了解SpringBoot

文章目录 一、SpringBoot简介1、什么是Spring Boot2、Spring Boot的优点3、Spring Boot功能 二、Spring与Spring Boot对比三、Spring Boot与Spring MVC四、Spring Boot体系结构五、Springboot Initializr1、Spring Initializr2、Spring Initializr模块 一、SpringBoot简介 1、…

docker ps 得到的ports列的含义

前言 每次使用docker ps 查询容器运行情况的时候就很容易搞混ports列的含义&#xff0c;今天浅记一下 docker ps ports列含义 首先看docker ps的查询结果显示&#xff1a; CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 容器ID …

Axure中继器教程及案例详解

Axure RP 是一款强大的原型设计工具&#xff0c;广泛应用于产品设计、UI/UX 设计及交互设计中。中继器&#xff08;Repeater&#xff09;作为 Axure 中的一个重要元件&#xff0c;以其强大的数据处理和动态交互能力&#xff0c;成为设计师们不可或缺的工具。本文将从中继器基础…

LLM agentic模式之multi-agent: ChatDev,MetaGPT, AutoGen思路

文章目录 Multi-agentChatDev设计阶段编码阶段测试阶段文档编写 MetaGPTSOP模式下的Agent通信协议带执行反馈的迭代编程 AutoGenconversable agentsConversation ProgrammingAutoGen的应用 参考资料 Multi-agent ChatDev ChatDev出自2023年7月的论文《ChatDev: Communicative…

告别文档处理烦恼,PDF Guru Anki一键搞定所有

前言 知识就像烛光&#xff0c;能照亮一个人&#xff0c;也能照亮无数人&#xff0c;科技之光更是如此&#xff1b;这一理念深刻地影响了我们如何看待和应用新技术。正是在这样的背景下&#xff0c;一款集PDF处理与高效学习工具于一体的软件——PDF Guru Anki应运而生&#xf…

AI论文生成可靠吗?分享6款AI论文题目生成器网站

在当今学术研究和写作领域&#xff0c;AI论文题目生成器网站的出现极大地简化了学术写作流程。这些工具不仅能够帮助用户快速生成高质量的论文题目&#xff0c;还能提供文献推荐、论文润色等功能&#xff0c;从而提高写作效率和质量。以下是六款值得推荐的AI论文题目生成器网站…

glsl着色器学习(六)

准备工作已经做完&#xff0c;下面开始渲染 gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);gl.clearColor(0.5, 0.7, 1.0, 1.0); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);gl.enable(gl.DEPTH_TEST); gl.enable(gl.CULL_FACE);设置视口 gl.viewport(0,…

轻量级的git-server工具:docker部署gogs

背景 创建一个自己使用的git server&#xff0c;让平时使用的代码之类的可以直接传到自己的服务器上&#xff0c;进行远程管理。由于一个人使用&#xff0c;gitlab 太重&#xff0c;所以选择gogs来实现功能。 系统&#xff1a;openEuler 22.03 (LTS-SP3) ip: 192.168.100.31 …

【Linux】Linux项目自动化构建工具 - make/Makefile

目录 一、make/Makefile的背景二、make/Makefile的基本概念三、依赖关系四、依赖方法五、make/Makefile原理六、Makefile的伪目标七、Makefile的变量八、Makefile的推导能力结尾 一、make/Makefile的背景 一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若…