首页
论坛
课程
招聘
雪    币: 1675
活跃值: 活跃值 (11)
能力值: ( LV15,RANK:670 )
在线值:
发帖
回帖
粉丝

[原创]Win32 编程进阶:打造自己的标准控件

2011-2-14 12:54 14294

[原创]Win32 编程进阶:打造自己的标准控件

2011-2-14 12:54
14294
Win32 编程进阶:打造自己的标准控件
作者:cntrump
前言
  Windows给我们提供了很多的标准控件,基本上够用的。但是有时候我们会对标准控件不满意,这时候就可以考虑自己编写控件。
  本教程的目的是编写一个出一个简单的标准控件,作用类似于网页上的超链接,除了可以接受Windows 常规消息还可以处理控件自定义的消息。
  程序运行的效果如下:

  
  鼠标点击之后就会打开在程序中所指定的链接。
准备工作:
  这个控件很简单,只要响应鼠标消息进行处理就可以了,在开始编码之前,先定义几个控件使用的消息和宏:
1. 控件可以设置文字的对齐方式:
// 文字的对齐方式,默认左对齐
// 左对齐
#define HLS_LEFT  DT_LEFT
// 居中对齐
#define HLS_CENTER  DT_CENTER
// 右对齐
#define HLS_RIGHT  DT_RIGHT

  
2. 还需要为控件设置超链接地址:
// 超链接控件可接收的消息
// 设置超链接
#define HLM_SETHYPERLINK  (WM_USER+0x0001)
// 获取超链接
#define HLM_GETHYPERLINK  (WM_USER+0x0002)
// 设置和获取超链接的宏
#define HyperLink_SetLink(hwnd, link) SendMessage(hwnd, HLM_SETHYPERLINK, 
0, (LPARAM)link)
#define HyperLink_GetLink(hwnd, link, length) SendMessage(hwnd, HLM_GETHYPERLINK, (WPARAM)length, (LPARAM)link)


开始编码:
1. 注册自己的控件类。
我把控件类名称定义为"HyperLinkCtrl",还要为窗口额外分配空间,这样才能进行更多的控制。
// 注册控件类
ATOM WINAPI RegisterHyperLinkCtrl(HINSTANCE hIns)
{
  WNDCLASSEX wndClass;
  ZeroMemory(&wndClass, sizeof(wndClass));
  wndClass.cbSize = sizeof(wndClass);
  wndClass.style = CS_PARENTDC|CS_GLOBALCLASS; // 使用全局类并和父窗口共享DC
  wndClass.lpszClassName = HyperLinkCtrlClassName;
  wndClass.hCursor = LoadCursor(NULL, IDC_HAND); // 手型鼠标
  wndClass.hInstance = hIns;
  wndClass.lpfnWndProc = (WNDPROC)CtrlProc; // 控件的消息处理过程
  wndClass.cbWndExtra = sizeof(INT*); // 为窗口分配额外内存,用来保存我们自己的指针.
  return RegisterClassEx(&wndClass);
}


2. 创建窗口
  成功注册窗口类之后就可以开始创建窗口了,创建窗口的过程和标准控件没有区别,为了方便使用,把它进行包装:
// 创建一个超链接控件
HWND WINAPI CreateHyperLink(LPCTSTR pszTitle, // 显示的文本
                            DWORD style, // 窗口风格
                            INT x, // x 坐标
                            INT y, // y 坐标
                            INT nWidth, // 宽度
                            INT nHeight, //高度
                            HWND hWndParent, // 父窗口句柄
                            UINT CtrlID) // 控件 ID
{
  return CreateWindow(HyperLinkCtrlClassName, 
                      pszTitle, 
                      WS_CHILD|style, // 必须是子窗口
                      x, 
                      y, 
                      nWidth, 
                      nHeight, 
                      hWndParent, 
                      (HMENU)CtrlID, 
                      NULL, 
                      0);
}


3. 处理控件窗口消息
控件窗口一旦创建成功,系统就会调用控件的消息处理过程,我们的这个控件只处理WM_PAINT和鼠标相关的几个消息和两个自定义消息,其他消息交给系统自动处理。
整个消息处理过程太占篇幅,只捡几个关键的代码片段,自定义的函数参见源文件:
WM_PAINT消息:
对文本的绘制都集中在这个消息里,是显示文字的关键:
  case WM_PAINT:
    {
      PAINTSTRUCT ps;
      HDC hDc = BeginPaint(hWnd, &ps);
      SetCtrlTextColor(hWnd, hDc, RGB(0,0,255)); // 自定义函数
      EndPaint(hWnd, &ps);
    }
    break;


WM_MOUSEMOVE,WM_MOUSEHOVER和WM_MOUSELEAVE:
在鼠标移动到控件上面的时候,会触发WM_MOUSEMOVE,在这个消息里对鼠标进行跟踪以获取鼠标的状态:
  case WM_MOUSEMOVE:
    {
      tms.cbSize = sizeof(tms);
      tms.hwndTrack = hWnd;
      tms.dwFlags = TME_HOVER|TME_LEAVE;
      tms.dwHoverTime = 10;
      TrackMouseEvent(&tms);
      SetCursor(LoadCursor(NULL, IDC_HAND)); 
    }
    break;
  case WM_MOUSEHOVER: // 鼠标在控件上面时颜色为红色
      SetCtrlTextColor(hWnd, NULL, RGB(255,0,0));
    break;
  case WM_MOUSELEAVE: // 鼠标离开时恢复原来的颜色
      SetCtrlTextColor(hWnd, NULL, RGB(0,0,255));
    break;


鼠标左键按下和弹起时,如果是对惯用左手的人还需要添加右键处理:
  case WM_LBUTTONDOWN:
    SetCursor(LoadCursor(NULL, IDC_HAND));
    break;
  case WM_LBUTTONUP: // 鼠标弹起时打开链接
    {
      TCHAR *text = (TCHAR*)GetWindowLong(hWnd, 0);
      SetCursor(LoadCursor(NULL, IDC_HAND));
      if (text == NULL)
        break;      
      ShellExecute(hWnd, _T("OPEN"), text, NULL, NULL, SW_SHOW);
    }


最后是自定义消息:设置超链接和获取超链接
HLM_SETHYPERLINK 和HLM_GETHYPERLINK
  case HLM_SETHYPERLINK:
    {
      int length = lstrlen((LPCTSTR)lParam);
      TCHAR *text = (TCHAR *)GetWindowLong(hWnd, 0);
      
      if (text != NULL)
        delete []text;

      text = new TCHAR[length+1];
      lstrcpy(text, (TCHAR*)lParam);
      text[length] = TCHAR(0);
      SetWindowLong(hWnd, 0, (LONG)text);
    }
    break;
  case HLM_GETHYPERLINK:
    {
      int length = 0;
      TCHAR *text = (TCHAR*)GetWindowLong(hWnd, 0);

      if (text == NULL)
        return 0;

      length = lstrlen(text);
      lstrcpyn((TCHAR*)lParam, text, min(length, (int)wParam));
      return min(length, (int)wParam);
    }


基本上整个控件就完成了,为了能在对话框程序中方便使用,我还定义了一个将控件子类化为超链接的函数:
// 子类化控件,在设计对话框程序时方便可视化调整
LONG WINAPI SubclassHyperLink(HWND hwnd)
{
  assert(hwnd);

  if (!SetWindowLong(hwnd, 0, 0))
    SetClassLong(hwnd, GCL_CBWNDEXTRA, sizeof(INT*));

  SetWindowLong(hwnd, 0, 0);

  return SetWindowLong(hwnd, GWL_WNDPROC, (LONG)CtrlProc);
}


如何使用?
1. 在WinMain函数里或者初始化函数里注册控件类:
RegisterHyperLinkCtrl(hInstance);
2. 再创建我们的自定义控件并设置超链接:
HWND hLink = CreateHyperLink(_T("Google 首页"), HLS_CENTER, 30, 80, 200, 20, *this, 0);
           HyperLink_SetLink(hLink, _T("http://www.google.com"));
3. 如果是在对话框中使用,计算坐标比较麻烦,可以采用子类化的方式:
SubclassHyperLink(GetDlgItem(ID_APP_ABOUT));
HyperLink_SetLink(GetDlgItem(ID_APP_ABOUT), _T("http://www.sina.com.cn"));
小结
整个控件我是直接写在源文件里实现的,如果是C语言的话可以直接包含进项目就可以使用了,但是如果是用在汇编语言中,则需要将控件编译成静态库或动态库,
使用前先调用RegisterHyperLinkCtrl 注册控件类。
欢迎提出建议或意见:cntrump@gmail.com

HWS计划·2020安全精英夏令营来了!我们在华为松山湖欧洲小镇等你

上传的附件:
最新回复 (21)
雪    币: 1675
活跃值: 活跃值 (11)
能力值: ( LV15,RANK:670 )
在线值:
发帖
回帖
粉丝
cntrump 活跃值 13 2011-2-14 12:56
2
0
Win32 编程进阶:打造自己的标准控件 后续
作者:cntrump
     上一章的教程中已经创建一个独立功能的控件,但是这个控件是封闭的,在父窗口的消息循环中接收不了控件的消息。如果只是单独地打开一个网页链接的话这是没有什么问题的,但如果需要像按钮一样单击之后进行其他处理就不行。
     本章教程目的就是解决这个问题,让自定义的控件能像标准控件一样在父窗口的 WM_COMMAND消息中对其进行响应。
     要解决这个问题很简单,只需要在控件相应的消息中把消息发给其父窗口就可以了。
     关键的消息有两个:WM_COMMAND和WM_NOTIFY。
     由于我们的这个控件很简单,WM_COMMAND消息就可以完全处理了,所以WM_NOTIFY通知不用理会。
     需要修改的地方是鼠标按下和弹起,还有转发WM_COMMAND消息给父窗口。
     修改后的地方:
  case WM_LBUTTONDOWN:
    SetCursor(LoadCursor(NULL, IDC_HAND));
    wParam = GetDlgCtrlID(hWnd);
    SendMessage(GetParent(hWnd), WM_COMMAND, wParam, (LPARAM)hWnd);
    break;
  case WM_LBUTTONUP:
    {
      SetCursor(LoadCursor(NULL, IDC_HAND));
      wParam = GetDlgCtrlID(hWnd);
      SendMessage(GetParent(hWnd), WM_COMMAND, wParam, (LPARAM)hWnd);
    }
break;
case WM_COMMAND:
    SendMessage(GetParent(hWnd), WM_COMMAND, wParam, lParam);
    break;


     这样在父窗口就可以进行处理了,当子类化按钮时也不会改变按钮的点击行为了。
上传的附件:
雪    币: 280
活跃值: 活跃值 (10)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
haithink 活跃值 1 2011-2-14 13:09
3
0
我以前倒是没想这么多。
雪    币: 25
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ffff七四 活跃值 2011-2-14 13:32
4
0
大牛啊,还在努力的啃汇编跟C,不知道什么时候才能到这种程度...
雪    币: 191
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
atlantiscc 活跃值 2011-2-15 18:38
5
0
谢谢楼主分享,mark~!
雪    币: 147
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
majinxin 活跃值 2011-2-15 22:58
6
0
cntrump是猛男啊是猛男
雪    币: 522
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
ddsoft 活跃值 2011-2-15 23:41
7
0
这个微软已经提供了吧。。CMFCXXXX后面是啥不记得了。。。
雪    币: 522
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
ddsoft 活跃值 2011-2-15 23:41
8
0
楼主编程水平很高。学习。
雪    币: 270
活跃值: 活跃值 (18)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
blueapplez 活跃值 14 2011-2-16 09:25
9
0
膜拜大牛~ 学习。。。
雪    币: 49
活跃值: 活跃值 (13)
能力值: ( LV15,RANK:520 )
在线值:
发帖
回帖
粉丝
邓韬 活跃值 9 2011-2-16 13:34
10
0
谢谢啊,123212
雪    币: 2190
活跃值: 活跃值 (40)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
sisess 活跃值 1 2011-2-16 15:44
11
0
换皮肤原理是不是也差不多?
雪    币: 377
活跃值: 活跃值 (17)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
blackwhite 活跃值 1 2011-2-16 16:04
12
0
case WM_LBUTTONUP: // 鼠标弹起时打开链接
    {
      TCHAR *text = (TCHAR*)GetWindowLong(hWnd, 0);
      SetCursor(LoadCursor(NULL, IDC_HAND));
      if (text == NULL)
        break;      
      ShellExecute(hWnd, _T("OPEN"), text, NULL, NULL, SW_SHOW);
    }

;这个地方应该再判断一下是否是在该控件上点击后再弹起鼠标左键的,否则在非控件区域点按下鼠标左键后,再移动到超链接控件,再弹起鼠标左键,这样也会执行以上流程,打开网址~
雪    币: 1675
活跃值: 活跃值 (11)
能力值: ( LV15,RANK:670 )
在线值:
发帖
回帖
粉丝
cntrump 活跃值 13 2011-2-16 18:58
13
0
http://www.cppblog.com/alantop/archive/2009/11/08/100410.html
可能是这个,MS提供的都是高级货。俺们只能山寨个能用的。
雪    币: 1675
活跃值: 活跃值 (11)
能力值: ( LV15,RANK:670 )
在线值:
发帖
回帖
粉丝
cntrump 活跃值 13 2011-2-16 18:59
14
0
对,这是个问题,应该在控件的鼠标左键被按下时捕捉鼠标。
雪    币: 346
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
YwdxY 活跃值 2011-2-16 20:15
15
0
学习下,又来马克了
雪    币: 201
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
张bing 活跃值 2011-2-17 00:31
16
0
绝对牛人啊,膜拜
雪    币: 39
活跃值: 活跃值 (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
jiangming 活跃值 2011-2-17 01:24
17
0
case WM_LBUTTONUP:
{
    if(GetCaptured() == hWnd)
      {
          TCHAR *text = (TCHAR*)GetWindowLong(hWnd, 0);
          SetCursor(LoadCursor(NULL, IDC_HAND));
            ..
       }
}
break;
雪    币: 362
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lapcca 活跃值 2011-2-17 16:24
18
0
学习了。。。。
雪    币: 230
活跃值: 活跃值 (10)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
winnip 活跃值 1 2011-2-18 12:40
19
0
不错啊。。。。。。。。。。学习下
雪    币: 377
活跃值: 活跃值 (17)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
blackwhite 活跃值 1 2011-2-23 11:31
20
0
看了下windows标准控件BUTTON的实现方式,是先在WM_LBUTTONDOWN的时候调用:SetCapture,这样的话所有鼠标输入都会发送给BUTTON按钮处理,直到收到WM_LBUTTONUP后。
雪    币: 1675
活跃值: 活跃值 (11)
能力值: ( LV15,RANK:670 )
在线值:
发帖
回帖
粉丝
cntrump 活跃值 13 2011-2-23 12:50
21
0
鼠标移出的时候也要处理,否则...
我在二楼添加了修正后的附件。
雪    币: 206
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
shyandsy 活跃值 2011-6-9 17:39
22
0
不胜感激 正头疼呢 找了一下午 就这一篇由价值
游客
登录 | 注册 方可回帖
返回