.Net Winform開發(fā)筆記(一)
更新時間:2013年01月14日 16:13:52 作者:
理解“Windows 窗體應用程序”項目中Program.cs文件中的main方法與傳統(tǒng)C++Console控制臺程序中的main方法的區(qū)別等等,感興趣的朋友可以了解下
1. 理解“Windows 窗體應用程序”項目中Program.cs文件中的main方法與傳統(tǒng)C++Console控制臺程序中的main方法的區(qū)別。從程序運行層次上講,兩者無區(qū)別,都是程序的入口點,屬于進程中的第一個線程。前者隱藏了UI應用程序必需的消息循環(huán),后者沒有。
2. 每個Windows桌面應用程序都必須包含至少一個UI線程,所謂UI線程,就是可以響應Windows消息的線程。通常情況下,除非特別需要,一個Windows桌面應用程序只包含一個UI線程。
3. UI線程本質(zhì)上跟普通線程一樣,一般為程序的入口線程,比如Program.cs文件中的main方法,就是UI線程,而Application.Run()方法中封裝了消息循環(huán)。如果沒有Application.Run()方法,那么它跟其他線程一模一樣。之所以叫做UI線程,是因為它之中包含一個類似
While(GetMessage(…))//取Windows消息
{
//處理windows消息,調(diào)用開發(fā)者編寫的回調(diào)方法,如事件處理程序 等。
}
的循環(huán)。
4. 有關(guān)Windows消息機制等內(nèi)容,請上網(wǎng)Google或者百度。
5. UI線程主要負責界面的實時更新,所以開發(fā)人員編寫代碼時,請遵守以下規(guī)律:
1) 不要在控件的事件處理程序中編寫(或者調(diào)用)耗時的代碼塊;
2) 不要在控件的事件處理程序中調(diào)用阻塞方法;
6. 明白程序設計中的 委托、事件、事件處理程序的區(qū)別
1) Publicdelegate void KeyPressEventHandler(KeyPressEventArgse);
2) Public eventKeyPressEventHandler KeyPress;
3) Public void Textbox1_KeyPress(objectsender,KeyPressEventArgs e)
{
//….
}
其中:
1為委托 2為事件 3為事件處理程序
7. 所有的事件處理程序都是在UI線程中調(diào)用,又因為UI線程負責更新界面,所以UI線程始終必須保持順暢(表現(xiàn)為3中的while循環(huán)體不能耗時太長),即不能出現(xiàn)長時間執(zhí) 行一個方法不返回的情況。所以,請遵守5中的規(guī)律。
8. 同一個方法,可以運行在多個線程之中,方法跟線程沒有一對一的原則
Private void thread_pro() //
{
}
1) privatebutton1_click(object sender,EventArgs e)
{
thread_pro(); //thread_pro運行在UI線程中
}
2)private button1_click(object sender,EventArgs e)
{
Thread t = new Thread(newThreadStart(thread_pro));
Thread t1 = new Thread(new ThreadStart(thread_pro));
Thread t2 = newThread(new ThreadStart(thread_pro));
t.start(); //thread_pro運行在t線程中
t1.start(); //thread_pro運行在t1線程中
t.2.start(); //thread_pro運行在t2線程中
}
3) 還可以通過Control.Invoke() 或者BenginInvoke方法將方法投遞到創(chuàng)建該控件的線程中執(zhí)行。
以上所有情況,請注意線程共享數(shù)據(jù)。
9. 多線程編程中,請注意“線程安全”問題,對于一些具備“非原子”操作的對象,必須采取措施避免發(fā)生錯誤。
UI控件(Button、datagridview等等)、集合(List、ArrayList)等屬于此類對象,控件任何時間都不能多線程訪問。
10. 堅決杜絕跨線程訪問UI控件,原因見9??缇€程訪問控件的方法見8中的3)。
11. 除了.Net Winform中的事件處理程序是在UI線程中調(diào)用以外,其它的回調(diào)方法幾乎所有都不會在UI線程中執(zhí)行,所以,開發(fā)人員在編寫回調(diào)方法時,請遵守第9,10兩大規(guī)律。
12. 明白什么叫回調(diào)方法。回調(diào)方法一般由開發(fā)者編寫,但不由開發(fā)者調(diào)用,由系統(tǒng)(或者說框架)調(diào)用。在Windows桌面應用程序開發(fā)過程中,控件的事件處理程序都屬于回調(diào)方法,回調(diào)方法一般用在“觀察者”設計模式中,當事件的激發(fā)者激發(fā)一個事件時,它就會調(diào)用回調(diào)方法??丶乃惺录紝儆诖祟?。
另外一種常見為,異步執(zhí)行某個操作,譬如,socket.BeginAccept()中的AsyncCallBack類型參數(shù)。
在框架橫行的時代,一般開發(fā)者編寫的代碼都屬于回調(diào)代碼。因為程序的主要結(jié)構(gòu)都由先輩們在框架中集成好了。開發(fā)者們只需要像填空一樣完善空缺的部分。
13. 阻塞方法指,由于方法體內(nèi)包含耗時較長的操作,所以方法不能及時返回。
所謂“及時”與“非及時”沒有絕對界限,示例如下:
int func1() //及時返回
{
Int index = 0;
For(int i=0;i<100;i++)
{
Index ++;
}
Return index;
}
Int func2() //非及時返回
{
Int index = 0;
For(int i=0;i<1000;i++)
{
For(int j=0;j<1000;++j)
{
Index ++;
}
}
Return index;
}
上述func1相對而言,屬于非阻塞方法,func2屬于阻塞方法。
14. Windows窗體應用程序不會直接跟鍵盤、鼠標等硬件設備交互,它只與Windows消息有直接交互。雖然表面上鼠標鍵盤等硬件設備是操作在窗體之上的,但實質(zhì)上,你 編寫的桌面應用程序是不會理解這些硬件設備的一舉一動。他們是通過操作系統(tǒng)(驅(qū)動程序)進行橋接的,操作系統(tǒng)先將硬件設備的一舉一動翻譯成windows消息(一種數(shù)據(jù)結(jié)構(gòu),程序可以理解),然后供程序理解,作出相應的反應。
15. 所謂“阻塞調(diào)用線程”,是指在某一個線程中調(diào)用了阻塞方法,從而使該線程不能及時執(zhí)行以后的代碼。
Void func()
{
Int index=0;
For(int i=0;i<10000;++i)
{
For(int j=0;j<10000;++j)
{
I ndex++;
}
}
}
Thread t = newThread(new ThreadStart(func));
t.Start(); //線程t中調(diào)用了阻塞方法func,因此線程t會被阻塞
在介紹func方法時,可以這樣描述:該方法會阻塞調(diào)用線程。
16. 同一個方法可以被多個線程調(diào)用,既可被UI線程調(diào)用,也可被非UI線程調(diào)用,那么在方法體內(nèi)怎么編寫訪問UI控件(UI元素)的代碼呢?(跨線程訪問UI控件會引發(fā)異常)
Void func()
{
Textbox1.Text=”測試”;
PictureBox1.Image = Image.FromFile(“a.jpg”);
}
1)以上func方法可能運行在UI線程中,如下:
Private voidbutton1_Click(object sender,EventArgs e)
{
func(); //調(diào)用func方法
}
2)有如下,func方法可能運行在其他非UI線程中
Private void button1_Click(object sender,EventArgs e)
{
Thread t = new Thread(newThreadStart(func));
t.Start(); //func訪問運行在t線程中
}
在2)中,可能引發(fā)異常。
以上問題的解決方案為:
修改func代碼為:
Func()
{
If(this.InvokeRequired)
{
This.BeginInvoke((Action)delegate(){func()});
}
Else
{
Textbox1.Text=”測試”;
PictureBox1.Image = Image.FromFile(“a.jpg”);
}
}
有關(guān)BeginInvoke或者Invoke方法的使用,請上網(wǎng)Google或者百度。
17. 有關(guān)“跨線程訪問UI控件可能引發(fā)異?!钡脑?,跟多線程訪問集合可能出現(xiàn)錯誤的原因基本相似。下面列舉一段代碼說明情況
ClassMyControl
{
Object root;
Public Draw()
{
GetRoot(root);
// 一系列操作…
ReleaseRoot(root);
}
Public OtherDraw()
{
GetRoot(root);
// 一系列操作 …
ReleaseRoot(root);
}
}
其中root變量同時只能被占用一次,GetRoot()獲取root的訪問權(quán),如果root已經(jīng)被占用,則拋出異常。ReleaseRoot()釋放root占用。
當在一個線程中(比如UI線程中)訪問MyControl類對象A,調(diào)用A.Draw()方法,執(zhí)行到GetRoot(root)方法后,該線程失去控制權(quán),暫停運行一下的代碼,即此時root已被占用。而另一線程中如果也要訪問同一對象A的Draw()方法,那么就會引發(fā)異常。
18.在.Net Winform應用程序中,程序與用戶的交互主要包含兩個方面,一是用戶用鼠標、鍵盤燈硬件設備進行操作,程序響應操作,然后進行反饋(比如更新界面、刷新數(shù)據(jù)等),二是不需要用戶用鼠標等硬件設備進行操作,程序自己自動進行反饋(比如QQ彈出新聞窗體、360彈窗等)。
第一種情況是我們所熟知的,比如用戶用鼠標點擊按鈕(button1),程序則彈出一個MessageBox,我們在程序中是這樣子寫的:事件處理程序如下
Private voidbutton1_Click(object sender,EventArgs e)
{
MessageBox.Show(“彈出對話框,或者其他操作”);
}
再來理一下這個過程,首先用戶拿起鼠標點擊button1,操作系統(tǒng)(鼠標驅(qū)動)會捕獲這個事件,經(jīng)過分析,操作系統(tǒng)得知用戶點擊的是哪個窗體(按鈕)、點擊的位置(坐標),點擊類型(左鍵還是右鍵或者其他),以及其他信息,之后,將這些信息封裝成一個類型(即windows消息)發(fā)送給創(chuàng)建該窗體(控件)的線程中的消息隊列,之后,操作系統(tǒng)(鼠標驅(qū)動)就不在負責了。接著,UI線程從線程消息隊列中獲取該消息(注意:這個過程是一直存在的),分析消息,調(diào)用開發(fā)人員編寫的一些回調(diào)方法,如button1_Click()方法,從而到達相應鼠標鍵盤操作的目的。從上面分析過程來看,再一次說明,程序是不會直接跟鼠標等硬件設備交互的,與它直接交互的只有Windows消息,而這個過程需要Windows操作系統(tǒng)起著重要作用。
第二種情況一般用在多線程編程中,當程序有耗時操作、或者需要一直監(jiān)聽等情況的時候,是不能放在UI線程之中的,這時候就需要另外開辟線程,在另外的線程中處理。這種情況中,另外開辟的線程有時候需要反饋跟用戶一些信息,即更新UI界面或者彈出一個窗體等,這就涉及到跨線程訪問UI元素的問題了,詳見5和
19.以上代碼部分均為現(xiàn)寫的,可能出現(xiàn)拼寫錯誤,包涵!
另外,請配合筆記(二)中的“DOT NETWinform應用程序運行結(jié)構(gòu)圖”閱讀。
2. 每個Windows桌面應用程序都必須包含至少一個UI線程,所謂UI線程,就是可以響應Windows消息的線程。通常情況下,除非特別需要,一個Windows桌面應用程序只包含一個UI線程。
3. UI線程本質(zhì)上跟普通線程一樣,一般為程序的入口線程,比如Program.cs文件中的main方法,就是UI線程,而Application.Run()方法中封裝了消息循環(huán)。如果沒有Application.Run()方法,那么它跟其他線程一模一樣。之所以叫做UI線程,是因為它之中包含一個類似
復制代碼 代碼如下:
While(GetMessage(…))//取Windows消息
{
//處理windows消息,調(diào)用開發(fā)者編寫的回調(diào)方法,如事件處理程序 等。
}
的循環(huán)。
4. 有關(guān)Windows消息機制等內(nèi)容,請上網(wǎng)Google或者百度。
5. UI線程主要負責界面的實時更新,所以開發(fā)人員編寫代碼時,請遵守以下規(guī)律:
1) 不要在控件的事件處理程序中編寫(或者調(diào)用)耗時的代碼塊;
2) 不要在控件的事件處理程序中調(diào)用阻塞方法;
6. 明白程序設計中的 委托、事件、事件處理程序的區(qū)別
復制代碼 代碼如下:
1) Publicdelegate void KeyPressEventHandler(KeyPressEventArgse);
2) Public eventKeyPressEventHandler KeyPress;
3) Public void Textbox1_KeyPress(objectsender,KeyPressEventArgs e)
{
//….
}
其中:
1為委托 2為事件 3為事件處理程序
7. 所有的事件處理程序都是在UI線程中調(diào)用,又因為UI線程負責更新界面,所以UI線程始終必須保持順暢(表現(xiàn)為3中的while循環(huán)體不能耗時太長),即不能出現(xiàn)長時間執(zhí) 行一個方法不返回的情況。所以,請遵守5中的規(guī)律。
8. 同一個方法,可以運行在多個線程之中,方法跟線程沒有一對一的原則
復制代碼 代碼如下:
Private void thread_pro() //
{
}
1) privatebutton1_click(object sender,EventArgs e)
{
thread_pro(); //thread_pro運行在UI線程中
}
2)private button1_click(object sender,EventArgs e)
{
Thread t = new Thread(newThreadStart(thread_pro));
Thread t1 = new Thread(new ThreadStart(thread_pro));
Thread t2 = newThread(new ThreadStart(thread_pro));
t.start(); //thread_pro運行在t線程中
t1.start(); //thread_pro運行在t1線程中
t.2.start(); //thread_pro運行在t2線程中
}
3) 還可以通過Control.Invoke() 或者BenginInvoke方法將方法投遞到創(chuàng)建該控件的線程中執(zhí)行。
以上所有情況,請注意線程共享數(shù)據(jù)。
9. 多線程編程中,請注意“線程安全”問題,對于一些具備“非原子”操作的對象,必須采取措施避免發(fā)生錯誤。
UI控件(Button、datagridview等等)、集合(List、ArrayList)等屬于此類對象,控件任何時間都不能多線程訪問。
10. 堅決杜絕跨線程訪問UI控件,原因見9??缇€程訪問控件的方法見8中的3)。
11. 除了.Net Winform中的事件處理程序是在UI線程中調(diào)用以外,其它的回調(diào)方法幾乎所有都不會在UI線程中執(zhí)行,所以,開發(fā)人員在編寫回調(diào)方法時,請遵守第9,10兩大規(guī)律。
12. 明白什么叫回調(diào)方法。回調(diào)方法一般由開發(fā)者編寫,但不由開發(fā)者調(diào)用,由系統(tǒng)(或者說框架)調(diào)用。在Windows桌面應用程序開發(fā)過程中,控件的事件處理程序都屬于回調(diào)方法,回調(diào)方法一般用在“觀察者”設計模式中,當事件的激發(fā)者激發(fā)一個事件時,它就會調(diào)用回調(diào)方法??丶乃惺录紝儆诖祟?。
另外一種常見為,異步執(zhí)行某個操作,譬如,socket.BeginAccept()中的AsyncCallBack類型參數(shù)。
在框架橫行的時代,一般開發(fā)者編寫的代碼都屬于回調(diào)代碼。因為程序的主要結(jié)構(gòu)都由先輩們在框架中集成好了。開發(fā)者們只需要像填空一樣完善空缺的部分。
13. 阻塞方法指,由于方法體內(nèi)包含耗時較長的操作,所以方法不能及時返回。
所謂“及時”與“非及時”沒有絕對界限,示例如下:
復制代碼 代碼如下:
int func1() //及時返回
{
Int index = 0;
For(int i=0;i<100;i++)
{
Index ++;
}
Return index;
}
Int func2() //非及時返回
{
Int index = 0;
For(int i=0;i<1000;i++)
{
For(int j=0;j<1000;++j)
{
Index ++;
}
}
Return index;
}
上述func1相對而言,屬于非阻塞方法,func2屬于阻塞方法。
14. Windows窗體應用程序不會直接跟鍵盤、鼠標等硬件設備交互,它只與Windows消息有直接交互。雖然表面上鼠標鍵盤等硬件設備是操作在窗體之上的,但實質(zhì)上,你 編寫的桌面應用程序是不會理解這些硬件設備的一舉一動。他們是通過操作系統(tǒng)(驅(qū)動程序)進行橋接的,操作系統(tǒng)先將硬件設備的一舉一動翻譯成windows消息(一種數(shù)據(jù)結(jié)構(gòu),程序可以理解),然后供程序理解,作出相應的反應。
15. 所謂“阻塞調(diào)用線程”,是指在某一個線程中調(diào)用了阻塞方法,從而使該線程不能及時執(zhí)行以后的代碼。
復制代碼 代碼如下:
Void func()
{
Int index=0;
For(int i=0;i<10000;++i)
{
For(int j=0;j<10000;++j)
{
I ndex++;
}
}
}
Thread t = newThread(new ThreadStart(func));
t.Start(); //線程t中調(diào)用了阻塞方法func,因此線程t會被阻塞
在介紹func方法時,可以這樣描述:該方法會阻塞調(diào)用線程。
16. 同一個方法可以被多個線程調(diào)用,既可被UI線程調(diào)用,也可被非UI線程調(diào)用,那么在方法體內(nèi)怎么編寫訪問UI控件(UI元素)的代碼呢?(跨線程訪問UI控件會引發(fā)異常)
復制代碼 代碼如下:
Void func()
{
Textbox1.Text=”測試”;
PictureBox1.Image = Image.FromFile(“a.jpg”);
}
1)以上func方法可能運行在UI線程中,如下:
復制代碼 代碼如下:
Private voidbutton1_Click(object sender,EventArgs e)
{
func(); //調(diào)用func方法
}
2)有如下,func方法可能運行在其他非UI線程中
復制代碼 代碼如下:
Private void button1_Click(object sender,EventArgs e)
{
Thread t = new Thread(newThreadStart(func));
t.Start(); //func訪問運行在t線程中
}
在2)中,可能引發(fā)異常。
以上問題的解決方案為:
修改func代碼為:
復制代碼 代碼如下:
Func()
{
If(this.InvokeRequired)
{
This.BeginInvoke((Action)delegate(){func()});
}
Else
{
Textbox1.Text=”測試”;
PictureBox1.Image = Image.FromFile(“a.jpg”);
}
}
有關(guān)BeginInvoke或者Invoke方法的使用,請上網(wǎng)Google或者百度。
17. 有關(guān)“跨線程訪問UI控件可能引發(fā)異?!钡脑?,跟多線程訪問集合可能出現(xiàn)錯誤的原因基本相似。下面列舉一段代碼說明情況
復制代碼 代碼如下:
ClassMyControl
{
Object root;
Public Draw()
{
GetRoot(root);
// 一系列操作…
ReleaseRoot(root);
}
Public OtherDraw()
{
GetRoot(root);
// 一系列操作 …
ReleaseRoot(root);
}
}
其中root變量同時只能被占用一次,GetRoot()獲取root的訪問權(quán),如果root已經(jīng)被占用,則拋出異常。ReleaseRoot()釋放root占用。
當在一個線程中(比如UI線程中)訪問MyControl類對象A,調(diào)用A.Draw()方法,執(zhí)行到GetRoot(root)方法后,該線程失去控制權(quán),暫停運行一下的代碼,即此時root已被占用。而另一線程中如果也要訪問同一對象A的Draw()方法,那么就會引發(fā)異常。
18.在.Net Winform應用程序中,程序與用戶的交互主要包含兩個方面,一是用戶用鼠標、鍵盤燈硬件設備進行操作,程序響應操作,然后進行反饋(比如更新界面、刷新數(shù)據(jù)等),二是不需要用戶用鼠標等硬件設備進行操作,程序自己自動進行反饋(比如QQ彈出新聞窗體、360彈窗等)。
第一種情況是我們所熟知的,比如用戶用鼠標點擊按鈕(button1),程序則彈出一個MessageBox,我們在程序中是這樣子寫的:事件處理程序如下
復制代碼 代碼如下:
Private voidbutton1_Click(object sender,EventArgs e)
{
MessageBox.Show(“彈出對話框,或者其他操作”);
}
再來理一下這個過程,首先用戶拿起鼠標點擊button1,操作系統(tǒng)(鼠標驅(qū)動)會捕獲這個事件,經(jīng)過分析,操作系統(tǒng)得知用戶點擊的是哪個窗體(按鈕)、點擊的位置(坐標),點擊類型(左鍵還是右鍵或者其他),以及其他信息,之后,將這些信息封裝成一個類型(即windows消息)發(fā)送給創(chuàng)建該窗體(控件)的線程中的消息隊列,之后,操作系統(tǒng)(鼠標驅(qū)動)就不在負責了。接著,UI線程從線程消息隊列中獲取該消息(注意:這個過程是一直存在的),分析消息,調(diào)用開發(fā)人員編寫的一些回調(diào)方法,如button1_Click()方法,從而到達相應鼠標鍵盤操作的目的。從上面分析過程來看,再一次說明,程序是不會直接跟鼠標等硬件設備交互的,與它直接交互的只有Windows消息,而這個過程需要Windows操作系統(tǒng)起著重要作用。
第二種情況一般用在多線程編程中,當程序有耗時操作、或者需要一直監(jiān)聽等情況的時候,是不能放在UI線程之中的,這時候就需要另外開辟線程,在另外的線程中處理。這種情況中,另外開辟的線程有時候需要反饋跟用戶一些信息,即更新UI界面或者彈出一個窗體等,這就涉及到跨線程訪問UI元素的問題了,詳見5和
19.以上代碼部分均為現(xiàn)寫的,可能出現(xiàn)拼寫錯誤,包涵!
另外,請配合筆記(二)中的“DOT NETWinform應用程序運行結(jié)構(gòu)圖”閱讀。
您可能感興趣的文章:
- .Net中導出數(shù)據(jù)到Excel(asp.net和winform程序中)
- ASP.NET也像WinForm程序一樣運行的實現(xiàn)方法
- 在WinForm和WPF中使用GMap.Net地圖插件簡單教程
- 關(guān)于C#.net winform程序驗證moss的集成身份認證實例
- .Net WInform開發(fā)筆記(五)關(guān)于事件Event
- .Net Winform開發(fā)筆記(四)透過現(xiàn)象看本質(zhì)
- .Net WInform開發(fā)筆記(三)談談自制控件(自定義控件)
- .Net WInform開發(fā)筆記(二)Winform程序運行結(jié)構(gòu)圖及TCP協(xié)議在Winform中的應用
- Winform實現(xiàn)調(diào)用asp.net數(shù)據(jù)接口實例
相關(guān)文章
C#中結(jié)構(gòu)體和字節(jié)數(shù)組轉(zhuǎn)換實現(xiàn)
這篇文章主要介紹了C#中結(jié)構(gòu)體和字節(jié)數(shù)組轉(zhuǎn)換實現(xiàn),本文直接給出了字節(jié)數(shù)組與結(jié)構(gòu)體的轉(zhuǎn)換代碼,代碼中包含詳細注釋,需要的朋友可以參考下2015-06-06如何使用Dapper處理多個結(jié)果集與多重映射實例教程
Dapper類是一個開源的數(shù)據(jù)庫操作類,下面這篇文章主要給大家介紹了關(guān)于如何使用Dapper處理多個結(jié)果集與多重映射的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧2018-09-09WinForm中KeyDown,KeyPress和KeyUp的順序與區(qū)別解析
這篇文章主要介紹了WinForm中KeyDown,KeyPress和KeyUp的順序與區(qū)別解析,對C#初學者來說很有學習借鑒價值,需要的朋友可以參考下2014-08-08DevExpress實現(xiàn)GridControl根據(jù)列選中一行
這篇文章主要介紹了DevExpress實現(xiàn)GridControl根據(jù)列選中一行,比較實用的功能,需要的朋友可以參考下2014-08-08