.Net WInform开发笔记(五)关于事件Event

我前面几篇博客中提到过.net中的事件与Windows事件的区别,本文讨论的是前者,也就是我们代码中经常用到的Event。Event很常见,Button控件的Click、KeyPress等等,PictureBox控件的Paint等等都属于本文讨论范畴,本文会例举出有关“事件编程”的几种方法,还会提及由“事件编程”引起的MemoryLeak(跟“内存泄露”差不多),以及由“事件编程”引起的一些异常。

引子
.net中事件最常用在“观察者”设计模式中,事件的发布者(subject)定义一个事件,事件的观察者(observer)注册这个事件,当发布者激发该事件时,所有的观察者就会响应该事件(表现为调用各自的事件处理程序)。知道这个逻辑过程后,我们可以写出以下代码:


代码如下:

ViewCode
ClassSubject
{
publiceventXXEventHandlerXX;
protectedvirtualvoidOnXX(XXEventArgse)
{
If(XX!=null)
{
XX(this,e);
}
}
publicvoidDoSomething()
{
//符合某一条件
OnXX(newXXEventArgs());
}
}
delegatevoidXXEventHandler(objectsender,XXEventArgse);
ClassXXEventArgs:EventArgs
{
}

以上就是一个最最原始的含有事件类的定义。外部对象可以注册Subject对象的XX事件,当某一条件满足时,Subject对象就会激发XX事件,所以观察者作出响应。

:编码中请按照标准的命名方式,事件名、事件参数名、虚方法名、参数名等等,标准请参考微软。
事件观察者注册事件代码为:


代码如下:

ViewCode
Subjectsub=newSubject();
Sub.XX+=newXXEventHandler(sub_XX);
voidsub_XX(objectsender,XXEventArgse)
{
//dosomething
}

以上是一个最简单的“事件编程”结构代码,其余所有的写法都是从以上扩展出来的,基本原理不变。
升级
在定义事件变量时,有时候我们可以这样写:


代码如下:

ViewCode
ClassSubject
{
privateXXEventHandler_xx;
publiceventXXEventHandlerXX
{
add
{
_xx=(XXEventHandler)Delegate.Combine(_xx,value);
}
remove
{
_xx=(XXEventHandler)Delegate.Remove(_xx,value);
}
}
protectedvirtualvoidOnXX(XXEventArgse)
{
if(_xx!=null)
{
_xx(this,e);
}
}
publicvoidDoSomething()
{
//符合某一条件
OnXX(newXXEventArgs());
}
}

其余代码跟之前一样,升级后的代码显示的实现了“add/remove”,显示实现“add/remove”的好处网上很多人都说可以在注册事件之前添加额外的逻辑,这个就像“属性”和“字段”的关系,


代码如下:

ViewCode
publiceventXXEventHandlerXX
{
add
{
//添加逻辑
_xx=(XXEventHandler)Delegate.Combine(_xx,value);
}
remove
{
//添加逻辑
_xx=(XXEventHandler)Delegate.Remove(_xx,value);
}
}

没错,确实与“属性(Property)”的作用差不多,但它不止这一个好处,我们知道(不知道的上网看看),在多线程编程中,很重要的一点就是要保证对象“线程安全”,因为多线程同时访问同一资源时,会出现预想不到的结果。当然,在“事件编程”中也要考虑多线程的情况。“引子”部分代码经过编译器编译后,确实可以解决多线程问题,但是存在问题,它经过编译后:


代码如下:

ViewCode
publiceventXXEventHandlerXX;
//该行代码编译后类似如下:
privateXXEventHandler_xx;
[MethodImpl(MethodImplOptions.Synchronized)]
publicvoidadd_XX(XXEventHandlerhandler)
{
_xx=(XXEventHandler)Delegate.Combine(_xx,handler);
}
[MethodImpl(MethodImplOptions.Synchronized)]
publicvoidremove_XX(XXEventHandlerhandler)
{
_xx=(XXEventHandler)Delegate.Remove(_xx,handler);
}

