SDK平台三态按钮的实现

Windows平台提供了丰富的控件,但是在使用中我们不会使用它提供的默认风格,有时候需要对控件进行改写,让它展现出更友好的一面,这次主要是说明三态按钮的实现。

  三态按钮指的是按钮在鼠标移到按钮上时显示一种状态,鼠标在按下时展现一种状态,在鼠标移开时又展现出另外一种状态,总共三种。当然鼠标按下和移出按钮展示的状态系统自己提供的有,这个时候在处理这两种状态只需要贴相应的图片就行了,三态按钮的实现关键在于如何判断鼠标已经移动到按钮上以及鼠标移出按钮,然后根据鼠标的位置将按钮做相应的调整。

  判断鼠标在按钮的相应位置,系统提供了一个函数_TrackMouseEvent用户处理鼠标移出、移入按钮。函数原型如下:

BOOL _TrackMouseEvent(       
    LPTRACKMOUSEEVENT lpEventTrack 
); 

函数需要传入一个TRACKMOUSEEVENT类型的指针,该结构的原型如下:

typedef struct tagTRACKMOUSEEVENT {
2     DWORD cbSize;//该结构体所占空间大小
3     DWORD dwFlags;//指定服务的请求(指定它需要侦听的事件),这次主要用到的是TME_HOVER和TME_LEAVE(侦听鼠标移开和移入事件)
4     HWND hwndTrack;//指定我们需要侦听的控件的句柄
5     DWORD dwHoverTime;//HOVER消耗的时间,可以用系统提供的一个常量HOVER_DEFAULT由系统默认给出,也可以自己填写,单位是毫秒
6 } TRACKMOUSEEVENT, *LPTRACKMOUSEEVENT;

在使用该函数时需要包含头文件commctrl.h和lib文件comctl32.lib

  解决了鼠标行为的检测之后,就是针对不同的鼠标行为重绘相应的按钮。重绘按钮需要在消息WM_DRAWITEM中,这个消息的处理是在相应控件的父窗口中实现的,而在一般情况下父窗口不会收到该消息,需要我们手工指定控件资源的属性为的OWNERDRAW为真,或者在创建相应的按钮窗口时将样式设置为BS_OWNERDRAW 。

设置完成后就可以在对应的父窗口处理函数中接收并处理WM_DRAWITEM,在该消息中重绘按钮

该消息中主要使用的参数是lpParam它里面包含的是一个指向DRAWITEMSTRUCT的结构体:

typedef struct tagDRAWITEMSTRUCT { 
  UINT CtlType; //控件类型
  UINT CtlID; //控件ID
  UINT itemID; //子菜单项的ID主要用于菜单
  UINT itemAction; //控件发出的动作,如ODA_SELECT表示控件被选中
  UINT itemState; //控件状态,这次需要用到的状态为ODS_SELECTED表示按钮被按下
  HWND hwndItem; //控件句柄
  HDC hDC; 
  RECT rcItem;//控件的矩形区域 
  ULONG_PTR itemData; 
} DRAWITEMSTRUCT; 
//该结构体中的一些成员需要根据控件类型赋值,同时结构体中的itemAction、itemState是可以由多个值通过位或组成在判断是否具有某种状态时需要使用位与运算

而绘制控件时我们可以使用函数DrawFrameControl,该函数可以根据指定的控件类型、控件所处的状态来绘制控件的样式,绘制出来的任然是系统的之前的标准样式,处理WM_DRAWITEN消息的具体代码如下:

LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam;
  
char szBuf[50];
GetWindowText(lpdis->hwndItem,szBuf,50);
if (ODT_BUTTON ==lpdis->CtlType)<BR>{
  UINTuState = DFCS_BUTTONPUSH;
  if(lpdis->itemState & ODS_SELECTED)
  {
    uState |= DFCS_PUSHED;
  }
  
  DrawFrameControl(lpdis->hDC,&(lpdis->rcItem),DFC_BUTTON,uState);
  SetTextColor(lpdis->hDC,RGB(255,0,0));
  DrawText(lpdis->hDC,szBuf,strlen(szBuf) + 1,&(lpdis->rcItem),DT_CENTER |
  DT_VCENTER | DT_SINGLELINE);
}

函数_TrackMouseEvent根据其检测的鼠标状态不同可以返回不同的消息,这次主要用的是WM_MOUSEHOVER(表示鼠标移动到按钮上)、WM_MOUSELEAVE(鼠标移出按钮),还需要注意的是这个函数每次检测完成返回后不会再次检测,需要我们自己主动调用函数检测鼠标状态,由于要多次调用,而每次调用都需要初始化所需要的结构体指针,所以我们封装一个函数专门用于调用_TrackMouseEvent:

void Track(HWND hWnd)
{
    TRACKMOUSEEVENT tme;
    tme.cbSize =sizeof(TRACKMOUSEEVENT);
    tme.dwFlags = TME_HOVER | TME_LEAVE;
    tme.dwHoverTime = 10;
    tme.hwndTrack = hWnd;
    _TrackMouseEvent(&tme);
}

 消息WM_MOUSEHOVER和消息WM_MOUSELEAVE的处理是在对应的窗口过程中处理的,而按钮的窗口过程由系统提供我们并不知道,所以只有使用子类化的方法在我们的窗口过程中处理这两个消息。在按钮创建后立马要检测鼠标所以可以按钮对应的父窗口完成创建后子类化,对于窗口可以在它的WM_CREATE消息中处理,对于对话框可以在WM_INITDIALOG消息中处理,子类化调用函数SetWindowLong:

g_OldProc = (LRESULT*)SetWindowLong(GetDlgItem(hDlg,IDC_BUTTON1),GWL_WNDPROC,(LONG)BtnProc);
return0;

 在新的窗口过程中处理消息,完成三态按钮:

switch(uMsg)
{
  caseWM_MOUSEMOVE:
    Track(hBtn);//当鼠标移动时检测
    break;
  caseWM_MOUSEHOVER:
  {
    charszBuf[50];
    RECT rtBtn;<BR>    GetClientRect(hBtn,&rtBtn);
    HDChDc = GetDC(hBtn);
    DrawFrameControl(hDc,&(rtBtn),DFC_BUTTON,DFCS_BUTTONPUSH);
    HBRUSHhBr = CreateSolidBrush(RGB(255,255,255));
    FillRect(hDc,&rtBtn,hBr);
    GetWindowText(hBtn,szBuf,50);
    SetBkMode(hDc,TRANSPARENT);
    DrawText(hDc,szBuf,strlen(szBuf),&rtBtn,DT_CENTER | DT_VCENTER | DT_SINGLELINE);
    ReleaseDC(hBtn,hDc);
  }
   break;
  caseWM_MOUSELEAVE:
  {
    charszBuf[50];
    RECT rtBtn;
    GetClientRect(hBtn,&rtBtn);
    HDChDc = GetDC(hBtn);
    DrawFrameControl(hDc,&(rtBtn),DFC_BUTTON,DFCS_BUTTONPUSH);
    GetWindowText(hBtn,szBuf,50);
    SetBkMode(hDc,TRANSPARENT);//设置字体背景为透明
    DrawText(hDc,szBuf,strlen(szBuf),&rtBtn,DT_CENTER | DT_VCENTER | DT_SINGLELINE);
    ReleaseDC(hBtn,hDc);
  }
    break;
  default:
    returnCallWindowProc((WNDPROC)g_OldProc,hBtn,uMsg,wParam, lParam);//在处理完我们感兴趣的消息后一定要记得将按钮的窗口过程还原
  }
return0;

 到这个地方为止,已经实现了三态按钮的基本样式,通过检测鼠标的位置设置按钮样式,上述代码只是改变了按钮的背景颜色和文字颜色,可能效果不好看。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏前端小叙

对事件委托绑定click的事件的解绑

大家都知道解绑事件的jquery写法,很简单: $("xxx").unbind("click"); 然后对于事件委托式的事件绑定,亲测,这种解绑方法是无效的, ...

2836
来自专栏非著名程序员

基础篇章:关于 React Native 之 ViewPagerAndroid 组件的讲解

今天我们来讲解一下关于 ViewPager 的使用,它是一个允许子视图左右滚动翻页的容器。而且每一个 ViewPagerAndroid 的子容器会被视作一个单独...

2225
来自专栏.Net移动开发

.Net语言 APP开发平台——Smobiler学习日志:Poplist控件的正确打开方式以及如何快速实现

PopList控件显示包括两种模式:展开模式和筛选分类模式;两种模式只能选其中一种

942
来自专栏前端知识分享

第89天:HTML5中 访问历史、全屏和网页存储API

history新增的两个方法history.replaceState()和history.pushState()方法属于HTML5浏览器新增的属性,所以IE9以...

1001
来自专栏葡萄城控件技术团队

Spread for Windows Forms高级主题(2)---理解单元格类型

理解单元格类型基本信息 Spread支持几十种单元格类型,如复选框单元格、日期时间单元格、或者一个简单的文本单元格。单元格类型可以对单独的单元格、列、行、一个单...

1908
来自专栏游戏杂谈

“穿透”层的鼠标事件

需要实现如下的效果,有一个浮动层,需要层级在它之下的一个元素也能照常响应相应的事件

1192
来自专栏技术小黑屋

Read Output From Shell

Python provides a lot of method to read output from a just executed shell. Howev...

1182
来自专栏Petrichor的专栏

Markdown 操作

  最开始是因为看到网上的教程写得很简洁漂亮,所以学的Markdown。个人感觉Markdown是一种学会了就回不去的语法,特别方便简洁。

2392
来自专栏老司机的简书

老司机读书笔记——Weex学习笔记

Weex整体上与Vue语法大概一直,基本用法由阿里进行二次封装。以下主要介绍Weex的一些内置组件。

2864
来自专栏HT

基于HT for Web矢量实现HTML5文件上传进度条

在HTML中,在文件上传的过程中,很多情况都是没有任何的提示,这在体验上很不好,用户都不知道到时有没有在上传、上传成功了没有,所以今天给大家介绍的内容是通过HT...

2039

扫码关注云+社区