深入浅出话窗体(一)
——窗体事件模型(上)
作者:CSDN 刘铁猛
小序:
工作中最大的挑战并不是那些Mission Impossible,而是你需要一边保持安静、平衡的心态以专注于工作,一边对抗公司体制、社会经济和人际环境对这种心态的破坏——这是对儿永远也解不开的矛盾。
正文:
记得我在前面一篇文章里提到过:垒砖头垒多少年也成不了建筑师——仍然只会砌墙。同样,堆控件堆多少年也成不了程序员——仍然只会拼凑窗体。成为建筑师的关键在于学习建筑的结构,成为程序员的关键则在于了解程序的结构。今天,就让我们告别在窗体上堆控件,剖析一下窗体与窗体上控件关系——特别是事件的由来以及事件激发(Event Fire)与事件响应(Event Handling)之间的联系。
事件的由来
传统的面向对象概念中是没有事件(Event)这个东西的,有的只是值域(Field)和方法(Method)。那么事件又是怎么来的呢?在传统的面向对象编程中,如果一个类想调用另一个类的方法,程序员有两种方法:
1. 在一个类的方法里通过另一个类的方法名进行直接调用,看上去会是这样:
#include<iostream>
classA
{
public:
voidMethod()
{
std::cout<<"ThisisA."<<std::endl;
}
};
classB
{
public:
voidMethod(Aarg)
{
arg.Method();//B与A紧密耦合在一起
}
};
intmain(intargc,char*argv[])
{
Aa;
Bb;
b.Method(a);
return0;
}
这样做的好处在于程序结构相当简单,但为这种“简单”所付出的代价就是——类与类之间耦合过于紧密,而使程序几乎不具有弹性和可伸缩性——于是有了另一种方法。
2. 在主调类里保有一个函数指针,并将这个指针指向一个函数,在这个函数里再调用其它类的方法。喔~~~你可能会问:“干吗不让这个指针直接指向其它类的方法,还要再借助一个中间函数做跳板呢?”呵呵,答案是:函数指针是不能指向类的成员函数的——《C++必知必会》里有详细解释,如果想再深挖一些,还可以看《深入探索C++对象模型》。这也就是我们常说的“间接引用”或者闻名遐迩的“回调函数”啦:D 大概的样子如下:
#include<iostream>
classA
{
public:
voidMethod()
{
std::cout<<"ThisisA."<<std::endl;
}
};
typedefvoid(*FunctionPointer)();//把函数指针定义成一种“类型”
classB
{
public:
FunctionPointerfunPointer;//声明一个函数指针成员,必需与将被调用的方法类型保持一致
voidMethod()
{
funPointer();//通过函数指针来调用目标方法,B类不与任何类明确耦合
}
};
voidFunction()//将被间接调用的函数
{
Aa;
a.Method();
}
intmain(intargc,char*argv[])
{
Bb;
b.funPointer=Function;//成员指针与函数绑定,不存在类与类耦合的问题
b.Method();//主调函数-->跳板函数-->被调函数
return0;
}
这个模型真的非常不错!而且在C/C++世界广为流传。然而,时过境迁,随着.NET时代的来临和指针的不安全性(程序crash和内存泄漏之母:p)日益为人诟病,C#最终放弃了指针——确切地讲是“囚禁”了指针。虽然放弃了指针,但C#并没有放弃这种通过间接调用而降低类间耦合度的模型。微软是怎样做到的呢?原来,微软为.NET Framework添加了一种新的数据类型——委托(Delegate)。
作为一种新的引用型数据类型,委托是一种类。既然是类,就没办法直接当作类的成员来使咯,所以能当作类成员来使用的只能是委托的实例(听起来真的是废话~~但很多初学的朋友就是在这里卡壳)。作为类的成员,委托的实例用起来的确很像函数指针,所以,我不得不再纠正一个流传甚广的谬误——有人说委托是函数指针的升级或委托是“超级函数指针”——实际上应该说委托的实例是函数指针的升级、委托的实例是“超级函数指针”。
对于“超级函数指针”这个title,委托的实例是当之无愧的。它除了可以像函数指针那样“挂接”一个方法(函数被封装在类里之后就称之为“方法”了)外,功能大大超越了函数指针,这体现在:
-
每个函数指针只能挂接一个目标函数,而委托的实例可以使用重载了的+=操作符肆意挂接N多方法,并美其名曰“多播委托”。
-
函数指针具有指针的多种“通病”,而委托作为一种.NET托管类变得非常安全而驯服。
-
函数指针不能指向类的成员函数,委托却可以指向类的成员函数——这倒不是委托的功劳,根本原因在于C#是完全面向对象的语言,所有函数都必须封装在类里变成成员函数(不再有散落在类之外的全局函数),如果委托不能指向成员函数,那委托还有什么用呢:p
如果阁下想对委托探个究竟,我推荐你去阅读在下的掘文《深入浅出话委托》。
OK,被C#粉饰一新的间接调用模型看上去会是这样:
//=<水之真谛>=出品,http://blog.csdn.net/FantasiaX
usingSystem;
delegatevoidMyDelegate();//声明委托这种类的方法与声明常规类不太一样
//它看上去更像是在声明“一个函数指针”,这也正是我上面说的混淆之源。
//从语义上讲,这句与上面C++代码中使用typedef把函数指针定义为一种类型是一致的
classA
{
publicvoidMethod(stringname)
{
Console.WriteLine("Hello,{0}!",name);
}
}
classB
{
publicMyDelegatedeleInstance;//声明一个MyDelegate作为成员
publicvoidMethod()
{
if(this.deleInstance!=null)//安全检验,这是函数指针做不到的
{
this.deleInstance();
}
}
}
classProgram
{
staticvoidFunction()//作为跳板的函数,将被挂接在委托的实例上
{
Aa=newA();
a.Method("Tim");
}
staticvoidMain(string[]args)
{
Bb=newB();
b.deleInstance+=newMyDelegate(Function);//创建实例,同时绑定。被绑定的函数返回值和参数类型必须与委托完全一致
b.Method();
}
}
你可能会问:干吗不把委托设计成与A的Method方法类型一致、再到B类中声明一个实例呢?这样不就可以抛开那个什么“当作跳板的函数”、直接调用A类实例的Method方法了吗?
主意不错!但请你考虑这样一个问题:根据客户需求和业务逻辑的需要,A类可能具有几百个参数和返回值类型千奇百怪的方法,那么,你打算专门针对A类开发出几百种委托类型吗?就算你开发出来了,B类的设计人员就乐意为B类声明上如此一大摞专门针对A类的委托实例吗?如果创建B类的程序员接纳了针对A类的几百个委托,那么从针对C到Z这些类的委托呢——要不要也接纳进来?不接纳——除非A和B是两口子;接纳——那B类的代码长度估计不会低于央视大楼!况且,A类、B类以及其它类的设计人员可能根本就不在一个公司、不在一起工作,怎么可能“串通”起来做这种把类耦合在一起的浩大工程呢:p
说了半天光说委托了,事件呢?其实,事件是微软对委托这个概念的进一步升级。就代码而言,事件仅仅是在委托的基础上向前迈进了一小步,但是从程序逻辑的角度上观察,事件却是在思想上向前迈进了一大步!为什么这样讲呢?且听下回分解。
TO BE CONTINUE
敬请关注:深入浅出话窗体(二)——窗体事件模型(下)
法律声明:本文章受到知识产权法保护,任何单位或个人若需要转载此文,必需保证文章的完整性(未经作者许可的任何删节或改动将视为侵权行为)。若您需要转载,请务必注明文章出处为CSDN以保障网站的权益;请务必注明文章作者为刘铁猛(http://blog.csdn.net/FantasiaX),并向no_sound@hotmail.com发送邮件,标明文章位置及用途。转载时必须将此法律声明一并转载。保护知识产权,人人有责,谢谢!
分享到:
相关推荐
在Windows应用程序开发中,"Windows窗体——皮肤"是一个关于用户界面美化的重要概念。Windows窗体,也称为WinForms,是.NET Framework中的一个组件,用于创建桌面应用的图形用户界面。它提供了丰富的控件和功能,使...
C语言项目——窗体版图书管理系统.zip C语言项目——窗体版图书管理系统.zip C语言项目——窗体版图书管理系统.zip C语言项目——窗体版图书管理系统.zip C语言项目——窗体版图书管理系统.zip C语言项目——窗体版...
3. 订阅事件:在接收数据的窗体(目标窗体)中,订阅源窗体的`DataModified`事件,并提供一个事件处理程序来接收和处理数据。 ```csharp // 在目标窗体的构造函数或初始化方法中订阅事件 public TargetForm...
在C#编程环境中,我们可以...这种特殊形状的窗体可以在许多场景下派上用场,例如创建引人注目的用户界面或者实现特定的设计需求。通过结合其他的图形和控件,你可以进一步定制窗体,使其具备更丰富的功能和视觉效果。
功能简介: ...2.用户数据分页列表显示 3.用户记录增删改查 ...注:由于时间关系,程序有一个功能未实现,就是管理员登录成功界面中左侧查询用户按钮事件,你可以仿照该界面数据列表的提取自己试着写一下。
当用户点击窗体标题栏上的最小化按钮时,窗体会从桌面缩放到任务栏,这个过程涉及到一个特定的事件——`SizeChanged`事件。本文将深入探讨C#中窗体最小化事件的处理及其相关知识点。 1. **窗体基本知识** - 窗体...
例如,在一个窗体中触发另一个窗体上的某个事件,这种需求在很多应用场景中非常常见。本文档将详细介绍如何在A窗体中调用B窗体中的某个事件,并通过具体代码示例进行解释。 #### 关键概念 1. **事件(Event)**:在...
本文将深入探讨如何在MDI环境中控制子窗体的显示与管理,特别是如何实现“在打开新子窗体时自动关闭当前存在的子窗体”的功能。 ### MDI基础概念 MDI架构的核心是`MDIForm`,即作为所有子窗体容器的主窗体。当一个...
在C#中,窗体之间的方法调用是一个常见的需求,特别是在复杂的桌面应用程序设计中。本文将根据提供的标题、描述、标签以及部分内容,详细介绍如何在C#的一个窗体中调用另一个窗体的方法,并探讨其中涉及的技术细节。...
在“在一个窗体中打开另一个窗体”这个场景中,描述提到要在当前窗体上添加一个按钮。在代码层面,这通常涉及到以下几个步骤: 1. **设计阶段**:使用Visual Studio等开发工具,在窗体设计器中拖放一个`Button`控件...
【标题】"简单的计算器——窗体开发"是一个基础的计算应用程序,主要针对ACCP 6.0 C#学习者设计,旨在教授如何利用C#语言和Windows窗体(Windows Forms)来构建用户界面,实现基本的数学运算。在这个项目中,开发者...
在子窗体中,我们定义一个事件,并在需要修改父窗体内容时触发这个事件。事件通常伴随着一个事件处理方法,该方法定义了当事件发生时应执行的操作。 接下来,在父窗体中,我们需要订阅子窗体的事件。这意味着父窗...
综上所述,"Delphi 模态窗体下更新另一个窗体内容"涉及到的关键技术包括窗体间的通信策略、多选数据的管理、事件处理、数据模型的设计以及异步编程等。在实际开发中,需要根据具体需求灵活应用这些知识点,确保用户...
例如,如果要创建一个圆形气泡窗体,可以在`Load`事件处理程序中执行以下代码: ```csharp IntPtr region = CreateRoundRectRgn(0, 0, Width, Height, Width / 2, Height / 2); SetWindowRgn(Handle, region, true);...
在C# WinForm应用开发中,我们经常遇到需要创建多个子窗体的情况,但有时我们需要确保同一时间只显示一个子窗体,以提供更整洁、更专注的用户体验。标题"‘C#-winform窗体只允许显示一个子窗体’"正是针对这一需求...
在.NET框架中,VB.NET WinForm应用程序开发时,有时我们需要创建一个无边框的窗体,这通常是出于设计上的特殊需求或为了实现自定义的界面效果。无边框窗体意味着窗体没有标准的标题栏和边框,因此无法通过默认的方式...
在C#编程中,"一个窗体向另一个窗体传值"是常见的应用场景,尤其是在开发桌面应用程序时。这个过程涉及到对象实例的创建、事件处理以及数据传递。下面将详细讲解这个知识点。 首先,我们创建两个窗体:Form1和Form2...
C#制作一个非矩形窗体之——文字形窗体,你见过用文字作为窗体吗?通过本实例,你将学会到这个技巧。当然在实际应用中,字体样式的选择很重要,如果字体不适合做窗体,即使做出来,窗体也不会漂亮,因此大家通过这个...
我们可以定义一个自定义事件,在子窗体中触发,然后在父窗体中订阅这个事件。这样,当子窗体中的特定操作完成时,可以通知父窗体进行更新。 2. **定义事件**: 在子窗体类中,定义一个自定义事件。例如: ```...
2. **委托与事件**:定义一个委托并在子窗体中触发相应事件,通知父窗体进行处理。 3. **参数传递**:通过构造函数或其他方法将必要的参数从父窗体传递到子窗体,并在适当的时候将结果回传给父窗体。 #### 实现方法...