以上转换为编译器自动完成,事件(取消)注册(+=、-=)间接转换由add_XX和remove_XX代劳,通过在add_XX方法和remove_XX方法前面添加类似[MethodImpl(MethodImplOptions.Synchronized)]声明,表明该方法为同步方法,也就是说多线程访问同一Subject对象时,同时只能有一个线程访问add_XX或者是remove_XX,这就确保了不可能同时存在两个线程操作_xx这个委托链表,也就不可能发生不可预测结果。那么,[MethodImpl(MethodImplOptions.Synchronized)]是怎么做到线程同步的呢?其实查看IL语言,我们不难发现,[MethodImpl(MethodImplOptions.Synchronized)]的作用类似于下:


代码如下:

ViewCode
ClassSubject
{
privateXXEventHandler_xx;
publicvoidadd_XX(XXEventHandlerhandler)
{
lock(this)
{
_xx=(XXEventHandler)Delegate.Combine(_xx,handler);
}
}
publicvoidremove_XX(XXEventHandlerhandler)
{
lock(this)
{
_xx=(XXEventHandler)Delegate.Remove(_xx,handler);
}
}
}

如我们所见,它就相当于给自己加了一个同步锁,lock(this),我不知道诸位在使用同步锁的时候有没有刻意去避免lock(this)这种,我要说的是,使用这种同步锁要谨慎。原因至少两个
1)将自己(Subject对象)作为锁定目标的话,客户端代码中很可能仍以自己为目标使用同步锁,造成死锁现象。因为this是暴露给所有人的,包括代码使用者。


代码如下:

ViewCode
privatevoidDoWork(Subjectsub)//客户端代码
{
lock(sub)//客户端代码锁定sub对象
{
sub.XX+=newXXEventHandler(…);//嵌套锁定同一目标
//sub.add_XX(newXXEventHandler(…));相当于调用add_XX,出现死锁
//
//
//
//dootherthing
}
}

2)当Subject类包含多个事件,XX1、XX2、XX3、XX4…时,每注册(或取消)一个事件时,都需要锁定同一目标(Subject对象),这完全没必要。因为不同的事件有不同的委托链表,多个线程完全可以同时访问不同的委托链表。然而,编译器还是这样做了。


代码如下:

ViewCode
ClassSubject
{
privateXXEventHandler_xx1
privateEventHandler_xx2;
publicvoidadd_XX1(XXEventHandlerhandler)
{
lock(this)
{
_xx1=(XXEventHandler)Delegate.Combine(_xx1,handler);
}
}
publicvoidremove_XX1(XXEventHandlerhandler)
{
lock(this)
{
_xx1=(XXEventHandler)Delegate.Remove(_xx1,handler);
}
}
publicvoidadd_XX2(EventHandlerhandler)
{
lock(this)
{
_xx2=(EventHandler)Delegate.Combine(_xx2,handler);
}
}
publicvoidremove_XX2(EventHandlerhandler)
{
lock(this)
{
_xx2=(EventHandler)Delegate.Remove(_xx2,handler);
}
}
}

在一个线程中执行sub.XX1+=newXXEventHandler(…)(间接调用sub.add_XX1(newXXEventHandler(…)))的时候,完全可以在另一线程中同时执行sub.XX2+=newEventHandler(…)(间接调用sub.add_XX2(newEventHandler(…)))。_xx1和_xx2两个没有任何联系,访问他们更不需要线程同步。如果这样做了,影响性能效率(编译器自动转换成的代码就是这样子)。

结合以上两点,可以将“升级”部分代码修改为以下,从而可以很好的解决“线程安全”问题而且不会像编译器自动转换的代码那样影响效率:


代码如下:

ViewCode
ClassSubject
{
privateXXEventHandler_xx;
privateobject_xxSync=newobject();
publiceventXXEventHandlerXX
{
add
{
lock(_xxSync)
{
_xx=(XXEventHandler)Delegate.Combine(_xx,value);
}
}
remove
{
lock(_xxSync)
{
_xx=(XXEventHandler)Delegate.Remove(_xx,value);
}
}
}
protectedvirtualvoidOnXX(XXEventArgse)
{
if(_xx!=null)
{
_xx(this,e);
}
}
publicvoidDoSomething()
{
//符合某一条件
OnXX(newXXEventArgs());
}
}

在Subject类中增加一个同步锁目标“_xxSync”,不再以对象本身为同步锁目标,这样_xxSync只在类内部可见(客户端代码不可使用该对象作为同步锁目标),不会出现死锁现象。另外,如果Subject有多个事件,那么我们可以完全增加多个类似“_xxSync”这样的东西,比如“_xx1Sync、_xx2Sync…”等等,每个同步锁目标之间没有任何关联。

当一个类(比如前面提到的Subject)中包含的事件增多时,几十个甚至几百个,而且派生类还会增加事件,在这种情况下,我们需要统一管理这些事件,由一个集合来统一管理这些事件是个不错的选择,比如:


代码如下:

ViewCode
ClassSubject
{
protectedDictionary<object,Delegate>_handlerList=newDictionary<object,Delegate>();
Staticobject_XX1_KEY=newobject();
Staticobject_XX2_KEY=newobject();
Staticobject_XXn_KEY=newobject();
//事件
publiceventEventHandlerXX1
{
add
{
if(_handlerList.ContainsKey(_XX1_KEY))
{
_handlerList[_XX1_KEY]=Delegate.Combine(_handlerList[_XX1_KEY],value);
}
else
{
_handlerList.Add(_XX1_KEY,value);
}
}
remove
{
if(_handlerList.ContainsKey(_XX1_KEY))
{
_handlerList[_XX1_KEY]=Delegate.Remove(_handlerList[_XX1_KEY],value);
}
}
}
publiceventEventHandlerXX2
{
add
{
if(_handlerList.ContainsKey(_XX2_KEY))
{
_handlerList[_XX2_KEY]=Delegate.Combine(_handlerList[_XX2_KEY],value);
}
else
{
_handlerList.Add(_XX2_KEY,value);
}
}
remove
{
if(_handlerList.ContainsKey(_XX2_KEY))
{
_handlerList[_XX2_KEY]=Delegate.Remove(_handlerList[_XX2_KEY],value);
}
}
}
publiceventEventHandlerXXn
{
add
{
if(_handlerList.ContainsKey(_XXn_KEY))
{
_handlerList[_XXn_KEY]=Delegate.Combine(_handlerList[_XXn_KEY],value);
}
else
{
_handlerList.Add(_XXn_KEY,value);
}
}
remove
{
if(_handlerList.ContainsKey(_XXn_KEY))
{
_handlerList[_XXn_KEY]=Delegate.Remove(_handlerList[_XXn_KEY],value);
}
}
}
protectedvirtualvoidOnXX1(EventArgse)
{
if(_handlerList.ContainsKey(_XX1_KEY))
{
EventHandlerhandler=_handlerList[_XX1_KEY]asEventHandler;
If(handler!=null)
{
Handler(this,e);
}
}
}
protectedvirtualvoidOnXX2(EventArgse)
{
if(_handlerList.ContainsKey(_XX2_KEY))
{
EventHandlerhandler=_handlerList[_XX2_KEY]asEventHandler;
if(handler!=null)
{
Handler(this,e);
}
}
}
protectedvirtualvoidOnXXn(EventArgse)
{
if(_handlerList.ContainsKey(_XXn_KEY))
{
EventHandlerhandler=_handlerList[_XXn_KEY]asEventHandler;
If(handler!=null)
{
Handler(this,e);
}
}
}
publicvoidDoSomething()
{
//符合某一条件
OnXX1(newEventArgs());
OnXX2(newEventArgs());
OnXXn(newEventArgs());
}
}

存放事件委托链表的容器为Dictionary<object,Delegate>类型,该容器存放各个委托链表的表头,每当有一个“事件注册”的动作发生时,先查找字典中是否有表头,如果有,直接加到表头后面;如果没有,向字典中新加一个表头。“事件注销”操作类似。

 
字典的作用是将每个委托链表的表头组织起来,便于查询访问。可能有人已经看出来修改后的代码并没有考虑“线程安全”问题,的确,引进了集合去管理委托链表之后,再也没办法解决“线程安全”而又不影响效率了,因为现在各个事件不再是独立存在的,它们都放在了同一集合。另外,集合Dictionary<object,Delegate>声明为protected,子类完全可以使用该集合对子类的事件委托链表进行管理。
:上图中委托链中各节点引用的都是实例方法,没有列举静态方法。
其实,.net中所有从System.Windows.Forms.Control类继承下来的类,都是用这种方式去维护事件委托链表的,只不过它不是用的字典(我只是用字典模拟),它使用一个EventHandlerList类对象来存储所有的委托链表表头,作用跟Dictionary<object,Delegate>差不多,并且,.net中也没去处理“线程安全”问题。总之,CLR在处理“线程安全”问题做得不是足够好,当然,一般事件编程也基本用在单线程中(比如Winform中的UI线程中),打个比方,在UI线程中创建的Control(或其派生类),基本上都在同一线程中访问它,基本不涉及跨线程去访问Control(或其派生类),所以大可不必担心事件编程中遇到“线程安全”问题。

事件编程中的内存泄露
说到“内存泄露”,可能很多人认为这不应该是.net讨论的问题,因为GC自动回收内存,不需要编程的人去管理内存,其实不然。凡是发生了不能及时释放内存的情况,都可以叫“内存泄露”,.net中包括“托管内存”也包括“非托管内存”,前者由GC管理,后者必然由编程者考虑了(类似C++中的内存),这里我们讨论的是前者,也就是托管内存的泄露。

我们知道(假设诸位都知道),当一个托管堆中的对象不可达时,也就是程序中没有对该对象有引用时,该对象所占堆内存就属于GC回收的范围了。可是,如果编程者认为一个对象生命期应该结束(该对象不再使用)的时候,同时也理所当然地认为GC会回收该对象在堆中占用的内存时,情况往往不是TA所认为的那样,应为很有可能(概率很大),该对象在其他的地方仍然被引用,而且该引用相对来说不会很明显,我们叫这个为“隐式强引用”(Implicitstrongreference),而对于ClassA=newClass();这样的代码,A就是“显示强引用”(Explicitstrongreference)了。(至于什么是强引用什么是弱引用,这个在这里我就不说了)那么,不管是“显示强引用”还是“隐式强引用”都属于“强引用”,一个对象有一个强引用存在的话,GC就不会对它进行内存回收。

事件编程中,经常会产生“隐式强引用”,参考前面的“图1”中委托链表中的每个节点都包含一个target,当一个事件观察者向发布者注册一个事件时,那么,发布者就会保持一个观察者的强引用,这个强引用不是很明显,因此我们称之为隐式强引用。因此,当观察者被编程者理所当然地认为生命期结束了,再没有任何对它的引用存在时,事件发布者却依然保持了一个强引用。如下图:


尽管有时候,Observer生命期结束(我们理所当然地那样认为),Subject(发布者)却依旧对Observer有一个强引用(strongreference)(图2中红色箭头),该引用称作为“隐式强引用”。GC不会对Observer进行内存回收,因为还有强引用存在。如果Observer为大对象,且系统存在很多这样的Observer,当系统运行时间足够长,托管堆中的“僵尸对象”(有些对象虽然已经没有使用价值了,但是程序中依旧存在对它的强引用)越来越多,总有一个时刻,内存不足,程序崩溃。

事件编程中引起的异常
其实还是因为我们的Observer注册了事件,但在Observer生命期结束(编程者认为的)时,释放了一些必备资源,但是Subject还是对Observer有一个强引用,当事件发生后,Subject还是会通知Observer,如果Observer在处理事件的时候,也就是事件处理程序中用到了之前已经释放了的“必备资源”,程序就会出错。导致这个异常的原因就是,编程者以为对象已经死了,将其资源释放,但对象本质上还未死去,仍然会处理它注册过的事件。


代码如下:

ViewCode
//Form1.cs中:
privatevoidform1_Load(objectsender,EventArgse)
{
Form2form2=newForm2();
form2.Click+=newEventHandler(form2_Click);
form2.Show();
}
privatevoidform2_Click(objectsender,EventArgse)
{
this.Show();
}

form1为Observer,form2为Subject,form1监听form2的Click事件,在事件处理程序中将自己Show出来,一切运行良好,但是,当form1关闭后,再次点击form2激发Click事件时,程序报错,提示form1已经disposed。原因就是我们关闭form1时,认为form1生命期已经结束了,事实上并非如此,form2中还有对form1的引用,当事件发生后,还是会通知form1,调用form1的事件处理程序(form2_Click),而碰巧的是,事件处理程序中调用了this.Show()方法,意思要将form1显示出来,可此时form1已经关闭了。

小结

不管是内存泄露还是引起的异常,都是因为我们注册了某些事件,在对象生命期结束时,没有及时将已注册的事件注销,告诉事件发布者“我已死,请将我的引用删除”。因此一个简单的方法就是在对象生命期结束时将所有的事件注销,但这个只对简单的代码结构有效,复杂的系统几乎无效,事件太多,根本无法记录已注册的事件,再者,你有时候根本不知道对象什么时候生命期结束。下次介绍利用弱引用概念(Weakreference)引申出来的弱委托(Weakdelegate),它能有效地解决事件编程中内存泄露问题。原理就是将图2中每个节点中的Target由原来的强引用(StrongReference)改为弱引用(WeakReference)。
希望有帮助O(∩_∩)O~。

跟之前一样,代码未调试运行,可能有错误。

(0)

相关推荐

  • winform使用委托和事件来完成两个窗体之间通信的实例

    单击按钮 复制代码 代码如下: /// <summary>    /// Form1    /// </summary>    /// <param name="message"></param>    public delegate void ClickDelegateHander(string message); //声明一个委托    public partial class Form1 : Form    {        pub

  • C#实现WinForm捕获最小化事件的方法

    一般来说,虽然Form类没有提供Minimize的事件,但还是可以通过重载Deactive来实现WinForm捕获最小化事件. 实现方法为:当Form失去焦点后,测试WindowState取得Form状态,若为Minimized既是最小化事件. 本例为最小化后隐藏窗口: 还有种方法更加直接,重载WndProc: 实现代码如下: const int WM_SYSCOMMAND = 0x112; const int SC_CLOSE = 0xF060; const int SC_MINIMIZE =

  • C#中winform实现自动触发鼠标、键盘事件的方法

    程序触发鼠标.键盘事件是C#程序设计中比较常见的功能,本文实例展示了C#中winform实现自动触发鼠标.键盘事件的方法,有不错的实用价值.具体如下: 要想在C#程序中触发鼠标.键盘事件就必须要调用windows函数. 一.鼠标事件的触发 1.引用windows函数mouse_event /// <summary> /// 鼠标事件 /// </summary> /// <param name="flags">事件类型</param> /

  • C# Winform实现捕获窗体最小化、最大化、关闭按钮事件的方法

    本文实例讲述了C# Winform实现捕获窗体最小化.最大化.关闭按钮事件的方法,主要是通过重写WndProc来实现的.分享给大家供大家参考.具体方法如下: 主要功能代码如下: const int WM_SYSCOMMAND = 0x112; const int SC_CLOSE = 0xF060; const int SC_MINIMIZE = 0xF020; const int SC_MAXIMIZE = 0xF030; protected override void WndProc(ref

  • WinForm判断关闭事件来源于用户点击右上角“关闭”按钮的方法

    本文实例讲述了WinForm判断关闭事件来源于用户点击右上角"关闭"按钮的方法.分享给大家供大家参考.具体如下: protected override void WndProc(ref Message msg) { const int WM_SYSCOMMAND = 0x0112; const int SC_CLOSE = 0xF060; if (msg.Msg == WM_SYSCOMMAND && ((int)msg.WParam == SC_CLOSE)) { /

  • WinForm实现移除控件某个事件的方法

    本文实例讲述了WinForm实现移除控件某个事件的方法,供大家参考借鉴一下.具体功能代码如下: 主要功能部分代码如下: /// <summary> /// 移除控件某个事件 /// </summary> /// <param name="control">控件</param> /// <param name="eventName">需要移除的控件名称eg:EventClick</param> p

  • winform拦截关闭按钮触发的事件示例

    用户关闭软件时,软件一般会给"是否确认关闭"的提示.通常,我们把它写在FormClosing 事件中,如果确定关闭,就关闭:否则把FormClosingEventArgs 的 Cancel 属性设置为 true,就取消了该窗体的关闭. 如果该窗体是主窗体,我们想在该窗体关闭时关闭整个应用程序,会遇到至少两种情况: (1)该窗体同时是启动窗体,即它是应用程序中所有窗体的父类,则整个应用程序会被关闭. (2)如果该窗体不是启动窗体,比如我们做了一个欢迎窗体,则应用程序中所有窗体的父类就是这

  • 解读在C#中winform程序响应键盘事件的详解

    在winform程序中给form添加了keyup事件,但是程序却不响应键盘事件,解决办法是重写Form基类的ProcessCmdKey(ref Message msg, Keys keyData)方法. 复制代码 代码如下: protected override bool ProcessCmdKey(ref Message msg, Keys keyData)        {            if (keyData == Keys.F4)            {            

  • .Net WInform开发笔记(五)关于事件Event

    我前面几篇博客中提到过.net中的事件与Windows事件的区别,本文讨论的是前者,也就是我们代码中经常用到的Event.Event很常见,Button控件的Click.KeyPress等等,PictureBox控件的Paint等等都属于本文讨论范畴,本文会例举出有关"事件编程"的几种方法,还会提及由"事件编程"引起的MemoryLeak(跟"内存泄露"差不多),以及由"事件编程"引起的一些异常. 引子: .net中事件最常用

  • .Net WInform开发笔记(三)谈谈自制控件(自定义控件)

    末日这天写篇博客吧,既然没来,那就纪念一下. 这次谈谈自制控件,也就是自定义控件,先上图,再说 1.扩展OpenFileDialog,在OpenFileDialog中添加各种文件(.txt,.jpg,.excel等等)的预览功能 2.重写ListBox,增加折叠.鼠标背影.分类等功能 -----------------------------分割线--------------------------------------------------------------一.扩展OpenFileD

  • .Net Winform开发笔记(四)透过现象看本质

    写在前面: 从一个窗体的创建显示,再到与用户的交互,最后窗体关闭,这中间经历过了一系列复杂的过程,本文将从Winform应用程序中的Program.cs文件的第一行代码开始,逐步分析一个Winform应用程序到底是怎样从出生走向死亡,这其中包括Form.Show()和Form.ShowDialog()的区别.模式对话框形成的本质原因.消息循环.Windows事件与.net中事件(Event)的区别.System.Windows.Form.Application类的作用.以及我之前一篇博客中(.N

  • .Net Winform开发笔记(一)

    1. 理解"Windows 窗体应用程序"项目中Program.cs文件中的main方法与传统C++Console控制台程序中的main方法的区别.从程序运行层次上讲,两者无区别,都是程序的入口点,属于进程中的第一个线程.前者隐藏了UI应用程序必需的消息循环,后者没有. 2. 每个Windows桌面应用程序都必须包含至少一个UI线程,所谓UI线程,就是可以响应Windows消息的线程.通常情况下,除非特别需要,一个Windows桌面应用程序只包含一个UI线程. 3. UI线程本质上跟普

  • .Net WInform开发笔记(二)Winform程序运行结构图及TCP协议在Winform中的应用

    中午没事,把去年刚毕业那会画的几张图翻出来了,大概介绍Winform应用程序运行的过程,以及TCP协议在Winform中的应用.如果有Windows消息机制等基础,很好理解这两张图. (1)Winform应用程序运行结构图 (2)TCP通讯协议在Winform程序中的应用示意图 熟悉整个程序的来龙去脉,编程的时候就会很轻松,不会云里雾里. 另附公司招聘面试题一份,用了几次,发现效果不好,不知啥原因 1.简述接口.抽象类的区别. 2.简述重载(overload)与重写(override)的区别.

  • 从零开始学习jQuery (五) jquery事件与事件对象

    一.摘要 事件是脚本编程的灵魂. 所以本章内容也是jQuery学习的重点. 本文将对jQuery中的事件处理以及事件对象进行详细的讲解. 二.前言 本篇文章是至今为止本系列内容最多的一篇, 足以可见其重要性.  大家反映要多列举示例. 我会在时间允许的情况下尽量多列举示例. 真正的投入生产使用的实例暂时还无法加入到文章中, 但是可能最后我会列举一些作品供大家借鉴. 另外本人水平有限, 因为我不是UI设计师. 文章可能有错误的地方, 希望大家帮忙指出, 一起学习一起进步. 在技术的世界里我们是没有

  • Android开发笔记之Android中数据的存储方式(一)

    对于开发平台来讲,如果对数据的存储有良好的支持,那么对应用程序的开发将会有很大的促进作用. 总体的来讲,数据存储方式有三种:一个是文件,一个是数据库,另一个则是网络.其中文件和数据库可能用的稍多一些,文件用起来较为方便,程序可以自己定义格式:数据库用起稍烦锁一些,但它有它的优点,比如在海量数据时性能优越,有查询功能,可以加密,可以加锁,可以跨应用,跨平台等等:网络,则用于比较重要的事情,比如科研,勘探,航空等实时采集到的数据需要马上通过网络传输到数据处理中心进行存储并进行处理,有实时性的需求等.

  • C#事件(event)使用方法详解

    事件(event),这个词儿对于初学者来说,往往总是显得有些神秘,不易弄懂.而这些东西却往往又是编程中常用且非常重要的东西.大家都知道windows消息处理机制的重要,其实C#事件就是基于windows消息处理机制的,只是封装的更好,让开发者无须知道底层的消息处理机制,就可以开发出强大的基于事件的应用程序来. 先来看看事件编程有哪些好处. 在以往我们编写这类程序中,往往采用等待机制,为了等待某件事情的发生,需要不断地检测某些判断变量,而引入事件编程后,大大简化了这种过程: - 使用事件,可以很方

  • iOS开发笔记之键盘、静态库、动画和Crash定位

    前言 本文主要分享了开发中遇到的问题,和相关的一些思考.分享出来给有需要的朋友们参考学习,下面话不多说了,来一起看看详细的介绍吧. iOS11键盘问题 功能背景: 弹出键盘时,如果有输入框的话,需要输入框的位置跟随键盘大小而变动. 问题描述: 当快速切换键盘之后,容易出现输入框的位置没有紧贴键盘,如下:(以简书键盘为例) 相关实现: 输入框监听系统的UIKeyboardWillShowNotification和UIKeyboardWillHideNotification事件,在回调的过程中用UI

  • Python GUI编程学习笔记之tkinter事件绑定操作详解

    本文实例讲述了Python GUI编程学习笔记之tkinter事件绑定操作.分享给大家供大家参考,具体如下: 相关内容: command bind protocol 首发时间:2018-03-04 19:26 command: command是控件中的一个参数,如果使得command=函数,那么点击控件的时候将会触发函数 能够定义command的常见控件有: Button.Menu- 调用函数时,默认是没有参数传入的,如果要强制传入参数,可以考虑使用lambda from tkinter imp

随机推荐