[.net 面向对象编程基础] (22) 事件
[.net 面向对象编程基础] (22) 事件
事件(Event)是学习.net面向对象编程很重要的一部分,在学习事件之前,我们实际上已经在很多地方使用了事件,比如控件的click事件等,这些都是.net设计控件的时候已经定义好的事件。除此之外,我们同样可以自己定义事件。
事件实际上是一种消息机制,当然点击控件时,click就通知处理他的方法去处理,实际上就是前面说的委托。因此我们可以说:事件是一种具有特殊签名的委托。而事件/消息机制是windows的核心,因此我们必须掌握他。
为了更加容易理解事件,我们还是使用前面的动物的示例来说明,有两三只动物,猫(名叫Tom),还有两只老鼠(Jerry和Jack),当猫叫的时候,触发事件(CatShout),然后两只老鼠开始逃跑(MouseRun)。在使用代码实现这个示例之前,我们先看一下事件的书面定义.
1.什么是事件(Event)?
事件(Event)是类或对象向其他类或对象通知发生的事情的一种特殊签名的委托.
2.事件的声明
public event 委托类型 事件名;
事件使用event关键词来声明,他的返回类值是一个委托类型。
通常事件的命名,以名字+Event 作为他的名称,在编码中尽量使用规范命名,增加代码可读性。
3.事件示例
下面我们实现本篇开始的猫捉老鼠的示例
首先看一下UML图
如上UML类图,有猫(Cat)和老鼠(Mouse)两个类,里面包含其成员.当猫叫(CatShout)时,触发事件(CatShoutEvent),事件通知老鼠,然后老鼠跑路(MouseRun).
两个类的代码如下:
class Cat
{
string catName;
string catColor { get; set; }
public Cat(string name, string color)
{
this.catName = name;
catColor = color;
} public void CatShout()
{
Console.WriteLine(catColor+" 的猫 "+catName+" 过来了,喵!喵!喵!\n"); //猫叫时触发事件
//猫叫时,如果CatShoutEvent中有登记事件,则执行该事件
if (CatShoutEvent != null)
CatShoutEvent();
} public delegate void CatShoutEventHandler(); public event CatShoutEventHandler CatShoutEvent; }
class Mouse
{
string mouseName;
string mouseColor { get; set; }
public Mouse(string name, string color)
{
this.mouseName = name;
this.mouseColor = color;
} public void MouseRun()
{
Console.WriteLine(mouseColor + " 的老鼠 " + mouseName + " 说:\"老猫来了,快跑!\" \n我跑!!\n我使劲跑!!\n我加速使劲跑!!!\n");
}
}
调用如下:
Console.WriteLine("[场景说明]: 一个月明星稀的午夜,有两只老鼠在偷油吃\n");
Mouse Jerry = new Mouse("Jerry", "白色");
Mouse Jack = new Mouse("Jack", "黄色"); Console.WriteLine("[场景说明]: 一只黑猫蹑手蹑脚的走了过来\n");
Cat Tom = new Cat("Tom", "黑色"); Console.WriteLine("[场景说明]: 为了安全的偷油,登记了一个猫叫的事件\n");
Tom.CatShoutEvent += new Cat.CatShoutEventHandler(Jerry.MouseRun);
Tom.CatShoutEvent += new Cat.CatShoutEventHandler(Jack.MouseRun); Console.WriteLine("[场景说明]: 猫叫了三声\n");
Tom.CatShout(); Console.ReadKey();
运行结果如下:
4.事件参数
上面的事件是最简单的事件,通过我们看到的事件,都会带两个参数,比如c# winform中的button点击事件的委托方法如下:
private void button1_Click(object sender, EventArgs e)
带有两个参数,不熟悉事件参数的小伙伴肯定要问,这两个参数sender和e到底有什么用呢?
第一个参数 sender,其中object类型的参数 sender表示的是发送消息的对象,为什么要使用object类型呢,这是因为不同类型的对象调用时使用object能很好的兼容。
第二个参数 e,他的类型为EventArgs.EventArgs这个类没有实际的用途,只是作为一个基类让其他对象继承。很多对象不需要传递额外的信息,例如按钮事件,只是调用一个回调方法就够了。当我们定义的事件不需要传递额外的信息时,这时调用EventArgs.Empty就行了,不需要重新构建一个EventArgs对象。
我们可以看到在Button事件登记时,只传了一个参数sender
为了更好的理解带参数的事件,我们改写一下上面猫捉老鼠的示例:
先看UML图:
实现代码如下:
class Cat
{
string catName;
string catColor { get; set; }
public Cat(string name, string color)
{
this.catName = name;
catColor = color;
} public void CatShout()
{
Console.WriteLine(catColor+" 的猫 "+catName+" 过来了,喵!喵!喵!\n"); //猫叫时触发事件
//猫叫时,如果CatShoutEvent中有登记事件,则执行该事件
if (CatShoutEvent != null)
CatShoutEvent(this, new CatShoutEventArgs() {catName=this.catName, catColor=this.catColor});
} public delegate void CatShoutEventHandler(object sender,CatShoutEventArgs e); public event CatShoutEventHandler CatShoutEvent; } /// <summary>
/// EventArgs类的作用就是让事件传递参数用的
/// 我们定义一个类CatShout包含两个成员属性,以方便传递
/// </summary>
class CatShoutEventArgs:EventArgs
{
public string catColor { get; set; }
public string catName { get; set; }
} class Mouse
{
string mouseName;
string mouseColor { get; set; }
public Mouse(string name, string color)
{
this.mouseName = name;
this.mouseColor = color;
} public void MouseRun(object sender, CatShoutEventArgs e)
{
if (e.catColor == "黑色")
Console.WriteLine(mouseColor + " 的老鼠 " + mouseName + " 说:\" " + e.catColor + " 猫 " + e.catName + " 来了,快跑!\" \n我跑!!\n我使劲跑!!\n我加速使劲跑!!!\n");
else
Console.WriteLine(mouseColor + " 的老鼠 " + mouseName + " 说:\" " + e.catColor + " 猫 " + e.catName + " 来了,慢跑!\" \n我跑!!\n我慢慢跑!!\n我慢慢悠悠跑!!!\n"); }
}
调用如下:
Console.WriteLine("[场景说明]: 一个月明星稀的午夜,有两只老鼠在偷油吃\n\n\n");
Mouse Jerry = new Mouse("Jerry", "白色");
Mouse Jack = new Mouse("Jack", "黄色"); Console.WriteLine("[场景说明]: 一只黑猫蹑手蹑脚的走了过来");
Cat Tom = new Cat("Tom", "黑色");
Console.WriteLine("[场景说明]: 为了安全的偷油,登记了一个猫叫的事件");
Tom.CatShoutEvent += new Cat.CatShoutEventHandler(Jerry.MouseRun);
Tom.CatShoutEvent += new Cat.CatShoutEventHandler(Jack.MouseRun);
Console.WriteLine("[场景说明]: 猫叫了三声\n");
Tom.CatShout(); Console.WriteLine("\n\n\n"); //当其他颜色的猫过来时
Console.WriteLine("[场景说明]: 一只蓝色的猫蹑手蹑脚的走了过来");
Cat BlueCat = new Cat("BlueCat", "蓝色");
Console.WriteLine("[场景说明]: 为了安全的偷油,登记了一个猫叫的事件");
BlueCat.CatShoutEvent += new Cat.CatShoutEventHandler(Jerry.MouseRun);
BlueCat.CatShoutEvent += new Cat.CatShoutEventHandler(Jack.MouseRun);
Console.WriteLine("[场景说明]: 猫叫了三声");
BlueCat.CatShout();
运行结果如下:
也可以使用前面学过的Lamda表达式来简洁的写以上的事件注册
Cat Doraemon = new Cat("哆啦A梦", "彩色");
Doraemon.CatShoutEvent += (sender, e) => Jerry.MouseRun(sender, e);
Doraemon.CatShoutEvent += (sender, e) => Jack.MouseRun(sender, e);
Doraemon.CatShout();
调用后结果一样.
5. 事件应用实例
如果上面的简单实例不够过瘾,我下面列举几个日常开发过程中应用事件解决实际问题的例子,加深对事件的理解。
示例一:我们使用一个事件来监控一个文件夹下文件的变更情况
先看一下UML类图
代码如下:
/// <summary>
/// 文件夹监控
/// </summary>
public class FolderWatch
{
public class Files
{
string _fileName;
public string FileName
{
get { return _fileName; }
set { _fileName = value; }
}
DateTime _fileLastDate;
public DateTime FileLastDate
{
get { return _fileLastDate; }
set { _fileLastDate = value; }
} }
/// <summary>
/// 文件夹路径
/// </summary>
public string folderPath; /// <summary>
/// 文件集合
/// </summary>
public List<Files> fileList=new List<Files>(); /// <summary>
/// 文件夹监控事件
/// </summary>
public event FolderWatchEventHandler FolderWatchEvent; /// <summary>
/// 构造函数
/// </summary>
public FolderWatch(string path)
{
folderPath = path; //获取目录下所有文件
foreach (var file in new System.IO.DirectoryInfo(path).GetFiles())
{
fileList.Add(
new Files()
{
FileName = file.Name,
FileLastDate=file.LastWriteTime
} );
}
}
public void OnFieldChange()
{
if (FolderWatchEvent != null)
FolderWatchEvent(this, new MonitorFileEventArgs { Files = fileList });
} /// <summary>
/// 委托实现方法
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public void MonitorFiles(object sender, MonitorFileEventArgs e)
{ while(true)
{
//遍历fileList文件列表,检测是否有变更(删除或修改)
if(e.Files!=null)
foreach(var file in this.fileList)
{
string fileFullName=folderPath + "\\" + file.FileName;
//检测是否存在
if (!System.IO.File.Exists(fileFullName))
Console.WriteLine("文件\"" + file.FileName + "\"已被删除或更名;\n");
else if (file.FileLastDate != (new System.IO.FileInfo(fileFullName)).LastWriteTime)
{
Console.WriteLine("文件\"" + file.FileName + "\"已被修改过(上次修改日期:" + file.FileLastDate + ",本次检测到日期为:" + (new System.IO.FileInfo(fileFullName)).LastWriteTime + ");\n"); } }
//重新获取目录下所有文件
List<Files> newFiles = new List<Files>();
foreach (var newFile in new System.IO.DirectoryInfo(this.folderPath).GetFiles())
{
newFiles.Add(
new Files()
{
FileName = newFile.Name,
FileLastDate = newFile.LastWriteTime
} );
if(!(fileList.Any(m=>m.FileName==newFile.Name)))
Console.WriteLine("新建文件\"" + newFile.Name+"\"\n");
}
fileList.Clear();
this.fileList = newFiles;
}
}
} /// <summary>
/// 文件夹监控委托
/// </summary>
public delegate void FolderWatchEventHandler(object sender, MonitorFileEventArgs e); /// <summary>
/// 事件传递参数类
/// </summary>
public class MonitorFileEventArgs : EventArgs
{
/// <summary>
/// 文件
/// </summary>
public List<FolderWatch.Files> Files { get; set; } }
调用如下:
string MyFolder = "MyFolder";
FolderWatch folder = new FolderWatch(System.IO.Directory.GetCurrentDirectory() +"\\"+ MyFolder); folder.FolderWatchEvent += new FolderWatchEventHandler(folder.MonitorFiles);
folder.OnFieldChange();
运行结果如下:
可以看到当我们增加,修改,删除文件时,就会返回文件夹内文件更改的提示信息。
实际上对于文件更改的监控.NET提供了专门的类FileSystemWatcher来完成。上面的示例只是为了加深理解事件,在实际应用中对文件的变更还是有缺陷的,比如同一文件更名、通过时间判断文件变更也是不科学的。
下面我们就使用.net提供的FileSystemWatcher类来完成文件夹监控,代码非常简单
static void watcher_Renamed(object sender,System.IO.RenamedEventArgs e)
{
Console.WriteLine("文件\"" + e.OldName + "\"更名为:"+e.Name+";\n");
}
static void watcher_Deleted(object sender, System.IO.FileSystemEventArgs e)
{
Console.WriteLine("文件\"" + e.Name + "\"已被删除;\n");
}
static void watcher_Changed(object sender, System.IO.FileSystemEventArgs e)
{
Console.WriteLine("文件\"" + e.Name + "\"已被修改;\n");
}
static void watcher_Created(object sender, System.IO.FileSystemEventArgs e)
{
Console.WriteLine("新创建了文件\"" + e.Name + "\";\n");
}
调用如下:
string MyFolder = "MyFolder"; System.IO.FileSystemWatcher watcher = new System.IO.FileSystemWatcher(System.IO.Directory.GetCurrentDirectory() + "\\" + MyFolder); watcher.Renamed+= watcher_Renamed;
watcher.Deleted+=watcher_Deleted;
watcher.Changed+=watcher_Changed;
watcher.Created +=watcher_Created; watcher.EnableRaisingEvents = true;
运行结果如下:
示例二:使用事件完成了一个文件下载进度条的示例,平时我们看到很多进度条程序员为了偷懒都是加载完成直接跳到100%,这个示例就是传说中的真进度条。
UML类图如下:
代码如下:
public partial class Form1 : Form
{
System.Threading.Thread thread;
public Form1()
{
InitializeComponent();
} private void downButton_Click(object sender, EventArgs e)
{ if(thread==null)
thread = new System.Threading.Thread(new System.Threading.ThreadStart(StartDown)); //使用子线程工作
if (this.downButton.Text == "开始下载文件")
{
this.downButton.Text = "停止下载文件";
if (thread.ThreadState.ToString() == "Unstarted")
{
thread.Start();
}
else if (thread.ThreadState.ToString() == "Suspended")
thread.Resume();
}
else
{
this.downButton.Text = "开始下载文件";
thread.Suspend();
}
} //开始加载进度
public void StartDown()
{
//注册事件
DownLoad down = new DownLoad();
down.onDownLoadProgress+=down_onDownLoadProgress;
down.onDownLoadProgress += down_ShowResult;
down.Start(); } public void down_onDownLoadProgress(long total,long current)
{ if (this.InvokeRequired)
{
this.Invoke(new DownLoad.DownLoadProgress(down_onDownLoadProgress), new object[] { total, current });
}
else
{
this.myProgressBar.Maximum = (int)total;
this.myProgressBar.Value = (int)current;
} } public void down_ShowResult(long total,long current)
{
Action<long, long> ac = (c, t) => { this.resultShow.Text = ((double)current / total).ToString("P"); ; };
this.Invoke(ac, new object[] { current, total });
} //下载处理类
class DownLoad
{
//委托
public delegate void DownLoadProgress(long total, long current); //事件
public event DownLoadProgress onDownLoadProgress; //事件
public event DownLoadProgress down_ShowResult; public void Start()
{
//下载模拟
for (int i = ; i <= ; i++)
{
if (onDownLoadProgress != null)
onDownLoadProgress(, i);
if (down_ShowResult != null)
down_ShowResult(, i);
System.Threading.Thread.Sleep();
}
} }
}
运行结果如下:
上面示例使用winform应用程序,实现了一个进度条即时计算进度的例子。在文件下载子类(DownLoad)中有两个事件,一个是进度条事件,一个是进度百分比显示事件,在初始化调用时,采用了线程。启用线程时,注册了两个事件。随着模拟进度的加载,触发了进度条事件和显示百分比事件。做到了即时显示。
关于线程相关知识,在后面有时间了会详细说明。
6 要点
6.1 事件:事件是对象发送的消息,发送信号通知客户发生了操作。这个操作可能是由鼠标单击引起的,也可能是由某些其他的程序逻辑触发的。事件的发送方不需要知道哪个对象或者方法接收它引发的事件,发送方只需知道它和接收方之间的中介(delegate)。
6.2 事件处理程序总是返回void,它不能有返回值。
6.3 只要使用EventHandler委托,参数就应是object和EventArgs。第一个参数是引发事件的对象。第二个参数EventArgs是包含有关事件的其他有用信息的对象;这个参数可以是任意类型,只要它派生自EventArgs即可。
6.4 方法的命名也应注意,按照约定,事件处理程序应遵循“object_event”的命名约定。
6.5 事件具有以下特点:
(1)发行者确定何时引发事件,订户确定执行何种操作来响应该事件。
(2)一个事件可以有多个订户。 一个订户可处理来自多个发行者的多个事件。
(3)没有订户的事件永远也不会引发。
(4)事件通常用于通知用户操作,例如,图形用户界面中的按钮单击或菜单选择操作。
(5)如果一个事件有多个订户,当引发该事件时,会同步调用多个事件处理程序。 要异步调用事件,请参见使用异步方式调用同步方法。
(6)在 .NET Framework 类库中,事件是基于 EventHandler 委托和 EventArgs 基类的。
6.6 事件的创建步骤
(1)、定义delegate对象类型,它有两个参数,第一个参数是事件发送者对象,第二个参数是事件参数类对象。
(2)、定义事件参数类,此类应当从System.EventArgs类派生。如果事件不带参数,这一步可以省略。
(3)、定义"事件处理方法,它应当与delegate对象具有相同的参数和返回值类型"。
(4)、用event关键字定义事件对象,它同时也是一个delegate对象。
(5)、用+=操作符添加事件到事件队列中(-=操作符能够将事件从队列中删除)。
(6)、在需要触发事件的地方用调用delegate的方式写事件触发方法。一般来说,此方法应为protected访问限制,既不能以public方式调用,但可以被子类继承。名字是OnEventName。
(7)、在适当的地方调用事件触发方法触发事件。
==============================================================================================
返回目录 <如果对你有帮助,记得点一下推荐哦,有不明白的地方或写的不对的地方,请多交流>
QQ群:467189533
==============================================================================================
[.net 面向对象编程基础] (22) 事件的更多相关文章
- [.net 面向对象编程基础] (1) 开篇
[.net 面向对象编程基础] (1)开篇 使用.net进行面向对象编程也有好长一段时间了,整天都忙于赶项目,完成项目任务之中.最近偶有闲暇,看了项目组中的同学写的代码,感慨颇深.感觉除了定义个类,就 ...
- [.net 面向对象编程基础] (23) 结束语
[.net 面向对象编程基础] (23) 结束语 这个系列的文章终于写完了,用了半个多月的时间,没有令我的粉丝们失望.我的感觉就是一个字累,两个字好累,三个字非常累.小伙伴们看我每篇博客的时间就知道 ...
- [.net 面向对象编程基础] (8) 基础中的基础——修饰符
[.net 面向对象编程基础] (8) 基础中的基础——修饰符 在进入C#面向对象核心之前,我们需要先对修饰符有所了解,其实我们在前面说到变量和常量的时候,已经使用了修饰符,并且说明了变量和常量的修改 ...
- [.net 面向对象编程基础] (9) 类和类的实例
[.net 面向对象编程基础] (9) 类和类的实例 类 ,顾名思义就是分类.类别的意思.我们要面向对象编程,就需要对不同的事物进行分类.类可以说是.net面向对象的核心. 类:就是具有相同的属性和功 ...
- [.net 面向对象编程基础] (10) 类的成员(字段、属性、方法)
[.net 面向对象编程基础] (10) 类的成员(字段.属性.方法) 前面定义的Person的类,里面的成员包括:字段.属性.方法.事件等,此外,前面说的嵌套类也是类的成员. a.类的成员为分:静态 ...
- [.net 面向对象编程基础] (16) 接口
[.net 面向对象编程基础] (16) 接口 关于“接口”一词,跟我们平常看到的电脑的硬件“接口”意义上是差不多的.拿一台电脑来说,我们从外面,可以看到他的USB接口,COM接口等,那么这些接口的目 ...
- [.net 面向对象编程基础] (17) 数组与集合
[.net 面向对象编程基础] (17) 数组与集合 学习了前面的C#三大特性,及接口,抽象类这些相对抽象的东西以后,是不是有点很累的感觉.具体的东西总是容易理解,因此我们在介绍前面抽象概念的时候,总 ...
- [.net 面向对象编程基础] (20) LINQ使用
[.net 面向对象编程基础] (20) LINQ使用 通过上节LINQ的基础知识的学习,我们可以开始使用LINQ来进行内存数据的查询了,我们上节说了LINQ的定义为:Language Integr ...
- [.net 面向对象编程基础] (21) 委托
[.net 面向对象编程基础] (20) 委托 上节在讲到LINQ的匿名方法中说到了委托,不过比较简单,没了解清楚没关系,这节中会详细说明委托. 1. 什么是委托? 学习委托,我想说,学会了就感觉简 ...
随机推荐
- js获取新浪天气接口
<!doctype html> <html class="no-js fixed-layout"> <head> <meta charse ...
- mysql在ubuntu下的安装
如果是调用的apt-get 那么应该是sudo apt-get install mysql-server-core-5.6 mysql-server-5.6 mysql-common-5.6 mys ...
- C语言之数组,字符串,指针
一. 数组的定义 1. 数组初始化 初始化方式 int a[3] = {10, 9, 6}; int a[3] = {10,9}; int a[] = {11, 7, 6}; int a[4] = ...
- python基础第四天(1)
冒泡算法--算法 需求:请按照从小到大对列表 [13, 22, 6, 99, 11] 进行排序 思路:相邻两个值进行比较,将较大的值放在右侧,依次比较! 第一步 li = [13, 22, 6, 99 ...
- <转>浅析长度为0的数组
前面在看Xen的源码时,遇到了一段代码,如下所示: 注意上面最后一行的代码,这里定义了一个长度为的数组,这种用法可以吗?为什么可以使用长度为0 的数组?长度为的数组到底怎么使用?……这篇文章主要针对该 ...
- gitlab安装过程总结
gitlab总算搭建好了,今天下午完成了第一个工程的导入. 梳理下搭建的过程: 1.下载bitnami的一键安装包,并安装 注意设置权限为777,以免出现某文件写入错误的问题 2.配置邮件服务 分别修 ...
- 14.Xcode8imageview图片圆角不显示的bug
第一种方法: 在imageView调用clipToBounds的之前,[super awakeFromNib]之后,加上[self.contentView layoutIfNeeded];就可以了,反 ...
- 四川软件人才网:打造四川最专业的IT人才招聘平台
四川软件人才网(www.tfsp.net),原名:天府软件人才网:为了更好的发展和拓展的业务需要,更名为:四川软件人才网,强力打造四川最专业的IT人才的招聘平台. 网站依托四川软件人才社区,微博,微信 ...
- js获取倒计时
<html> <head> <title>出错啦~~~</title> <link href="css/login1.css" ...
- Cocoapods的安装,卸载和使用
[一]Cocoapods的安装 (1)Cocoapods的官方网站为: https://cocoapods.org/ .如果你的电脑已经安装了Ruby开发环境,那么在终端(Terminal)中使用以下 ...