⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 编写 windows 标准控件㈡ .txt

📁 会变语言实现的一些程序
💻 TXT
📖 第 1 页 / 共 3 页
字号:
编写 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"
    ml.exe /c /coff /Fo"Control.obj" "D:\MASMPlus1.2\Project\ControlLIB\Control.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 + -