亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

.Net Winform開發(fā)筆記(四)透過現(xiàn)象看本質(zhì)

 更新時(shí)間:2013年01月14日 16:49:32   投稿:whsnow  
本文將從Winform應(yīng)用程序中的Program.cs文件的第一行代碼開始逐步分析一個(gè)Winform應(yīng)用程序到底是怎樣從出生走向死亡

寫在前面
從一個(gè)窗體的創(chuàng)建顯示,再到與用戶的交互,最后窗體關(guān)閉,這中間經(jīng)歷過了一系列復(fù)雜的過程,本文將從Winform應(yīng)用程序中的Program.cs文件的第一行代碼開始,逐步分析一個(gè)Winform應(yīng)用程序到底是怎樣從出生走向死亡,這其中包括Form.Show()和Form.ShowDialog()的區(qū)別、模式對(duì)話框形成的本質(zhì)原因、消息循環(huán)、Windows事件與.net中事件(Event)的區(qū)別、System.Windows.Form.Application類的作用、以及我之前一篇博客中(.Net開發(fā)筆記(二)網(wǎng)址)面試題中的最后一題,從Windows消息層次講述點(diǎn)擊按鈕彈出一個(gè)MessageBox的詳細(xì)過程。

我承認(rèn),不了解以上問題的Coder可能也能寫出非常出色非常復(fù)雜的Winform應(yīng)用程序出來(lái),但不是有句老話么,知其然,亦要知其所以然。

另外,看本篇博客(或者接下來(lái)幾篇)必須了解Win32編程知識(shí),如果不清楚的同學(xué),可以先上網(wǎng)學(xué)習(xí)學(xué)習(xí),這就像學(xué)習(xí)MFC最好也得懂點(diǎn)Win32編程,本文不解釋什么是Win32 API、什么是句柄、更不會(huì)解釋什么是回調(diào)方法。

一個(gè)引子
一個(gè)線程,具體啥定義我也就不說(shuō)了,太抽象,我覺得還是把它看做是一個(gè)方法(函數(shù)),當(dāng)然包括方法體中調(diào)用的其它方法,線程有開始,也有結(jié)束,分別可以比作方法的開始和結(jié)束,我們不管一個(gè)方法體內(nèi)調(diào)用了多少其它方法,只要程序沒寫錯(cuò),這個(gè)方法肯定有返回的時(shí)候,也就是說(shuō),在正常情況下,一個(gè)線程開始后,肯定會(huì)有退出(結(jié)束)的時(shí)候,那么,如果想讓一個(gè)線程不會(huì)太快結(jié)束,我們可以在方法體內(nèi)寫些啥?“阻塞方法!”有人可能馬上說(shuō),因?yàn)樽枞椒ㄒ话悴粫?huì)馬上返回,只有等它執(zhí)行完畢后,才會(huì)返回,在它返回前,調(diào)用它的方法不會(huì)繼續(xù)運(yùn)行下去,的確,在我學(xué)習(xí)C++語(yǔ)言的時(shí)候,經(jīng)常寫Console程序(那時(shí)候也只會(huì)寫這玩意兒),為了不讓黑屏閃一下就消失了,看不到運(yùn)行結(jié)果,我經(jīng)常在程序最后加上一行“int a;cin>>a;”,我當(dāng)時(shí)也不知道為啥要這樣寫,只知道這樣寫了,程序不會(huì)馬上結(jié)束。其實(shí)后來(lái)才知道,那行代碼就是阻塞了整個(gè)程序,當(dāng)你輸入一個(gè)整數(shù),按下回車,程序就會(huì)結(jié)束。

“阻塞方法”確實(shí)是一種方法,但是如果我們想在線程執(zhí)行過程中,與外部(用戶)進(jìn)行交互,也就是說(shuō),在線程執(zhí)行期間,用戶可以通過輸入來(lái)控制線程的運(yùn)行情況,同樣在Console程序中,該怎么實(shí)現(xiàn)?現(xiàn)在問題來(lái)了,不緊不能讓線程馬上結(jié)束,還要與用戶有所交互,而且不應(yīng)該只交互一次(否則,上面提到的cin>>a;完全夠用),該怎么搞?不止交互一次?那么很容易就能想到“循環(huán)”,用循環(huán)來(lái)使線程與用戶進(jìn)行交互再好不過了,為了與本文相聯(lián)系,用C#代碼編寫如下:

復(fù)制代碼 代碼如下:

View Code
void main()
{
string input = “quit”;
while((input=Console.ReadLine())!=”quit”)
{
Console.WriteLine(“input string :” + input );
}
Console.WriteLine(“thread exit”);
Console.ReadKey();
}

非常簡(jiǎn)單的一段代碼,程序運(yùn)行后,有了while循環(huán),不會(huì)馬上結(jié)束,它會(huì)不停的等待用戶輸入,然后輸出用戶輸入的字符串(模擬響應(yīng)用戶操作),直到用戶輸入“quit”后,循環(huán)才結(jié)束。這段利用while循環(huán)和Console.ReadLine()寫出來(lái)的程序雖然短小簡(jiǎn)單,卻是后面我們要談到的Winform應(yīng)用程序(其實(shí)所有的Windows應(yīng)用程序都一樣,無(wú)論是MFC還是Delphi或者其他搞出來(lái)的桌面程序)的精髓。當(dāng)然,這段代碼確實(shí)太簡(jiǎn)陋了,所以我才說(shuō)它是精髓,O(∩_∩)O~。既然太簡(jiǎn)陋,那我們?cè)俑母陌伞R木透膹?fù)雜一點(diǎn)。
初加工:
復(fù)制代碼 代碼如下:

View Code
///矩形類
Class Rect
{
int _id; //矩形唯一標(biāo)示
string _text; //矩形中心顯示的文本
Size _size; //矩形大小
Point _location; //矩形的位置
Bool _alive; //是否存活
Public int ID
{
Get
{
Return _id
}
Set
{
_id = value;
}
}
Public string Text
{
Get
{
Return _text;
}
Set
{
_text = value;
}
}
Public Size Size
{
Get
{
Return _size;
}
Set
{
_size = value;
}
}
Public Point Location
{
Get
{
Return _location;
}
Set
{
_location = value;
}
}
Public bool Alive
{
Get
{
Return _alive;
}
Set
{
_alive = value;
}
}
Public Rect(int id,string text,Size size,Point location)
{
_id = id;
_text = text;
_size = size;
_location = location;
_alive = true;
Console.WriteLine(“[” + ID.ToString() + “] 號(hào)矩形創(chuàng)建成功!”);
}
//矩形對(duì)外唯一接口,對(duì)矩形的所有操作必須調(diào)用此方法,下稱“矩形過程”
Public void RectProc(int id,int type,object leftParam,object rightParam)
{
Switch(type) //這個(gè)type就是后面說(shuō)的“信號(hào)類型”, 應(yīng)該跟Sgl枚舉一一對(duì)應(yīng)
{
Case 1: //移動(dòng)、改變大小
{
Size newSize = (Size)leftParam;
Position newLocation = (Point)rightParam;
This.Size = newSize;
This.Location = newLocation;
Console.WriteLine(“[” + ID.ToString() + “] 號(hào)矩形改變位置:大小為(” + this.Size.Width+”,” + this.Size.Height + “),位置為(”+this.Location.Left + “,” + this.Location.Top + “)” );
Break;
}
Case 2: //顯示信息
{
Console.WriteLine(“[” + ID.ToString() + “] 號(hào)矩形顯示信息:大小為(” + this.Size.Width+”,” + this.Size.Height + “),位置為(”+this.Location.Left + “,” + this.Location.Top + “),Text為 ” + this.Text );
Break;
}
Case 3: //關(guān)閉
{
Console.WriteLine(“[” + ID.ToString() + “] 號(hào)矩形關(guān)閉”);
Alive = false;
Break;
}
//……
Default:
{
//默認(rèn)處理
}
}
}
}
//信號(hào)類,表示一種信號(hào),包含信號(hào)接收者ID,信號(hào)類型Type,信號(hào)兩個(gè)參數(shù)LeftParam、RightParam
Class Signal
{
Int _id; //接受者id
Int _type; //信號(hào)類型
Object _leftParam; //參數(shù)1
Object _rightParam; //參數(shù)2
Public int ID
{
Get
{
Return _id;
}
Set
{
_id = value;
}
}
Public int Type
{
Get
{
Return _type;
}
Set
{
_type = value;
}
}
Public object LeftParam
{
Get
{
Return _leftParam;
}
Set
{
_leftParam = value;
}
}
Public object RightParam
{
Get
{
Return _rightParam;
}
Set
{
_rightParam = value;
}
}
Public Signal(int id,int type,object leftParam,object rightParam)
{
_id = id;
_type = type;
_leftParam = leftParam;
_rightParam = rightParam;
}
}
// 信號(hào)類型枚舉 RS即為RectSignal
Enum Sgl
{
RS_POSITIONCHANGE = 1, //移動(dòng)矩形,大小變化
RS_SHOWINFO = 2, //矩形顯示自己信息
RS_KILL = 3 //關(guān)閉矩形
//……很多省略
}
/* 信號(hào)格式(不同的Sgl,Signal對(duì)象內(nèi)容完整度不一樣)
* RS_POSITIONCHANGE: ID必須,Type必須,LeftParam必須,RightParam必須
* RS_SHOWINFO ID必須,Type必須
* RS_KILL: ID必須,Type必須
* ……很多省略
*/
/// 主線程
/// 測(cè)試代碼
Static class ZZThread
{
Static List<Rect> allRects = new List<Rect>(); //整個(gè)線程運(yùn)行過程中,存在的Rect對(duì)象
//線程入口
Public Static void Main()
{
//初始化4個(gè)Rect對(duì)象,添加到集合中
allRects.Add(new Rect(1,”my name is Rect1”,new Size(100,100),new Point(10,10)));
all.Rects.Add(new Rect(2,”my name is Rect2”,new Size(455,250),new Point(100,150));
allRects.Add(new Rect(3,”my name is Rect3”,new Size(300,500),new Point(250,100));
allRects,Add(new Rect(4,”my name is Rect4”,new Size(300,600),new Point(50,80));
//開始循環(huán)接收用戶輸入,作出反應(yīng)
Signal signal = null;
While(GetSignal(out signal)) //接收信號(hào)
{
DispatchSignal(signal); //分配信號(hào)到各個(gè)Rect
}
Console.WriteLine(“The Thread Exit”);
// Console.ReadKey(); //阻塞查看運(yùn)行情況
}
Static bool GetSignal(out Signal signal)
{
START:
String input = Console.ReadLine(); //接受用戶輸入
String[] inputs = input.Split(“ ”);
If(inputs.Length == 1) //用戶輸入QUIT,退出
{
If(inputs[0] == “QUIT”)
{
Return false;
}
Else
{
Console.WriteLine(“參數(shù)格式錯(cuò)誤!”);
Goto START;
}
}
// 必須提供Rect的id、以及信號(hào)類型,參數(shù)可選
// 沒做格式驗(yàn)證,所有必須輸入整形數(shù)據(jù)
If(inputs.Length == 2) //只提供了Rect的id和信號(hào)類型
{
signal = new Signal(int.parse(intputs[0]),int.Parse(inputs[1]),null,null);
return true;
}
If(inputs.Length == 4) //只提供了Rect的id、信號(hào)類型以及第一個(gè)參數(shù)
{
signal = new Signal(int.Parse(inputs[0]),int.Parse(intputs[1]),new Size(int.Parse(inputs[2]),int.Parse(inputs[3])),null);
return true;
}
If(inputs.Length == 6) //四個(gè)參數(shù)全部提供
{
signal = new Signal(int.Parse(inputs[0]),int.Parse(inputs[1]),new Size(int.Parse(inputs[2]),int.Parse(inputs[3])),new Point(int.Parse(inputs[4]),int.Parse(inputs[5])));
return true;
}
Console.WriteLine(“參數(shù)格式錯(cuò)誤!”);
Goto START;
}
Static void DispatchSignal(Signal signal)
{
Foreach(Rect rect in allRects)
{
If(rect.ID == signal.ID && rect.Alive)
{
rect.RectProc(signal.ID,signal.Type,signal.LeftParam,signal.RightParam);
break;
}
}
}
}

解釋一下,代碼雖然多了一點(diǎn),可大概結(jié)構(gòu)還是沒變(其實(shí)我們見到的其他所有框架,結(jié)構(gòu)雖然復(fù)雜得很,可其精髓的代碼也就不到一半,其余的都是在精髓代碼上擴(kuò)充來(lái)的,增加各種各樣的功能),如你所見,跟之前的意思一樣,線程中有一個(gè)While循環(huán)、接收用戶輸入、響應(yīng)用戶輸入(操作)。不一樣的是,將接受用戶輸入部分封裝到一個(gè)GetSignal方法中去了,將響應(yīng)用戶輸入部分封裝到一個(gè)DispatchSignal方法中去了,為了更好的反應(yīng)用戶操作可以“多樣化”(不再是以前輸入一個(gè)字符串,線程再將源字符串輸出),我定義了一個(gè)Rect類,該類表示一個(gè)矩形,可以供用戶操作,我還定義了一個(gè)Signal類,該類表示一個(gè)信號(hào),用戶的所有輸入都可以看做是一個(gè)信號(hào),信號(hào)中包括信號(hào)接受者(ID)、信號(hào)類型、以及信號(hào)可能附帶的參數(shù),此外,(不要嫌麻煩O(∩_∩)O~)我還定義了一個(gè)信號(hào)類型枚舉,用來(lái)表示用戶操作的類型。
現(xiàn)在,我們來(lái)理清一下整個(gè)線程運(yùn)行的流程:
1.ZZThread中的靜態(tài)方法Main開始運(yùn)行,線程開始
2.新建四個(gè)Rect對(duì)象,將其加到一個(gè)集合中,供用戶操作
3.開始一個(gè)while循環(huán),GetSignal接受用戶輸入,輸入格式需按照規(guī)定格式
4.GetSignal方法返回,如果用戶輸入不是“QUIT”字符串,返回true,否則返回false,while循環(huán)結(jié)束,線程退出。
5.用戶輸入不是“QUIT”,GetSignal方法的signal參數(shù)即為用戶輸入的信息(該信號(hào)應(yīng)該包括用戶想要操作的對(duì)象、操作的類型、以及一些附帶參數(shù)),其實(shí)就是上面的“信號(hào)”概念。
6.信號(hào)有了,需要將信號(hào)發(fā)給接受者,那么,DispatchSignal方法就負(fù)責(zé)將信號(hào)發(fā)給對(duì)應(yīng)的Rect對(duì)象(通過rect.ID ?= signal.ID來(lái)判斷)。
7.接受者(Rect對(duì)象)使用自己的RectProc來(lái)處理信號(hào),RectProc方法中根據(jù)不同的信號(hào)類型,作出相應(yīng)的反應(yīng)。
可能文字不太直觀,上一張圖,來(lái)解釋一下,圖文結(jié)合更有效。

 
好了,改了之后的代碼復(fù)雜很多,當(dāng)然了,功能也比之前的多了很多,但是還是那句話,大概結(jié)構(gòu)沒有變,一個(gè)while循環(huán)、一個(gè)接收用戶輸入部分、一個(gè)響應(yīng)用戶操作部分。(看完代碼和圖的同學(xué),或者說(shuō)有Win32編程基礎(chǔ)的同學(xué),到現(xiàn)在為止,可能已經(jīng)看出這是個(gè)啥意思,我們暫且先不說(shuō),聽我慢慢道來(lái)O(∩_∩)O~)
現(xiàn)在我來(lái)說(shuō)說(shuō)改了之后的代碼還有哪些地方的不足:

1.每個(gè)Rect對(duì)象之間無(wú)法通信,因?yàn)楦鱾€(gè)Rect對(duì)象之間是相互的,每個(gè)Rect對(duì)象在響應(yīng)用戶輸入(執(zhí)行RectProc,下同)的時(shí)候不能影響其他的Rect對(duì)象,因?yàn)槟愀静恢懒硗獾腞ect對(duì)象在哪、什么狀態(tài)。
2.在響應(yīng)用戶輸入的時(shí)候,也就是while循環(huán)體執(zhí)行期間,我們不能改變while循環(huán)條件,讓循環(huán)結(jié)束,意思就是,現(xiàn)在這個(gè)線程,有兩種情況退出,第一種就是用戶直接輸入“QUIT”,第二種就是強(qiáng)制關(guān)閉程序,后者明顯不可取,那么前者一種方法能滿足我們的需求嗎?答案是不能,現(xiàn)在考慮這種情況:在線程運(yùn)行期間,所有存在的Rect對(duì)象中有一個(gè)是主Rect,也就是說(shuō),這個(gè)主Rect對(duì)象跟其他不一樣,當(dāng)這個(gè)主Rect對(duì)象被用戶關(guān)閉后(RS_KILL),最好的效果就是,整個(gè)線程結(jié)束。因此,在主Rect對(duì)象處理RS_KILL信號(hào)后,應(yīng)立馬“模仿”用戶向線程再發(fā)送一個(gè)“QUIT”字符串,讓while循環(huán)下一次退出。
3.Rect對(duì)象既然是用戶主要操作的目標(biāo),那么就應(yīng)該允許我們?cè)赗ect類上繼承新的類,來(lái)實(shí)現(xiàn)更豐富的效果,而且,新擴(kuò)展出來(lái)的類也應(yīng)該像Rect類一樣響應(yīng)用戶輸入。
4.同樣,Rect類對(duì)象的一舉一動(dòng),勢(shì)必會(huì)影響另外一些對(duì)象,所以,Rect類應(yīng)該加上一些事件(此處事件為.net中的Event,它與Windows事件的區(qū)別稍后會(huì)講)。
5.在Rect類對(duì)象響應(yīng)用戶的某一次操作后,可能需要再次通知自己進(jìn)行其他操作,比如一個(gè)Rect對(duì)象在響應(yīng)“改變位置”這個(gè)信號(hào)之后,立馬需要顯示自己信息,也就是說(shuō)在處理完RS_POSITIONCHANGE信號(hào)后,立刻需要給自己發(fā)一個(gè)RS_SHOWINFO信號(hào),它才能顯示自己的信息。這就出現(xiàn)一個(gè)問題,“信號(hào)”會(huì)產(chǎn)生“信號(hào)”,這個(gè)過程完全不需要用戶區(qū)操控,當(dāng)然,用戶也無(wú)法去操控。
6.最后,不知道諸位發(fā)現(xiàn)沒有,用戶的輸入與Rect對(duì)象的響應(yīng)是(也只能是)同步的,啥叫同步?簡(jiǎn)單來(lái)說(shuō)就是,A做完什么之后,B才能行動(dòng),或者等B行動(dòng)完后,A才能繼續(xù)。只有等用戶輸入后,GetSignal方法才能返回,Rect對(duì)象才能做出反應(yīng),同理,只有Rect對(duì)象響應(yīng)完成后,用戶才可能繼續(xù)輸入,一次輸入一次響應(yīng),輸入沒完成,就沒有響應(yīng),響應(yīng)沒完成,用戶也不能輸入。理想情況應(yīng)該是這樣的:用戶在想要輸入的時(shí)候就可以輸入,而不用去管Rect對(duì)象有沒有響應(yīng)完成(DispatchSignal返回),當(dāng)然,在這種情況下,用戶的輸入仍然會(huì)陸陸續(xù)續(xù)的被響應(yīng)。
分析一下上面6條,其中1、2、5條其實(shí)意思差不多,就是在while循環(huán)體執(zhí)行期間,需要“模仿”用戶輸入,然而現(xiàn)在的情況是,GetSignal方法是“主動(dòng)型”的,只有它主動(dòng)去接收用戶輸入,它才會(huì)有結(jié)果,當(dāng)它沒有準(zhǔn)備好,就算有輸入,也不會(huì)被接收。這樣看來(lái),我們只有增加一個(gè)類似“緩沖區(qū)”的東西,不管GetSignal有沒有準(zhǔn)備,所有的輸入信號(hào)全部存放在這個(gè)緩沖區(qū)中,等到GetSignal準(zhǔn)備好獲取輸入信號(hào)時(shí),直接從這個(gè)緩沖區(qū)中取得。
說(shuō)到“緩沖區(qū)”,我們第一應(yīng)該想到用“隊(duì)列”,不錯(cuò),就是隊(duì)列!我們來(lái)看一下MSDN上對(duì)“隊(duì)列”(Queue類)的解釋:
Queues are useful for storing messages in the order they were received for sequential processing. This class implements a queue as a circular array. Objects stored in a Queue are inserted at one end and removed from the other.
大概意思就是隊(duì)列一般用于存儲(chǔ)需要按順序處理的消息。
第6條其實(shí)也可以用隊(duì)列來(lái)實(shí)現(xiàn),用戶不停地向隊(duì)列輸入,而不用管Rect對(duì)象是否立刻去響應(yīng),隊(duì)列起到一個(gè)緩沖的作用,當(dāng)然,如果這樣設(shè)計(jì)的話,用戶輸入和Rect對(duì)象響應(yīng)輸入應(yīng)該不在同一線程,這就要用到多線程了。
第3、4條其實(shí)就是OO中的繼承、虛方法引起的“多態(tài)性”,以及.net中常用到的Observer模式,用Event很好實(shí)現(xiàn)。
升華:
經(jīng)過分析,Rect類改為:(虛方法以及事件只舉例定義了兩個(gè),現(xiàn)實(shí)中應(yīng)該有很多個(gè))

復(fù)制代碼 代碼如下:

View Code
///矩形類
Class Rect
{
int _id; //矩形唯一標(biāo)示
string _text; //矩形中心顯示的文本
Size _size; //矩形大小
Point _location; //矩形的位置
Bool _alive; //是否存活
Public int ID
{
Get
{
Return _id
}
Set
{
_id = value;
}
}
Public string Text
{
Get
{
Return _text;
}
Set
{
_text = value;
}
}
Public Size Size
{
Get
{
Return _size;
}
Set
{
_size = value;
}
}
Public Point Location
{
Get
{
Return _location;
}
Set
{
_location = value;
}
}
Public bool Alive
{
Get
{
Return _alive;
}
Set
{
_alive = value;
}
}
Public Rect(int id,string text,Size size,Point location)
{
_id = id;
_text = text;
_size = size;
_location = location;
_alive = true;
}
//矩形對(duì)外唯一接口,對(duì)矩形的所有操作必須調(diào)用此方法,下稱“矩形過程”
Public virtual void RectProc(int id,int type,object leftParam,object rightParam)
{
Switch(type) //這個(gè)type就是后面說(shuō)的“信號(hào)類型”, 應(yīng)該跟Sgl枚舉一一對(duì)應(yīng)
{
Case 1: //移動(dòng)、改變大小
{
Size newSize = (Size)leftParam;
Position newLocation = (Point)rightParam;
This.Size = newSize;
This.Location = newLocation;
OnPositionChanged(new PositionChangedEventArgs(this.Size,this.Location));
Break;
}
Case 2: //顯示信息
{
//調(diào)用對(duì)應(yīng)虛方法
Break;
}
Case 3: //關(guān)閉
{
Alive = false;
OnKill(new EventArgs());
Break;
}
//……很多省略
Default:
{
//默認(rèn)處理
}
}
}
Protected virtual void OnPositionChanged(PositionChangedEventArgs e)
{
If(PositionChanged!=null)
{
PositionChanged(this,e);
}
}
Protected virtual void OnKill(EventArgs e)
{
If(Kill!=null)
{
Kill(this,e);
}
}
Public event PositionChangedEventHandler PositionChanged;
Public event EventHandler Kill;
}

還需添加以下:
復(fù)制代碼 代碼如下:

View Code
Public delegate void PositionChangedEventHandler(object sender,PositionChangedEventArgs e)
Public class PositionChangedEventArgs
{
Size _size;
Point _location;
Public Size Size
{
Get
{
Return _size;
}
}
Public Point Location
{
Get
{
Return _location;
}
}
Public PositionChangedEventArgs(Size size,Point location)
{
_size = size;
_location = location;
}
}

再?gòu)腞ect類派生出一個(gè)新的類DeriveRect,該類為Rect類的子類:
復(fù)制代碼 代碼如下:

View Code
class DeriveRect:Rect
{
public DeriveRect(int id,string text,Size size,Point location):base(id,text,size,location)
{
}
public override void RectProc(int id,int type,object leftParam,object rightParam)
{
//攔截信號(hào)
base.RectProc(int id,int type,object leftParam,object rightParam);
}
protected override void OnPositionChanged(PositionChangedEventArgs e)
{
//添加自己的代碼
//ZZThread.SendSignal(…)發(fā)送信號(hào)到本線程信號(hào)隊(duì)列中
base.OnPositionChanged(e); //觸發(fā)基類事件
}
//你可以重寫其他虛方法,就像繼承一個(gè)Form類,重寫它的虛方法一樣。
}

為了統(tǒng)一處理信號(hào),我們?cè)赟gl枚舉類型中再加一個(gè)枚舉變量RS_QUIT,它指示while循環(huán)退出(這個(gè)信號(hào)不再唯一由用戶輸入,為什么請(qǐng)看前面提出的6條),Sgl枚舉改為:
復(fù)制代碼 代碼如下:

View Code
Enum Sgl
{
RS_POSITIONCHANGE = 1, //移動(dòng)矩形,大小變化
RS_SHOWINFO = 2, //矩形顯示自己信息
RS_KILL = 3, //關(guān)閉矩形
RS_QUIT = 4 //退出循環(huán)
//……很多省略
}

ZZThread類則改為:(主要增加了一個(gè)信號(hào)列表,然后修改了一下GetSignal方法,讓其直接從信號(hào)列表中獲取信號(hào),而不需要再等待用戶輸入,當(dāng)然,我這里沒有寫出專門讓用戶輸入的線程,因?yàn)檫@個(gè)示意性代碼本身就是一個(gè)Console程序,多線程去接收用戶輸入的話,“輸入內(nèi)容”會(huì)和“響應(yīng)用戶輸入的內(nèi)容”相混淆,只需要知道用戶會(huì)在另外一個(gè)線程中向signalList中添加信號(hào),而這個(gè)動(dòng)作不需要我們有多少了解,原因后面會(huì)講到)。另外,現(xiàn)在用戶可以操作的不單單是Rect類對(duì)象了,可以是Rect類的派生類,而且你還可以Rect類(或其派生類的事件)。為了在循環(huán)體執(zhí)行期間,控制循環(huán)退出,增加了一個(gè)PostQuit方法,該方法只是簡(jiǎn)單的向signalList隊(duì)列添加一個(gè)“退出”信號(hào)。
復(fù)制代碼 代碼如下:

View Code
/// 主線程
/// 測(cè)試代碼
Static class ZZThread
{
Static List<Rect> allRects = new List<Rect>(); //整個(gè)線程運(yùn)行過程中,存在的Rect對(duì)象
Static Queue signalList = new Queue(); //線程信號(hào)隊(duì)列(考慮到另有線程接收用戶輸入,也會(huì)操作此信號(hào)隊(duì)列,所以請(qǐng)考慮線程同步問題)
//線程入口
Public Static void Main()
{
//初始化一個(gè)Rect對(duì)象,一個(gè)為DeriveRect對(duì)象,前者為主Rect,當(dāng)它關(guān)閉的時(shí)候,退出循環(huán),結(jié)束線程
Rect chiefRect = new Rect(1,”I am the chief Rect!”,new Size(100,100),new Point(10,10));
chiefRect.Kill += (EventHandler)(delegate(sender,e){PostQuit();}); //主Rect的關(guān)閉事件,向所在線程信號(hào)隊(duì)列發(fā)送一個(gè)退出信號(hào)
Rect derivedRect = new DeriveRect(2,”I am the Derived Rect”,new Size(150,150),new Point(200,200));
allRects.Add(chiefRect);
allRects.Add(derivedRect);
//開始循環(huán)從線程的信號(hào)隊(duì)列里獲取信號(hào)
Signal signal = null;
While(GetSignal(out signal)) //接收信號(hào)
{
DispatchSignal(signal); //分配信號(hào)到各個(gè)Rect
}
Console.WriteLine(“The Thread Exit”);
// Console.ReadKey(); //阻塞查看運(yùn)行情況
}
Static bool GetSignal(out Signal signal)
{
//從隊(duì)列獲取信號(hào),如果隊(duì)列為空,阻塞直到隊(duì)列有信號(hào)為止,否則如果非RS_QUIT,返回true,如果RS_QUIT,則返回false
START:
If(signalList.Count!=0) //注意需要處理線程同步
{
signal = signalList.DeQueue() as Signal;
if(signal.Type != (int)Sgl.RS_QUIT)
{
return true;
}
else
{
return false;
}
}
Else
{
Thread.Sleep(1);
goto START;
}
}
Static void DispatchSignal(Signal signal)
{
Foreach(Rect rect in allRects)
{
If(rect.ID == signal.ID && rect.Alive)
{
rect.RectProc(signal.ID,signal.Type,signal.LeftParam,signal.RightParam);
break;
}
}
}
public static void PostQuit()
{
signalList.EnQueue(new Signal(0,(int)Sgl.RS_QUIT,null,null));
}
public static void SendSignal(int id,int type,object leftParam,object rightParam)
{
signalList.EnQueue(new Signal(id,type,leftParam,rightParam));
}
}

好了,改完了,解釋一下改完后的代碼。改完后的代碼和之前的大概結(jié)構(gòu)仍然相同,一個(gè)while循環(huán)、一個(gè)獲取信號(hào)(這里不再單單是用戶輸入了,還包括循環(huán)體內(nèi)向循環(huán)體外發(fā)送的信號(hào))的GetSignal方法、一個(gè)處理信號(hào)(將信號(hào)分發(fā)給線程中對(duì)應(yīng)的Rect對(duì)象以及其派生類)的DispatchSignal方法。
再上張圖,圖文結(jié)合,效果杠杠的。


再分析一下,代碼修改之前提出的6條不足,基本上全部解決了
1.在Rect對(duì)象(或其派生類對(duì)象,下同)處理信號(hào)的時(shí)候,只要知道任何一個(gè)相同線程中的其它Rect對(duì)象的ID,那么就可以利用ZZThread.SendSignal()向其發(fā)送信號(hào)。
2.同理,在某一Rect對(duì)象(我們?cè)谶@成為主Rect)關(guān)閉的時(shí)候(處理RS_QUIT信號(hào)),它可以通過ZZThread.PostQuit()方法向循環(huán)發(fā)送退出信號(hào)。
3.通過允許Rect類被繼承,就可以實(shí)現(xiàn)多樣化的效果,響應(yīng)用戶輸入不再僅僅只是Rect類了,還可以是Rect的派生類。
4.通過向響應(yīng)者(Rect類)添加事件(Event)的方法,外部其他Rect對(duì)象就可以到事件的發(fā)生,做出相應(yīng)響應(yīng)。
5.與1相似,在處理某一信號(hào)的時(shí)候,完全可以通過ZZThread.SendSignal方法將ID參數(shù)設(shè)為自己的ID,向自己發(fā)送一個(gè)信號(hào),這就可以達(dá)到“在處理完一個(gè)信號(hào)后緊接著向自己發(fā)送另外一個(gè)信號(hào)”的功能。
6.通過增加信號(hào)隊(duì)列和一個(gè)專門接受用戶輸入的線程(以上示意性代碼中未給出),完全可以達(dá)到“讓用戶輸入和Rect對(duì)象響應(yīng)”異步發(fā)生。
以上6條確實(shí)完美解決了,現(xiàn)在繼續(xù)考慮幾個(gè)問題(你們可能知道改完之后的這個(gè)東西肯定不會(huì)是我們最終想要的,因?yàn)樗菜聘乙v的Winform幾乎沒有任何聯(lián)系,所以,繼續(xù)考慮下面幾個(gè)問題)。
(由于每個(gè)問題跟上一個(gè)問題有聯(lián)系,所以我依次給出了問題的解決辦法。)
1. 這個(gè)只能在一個(gè)線程中使用,也就是說(shuō),同一個(gè)程序中,只能存在一個(gè)這樣的線程,因?yàn)閆ZThread類是個(gè)靜態(tài)類,所有的成員也是靜態(tài)的,如果多個(gè)線程使用它的話,就會(huì)全亂了。舉例看下面:
Thread th1 = new Thread((ThreadStart)(delegate(){
ZZThread.Main();
}));
th1.Start();
Thread th2 = new Thread((ThreadStart)(delegate(){
ZZThread.Main();
}));
th2.Start();
th1與th2兩個(gè)線程中,用到的signalList、allRects是同一個(gè),兩個(gè)線程中的while循環(huán)也是從同一個(gè)信號(hào)隊(duì)列中去取信號(hào),然后分配給同一個(gè)Rect對(duì)象集合中的對(duì)象,雖然可以做一些同步“線程安全”的處理,但是仍然有問題,仔細(xì)想一想(比如發(fā)送RS_QUIT信號(hào)想讓本線程退出,到底哪個(gè)退出不確定)。因此,理想情況應(yīng)該是這樣的:每一個(gè)線程有自己的信號(hào)隊(duì)列(signalList),有自己的Rect對(duì)象集合(allRects),有自己的while循環(huán)和自己的DispatchSignal方法,換句話說(shuō),兩個(gè)線程之間不應(yīng)該有瓜葛,而應(yīng)該互不影響,相互。(當(dāng)然,除了這些,兩個(gè)線程理論上可以有其他聯(lián)系,后面會(huì)提到)。
解決方法:
既然ZZThread類是靜態(tài)的,那么我們就可以把它設(shè)置成非靜態(tài),每個(gè)線程對(duì)應(yīng)一個(gè)ZZThread對(duì)象,這樣線程與線程之間就不會(huì)有影響,每個(gè)線程都有自己的信號(hào)隊(duì)列、自己的Rect對(duì)象集合以及自己的while循環(huán)和DispatchSignal方法。當(dāng)然,如果這樣處理的話,就應(yīng)該考慮怎么確保每個(gè)線程擁有自己的ZZThread對(duì)象,就是說(shuō),怎么保證一個(gè)線程能找到與它對(duì)應(yīng)的ZZThread對(duì)象?很簡(jiǎn)單,每個(gè)線程都有唯一一個(gè)ID(整個(gè)系統(tǒng)范圍內(nèi)唯一),可以定義一個(gè)Dictionary<int,ZZThread>字典,在每個(gè)線程中要使用ZZThread對(duì)象的地方,先根據(jù)線程ID(這個(gè)可以隨時(shí)取得,只要在同一線程中,ID肯定相同)查找字典,如果存在,直接拿出來(lái)使用,如果不存在,說(shuō)明還沒有創(chuàng)建,那就新建一個(gè)ZZThread對(duì)象,加到字典中,將新建的ZZThread對(duì)象拿來(lái)使用。這樣的話,在每個(gè)線程的任何一個(gè)地方要使用ZZThread對(duì)象的話,都能通過該方法取得同一個(gè)ZZThread對(duì)象。但要考慮怎么去維護(hù)這樣一個(gè)字典?
2. ZZThread類不能直接暴露給使用者。還是考慮多個(gè)線程的情況,ZZThread類中的while循環(huán)入口(之前一直是Main方法)、以及諸如像PostQuit、SendSignal等(后面可能還會(huì)增加)都是public類型的,如果ZZThread直接暴漏給使用者,使用者完全可以在一個(gè)線程中使用另外一個(gè)ZZThread對(duì)象(注:1中的解決方法只解決了“怎樣讓一個(gè)線程正確地使用同一個(gè)ZZThread對(duì)象”,并沒有解決“一個(gè)線程只能使用一個(gè)ZZThread對(duì)象”)。
解決方法:
一個(gè)很好的解決方法就是將“精髓部分”封裝起來(lái),封裝成一個(gè)庫(kù)(或者模塊、框架隨便叫),只對(duì)外開放必要的類,而像ZZThread這樣的類也就沒必要開放,最關(guān)鍵的是,1中提到的字典也不應(yīng)該對(duì)外開放,使用者的不正當(dāng)操作很可能。
3. 考慮一種情況,Rect對(duì)象在響應(yīng)信號(hào)時(shí)(RectProc執(zhí)行期間),耗時(shí)時(shí)間太長(zhǎng),即DispatchSignal方法長(zhǎng)時(shí)間不能返回,也就是說(shuō),長(zhǎng)時(shí)間不能再次調(diào)用GetSignal方法,導(dǎo)致線程中信號(hào)大量累積,不能及時(shí)處理,因此用戶的輸入也不會(huì)及時(shí)得到響應(yīng),造成用戶體驗(yàn)明顯下降,這時(shí)候改怎么處理?
解決方法:
既然DispatchSignal方法不能及時(shí)返回,導(dǎo)致信號(hào)隊(duì)列的信號(hào)不能即使被處理,那么我們可以在Rect對(duì)象處理信號(hào)的耗時(shí)操作中(RectProc執(zhí)行期間),執(zhí)行適當(dāng)次數(shù)的while循環(huán),也就是說(shuō),在一個(gè)while循環(huán)體內(nèi),再次執(zhí)行一個(gè)while循環(huán),及時(shí)處理信號(hào),這就是嵌套執(zhí)行while循環(huán)了,當(dāng)然,內(nèi)部的while循環(huán)跟外部的while循環(huán)有稍微差別,即內(nèi)部while循環(huán)每次就執(zhí)行一次,執(zhí)行完后,繼續(xù)做其他的耗時(shí)操作,如果需要大量循環(huán)處理一個(gè)內(nèi)容,可以在每次循環(huán)結(jié)束后調(diào)用一次while循環(huán),保證信號(hào)隊(duì)列的信號(hào)能夠及時(shí)處理。上一張圖,看得明白一些:


另外,還有一種內(nèi)嵌的while循環(huán),它不止執(zhí)行一次循環(huán)就退出,而是當(dāng)某種條件為真時(shí),才退出,這種現(xiàn)在涉及不到??傊覀兛梢钥闯鲆粋€(gè)線程中可以有多個(gè)while循環(huán)來(lái)處理信號(hào),這些while循環(huán)大多是嵌套調(diào)用的(不排除這種情況:一個(gè)while循環(huán)退出后,接著再跟一個(gè)while循環(huán),這兩個(gè)while循環(huán)完全相同,但這種出現(xiàn)的幾率很少,以后講到Winform相關(guān)的時(shí)候我會(huì)談到這個(gè)東西的)。由此可以看出,一個(gè)線程中的while循環(huán)有好幾種,所以我們需要每次調(diào)用while循環(huán)的時(shí)候加以區(qū)別。
4. 由1中得知一個(gè)應(yīng)用程序可能由好幾個(gè)需要處理信號(hào)的線程組成,每個(gè)線程之間相互,由2中得知,需要將不必要的類型封裝起來(lái),只向用戶提供部分類型,使用者利用僅有提供的公開類型就可以寫出各種各樣自己想要的效果。既然要將代碼關(guān)鍵部分與用戶可擴(kuò)展部分分開,那么現(xiàn)在就要分清哪些東西不需要使用者操心、而哪些則需要使用者操心。
解決方法:
前面說(shuō)過,ZZThread類肯定不能公開,也就是說(shuō)while循環(huán)、信號(hào)隊(duì)列、Rect對(duì)象集合都不公開,Sgl枚舉和Signal類也沒必要公開,因?yàn)槭褂谜吒静恍枰肋@些東西(從框架設(shè)計(jì)角度,這些東西也不是公開的),使用者唯一需要了解的就是Rect類,使用者知道了Rect類之后,就可以從該類派生出各種各樣的子類,在子類中重寫Rect的虛方法,然后注冊(cè)子類的一些事件等等(這要求必須提前將Rect類中需要處理的信號(hào)考慮完整,也就是說(shuō)一切信號(hào)類型在Rect類中全部都有默認(rèn)處理,而且還要完善虛方法,子類才能重寫任何一個(gè)它想重寫的虛方法)。另外,如果ZZThread類不公開,那么使用者怎么讓線程進(jìn)入while循環(huán)?因此,需要定義一個(gè)類,該類跟普通類不一樣(見代碼),然后將該類公開給使用者。
精包裝:
框架部分
(1)ZZAplication類

復(fù)制代碼 代碼如下:

View Code
public class ZZApplication
{
public static event EventHandler ApplicationExit;
public static event EventHandler ThreadExit;
//用一個(gè)Rect對(duì)象作為主Rect,開啟信號(hào)循環(huán)
public static void Start(Rect rect)
{
ZZThread.FromCurrent().StartLoop(1,rect);
}
//執(zhí)行一次信號(hào)循環(huán),進(jìn)行一次信號(hào)處理
public static void DoThing()
{
ZZThread.FromCurrent().StartLoop(2,null);
}
internal static void RaiseThreadExit()
{
if(ThreadExit!=null)
{
ThreadExit(null,EventArgs.Empty);
}
}
internal static void RaiseApplicationExit()
{
if(ApplicationExit!=null)
{
ApplicationExit(null,EventArgs.Empty);
}
}
}

(2)ZZThread類
復(fù)制代碼 代碼如下:

View Code
internal class ZZThread
{
private Queue signalList = new Queue();
private List<Rect> allRects = new List<Rect>();
private int mySignalLoopCount = 0;
private static totalSignalLoopCount = 0;
private static Dictionary<int,ZZThread> allZZThreads = new Dictionary<int,ZZThread>();
public static ZZThread FromCurrent() //獲取與當(dāng)前線程相關(guān)聯(lián)的ZZThread對(duì)象
{
int id = Thread.CurrentThread.ManagedThreadId;
if(allThreads.ContainKey(id))
{
return allThreads[id];
}
else
{
ZZThread zzthread = new ZZThread();
allZZThreads.Add(id,zzthread);
return zzthread;
}
}
//開始一個(gè)信號(hào)循環(huán),1表示第一層循環(huán),2表示第二層循環(huán)
public void StartLoop(int reason, Rect rect)
{
mySignalLoopCount++;
totalSignalLoopCount++;
if(reason == 1)
{
if(mySignalLoopCount!=1)
{
return; //不能嵌套調(diào)用第一層循環(huán)
}
rect.Kill+=(EventHandler)(delegate(sender,e){PostQuit();});
}
Signal signal = null;
while(GetSignal(out signal))
{
DispatchSignal(signal);
if(reason == 2)
{
break;
}
}
mySignalLoopCount--;
totalSignalCount--;
if(reason == 1) //退出外層循環(huán)
{
Dispose();
}
}
public void AddRect(Rect rect)
{
allRects.Add(rect);
}
public void Dispose()
{
if(mySignalCount != 0)
{
PostQuit();
}
else
{
ZZApplication.RaiseThreadExit();
if(totalSignalLoopCount==0)
{
ZZApplication.RaiseApplicationExit();
}
}
}
public void SendSignal(int id,int type,object leftParam,rightParam)
{
signalList.EnQueue(new Signal(id,type,leftParam,rightParam));
}
private bool GetSignal(out Signal signal)
{
START:
If(signalList.Count!=0) //注意需要處理線程同步
{
signal = signalList.DeQueue() as Signal;
if(signal.Type != (int)Sgl.RS_QUIT)
{
return true;
}
else
{
return false;
}
}
Else
{
Thread.Sleep(1);
goto START;
}
}
private void DispatchSignal(Signal signal)
{
Foreach(Rect rect in allRects)
{
If(rect.ID == signal.ID && rect.Alive)
{
rect.RectProc(signal.ID,signal.Type,signal.LeftParam,signal.RightParam);
break;
}
}
}
private void PostQuit()
{
signalList.EnQueue(new Signal(0,(int)RS_QUIT,null,null));
}
}

(3)Rect類(其他派生自Rect)
復(fù)制代碼 代碼如下:

View Code
public class Rect
{
int _id; //矩形唯一標(biāo)示
string _text; //矩形中心顯示的文本
Size _size; //矩形大小
Point _location; //矩形的位置
Bool _alive; //是否存活
Public int ID
{
Get
{
Return _id
}
Set
{
_id = value;
}
}
Public string Text
{
Get
{
Return _text;
}
Set
{
_text = value;
}
}
Public Size Size
{
Get
{
Return _size;
}
Set
{
_size = value;
}
}
Public Point Location
{
Get
{
Return _location;
}
Set
{
_location = value;
}
}
Public bool Alive
{
Get
{
Return _alive;
}
Set
{
_alive = value;
}
}
Public Rect(int id,string text,Size size,Point location)
{
_id = id;
_text = text;
_size = size;
_location = location;
_alive = true;
ZZThread.FromCurrent().AddRect(this); //添加到創(chuàng)建自己的線程的ZZThread對(duì)象中
}
public Rect()
{
_id = GetUID() // 獲取Rect的全局唯一標(biāo)示
_text = “”;
_size = new Size(100,100);
_location = new Point(100,100);
_alive = true;
ZZThread.FromCurrent().AddRect(this); //添加到創(chuàng)建自己的線程的ZZThread對(duì)象中
}
//矩形對(duì)外唯一接口,對(duì)矩形的所有操作必須調(diào)用此方法,下稱“矩形過程”
Public virtual void RectProc(int id,int type,object leftParam,object rightParam)
{
Switch(type) //這個(gè)type就是后面說(shuō)的“信號(hào)類型”, 應(yīng)該跟Sgl枚舉一一對(duì)應(yīng)
{
Case 1: //移動(dòng)、改變大小
{
Size newSize = (Size)leftParam;
Position newLocation = (Point)rightParam;
This.Size = newSize;
This.Location = newLocation;
OnPositionChanged(new PositionChangedEventArgs(this.Size,this.Location));
Break;
}
Case 2: //顯示信息
{
Break;
}
Case 3: //關(guān)閉
{
Alive = false;
OnKill(new EventArgs());
Break;
}
//……很多省略
Default:
{
//默認(rèn)處理
}
}
}
Protected virtual void OnPositionChanged(PositionChangedEventArgs e)
{
If(PositionChanged!=null)
{
PositionChanged(this,e);
}
}
Protected virtual void OnKill(EventArgs e)
{
If(Kill!=null)
{
Kill(this,e);
}
}
Public event PositionChangedEventHandler PositionChanged;
Public event EventHandler Kill;
}

(4)事件參數(shù)類
復(fù)制代碼 代碼如下:

View Code
Public delegate void PositionChangedEventHandler(object sender,PositionChangedEventArgs e)
Public class PositionChangedEventArgs
{
Size _size;
Point _location;
Public Size Size
{
Get
{
Return _size;
}
}
Public Point Location
{
Get
{
Return _location;
}
}
Public PositionChangedEventArgs(Size size,Point location)
{
_size = size;
_location = location;
}
}

(5)信號(hào)類
復(fù)制代碼 代碼如下:

View Code
internal class Signal
{
Int _id; //接受者id
Int _type; //信號(hào)類型
Object _leftParam; //參數(shù)1
Object _rightParam; //參數(shù)2
Public int ID
{
Get
{
Return _id;
}
Set
{
_id = value;
}
}
Public int Type
{
Get
{
Return _type;
}
Set
{
_type = value;
}
}
Public object LeftParam
{
Get
{
Return _leftParam;
}
Set
{
_leftParam = value;
}
}
Public object RightParam
{
Get
{
Return _rightParam;
}
Set
{
_rightParam = value;
}
}
Public Signal(int id,int type,object leftParam,object rightParam)
{
_id = id;
_type = type;
_leftParam = leftParam;
_rightParam = rightParam;
}
}

(6)Sgl枚舉類型
復(fù)制代碼 代碼如下:

View Code
internal Enum Sgl
{
RS_POSITIONCHANGE = 1, //移動(dòng)矩形,大小變化
RS_SHOWINFO = 2, //矩形顯示自己信息
RS_KILL = 3, //關(guān)閉矩形
RS_QUIT = 4 //退出
//……很多省略
}

客戶端:
復(fù)制代碼 代碼如下:

View Code
static class Program
{
/// <summary>
/// 應(yīng)用程序的主入口點(diǎn)。
/// </summary>
[STAThread]
static void Main()
{
ZZApplication.ApplicationExit += new EventHandler(ZZApplication_ApplicationExit);
ZZApplication.ThreadExit += new EventHandler(ZZApplication_ThreadExit);
ZZApplication.Start(new Rect1());
}
static void ZZApplication_ApplicationExit(object sender,EventArgs e)
{
//應(yīng)用程序退出
}
static void ZZApplication_ThreadExit(object sender,EventArgs e)
{
//單個(gè)信號(hào)處理線程退出
}
}
//定義一個(gè)新的Rect派生類
class Rect1:Rect
{
public Rect1(int id,string text,Size size,Point location):base(id,text,size,location)
{
Init();
}
public Rect1()
{
Init();
}
private void myChildRect_Kill(object sender,EventArgs e)
{
//大循環(huán)耗時(shí)計(jì)算,不能及時(shí)返回
for(int i=0;i<10000*1000;++i)
{
//計(jì)算
ZZApplication.DoThing(); //及時(shí)處理信號(hào)
}
}
}
partial class Rect1:Rect
{
private Rect myChildRect;
private void Init()
{
myChildRect = new Rect();
myChildRect.Kill += new EventHandler(myChildRect_Kill);
}
}

如你所見,使用者在客戶端知道的東西少之又少,只有ZZApplication類、Rect類以及一些事件參數(shù)類,其中ZZApplication類主要負(fù)責(zé)跟ZZThread有關(guān)的內(nèi)容,為使用者和ZZThread類之間起到一個(gè)橋梁作用,ZZApplication類中可以放一些ZZThread類對(duì)象公共的數(shù)據(jù)(如代碼中的ApplicationExit、ThreadExit等等);Rect類則完全是為了方便使用者擴(kuò)展出各種各樣的信號(hào)響應(yīng)者,這就像一個(gè)公司剛開始只有一個(gè)部門,該部門負(fù)責(zé)設(shè)計(jì)編碼測(cè)試以及后期維護(hù),那么每次開會(huì)的時(shí)候,老板下達(dá)命令,只有一個(gè)部門負(fù)責(zé)人響應(yīng),現(xiàn)在公司做大了,分出來(lái)了開發(fā)部、測(cè)試部、以及人事部和市場(chǎng)部,現(xiàn)在老板一開會(huì),就會(huì)有多個(gè)部門負(fù)責(zé)人響應(yīng)。這個(gè)例子里面老板就是使用由該框架開發(fā)出來(lái)的系統(tǒng)的人,而各部門負(fù)責(zé)人則是Rect類對(duì)象或其派生類對(duì)象。
為了更直觀的理解這次修改后的代碼,再上一張圖:


總結(jié)加過渡:
任何一個(gè)系統(tǒng),都是給用戶使用的,系統(tǒng)要不直接面對(duì)用戶,要不間接面對(duì)用戶,反正最終都會(huì)跟用戶交互。因此,對(duì)于任何一個(gè)系統(tǒng),它必備三個(gè)部分:第一,接收用戶命令部分;第二,處理命令部分;第三,顯示處理結(jié)果部分(讓用戶知道自己的命令產(chǎn)生怎樣的效果)。我們現(xiàn)在來(lái)分析一下我們之前每個(gè)階段的代碼是否包含以上三個(gè)部分:
(1)一個(gè)引子:
該部分可以說(shuō)是,麻雀雖小,五臟俱全,包含“接收用戶命令”的Console.ReadLine()、“處理命令”的Console.WriteLine()、和“顯示處理結(jié)果”的Console.WriteLine()(這里處理命令部分和顯示處理結(jié)果部分明顯是一個(gè)東西),代碼雖然簡(jiǎn)陋,卻包含了一個(gè)完整系統(tǒng)的所有部分,所以我之前說(shuō)它是整個(gè)系統(tǒng)的“精髓”,其實(shí)一點(diǎn)都不假。
(2)初加工:
這部分說(shuō)它是“初加工”,其實(shí)不太合適,因?yàn)橄鄬?duì)于修改之前,變化確實(shí)大了點(diǎn),用“初”字來(lái)形容不太貼切,但我又確實(shí)想不到更簡(jiǎn)單而又與前面不重復(fù)的例子,所以只好這樣了。這部分其實(shí)也是完整的包含三個(gè)部分的,它有“接收用戶命令”的GetSignal方法、“處理命令”的Rect.RectProc方法以及“顯示處理結(jié)果”Console.WriteLine方法(包含在Rect.RectProc方法中)。
(3)升華:
從這部分開始,系統(tǒng)逐漸變得不完善,一是因?yàn)槲乙竺嬷vWinform關(guān)聯(lián)起來(lái),二是說(shuō)句實(shí)話,代碼多了復(fù)雜起來(lái)后,再想模擬一個(gè)完整的系統(tǒng)結(jié)構(gòu)太困難,根本不容易,讀者看起來(lái)也顧東顧不了西了。這部分只包含一個(gè)部分,那就是處理命令部分,沒錯(cuò),它就只包含“處理命令的部分”,沒有接收用戶命令(我前面說(shuō)過需要另開線程接收用戶輸入),也沒有顯示處理結(jié)果。這個(gè)聽起來(lái)好像讓人太難接收,叫它“升華”,代碼居然減少到只包含一個(gè)部分,好吧,這個(gè)留著以后再解釋,現(xiàn)在太早。該部分把全部重點(diǎn)放在了“處理命令”部分,擴(kuò)充了Rect類,可以從它派生出各種各樣的子類來(lái)響應(yīng)命令。
(4)精包裝:
顧名思義,包裝有點(diǎn)封裝的意思,該部分從框架設(shè)計(jì)角度來(lái)考慮代碼的實(shí)現(xiàn),將使用者無(wú)需了解的部分封裝起來(lái),提供若干必需的接口。跟“升華”階段一樣,代碼只有“處理命令”部分,不同的,一是前面說(shuō)的封裝部分類型,公開部分類型;二是將“處理信號(hào)”這個(gè)邏輯對(duì)象化,一個(gè)線程使用一個(gè)ZZThread類對(duì)象,各個(gè)線程擁有自己的信號(hào)隊(duì)列、自己的Rect對(duì)象集合、自己的信號(hào)循環(huán)等等,各自的信號(hào)循環(huán)獲取各自的信號(hào)隊(duì)列中的信號(hào),分配給各自的Rect對(duì)象,由各自的Rect對(duì)象進(jìn)行處理,各個(gè)線程在處理信號(hào)這個(gè)方面沒有任何交集(簡(jiǎn)單設(shè)計(jì)的話,應(yīng)該是這樣的,但如果需要實(shí)現(xiàn)復(fù)雜效果的話就會(huì)涉及到各個(gè)線程之間發(fā)送信號(hào),這個(gè)就麻煩點(diǎn),以后在講Winform部分會(huì)提到)。
既然這部分標(biāo)題叫“總結(jié)加過渡”,那明顯有兩個(gè)意思,“總結(jié)”剛才已經(jīng)搞過了,現(xiàn)在來(lái)說(shuō)說(shuō)后面的事情,其實(shí)相信大部分人已經(jīng)看出來(lái)前面那些代碼到最后有點(diǎn)Winform結(jié)構(gòu)的意思,我不敢說(shuō)它完全就是,但至少大概結(jié)構(gòu)就是“精包裝”階段那樣的,不信請(qǐng)查看.net源碼,我之所以先扯30多頁(yè)word文檔連看似跟Winform半毛關(guān)系也沒有的東西,而沒有一上來(lái)直接拿Application、Form、WndProc、OnClick甚至消息循環(huán)、消息隊(duì)列等等這些開刀,我只是想讓諸位在沒有任何Winform思想干擾的情況下,從底層對(duì)整個(gè)系統(tǒng)結(jié)構(gòu)有個(gè)大概的了解,這就像先給人傳授一種技能,人們都已經(jīng)使得很熟練了,哪天突然叫你研究原理的東西,你肯定會(huì)先從你熟悉的地方一點(diǎn)點(diǎn)往底層原理性方面走,卻不知終點(diǎn)在哪,搞不好走偏了,進(jìn)了無(wú)底洞。再者,說(shuō)句不好聽的,很多人連Control.WndProc是什么都不知道,更別說(shuō)什么消息循環(huán)了,一上來(lái)扯這些概念,相當(dāng)一部分人肯定會(huì)蒙,畢竟,本文并不打算只服務(wù)于基礎(chǔ)扎實(shí)的讀者O(∩_∩)O~。
注:以上(包括接下來(lái)的)所有代碼均未測(cè)試,不知是否可以運(yùn)行,全部都在word中敲進(jìn)去的,如果能運(yùn)行的話當(dāng)然更好,不能的話,那就權(quán)當(dāng)作是偽代碼吧,看看思路就ok,再者我覺得這個(gè)運(yùn)行也看不出啥效果。
顯而易見,Winform應(yīng)用程序(或其他Windows桌面應(yīng)用程序,下同)屬于典型的需要與用戶交互的系統(tǒng),應(yīng)該包含上面提到的三個(gè)完整的部分:“接收用戶命令”、“處理命令”、“顯示處理結(jié)果”。下面我們來(lái)做一個(gè)對(duì)號(hào)入座,將Winform開發(fā)中的一些概念與之前的代碼做一個(gè)一一映射(當(dāng)然,只能簡(jiǎn)單的一一對(duì)應(yīng),Winform內(nèi)部實(shí)現(xiàn)實(shí)際上比我們之前寫的代碼復(fù)雜得多,而且好多我都只能靠模仿,因?yàn)樗S多部分跟操作系統(tǒng)緊密相關(guān),除了寫C#代碼去模仿,我沒辦法給你們解釋它到底怎么做到的):
1)接收用戶命令:
Winform應(yīng)用程序當(dāng)然是靠鍵盤鼠標(biāo)等輸入設(shè)備,而我們之前的代碼沒有這部分,我那時(shí)候說(shuō)過,需要另開線程接收用戶輸入,當(dāng)然這個(gè)只能是簡(jiǎn)單的模仿,Winform應(yīng)用程序接收用戶命令要比這個(gè)復(fù)雜得多。打個(gè)比方,鼠標(biāo)在某一窗體上點(diǎn)擊,操作系統(tǒng)就能捕獲該事件(此事件為Windows事件,跟.Net編程中的事件不同,前者可以說(shuō)是物理意義上的,而后者更為抽象),然后將該事件轉(zhuǎn)換成一個(gè)Windows消息,該消息為一種數(shù)據(jù)結(jié)構(gòu),攜帶消息接受者信息(被點(diǎn)擊的窗口句柄)、消息類型(比如WM_LBUTTONDOWN)以及一些參數(shù)(鼠標(biāo)坐標(biāo)等),然后操作系統(tǒng)將該數(shù)據(jù)結(jié)構(gòu)發(fā)送到被點(diǎn)擊窗體所在線程的消息隊(duì)列中,之后,操作系統(tǒng)不會(huì)再去管了。我之前的博客中已經(jīng)說(shuō)過,我們的應(yīng)用程序是讀不懂鍵盤鼠標(biāo)的,唯獨(dú)能讀懂的就是數(shù)據(jù),所以操作系統(tǒng)在“接收用戶命令”部分幫了我們很大的忙,直接將一些物理事件轉(zhuǎn)換成一種統(tǒng)一的數(shù)據(jù)結(jié)構(gòu),然后投遞給線程消息隊(duì)列。

我們可以看出,Winform應(yīng)用程序接收用戶命令已經(jīng)太強(qiáng)大了,相比我們之前(一個(gè)引子和初加工中)的Console.ReadLine()接收輸入,然后還要把輸入轉(zhuǎn)換成標(biāo)準(zhǔn)的Signal,用戶輸入很容易出錯(cuò),因此,相比起來(lái),兩個(gè)差別實(shí)在太大。
2)處理命令:
這部分可以說(shuō)大體上還是一樣的,Winform應(yīng)用程序中UI線程(也就是 我們說(shuō)的管理界面的線程)中有while消息循環(huán),不停地將線程消息隊(duì)列中的消息取出,分配給目標(biāo)窗口,然后調(diào)用目標(biāo)窗口的窗口過程(WndProc),這個(gè)基本上跟我們前面寫的代碼一樣。只是我想說(shuō)的是,我們之前的代碼中模仿了消息隊(duì)列(signalList)以及線程中的窗口集合(allRects),Winform應(yīng)用程序中這兩個(gè)東西是靠操作系統(tǒng)維護(hù)的。
3)顯示處理結(jié)果:
跟“接收用戶命令”一樣,我們之前的代碼中沒有“顯示處理結(jié)果”部分,在Winform中,眾所周知的是,鼠標(biāo)按住標(biāo)題欄移動(dòng)鼠標(biāo),結(jié)果就是窗體跟著鼠標(biāo)移動(dòng),摁住鼠標(biāo)移動(dòng)就是“用戶命令”,窗體跟著鼠標(biāo)移動(dòng)就是“處理結(jié)果”,直接通過圖形展示給用戶了。這其中的奧秘就是,Winform應(yīng)用程序中,窗體的窗口過程在處理消息的時(shí)候,調(diào)用了Windows API,API操作窗體,讓其改變位置。而我們的代碼在“一個(gè)引子和初加工”階段,唯一能做的,就是在窗口過程中將自己(Rect對(duì)象)的信息Console.WriteLine()顯示出來(lái),以此來(lái)模仿顯示處理結(jié)果。
4)我們代碼中的“信號(hào)循環(huán)”對(duì)應(yīng)于Winform應(yīng)用程序中的“消息循環(huán)”。
5)我們代碼中的GetSignal對(duì)應(yīng)于Winform應(yīng)用程序中的GetMessage,當(dāng)然后者為API方法,具體內(nèi)部實(shí)現(xiàn)不清楚,GetSignal只是為了模仿。
6)我們代碼中的DispatchSignal對(duì)應(yīng)于Winform應(yīng)用程序中的DispatchMessage,后者也是API方法,具體內(nèi)部實(shí)現(xiàn)不清楚,DispatchSignal只是為了模仿。
7)我們代碼中的Signal類對(duì)應(yīng)于Winform應(yīng)用程序中的Message結(jié)構(gòu)體,不同框架中的可能不太一樣,但基本上都是含有窗口句柄、消息類型、W參數(shù)、L參數(shù)。
8)我們代碼中的Sgl.RS_QUIT枚舉類型對(duì)應(yīng)于Winform應(yīng)用程序中的WM_QUIT,其他類似。
9)我們代碼中的Rect類對(duì)應(yīng)于Winform應(yīng)用程序中的Form類。
10)我們代碼中的Rect.RectProc對(duì)應(yīng)于Winform應(yīng)用程序中的Form.WndProc。
11)我們代碼中的Rect1(最后精包裝階段),很明顯對(duì)應(yīng)于Winform應(yīng)用程序中的開發(fā)時(shí),自動(dòng)生成的Form1類。
12)我們代碼中的ZZApplication類對(duì)應(yīng)于Winform應(yīng)用程序開發(fā)中的Application類。
13)我們代碼中的ZZApplication.Start()對(duì)應(yīng)于Winform應(yīng)用程序開發(fā)中的Application.Run()。
14)我們代碼中的ZZApplication.DoThing()對(duì)應(yīng)于Winform應(yīng)用程序開發(fā)中的Application.DoEvents()。
15)至于我們代碼中的ZZThread類,跟Winform應(yīng)用程序開發(fā)中的ThreadContext類相似(該類沒有對(duì)外公布,查看.net源碼可以看詳細(xì)信息 )
為了更清楚的了解Winform應(yīng)用程序整個(gè)運(yùn)行流程,再上一張圖:


現(xiàn)在我們知道,對(duì)于Winform應(yīng)用程序來(lái)講,鼠標(biāo)鍵盤等操作可以視為“用戶輸入命令”,只是Winform應(yīng)用程序并不能直接識(shí)別此命令,因此需要操作系統(tǒng)作為橋梁,將鼠標(biāo)鍵盤等“Windows事件”轉(zhuǎn)換成程序可以識(shí)別的數(shù)據(jù)結(jié)構(gòu)(Windows消息),再投遞到相關(guān)線程中去,再由線程中的消息循環(huán)獲取消息,分派給本線程中對(duì)應(yīng)的窗體,調(diào)用窗口過程,在窗口過程中我們?cè)俑鶕?jù)需要處理消息,一般是調(diào)用Windows API,只是Winform中對(duì)API做了一層封裝,再加進(jìn)去了OO思想,引進(jìn)一些虛方法、事件(Event)等等概念,讓使用者更方便的編寫窗口過程。因此,當(dāng)我們用鼠標(biāo)點(diǎn)擊Winform窗體,Winform代碼中會(huì)激發(fā)Click事件,我們?cè)僭谑录幚沓绦蛑袑懸恍┻壿嫶a,這一過程拐了好幾個(gè)彎,并沒有我們想象的那么簡(jiǎn)單:點(diǎn)擊鼠標(biāo),鼠標(biāo)激發(fā)Click事件,調(diào)用事件處理程序。

另外,我們還能總結(jié)一個(gè)結(jié)論,我們?cè)赪inform中編寫的所有跟UI有關(guān)的代碼,其實(shí)基本上都是擴(kuò)展窗體(控件)的“窗口過程”,我們例子中的“RectProc”。最后,“點(diǎn)擊一個(gè)button,彈出一個(gè)messagebox對(duì)話框,從windows消息層次描述該過程”這個(gè)問題已經(jīng)很清楚明了了。送一句話,對(duì)Windows操作系統(tǒng)的概括:
它是一個(gè)以消息為基礎(chǔ),事件驅(qū)動(dòng)的多任務(wù)搶占式操作系統(tǒng)。
這句話完完整整的說(shuō)明了所有Windows桌面應(yīng)用程序開發(fā)規(guī)律。

寫到目前為止,我并沒有講到winform框架的一個(gè)整體結(jié)構(gòu),更沒貼出相關(guān)代碼,之后我也沒打算這樣做,因?yàn)檫@塊東西實(shí)在是太多,有興趣的可以用Reflector研究.net源碼,再者,前面模仿的例子完全可以拿來(lái)類比。之后幾篇我打算挑幾個(gè)沒說(shuō)完的繼續(xù)說(shuō),但也只是很小的地方,比如最前面提到的Form.Show()與Form.ShowDialog()的區(qū)別,模式對(duì)話框形成的本質(zhì)原因等等。

斷斷續(xù)續(xù)寫了兩個(gè)多禮拜,可能前面講的和后面說(shuō)的偶爾不太相呼應(yīng),請(qǐng)包涵,另外希望有幫助,O(∩_∩)O~。

相關(guān)文章

  • c#?復(fù)寫Equals方法的實(shí)現(xiàn)

    c#?復(fù)寫Equals方法的實(shí)現(xiàn)

    本文主要介紹了c#?復(fù)寫Equals方法的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • Unity 點(diǎn)擊UI與點(diǎn)擊屏幕沖突的解決方案

    Unity 點(diǎn)擊UI與點(diǎn)擊屏幕沖突的解決方案

    這篇文章主要介紹了Unity 點(diǎn)擊UI與點(diǎn)擊屏幕沖突的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧
    2021-04-04
  • C# DI依賴注入的實(shí)現(xiàn)示例

    C# DI依賴注入的實(shí)現(xiàn)示例

    依賴注入是一種實(shí)現(xiàn)的方法,用于減少代碼之間的耦合,本文主要介紹了C# DI依賴注入的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣可以了解一下
    2023-12-12
  • C#使用Selenium+PhantomJS抓取數(shù)據(jù)

    C#使用Selenium+PhantomJS抓取數(shù)據(jù)

    本文主要介紹了C#使用Selenium+PhantomJS抓取數(shù)據(jù)的方法步驟,具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧
    2017-02-02
  • C#監(jiān)控文件夾變化的方法

    C#監(jiān)控文件夾變化的方法

    這篇文章主要介紹了C#監(jiān)控文件夾變化的方法,通過FileSystemWatcher類的方法來(lái)實(shí)現(xiàn)對(duì)文件夾的監(jiān)控,是非常實(shí)用的技巧,需要的朋友可以參考下
    2014-11-11
  • 深入C#字符串和享元(Flyweight)模式的使用分析

    深入C#字符串和享元(Flyweight)模式的使用分析

    本篇文章是對(duì)C#字符串與享元(Flyweight)模式的使用進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • C#利用GDI+繪制旋轉(zhuǎn)文字等效果實(shí)例

    C#利用GDI+繪制旋轉(zhuǎn)文字等效果實(shí)例

    這篇文章主要介紹了C#利用GDI+繪制旋轉(zhuǎn)文字等效果實(shí)例,是非常實(shí)用的重要技巧,需要的朋友可以參考下
    2014-09-09
  • WPF利用RPC調(diào)用其他進(jìn)程的方法詳解

    WPF利用RPC調(diào)用其他進(jìn)程的方法詳解

    這篇文章主要給大家介紹了關(guān)于WPF利用RPC調(diào)用其他進(jìn)程的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-05-05
  • C#3.0使用EventLog類寫Windows事件日志的方法

    C#3.0使用EventLog類寫Windows事件日志的方法

    這篇文章主要介紹了C#3.0使用EventLog類寫Windows事件日志的方法,以簡(jiǎn)單實(shí)例形式分析了C#寫windows事件日志的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-08-08
  • C#多線程之線程同步

    C#多線程之線程同步

    這篇文章介紹了C#多線程之線程同步,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-03-03

最新評(píng)論