?? 編寫 windows 標準控件㈡ .txt
字號:
編寫 Windows 標準控件㈡
相關的例子:下載>>> 作者:AoGo 于2007-12-16上傳
--------------------------------------------------------------------------------
上一章我們編寫了一個簡單地平坦按鈕控件,最后留的習題,不知道大家有沒有試著去完成,其實是很簡單地,問題在于繪出右邊的小三角形的圖形,自己畫?用圖片?呵呵,都不用,用 API 函數吧:
invoke DrawFrameControl,hdc,lpRT,DFC_SCROLL,DFCS_SCROLLDOWN
這個函數通過指定不同的標志性參數,來重繪按鈕,菜單,滾動條,窗口標題欄等不同的部位,比如上面函數就表示要重畫滾動條的向下滾動鍵頭的圖形。多試試不同的參數看看,你會發現很有意思的東西。
學習編程,舉一反三是很重要的,可是讓你快速積累經驗,比如,在 MSDN 或者 API 手冊中,搜索上面這個函數時,會有相似的函數列出來,這個時候一定要依次稍稍看一下,不要求立即使用,但是腦子里有印象,下一次說不定就能用上。
除了這個圖形,還有那個分隔線,用 DraEdge 吧:
invoke SetRect,addr lpRT,top,left,bottom,left+2 ;寬度2個像素,可畫出分隔線
invoke DrawEdge,hdc,lpRT,BDR_SUNKENOUTER,BF_RECT
原來本章是打算完成這個習題,然后封裝。并且,告訴大家在不動控件代碼的情況下,通過接管通知消息來擴展在為顏色選擇器的功能,文章都已經寫得差不多了,突然發現,我是要寫“編寫”的教程,而不是使用,而接管消息實現什么功能的,這已經脫離了這個范疇,所以,苦惱了幾天,還是下決心重寫。T_T 。。。
在本章中,我們將編寫一個新的控件,這個控件和之前的相比,會復雜得多,它的重點在于告訴大家如何使用不可定的操作集,比較簡單的,就是列表控件了。
同樣,例子已經寫好,代碼仍然很多,下載之后,使用 MASMPlus 打開它,先編譯一下吧。
程序運行界面如下:
正在處理工程文件...
ml.exe /c /coff /Fo"List.obj" "D:\MASMPlus1.2\Project\ControlLIB\List.asm"
link.exe /LIB "list.obj" /OUT:"Control.LIB" "Control.obj"
正在處理工程 ...
ml.exe /c /coff /nologo /Fo"UseControl.obj" "D:\MASMPlus1.2\Project\ControlLIB\UseControl.ASM"
link.exe /SUBSYSTEM:WINDOWS /nologo /OUT:"UseControl.exe" "UseControl.obj"
準備就緒:D:\MASMPlus1.2\Project\ControlLIB\UseControl.exe
看上面的編譯信息,你是否查覺到了什么呢?呵呵,從這一章開始,我們將使用建立LIB的方式來寫控件,前一章的FlatButton控件也包含在內,因為兩個控件都是單獨的OBJ鏈接成LIB的,所以,如果你只使用其中一個控件,只需要調用其相應的注冊函數就可以了,沒有注冊的控件調用,是不會包含到你的程序中的,這樣,當你的控件越來越多時,也只需引用一個庫,用多少個就插入多少個到程序中,簡單方便。這就是控件庫了。
同時,不僅僅是控件,包含單獨的函數也是可以的,比如你寫了一個排序的函數,只需要按獨立的方式編寫并編譯,再全部一次鏈接,這樣這個函數也是一個模塊。
有任何不明折的地方,單擊右鍵看一下參數設置吧。
本章寫的控件,是 List,列表控件,按上一章的方式,我們來進行解說。
首先,是確定 List 所需的結構:
列表控件是一個多操作無素的控件,比如可以有不定個數的項目,每個項目文本不同,所以,我們需要使用內存來進行管理,我使用的方法是與索引對應的數組來保存每一個項目內存,當刪除時釋放對應的內存并移動數組,為什么不使用更好的內存管理呢?更好的結構同時也是更復雜的,這個時候就要按最初的想法來確定,如果每一個項目都要有狀態,比如多選,或者是為了適應更大量數據處理,就必須用鏈表或者別的方法,而我的最初設計,只是做一個純列表框控件,所以,這個時候使用直接的DWORD數組,反而會更好,因為它操作簡單,尋址方便。
下面針對某些成員進行介紹。
hHeap
保存堆句柄,關于內存模式的選擇,我用的是堆內存,自己創建一個堆來使用,好處是,只需要釋放堆,內存就全部釋放了,不需要每個項目都去遍歷再釋放。當然大型數據處理時,就需要根據用戶的情況而定。這里我就不重復了,關于內存管理,如果細說幾乎可以寫本書了。
fStyle
保存控件的某些設置,MLFS_FOCUS表示控件是當前焦點,這樣不需要每次去GetFocus再判斷,這個標志在WM_SETFOCUS/WM_KILLFOCUS中被添加或刪除,MLFS_SHARED標識當前的圖像列表是否是用戶設置的,比如用戶手動設置了圖像列表,這樣我們不會在控件銷毀時去刪除它。
iMaxWidth
水平最大寬度,每添加一個項目,會自動獲得字符串的長度對比后保存最長的,用于設置水平滾動條
iHeight
項目高度,所有項目的高度都是相同的。當翻頁,上下,刷新等操作時,需要高度進行計算。
rt
客戶區域,在WM_SIZE消息中保存,因為大小的變化是一定會有WM_SIZE消息的,保存后在其它消息中使用時,不需每次都使用GetClientRect來得到。
lpItems是一個指向 DWORD 的數組,用于保存每一個項目內存的地址,而列表框項目的索引與數組的索引是一樣的,所以操作與尋址都是很方便的,而指向的項目的結構,則是這樣:
本控件的窗口風格比較少:
MLS_HASBITMAP equ 1h ;顯示圖像
MLS_OWNERDRAW equ 8h ;由父級重繪
MLS_CLASS equ 20h ;需要所有按鍵,默認只處理方向鍵,其它交給對話框處理(如果是在對話框中)
我們不再使用 WM_COMMAND 通知碼了,只使用通知消息,也即是WM_NOTIFY,事實上,只有簡單地控件,才會不使用 WM_NOTIFY 而使用 WM_COMMAND。
消息的文檔我就不再細述了,單單對文檔來解說,是沒有效果的,參照代碼來看,首先是函數:
注冊控件函數 RegisterMyList
注冊列表控件的函數,wc.style不再使用 CS_PARENTDC,并且需要雙擊。
發送通知消息函數 ParentNotify
向父窗口發送通知消息,與上一章不同的時,列表控件發送通知消息時,使用的是自己的結構:
前面的 hdr 是原本的 NMHDR 結構,這是固定的,后面是一個枚舉結構體,其實都是表示一個成員,只是在使用時用于區分是哪個通知消息。
;通知消息要小于NM_LAST,大于NM_LAST的是系統使用的。注意系統通知消息為負數,所以這里是減而不是加
NMN_SELCHANGING equ NM_LAST-1 ;選擇更改中,返回非零值取消選擇更改,iCurrPos=要選擇的項目
MLN_SELCHANGED equ NM_LAST-2 ;選擇更改的,iOldSel=指向舊的選擇索引
MLN_CLICK equ NM_CLICK ;項目單擊,XY=當前坐標,可以取消
MLN_SETFOCUS equ NM_SETFOCUS ;設置焦點,hWndFocus=失去焦點的窗口句柄
MLN_KILLFOCUS equ NM_KILLFOCUS ;失去焦點.hWndFocus=得到焦點的窗口句柄
MLN_DBLCLK equ NM_DBLCLK ;雙擊,iPos=當前選擇
MLN_IMAGECHANGED equ NM_LAST-3 ;圖像列表更改了。hImageList=新的圖像列表
重繪項目函數 DrawItem
DrawItem proc uses esi edi ebx,hDlg,hdc,lpMYLIST,Item_Index,lpRECT,WndStyle
hDlg 列表控件句柄
hdc 設備上下文,這個參數如果沒0,函數將從hDlg獲得DC。
lpMYLIST 窗口結構地址
Item_Index 項目索引,為-1表示重繪的是空白部分
LPRECT 重繪區域
WNdStyle 窗口風格,將從這里判斷是否包含父組重繪風格。
這個函數是公用也是唯一的重繪項目函數,自動判斷是重繪還是通知父級。
消息處理
WM_CREATE 窗口創建
invoke LocalAlloc,LMEM_ZEROINIT or LMEM_FIXED,sizeof MYLIST
test eax,eax
jnz @@SkipFail
@@: ;內存不足,直接返回-1,窗口創建失敗
dec eax
jmp @@Ret
@@SkipFail:
mov ebx,eax
invoke SetWindowLong,hList,0,ebx ;保存到實例的窗口字中
mov [ebx].iHeight,MIN_HEIGHT
invoke ImageList_Create,ICON_SIZE,ICON_SIZE,ILC_COLOR32 or ILC_MASK,0,0
mov [ebx].hImage,eax
invoke SendMessage,hList,MLM_RESTORE,0,0
創建內存并保存,設置小最高度,創建圖像列表,然后初始化。
WM_SIZE 窗口大小變化
invoke GetClientRect,hList,addr [ebx].rt
mov sci.fMask,SIF_RANGE or SIF_PAGE or SIF_DISABLENOSCROLL
mov eax,[ebx].rt.bottom
xor edx,edx
div [ebx].iHeight
mov ecx,[ebx].iCount
sub ecx,eax
.if [ebx].iTop>ecx ;自動設置正確位置
mov [ebx].iTop,ecx
mov sci.nPos,ecx
or sci.fMask,SIF_POS
.endif
mov sci.cbSize,sizeof SCROLLINFO
mov sci.nPage,eax ;設置滾動條一頁的范圍
mov eax,[ebx].iCount
dec eax
mov sci.nMax,eax
xor eax,eax
mov sci.nMin,eax
invoke SetScrollInfo,hList,SB_VERT,addr sci,TRUE
mov sci.fMask,SIF_RANGE or SIF_PAGE or SIF_DISABLENOSCROLL
mov eax,[ebx].iMaxWidth
mov sci.nMax,eax
mov eax,[ebx].rt.right
mov sci.nPage,eax
xor eax,eax
mov sci.nMin,eax
invoke SetScrollInfo,hList,SB_HORZ,addr sci,TRUE
invoke InvalidateRect,hList,0,0
保存窗口客戶區域,然后將高度和項目高做除法計算出一頁顯示的項目數。設置到滾動條上。消息滾動條總是使用iMaxWidth來設置。
然后刷新整個客戶區域,因為我們的控件并沒有包含大小變化時刷新的類標志。
WM_PAINT ;界面重繪
invoke BeginPaint,hList,addr ps
mov eax,[ebx].rt.top
mov p.y,eax
mov edi,[ebx].iTop
@@:
cmp edi,[ebx].iCount
jae @F
mov eax,[ebx].iHeight
add eax,p.y
invoke SetRect,addr ItemRt,[ebx].rt.left,p.y,[ebx].rt.right,eax
invoke DrawItem,hList,ps.hdc,ebx,edi,addr ItemRt,fStyle
inc edi
mov eax,ItemRt.bottom
mov p.y,eax
cmp eax,[ebx].rt.bottom ;比較項目是否已經在底部
jb @B
@@:
;如果項目不足一頁,使用默認畫刷畫出下面的部分
mov eax,[ebx].rt.bottom
.if p.y<eax
invoke SetRect,addr ItemRt,[ebx].rt.left,p.y,[ebx].rt.right,[ebx].rt.bottom
invoke DrawItem,hList,ps.hdc,ebx,-1,addr ItemRt,fStyle
.endif
invoke EndPaint,hList,addr ps
從保存的iTop開始重繪,iTop是當前可見頁的第一項索引,設置區域之后,交給重繪函數。
當超過總數或者項目不足一頁,自動跳出,之后,判斷是否需要畫空白部分。同樣是交給重繪函數
WM_LBUTTONDOWN 鼠標按下
invoke ParentNotify,hList,MLN_CLICK,lParam
.if eax==0
mov eax,lParam
shr eax,16
xor edx,edx
div [ebx].iHeight
add eax,[ebx].iTop
invoke SendMessage,hList,MLM_SETCURSEL,eax,MSC_SETFOCUS
.endif
首先發送消息給父窗口,如果返回0,表示繼續處理,計算當前點擊位置,然后再發送消息設置當前選擇項目。
這樣做是為什么呢?比如,用戶想做復選項目時,可以使用兩個圖形,設置dwData,然后在這里處理點擊并復選或者不選。呵呵。
WM_LBUTTONDBLCLK 雙擊
invoke ParentNotify,hList,MLN_DBLCLK,[ebx].iSel
并沒有特別的處理,但是發送通知消息給父窗口。讓父窗口處理
WM_KEYDOWN ;鍵盤按下
mov edi,[ebx].iTop
mov ecx,[ebx].iSel
mov eax,wParam
.if eax==VK_UP
.if sdword ptr ecx>0
dec ecx
invoke SendMessage,hList,MLM_SETCURSEL,ecx,MSC_SETVISIBLE
.endif
.elseif eax==VK_DOWN
mov eax,[ebx].iCount
dec eax
.if ecx<eax
inc ecx
invoke SendMessage,hList,MLM_SETCURSEL,ecx,MSC_SETVISIBLE
.endif
上下鍵按下時,判斷是否在可移動區域,然后設置索引,調用MLM_SETCURSEL消息
.elseif eax==VK_PGUP
mov eax,[ebx].rt.bottom
xor edx,edx
div [ebx].iHeight
sub edi,eax
.if sdword ptr edi<0
xor edi,edi
.endif
sub ecx,eax
.if SDWORD ptr ecx<0
xor ecx,ecx
.endif
invoke SendMessage,hList,MLM_SETCURSEL,ecx,0
mov [ebx].iTop,edi
mov sci.nPos,edi
call @@SetScrollPos
.elseif eax==VK_PGDN
mov eax,[ebx].rt.bottom
xor edx,edx
div [ebx].iHeight
add ecx,eax
mov edx,[ebx].iCount
.if ecx>=edx
mov ecx,edx
dec ecx
.endif
add edi,eax
mov edx,[ebx].iCount
sub edx,eax
.if edi>=edx
mov edi,edx
dec edi
.endif
invoke SendMessage,hList,MLM_SETCURSEL,ecx,0
mov [ebx].iTop,edi
mov sci.nPos,edi
call @@SetScrollPos
.endif
向下/向上翻頁。思路是,翻頁是以iTop為準,向上時,如果位置是最頂或最底部了,則自動限制在范圍內。當前選擇與當前頂位置單獨設置可以在翻頁后,選擇項目位置坐標不變。
WM_SETFOCUS ;設置焦點
invoke ParentNotify,hList,MLN_SETFOCUS,wParam
or [ebx].fStyle , MLFS_FOCUS
invoke InvalidateRect,hList,0,TRUE
通知父窗口,然后加上MLFS_FOCUS風格。接著重繪
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -