📄 编写 windows 标准控件㈠ .txt
字号:
.if [ebx].fState & FBF_PUSHED
.if edi & FBS_3DLOOK
invoke DrawEdge,ps.hdc,addr rt,EDGE_SUNKEN ,BF_RECT
invoke OffsetRect,addr rt,2,2
.else
invoke DrawEdge,ps.hdc,addr rt,BDR_SUNKENOUTER ,BF_RECT
invoke OffsetRect,addr rt,1,1
.endif
如果是按下风格,然后通过是否包含3D外观来显示。DrawEdge是重画边框的API函数,通过参数来重绘不同样式的3D边框。在写控件时,这个函数是很常用。大家可以多试试。
.elseif edi & FBS_BORDER || [ebx].fState & FBF_HOVER
.if edi & FBS_3DLOOK
invoke DrawEdge,ps.hdc,addr rt,EDGE_RAISED,BF_RECT
.else
invoke DrawEdge,ps.hdc,addr rt,BDR_RAISEDINNER,BF_RECT
.endif
这里则是没有按下时的处理,必须有边框风格,或者鼠标触发,都是突出重绘。如果都没有,则是无边框的显示方式。
.if edi & WS_DISABLED
invoke SetBkMode,ps.hdc,TRANSPARENT
invoke GetSysColor,COLOR_3DHIGHLIGHT
invoke SetTextColor,ps.hdc,eax
invoke DrawText,ps.hdc,addr buffer,-1,addr rt,DT_VCENTER or DT_SINGLELINE or DT_CENTER
invoke GetSysColor,COLOR_3DSHADOW
invoke SetTextColor,ps.hdc,eax
invoke OffsetRect,addr rt,-1,-1
invoke DrawText,ps.hdc,addr buffer,-1,addr rt,DT_CENTER or DT_VCENTER or DT_SINGLELINE
.else
invoke DrawText,ps.hdc,addr buffer,-1,addr rt,DT_VCENTER or DT_SINGLELINE or DT_CENTER
.endif
文本显示,这里我们使用的是系统文本,所以需要自己获得再显示,如果是经常要对文本进行处理的,则需要自己申请内存来对文本进行结构化,比如编辑控件,列表控件,这些都是多文本的控件。以后我们将会尝试使用的。这里还判断了是否是无效风格,否则就使用无效的文本显示。
.if [ebx].fState & FBF_FOCUS && !(edi & FBS_NOFOCUS)
invoke GetClientRect,hButt,addr rt
mov eax,3
add rt.left,eax
add rt.top,eax
sub rt.right,eax
sub rt.bottom,eax
invoke DrawFocusRect,ps.hdc,addr rt
.endif
这里是显示焦点框,必须是当前键盘焦点,并且,没有包含 FBS_NOFOCUS 的风格,这样使用DrawFocusRect函数来显示一个虚线框。是否是焦点,在WM_SETFOCUS/WM_KILLFOCUS中修改这个风格。
WM_NCCALCSIZE 非客户区计算大小
非客户区,一般是不需要处理的。这里,我们自己来处理,这样,任何非客户框的风格都无效.如 WS_BORDER,WS_EX_CLIENTEDGE,WS_EX_STATICEDGE,去掉这个消息,或者返回0,即可以使用系统默认的这些非客户区风格,还可以修改lParam指向的结构中的值来自定义非客户区的大小。
WM_LBUTTONDOWN 鼠标左键按下
invoke SetCapture,hButt
invoke SetFocus,hButt
or [ebx].fState,FBF_PUSHED
invoke InvalidateRect,hButt,0,TRUE
按下时,着先设定鼠标捕捉,这样,在释放之前,就算鼠标移出客户区,也一样可以收到鼠标消息,防止错误地定位到别的控件上,而取消操作。然后设置焦点,之后WM_SETFOCUS会自动发生,接着,添加风格FBF_PUSHED,然后刷新,这样外观变化就出来了。
.if edi & FBS_NOTIFY
invoke GetDlgCtrlID,hButt
mov wParam,eax
invoke GetParent,hButt
mov ecx,FBN_PUSHED
shl ecx,16
or wParam,ecx
invoke SendMessage,eax,WM_COMMAND,wParam,hButt
.endif
判断是否包含能够通知的风格,有则发送给父窗口,采用标准的WM_COMMAND消息规则,这里的代码其实可以写成一个函数,但是为了简单。。。
向父窗口发送WM_COMMAND命令,按统一的格式,高16位是消息号,低16位是ID
这种利用WM_COMMAND传送的父级通知,只能做简单地通知,无法再进行更好的处理
本例子中,只使用了几个通知消息,另外可以试试专用的API函数 SendNotifyMessage。
WM_LBUTTONUP 鼠标松开
invoke GetCapture
.if eax==hButt
invoke ReleaseCapture
.if [ebx].fState & FBF_PUSHED
and [ebx].fState,not FBF_PUSHED
invoke InvalidateRect,hButt,0,TRUE
着先判断是否是控件本身上点击后再松开的,也就是判断当前获得捕捉的窗口句柄,这样是防止从别的地方按下鼠标后在控件上松开而误触发。接着直接释放捕捉,然后判断是否已经按下,否则就修改状态然后刷新,这与WM_LBUTTONDOWN的前部分处理是一样的。
.if edi & FBS_NOTIFY
… …
mov ecx,FBN_UNPUSHED
shl ecx,16
这里与WM_LBUTTONDOWN中的通知代码功能类似。
@@ButtonClick:
invoke GetDlgCtrlID,hButt
;在这个消息中下面代码不需要再用到wParam和lParam,可以做为变量使用
mov wParam,eax
invoke GetParent,hButt
mov ecx,FBN_CLICKED
shl ecx,16
or wParam,ecx
invoke SendMessage,eax,WM_COMMAND,wParam,hButt
.endif
.endif
这里就是命令消息了,同样是使用WM_COMMAND通知给父窗口,通知码FBN_CLICKED是默认的0,所以这里不需要判断是否包含通知风格,并且,有一个全局标签在这里,是其它地方调用的直接跳转点。
WM_SETFOCUS 获得焦点
or [ebx].fState , FBF_FOCUS
invoke InvalidateRect,hButt,0,TRUE
直接设置焦点风格,然后刷新
WM_KILLFOCUS 失去焦点
and [ebx].fState ,not FBF_FOCUS
invoke InvalidateRect,hButt,0,TRUE
直接去掉焦点风格,然后刷新
WM_STYLECHANGED 风格有变化
.if edi & WS_DISABLED
.endif
invoke InvalidateRect,hButt,0,TRUE
当风格有改变时,即时的处理,本控件不需要针对风格的变化而修改内部的信息,因而只是刷新。
WM_MOUSEMOVE 鼠标移动
invoke GetCapture
.if eax==hButt && wParam & MK_LBUTTON
invoke GetClientRect,hButt,addr rt
invoke GetCursorPos,addr p
invoke ClientToScreen,hButt,addr rt
invoke ClientToScreen,hButt,addr rt.right
invoke PtInRect,addr rt,p.x,p.y
.if eax!=0 && !([ebx].fState & FBF_PUSHED) ;按下的同时,鼠标在控件中
or [ebx].fState,FBF_PUSHED
invoke InvalidateRect,hButt,0,TRUE
.elseif eax==0 && [ebx].fState & FBF_PUSHED ;按下的同时,鼠标在控件外
and [ebx].fState,not FBF_PUSHED
invoke InvalidateRect,hButt,0,TRUE
.endif
同样是先判断是否是当前捕捉,然后,判断是鼠标左键按下,就说明,用户在按下鼠标左键后一直没有放开,并且同时移动鼠标,这时当鼠标移出窗口外时,恢复默认的显示状态。再移回来时,重新显示按下状态。具体操作是,获得当前鼠标坐标,并将客户区域转换成屏幕坐标,再判断是否在鼠标是否在客户区域内,来重新设置或去掉鼠标按下风格并刷新。这样可以防止多余的操作来防止闪烁。
.else
.if !(edi & FBS_BORDER) ;没有边框,进行鼠标的触发处理
mov tms.cbSize,sizeof TRACKMOUSEEVENT
mov tms.dwFlags,TME_HOVER or TME_LEAVE
mov eax,hButt
mov tms.hwndTrack,eax
mov tms.dwHoverTime,10
invoke TrackMouseEvent,addr tms
.endif
.endif
如果只是移动,并没有按下键,当没有包含边框时,我们进行鼠标触发的风格设置。通过鼠标跟踪,每10ms测试一次鼠标是移进窗口还是移出窗口,触发WM_MOUSEHOVER与WM_MOUSELEAVE。
WM_MOUSEHOVER ;鼠标停下
or [ebx].fState,FBF_HOVER
invoke InvalidateRect,hButt,0,TRUE
直接添加鼠标触发风格。并刷新
WM_MOUSELEAVE ;鼠标移出
and [ebx].fState,not FBF_HOVER
invoke InvalidateRect,hButt,0,TRUE
直接去掉鼠标触发风格。并刷新
WM_KEYDOWN 键盘按下
invoke GetCapture
.if eax!=hButt
cmp wParam,VK_SPACE
jz @@ButtonClick
cmp wParam,VK_RETURN
jz @@ButtonClick
.endif
直接转到命令发送处。这里与鼠标消息不同,必须是没有捕捉时才处理,防止鼠标按下过程中去处理键。
同时,因为只有键盘焦点时才会收到这个消息,所以不需要判断[ebx].fState是否有FBF_FOCUS风格。
WM_GETDLGCODE 获得对话框代码
.if (edi & WS_TABSTOP) ;TAB停止
mov eax,DLGC_STATIC
.elseif edi & FBS_DEFBUTTON
mov eax,DLGC_DEFPUSHBUTTON ;默认按钮
.elseif edi & FBS_CLASS
mov eax,DLGC_WANTALLKEYS
.else
mov eax,DLGC_BUTTON
.endif
在对话框中,对话框会发送这个消息来确认控件是否需要由对话框接管处理
如果返回DLGC_WANTALLKEYS,表示控件需要处理所有的按键,这时候,按TAB,上下左右等等对话框自动处理的键,对话框都会让给控件本身处理,你可以理解为:对话框询问你,你需要我怎么样帮你处理键位?返回的DLGC*,就是答案,对话框根据你的返回值来处理。
当包含WS_TABSTOP风格时,表示焦点在你的上一个控件或下一个控件时,按TAB或Shift+TAB停止处理。如果包含FBS_DEFBUTTON,则返回DLGC_DEFPUSHBUTTON,告诉对话框,这个按钮是默认的。
WM_SYSKEYUP 系统按键处理
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -