欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

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

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

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

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

“阻塞方法”確實是一種方法,但是如果我們想在線程執(zhí)行過程中,與外部(用戶)進行交互,也就是說,在線程執(zhí)行期間,用戶可以通過輸入來控制線程的運行情況,同樣在Console程序中,該怎么實現(xiàn)?現(xiàn)在問題來了,不緊不能讓線程馬上結(jié)束,還要與用戶有所交互,而且不應(yīng)該只交互一次(否則,上面提到的cin>>a;完全夠用),該怎么搞?不止交互一次?那么很容易就能想到“循環(huán)”,用循環(huá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();
}

非常簡單的一段代碼,程序運行后,有了while循環(huán),不會馬上結(jié)束,它會不停的等待用戶輸入,然后輸出用戶輸入的字符串(模擬響應(yīng)用戶操作),直到用戶輸入“quit”后,循環(huán)才結(jié)束。這段利用while循環(huán)和Console.ReadLine()寫出來的程序雖然短小簡單,卻是后面我們要談到的Winform應(yīng)用程序(其實所有的Windows應(yīng)用程序都一樣,無論是MFC還是Delphi或者其他搞出來的桌面程序)的精髓。當(dāng)然,這段代碼確實太簡陋了,所以我才說它是精髓,O(∩_∩)O~。既然太簡陋,那我們再改改吧。要改就改復(fù)雜一點。
初加工:
復(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() + “] 號矩形創(chuàng)建成功!”);
}
//矩形對外唯一接口,對矩形的所有操作必須調(diào)用此方法,下稱“矩形過程”
Public void RectProc(int id,int type,object leftParam,object rightParam)
{
Switch(type) //這個type就是后面說的“信號類型”, 應(yīng)該跟Sgl枚舉一一對應(yīng)
{
Case 1: //移動、改變大小
{
Size newSize = (Size)leftParam;
Position newLocation = (Point)rightParam;
This.Size = newSize;
This.Location = newLocation;
Console.WriteLine(“[” + ID.ToString() + “] 號矩形改變位置:大小為(” + this.Size.Width+”,” + this.Size.Height + “),位置為(”+this.Location.Left + “,” + this.Location.Top + “)” );
Break;
}
Case 2: //顯示信息
{
Console.WriteLine(“[” + ID.ToString() + “] 號矩形顯示信息:大小為(” + this.Size.Width+”,” + this.Size.Height + “),位置為(”+this.Location.Left + “,” + this.Location.Top + “),Text為 ” + this.Text );
Break;
}
Case 3: //關(guān)閉
{
Console.WriteLine(“[” + ID.ToString() + “] 號矩形關(guān)閉”);
Alive = false;
Break;
}
//……
Default:
{
//默認處理
}
}
}
}
//信號類,表示一種信號,包含信號接收者ID,信號類型Type,信號兩個參數(shù)LeftParam、RightParam
Class Signal
{
Int _id; //接受者id
Int _type; //信號類型
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;
}
}
// 信號類型枚舉 RS即為RectSignal
Enum Sgl
{
RS_POSITIONCHANGE = 1, //移動矩形,大小變化
RS_SHOWINFO = 2, //矩形顯示自己信息
RS_KILL = 3 //關(guān)閉矩形
//……很多省略
}
/* 信號格式(不同的Sgl,Signal對象內(nèi)容完整度不一樣)
* RS_POSITIONCHANGE: ID必須,Type必須,LeftParam必須,RightParam必須
* RS_SHOWINFO ID必須,Type必須
* RS_KILL: ID必須,Type必須
* ……很多省略
*/
/// 主線程
/// 測試代碼
Static class ZZThread
{
Static List<Rect> allRects = new List<Rect>(); //整個線程運行過程中,存在的Rect對象
//線程入口
Public Static void Main()
{
//初始化4個Rect對象,添加到集合中
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)) //接收信號
{
DispatchSignal(signal); //分配信號到各個Rect
}
Console.WriteLine(“The Thread Exit”);
// Console.ReadKey(); //阻塞查看運行情況
}
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ù)格式錯誤!”);
Goto START;
}
}
// 必須提供Rect的id、以及信號類型,參數(shù)可選
// 沒做格式驗證,所有必須輸入整形數(shù)據(jù)
If(inputs.Length == 2) //只提供了Rect的id和信號類型
{
signal = new Signal(int.parse(intputs[0]),int.Parse(inputs[1]),null,null);
return true;
}
If(inputs.Length == 4) //只提供了Rect的id、信號類型以及第一個參數(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) //四個參數(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ù)格式錯誤!”);
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;
}
}
}
}

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

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

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

復(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;
}
//矩形對外唯一接口,對矩形的所有操作必須調(diào)用此方法,下稱“矩形過程”
Public virtual void RectProc(int id,int type,object leftParam,object rightParam)
{
Switch(type) //這個type就是后面說的“信號類型”, 應(yīng)該跟Sgl枚舉一一對應(yīng)
{
Case 1: //移動、改變大小
{
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)用對應(yīng)虛方法
Break;
}
Case 3: //關(guān)閉
{
Alive = false;
OnKill(new EventArgs());
Break;
}
//……很多省略
Default:
{
//默認處理
}
}
}
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;
}
}

再從Rect類派生出一個新的類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)
{
//攔截信號
base.RectProc(int id,int type,object leftParam,object rightParam);
}
protected override void OnPositionChanged(PositionChangedEventArgs e)
{
//添加自己的代碼
//ZZThread.SendSignal(…)發(fā)送信號到本線程信號隊列中
base.OnPositionChanged(e); //觸發(fā)基類事件
}
//你可以重寫其他虛方法,就像繼承一個Form類,重寫它的虛方法一樣。
}

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

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

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

View Code
/// 主線程
/// 測試代碼
Static class ZZThread
{
Static List<Rect> allRects = new List<Rect>(); //整個線程運行過程中,存在的Rect對象
Static Queue signalList = new Queue(); //線程信號隊列(考慮到另有線程接收用戶輸入,也會操作此信號隊列,所以請考慮線程同步問題)
//線程入口
Public Static void Main()
{
//初始化一個Rect對象,一個為DeriveRect對象,前者為主Rect,當(dāng)它關(guān)閉的時候,退出循環(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)閉事件,向所在線程信號隊列發(fā)送一個退出信號
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)從線程的信號隊列里獲取信號
Signal signal = null;
While(GetSignal(out signal)) //接收信號
{
DispatchSignal(signal); //分配信號到各個Rect
}
Console.WriteLine(“The Thread Exit”);
// Console.ReadKey(); //阻塞查看運行情況
}
Static bool GetSignal(out Signal signal)
{
//從隊列獲取信號,如果隊列為空,阻塞直到隊列有信號為止,否則如果非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)仍然相同,一個while循環(huán)、一個獲取信號(這里不再單單是用戶輸入了,還包括循環(huán)體內(nèi)向循環(huán)體外發(fā)送的信號)的GetSignal方法、一個處理信號(將信號分發(fā)給線程中對應(yīng)的Rect對象以及其派生類)的DispatchSignal方法。
再上張圖,圖文結(jié)合,效果杠杠的。


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


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

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

View Code
public class ZZApplication
{
public static event EventHandler ApplicationExit;
public static event EventHandler ThreadExit;
//用一個Rect對象作為主Rect,開啟信號循環(huán)
public static void Start(Rect rect)
{
ZZThread.FromCurrent().StartLoop(1,rect);
}
//執(zhí)行一次信號循環(huán),進行一次信號處理
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對象
{
int id = Thread.CurrentThread.ManagedThreadId;
if(allThreads.ContainKey(id))
{
return allThreads[id];
}
else
{
ZZThread zzthread = new ZZThread();
allZZThreads.Add(id,zzthread);
return zzthread;
}
}
//開始一個信號循環(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對象中
}
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對象中
}
//矩形對外唯一接口,對矩形的所有操作必須調(diào)用此方法,下稱“矩形過程”
Public virtual void RectProc(int id,int type,object leftParam,object rightParam)
{
Switch(type) //這個type就是后面說的“信號類型”, 應(yīng)該跟Sgl枚舉一一對應(yīng)
{
Case 1: //移動、改變大小
{
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:
{
//默認處理
}
}
}
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)信號類
復(fù)制代碼 代碼如下:

View Code
internal class Signal
{
Int _id; //接受者id
Int _type; //信號類型
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, //移動矩形,大小變化
RS_SHOWINFO = 2, //矩形顯示自己信息
RS_KILL = 3, //關(guān)閉矩形
RS_QUIT = 4 //退出
//……很多省略
}

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

View Code
static class Program
{
/// <summary>
/// 應(yīng)用程序的主入口點。
/// </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)
{
//單個信號處理線程退出
}
}
//定義一個新的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)耗時計算,不能及時返回
for(int i=0;i<10000*1000;++i)
{
//計算
ZZApplication.DoThing(); //及時處理信號
}
}
}
partial class Rect1:Rect
{
private Rect myChildRect;
private void Init()
{
myChildRect = new Rect();
myChildRect.Kill += new EventHandler(myChildRect_Kill);
}
}

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


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

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


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

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

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

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

相關(guān)文章

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

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

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

    Unity 點擊UI與點擊屏幕沖突的解決方案

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    C#多線程之線程同步

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

最新評論