?? 編寫 windows 標(biāo)準(zhǔn)控件 小結(jié).txt
字號(hào):
編寫 Windows 標(biāo)準(zhǔn)控件 小結(jié) 作者:AoGO 于2008-1-17上傳
--------------------------------------------------------------------------------
在前面兩章,我們編寫了兩個(gè)標(biāo)準(zhǔn)行為的控件,這一章,我們來回顧一下這兩章的重點(diǎn),以及我的一些遺漏。在本章,所有涉及控件的含義,僅僅表示標(biāo)準(zhǔn)控件,自定義的控件不在討論范圍之內(nèi)。
1.什么時(shí)候需要自己寫控件
寫控件并不是一件很輕松的事,復(fù)雜的控件尤其如此,在決定寫一個(gè)控件之前,考慮一下是否有更好的辦法,比如,覺得按鈕的 3D 邊框難看,想有平坦風(fēng)格,只因?yàn)檫@個(gè)原因而自己寫一個(gè)新控件,這是得不嘗失的,代碼不多,但也不少,以后的擴(kuò)展性,維護(hù)性,如果是團(tuán)隊(duì)開發(fā),如果讓使用者最快上手的使用文檔,等等,像上述的控件,完全可以超類化原有的按鈕控件,簡(jiǎn)單地接管幾個(gè)消息即可達(dá)到同樣的目的。還有,控件之間的組合,同樣可以擴(kuò)展為新的控件,比如,ListView 控件中的列標(biāo)題其實(shí)是一個(gè)單獨(dú)的控件,并不需要在ListView中單獨(dú)再實(shí)現(xiàn)列標(biāo)題控件的功能,這種控件組合是很方便的。
2.寫之前的設(shè)計(jì)
要寫控件,首先要做的事,就是設(shè)計(jì)控件的文檔,比如,第一章與第二章中的FlatButton及MyList控件,代碼我一個(gè)字都沒打,文檔已經(jīng)定義好了,首先,是控件名字,假設(shè)是 QuickHelper ,那取單詞的首字符,來做為我們定義的前綴。
比如消息是 QHM_ ,窗口風(fēng)格是 QHS_ ,通知消息是 QHN_ ,等等。
接下來就是各細(xì)節(jié)的定義,這個(gè)并不一定要局限于一套行為,按自己的想法固定就可以了,但是一定要注意就是不要散亂,一直使用自己的固定定義格式,形成自己的風(fēng)格,這樣,當(dāng)寫新的控件時(shí),建立一個(gè)文檔是很快速的。而不需要每次都去想這個(gè)那個(gè)要放哪。我在這里說一下我的習(xí)慣,可以參考一下,因?yàn)槲沂前礃?biāo)準(zhǔn)的 Windows 控件的方式定義文檔的(再次重申并不一定要這樣做)。
首先是窗口風(fēng)格,所有的控件可以使用32個(gè)風(fēng)格位,高16位是系統(tǒng)使用的,也就是所有的 WS_ 開頭的風(fēng)格,低16位可以自由使用,這16個(gè)系統(tǒng)給你的變量,應(yīng)該如何使用最好呢?標(biāo)準(zhǔn)的行為是優(yōu)先用來控制顯示功能,如,幾乎所有的 Windows 標(biāo)準(zhǔn)控件(系統(tǒng)自帶的)都是通過更改風(fēng)格來設(shè)置不同的顯示,Button 可以更改成復(fù)選框,單選框,文本對(duì)齊,圖形,列表框通過風(fēng)格來選擇是下拉/列表/下拉列表,等等,這樣在對(duì)話框設(shè)計(jì)時(shí),就可以直接看到效果。
如果還有余,而不需要用來預(yù)留給以后擴(kuò)展,那其次是用來控制控件與用戶的交互行為,比如是否需要按鍵,是否需要自動(dòng)對(duì)齊等等用戶簡(jiǎn)單控制地功能。
如果還有余,那就自由發(fā)揮了,一般的做法,是預(yù)留,方便以后擴(kuò)展其它外觀顯示功能。
如果不夠,那我們就只能在窗口的結(jié)構(gòu)內(nèi)存中保存了,但一個(gè)控件一般16個(gè)位是完全夠了的,因?yàn)檫€要考慮到位的組合,比如,文本對(duì)齊:
QHS_CENTER equ 1h
QHS_RIGHT equ 2h
QHS_LEFT equ QHS_CENTER or QHS_RIGHT
這樣,三個(gè)風(fēng)格只使用了二個(gè)位,在獲得窗口風(fēng)格后這樣:
GetWindowLong,hWnd,GW_STYLE
and eax,QHS_LEFT
.if eax == QHS_LEFT
...
.elseif eax & QHS_CENTER
...
.else
...
.endif
通過位的組合,可以把16個(gè)擴(kuò)展為20個(gè)甚至30個(gè),當(dāng)然,前提是,這些位是同一個(gè)功能的不同控制,沒有沖突,比如像 Button 控件,通過幾個(gè)大的風(fēng)格,擴(kuò)展其它風(fēng)格時(shí)使用同樣的值,因?yàn)樗鼈兪遣粫?huì)沖突的:
BS_PUSHBUTTON equ 0h
BS_DEFBUTTON equ 10h
BS_RADIOBOX equ 1h
BS_ABC equ 10h
BS_CHECKBOX equ 2h
BS_AUTOCHECKED equ 10h
全局控制位:
BS_CHECKBOX equ 8000h
看上面的信息,就會(huì)知道,0/1/2這幾個(gè)是大的風(fēng)格變化,而equ 10的,是不同的風(fēng)格,但是只針對(duì)各自的大風(fēng)格才有用,在其它風(fēng)格中是無另外的控制,就可以通過這樣來實(shí)現(xiàn)擁有更多的控制位,而全局控制位則適用于所有不同類型的風(fēng)格。
接下來我們看通知碼 QHN_ ,WM_COMMAND 這個(gè)通知消息,它只能進(jìn)行最簡(jiǎn)單地通知,使用者只能收到消息,而無法按管,我們這里就不再詳細(xì)討論。WM_NOTIFY 則不同,它能夠傳遞隨意多的信息,這個(gè)沒有什么具體的限制與要求,結(jié)構(gòu)可以使用系統(tǒng) NMHDR,也可以使用自己的,但是前提是前面必須包含 NMHDR 結(jié)構(gòu),這是系統(tǒng)通知消息格式,不可更改,其它的就沒什么要求,應(yīng)該通知什么,如何處理通知,是否根據(jù)返回值決定哪位操作,這是控件的功能來決定的。
接下來就是消息了,自己的消息必須大于 WM_USE,小于 WM_USER 是系統(tǒng)消息,控件消息是控件自身的功能,我們無法針對(duì)性討論,談?wù)剺?biāo)準(zhǔn)消息,一般而言,小部分消息是所有控件都要使用到的,比如WM_PAINT,WM_DESTORY等等,如果需要鼠標(biāo),那WM_LBUTTONDOWN/WM_LBUTTONUP等等,鍵盤WM_KEYDOWN/WM_KEYUP,風(fēng)格更改時(shí)WM_CAPTURECHANGED,這些消息都是系統(tǒng)消息,我們只管處理。我這里著重要講的是可以按管的消息,舉個(gè)列子:
WM_SETFONT 設(shè)置字體,事實(shí)上,這個(gè)消息僅僅是通知而已,標(biāo)準(zhǔn)文檔是:
wParam==hFont ;字體句柄
lParam==bDraw ;是否立即重繪
返回前一次的字體
那么,根據(jù)這個(gè)定義,你按管這個(gè)消息,所wParam指向的字體句柄保存到控件結(jié)構(gòu)中,然后根據(jù)lParam來是否刷新,然后返回前一次的控件句柄即可。
類似的還是WM_GETDLGCODE,WM_CAPTURECHANGING,WM_SIZING等等。。。其中WM_SETTEXT也可以完全接管,這里就不一一詳述了。
消息的定義,也盡量化繁為簡(jiǎn),比如,第二章的MyList控件,就有消息MLM_ADDSTRING與MLM_SETITEM,MLM_SETITEM 對(duì)于添加一個(gè)基本項(xiàng)目來說過于麻煩,則多定義一個(gè)消息MLM_ADDSTRING來實(shí)現(xiàn)快速添加,當(dāng)用戶添加之后,他就會(huì)考慮,為什么有圖標(biāo)呢,就會(huì)去翻文檔找到WM_SETITEM,則引起興趣。如果沒有MLM_ADDSTRING,一個(gè)新用戶要多費(fèi)上幾分鐘的時(shí)間去嘗試,考慮一下每次都要添加,他自己就會(huì)去寫個(gè)類似MLM_ADDSTRING的函數(shù)了。
同時(shí),消息定義要完善好文檔,wParam是什么,lParam是什么,返回值是什么,要清楚,消息地這些格式有變化,要同步修改文檔,否則過上幾天,自己用都要再參考代碼。
另外一個(gè)比較重要的,是界面重繪通知消息,這個(gè)也沒有定性,簡(jiǎn)單地,可以使用WM_DRAWITEM來實(shí)現(xiàn),但是這個(gè)消息標(biāo)準(zhǔn)行為是完全性按管,也就是父級(jí)重繪,有些時(shí)候我們的控件界面顯示很復(fù)雜,分為多個(gè)不同單元,每個(gè)單元都想通知并由父級(jí)處理,那好的方法,是使用自己的消息,定義wParam與lParam,然后傳一個(gè)指針,標(biāo)識(shí)重繪區(qū)域與目標(biāo)信息,再根據(jù)返回值,適當(dāng)?shù)赝瓿山酉聛淼墓ぷ鳎@種方式不是很常用,只有像電子表格這種復(fù)雜的控件,或者組合控件才有可能會(huì)用到,比如一個(gè)組合控件,把子控件的重繪通過消息再次發(fā)給自己的父窗口,由它去處理。
另外一個(gè),我發(fā)現(xiàn)一個(gè)奇怪的現(xiàn)象,可能是受羅云彬的影響,大部分人的窗口回調(diào)中,喜歡在ret之前 加上xor eax,eax,而在消息中返回值,如:
xxx proc ... ...
.if uMsg == *
mov eax,true
ret
.else
invoke DefWindowProc....
.endif
xor eax,eax
ret
xxx endp
這是不可取的,正確的方法應(yīng)該是直接 ret,而在消息里面消息地處理回調(diào)函數(shù):
xxx proc ... ...
.if uMsg == *
...
mov eax,TRUE
.else
invoke DefWindowProc....
.endif
ret
xxx endp
理由我就不多說了,如果把這習(xí)慣帶到寫控件的程序里,你會(huì)想殺人的。。
3 無閃爍刷新
前面二章的控件,都是簡(jiǎn)單重繪的控件,不需要也沒必要搞無閃爍刷新,只有像同時(shí)顯示大量文本/圖形的控件,才需要考慮這個(gè),其實(shí)重點(diǎn)就是,不要背景重繪,WM_ERASEBKGND直接返回TRUE,或注冊(cè)控件時(shí)設(shè)置空畫刷背景。在 WM_PAINT 中,畫背景時(shí)也要分開,把各個(gè)可以分開的部分使用不同的函數(shù)來畫,在其它消息中。不要去InvalidateRect,而是獲得需要重繪的部分,直接調(diào)用對(duì)應(yīng)的函數(shù)去畫。同時(shí),如果有滾動(dòng)條,則使用ScrollWindow來滾動(dòng),然后重繪滾動(dòng)后的空白部分,這其實(shí)就是無閃爍刷新的關(guān)鍵。
小結(jié)就到這里,還是那句話,學(xué)編程就像追MM,看著有感覺,就動(dòng)手干吧,不要錯(cuò)過機(jī)會(huì)。人都有低潮的時(shí)候,在有感覺的時(shí)候不動(dòng)手學(xué),就浪費(fèi)這大好光陰了。
最后,過年了,祝大家新年快樂,事業(yè)進(jìn)步!
--------------------------------------------------------------------------------
歡迎訪問AoGo匯編小站:http://www.aogosoft.com/
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -