之前有学MFC的同学告诉我觉得Windows的控件重绘难以理解,就算重绘成功了还是有些地方不明白,我觉得可能很多人都有这样的问题,在这里我从Windows窗体的最基本原理来讲解,如果你有类似的疑惑希望这篇文章可以帮你解惑。

1.Windows窗体原理

首先,如果看过Win32 SDK编程的都知道Windows的三大核心系统:负责窗口对象产生和消息分发的USER模块,负责图像显示绘制的GDI模块,负责内存、进程、IO管理的KERNEL模块。试想象一下如何在一个像素阵列上产生窗口对象,其实就是使用GDI绘制窗口,不停的以一定的频率刷新显示在屏幕上,这就是图形界面,如果由在DOS或Windows DOS模拟器下编写图形界面的经验这个比较好理解。所以说其实USER模块中的窗口产生是依靠GDI模块的(包括菜单、滚动条等都是使用GDI来绘制的)。

那么,下面我们就从USER模块和GDI模块来说说Windows 的窗体原理。

如果接触过Win32 SDK编程的知道一个标准Windows窗体的产生过程:设计窗口类、注册窗口类、创建窗口、显示窗口、启动消息循环泵循环获取消息分发到窗体过程函数处理。为了保证博客的连贯性,在这里我贴上一个标准Windows窗体的产生代码。

  1. #include <windows.h>
  2. LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
  3. int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
  4. {
  5. static TCHAR szAppName[] = TEXT ("窗口类名称");
  6. HWND         hwnd;
  7. MSG          msg;
  8. WNDCLASSEX   wndclassex = {0};
  9. //设计窗口类
  10. wndclassex.cbSize        = sizeof(WNDCLASSEX);
  11. wndclassex.style         = CS_HREDRAW | CS_VREDRAW;
  12. wndclassex.lpfnWndProc   = WndProc;
  13. wndclassex.cbClsExtra    = 0;
  14. wndclassex.cbWndExtra    = 0;
  15. wndclassex.hInstance     = hInstance;
  16. wndclassex.hIcon         = LoadIcon (NULL, IDI_APPLICATION);
  17. wndclassex.hCursor       = LoadCursor (NULL, IDC_ARROW);
  18. wndclassex.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
  19. wndclassex.lpszMenuName  = NULL;
  20. wndclassex.lpszClassName = szAppName;
  21. wndclassex.hIconSm       = wndclassex.hIcon;
  22. //注册窗口类
  23. if (!RegisterClassEx (&wndclassex))
  24. {
  25. MessageBox (NULL, TEXT ("RegisterClassEx failed!"), szAppName, MB_ICONERROR);
  26. return 0;
  27. }
  28. //产生窗口
  29. hwnd = CreateWindowEx (WS_EX_OVERLAPPEDWINDOW,
  30. szAppName,
  31. TEXT ("窗口名称"),
  32. WS_OVERLAPPEDWINDOW,
  33. CW_USEDEFAULT,
  34. CW_USEDEFAULT,
  35. CW_USEDEFAULT,
  36. CW_USEDEFAULT,
  37. NULL,
  38. NULL,
  39. hInstance,
  40. NULL);
  41. //显示窗口
  42. ShowWindow (hwnd, iCmdShow);
  43. UpdateWindow (hwnd);
  44. //启动消息循环泵循环获取消息分配到窗体过程函数处理
  45. while (GetMessage (&msg, NULL, 0, 0))
  46. {
  47. TranslateMessage (&msg);
  48. DispatchMessage (&msg);
  49. }
  50. return msg.wParam;
  51. }
  52. //窗体过程函数
  53. LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  54. {
  55. HDC hdc;
  56. PAINTSTRUCT ps;
  57. switch (message)
  58. {
  59. case WM_CREATE:
  60. return (0);
  61. case WM_PAINT:
  62. hdc = BeginPaint (hwnd, &ps);
  63. EndPaint (hwnd, &ps);
  64. return (0);
  65. case WM_DESTROY:
  66. PostQuitMessage (0);
  67. return (0);
  68. }
  69. return DefWindowProc (hwnd, message, wParam, lParam);
  70. }

需要明白的是,所有Windows的窗体及控件归根结底都是使用CreateWindow或CreateWindowEx来创建的,他们都需要标准Windows窗体的产生过程。

普通的窗体好理解,主要需要弄清楚是对话框及控件的产生和消息分派处理流程。

对话框及其子控件的管理依靠Windows内建的对话框管理器,对话框管理器的工作包括:

1.根据我们在资源设计器中设计的对话框及子控件产生的.rc文件来自动生成对话框和子控件(如果有手动编写.rc文件的经历的话,知道编写RC文件其实就是指定窗口和子控件大小、类型、样式等参数,对话框管理器将这些参数传入CreateWindow函数产生窗体)

2.模态对话框直接显示窗体,非模态对话框消息指明WS_VISIBLE属性的话,需要调用ShowWindow来显示窗体。

4.维护一个消息循环泵,对于模态对话框来说这个消息泵的消息不经过父窗口,所以表现为模态;对于非模态对话框这个消息泵消息经过主窗口,必须由主窗口传给非模态对话框,表现为非模态。

3.维护一个内建的窗体过程函数,对于对话框来说会处理对话框的关闭打开及子窗口的焦点、tab等,对于子控件也是一样,每个子控件会有自己类型的窗体过程函数,窗体过程函数处理子控件的获得或失去焦点、按下或弹起、创建等表现样式和行为。对于对话框来说,他会开放一个对话框过程函数,让部分消息先通过对话框管理函数处理,如果对话框过程函数不处理才交给默认的内建过程函数处理,对于子控件来说,他们并没有开放过程函数,而是由内建窗体函数将要处理的消息发给父窗口处理。

那么对话框管理器完成了标准Windows窗体的产生中后半部分工作,至于设计窗口类和注册窗口类这是由Windows自己预先做好了的,如常见的“button”、“listbox”、“edit”类等等。

一个简要的示意图如下

那么既然所有的窗体(包括对话框和控件)产生过程一样,那么我们就可以将对话框管理器的部分工作替换掉:

1.不使用对话框读取.rc模板的方式,直接将参数传递给CreateWindow函数来创建对话框和控件,这就是常见的动态创建控件原理。

2.设置控件自绘制如BS_OWNDRAW属性,开放控件的WM_DRAWITEM消息给父窗口,由父窗口来绘制按钮样式,这就是常见的控件重绘原理。

3.替换掉内建的窗体函数,将消息传到自定义的窗体过程函数处理,这就是常见的控件子类化原理。

下面,为了做演示,先用通用模板创建的方式创建一个模态对话框和其子控件,然后模板创建一个非模态对话框,在非模态对话框中使用动态创建的方式创建和模态对话框中模板创建一样的按钮(当然位置和大小等可能不一样,这里只是为了说明原理故笔者并没有去管这些细节,如果你愿意完全可以把它们做的一模一样)。

代码太长,这里只贴出部分代码,详细代码请下载演示文件

主窗口消息泵

  1. while (GetMessage (&msg, NULL, 0, 0))
  2. {
  3. //注意非模态对话框消息由主窗口分发
  4. if (hDlgModeless == NULL || !IsDialogMessage(hDlgModeless, &msg))
  5. {
  6. TranslateMessage (&msg);
  7. DispatchMessage (&msg);
  8. }
  9. }

主窗口菜单响应

  1. case IDM_TEMPLATE:
  2. DialogBox(GetWindowLong(hwnd, GWL_HINSTANCE),
  3. IDD_TEMPLATE,
  4. hwnd,
  5. TemplateProc);
  6. break;
  7. case IDM_CREATE:
  8. hDlgModeless = CreateDialog(GetWindowLong(hwnd, GWL_HINSTANCE),
  9. MAKEINTRESOURCE(IDD_CREATE),
  10. hwnd,
  11. CreateProc);
  12. ShowWindow(hDlgModeless, SW_NORMAL);//注意非模态对话框不指明WS_VISIBLE属性必须显示调用ShowWindow来显示
  13. break;

模板创建的模态对话框对话框过程函数

  1. BOOL CALLBACK  TemplateProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
  2. {
  3. switch(message)
  4. {
  5. case WM_CLOSE:
  6. {
  7. EndDialog(hDlg,0);
  8. }
  9. return (TRUE);
  10. case WM_COMMAND:
  11. switch (LOWORD(wParam))
  12. {
  13. case IDCANCEL:
  14. {
  15. SendMessage(hDlg, WM_CLOSE, 0, 0);
  16. }
  17. return (TRUE);
  18. case IDOK:
  19. {
  20. }
  21. return (TRUE);
  22. }
  23. return (FALSE);
  24. }
  25. return (FALSE);
  26. }

模板创建的非模态对话框的对话框过程函数

  1. BOOL CALLBACK  CreateProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
  2. {
  3. switch(message)
  4. {
  5. case WM_INITDIALOG:
  6. {
  7. //动态创建控件子窗口
  8. CreateWindow(TEXT("button"),
  9. TEXT("确定"),
  10. WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
  11. 10, 10,
  12. 100, 50,
  13. hDlg,
  14. (HMENU)IDOK,
  15. GetWindowLong(hDlg, GWL_HINSTANCE),
  16. NULL);
  17. CreateWindow(TEXT("button"),
  18. TEXT("取消"),
  19. WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
  20. 10, 100,
  21. 100, 50,
  22. hDlg,
  23. (HMENU)IDCANCEL,
  24. GetWindowLong(hDlg, GWL_HINSTANCE),
  25. NULL);
  26. }
  27. return (TRUE);
  28. case WM_CLOSE:
  29. DestroyWindow(hDlg);
  30. hDlgModeless = NULL;//注意及时将指针置0防止窗口销毁后消窗口分发消息
  31. return (TRUE);
  32. case WM_COMMAND:
  33. switch (LOWORD(wParam))
  34. {
  35. case IDCANCEL:
  36. {
  37. SendMessage(hDlg, WM_CLOSE, 0, 0);
  38. }
  39. return (TRUE);
  40. case IDOK:
  41. {
  42. }
  43. return (TRUE);
  44. }
  45. return (FALSE);
  46. }

创建效果

模态对话框

非模态对话框

二者起到了相同的作用,动态创建比模板创建要灵活的多,这个深入学习请自行查找相关资料。上例中需要注意的模态对话框和非模态对话框,前者的消息不流经主窗口消息泵,后者的消息要先流经主窗口消息泵。

2.控件重绘(WM_DRAWITEM)

写这篇博文的初衷就是讲解控件重绘原理,自然不能少了这一内容,在刚刚提到了修改对话框管理器的行为的几种方式,后两种(开放WM_DRAWITEM消息和控件子类化)都是常用的控件重绘技巧,在这一节先讲WM_DRAWITEM消息重绘,下一节讲控件子类化重绘,都是以按钮的重绘为例来讲解。
WM_DRAWITEM顾名思义当控件需要重绘的时候发给主窗口的消息,一般在按钮按下或弹起、获得焦点或失去焦点、创建等时候会产生这一消息,默认是不开启重绘消息的,如果使用模板创建按钮必须在按钮属性中设置OwnDraw属性为True,如果动态创建按钮必须加上BS_OWNDRAW这一属性。
下面我要重绘两个个按钮,按钮是模板创建的,是默认的IDOK和IDCANCEL按钮,希望达到的效果是
按钮普通状态分别为
按钮获得焦点分别为
按钮按下状态分别为
下面先贴出绘制部分代码,再讲解
  1. BOOL CALLBACK SelfDrawProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
  2. {
  3. LPDRAWITEMSTRUCT pdis;
  4. HDC hdc;
  5. HDC hMemDc;
  6. static HINSTANCE hInstance;
  7. static HBITMAP hBitmapOK_D;
  8. static HBITMAP hBitmapOK_U;
  9. static HBITMAP hBitmapOK_F;
  10. static HBITMAP hBitmapCANCEL_D;
  11. static HBITMAP hBitmapCANCEL_U;
  12. static HBITMAP hBitmapCANCEL_F;
  13. static HWND    hwndOk;
  14. static HWND    hwndCanel;
  15. static BITMAP  bm;
  16. switch(message)
  17. {
  18. case WM_INITDIALOG:
  19. {
  20. hInstance = GetWindowLong(hDlg, GWL_HINSTANCE);
  21. hwndOk = GetDlgItem(hDlg, IDOK);
  22. hwndCanel = GetDlgItem(hDlg, IDCANCEL);
  23. hBitmapOK_D = LoadBitmap(hInstance, TEXT("image1d"));
  24. hBitmapOK_U = LoadBitmap(hInstance, TEXT("image1u"));
  25. hBitmapOK_F = LoadBitmap(hInstance, TEXT("image1f"));
  26. hBitmapCANCEL_D = LoadBitmap(hInstance, TEXT("image2d"));
  27. hBitmapCANCEL_U = LoadBitmap(hInstance, TEXT("image2u"));
  28. hBitmapCANCEL_F = LoadBitmap(hInstance, TEXT("image2f"));
  29. GetObject(hBitmapCANCEL_D, sizeof(BITMAP), (PTSTR)&bm);
  30. //调整按钮大小和最大图片一样大
  31. SetWindowPos(hwndOk, HWND_TOPMOST, 0, 0, bm.bmWidth, bm.bmHeight, SWP_NOZORDER | SWP_NOMOVE);
  32. SetWindowPos(hwndCanel, HWND_TOPMOST, 0, 0,  bm.bmWidth, bm.bmHeight, SWP_NOZORDER | SWP_NOMOVE);
  33. }
  34. return (TRUE);
  35. case WM_CLOSE:
  36. {
  37. EndDialog(hDlg,0);
  38. }
  39. return (TRUE);
  40. case WM_COMMAND:
  41. switch (LOWORD(wParam))
  42. {
  43. case IDCANCEL:
  44. {
  45. SendMessage(hDlg, WM_CLOSE, 0, 0);
  46. }
  47. return (TRUE);
  48. case IDOK:
  49. {
  50. }
  51. return (TRUE);
  52. }
  53. return (FALSE);
  54. //自绘制按钮
  55. case WM_DRAWITEM:
  56. //获得绘制结构体,包含绘制的按钮DC和当前按钮状态等
  57. pdis = (LPDRAWITEMSTRUCT)lParam;
  58. if (pdis->CtlType == ODT_BUTTON)//只绘制button类型
  59. {
  60. hdc = pdis->hDC;
  61. SaveDC(hdc);//保存DC,绘制完必须恢复默认
  62. //绘制默认状态
  63. hMemDc = CreateCompatibleDC(hdc);
  64. SelectObject(hMemDc, pdis->CtlID == IDOK ? hBitmapOK_U : hBitmapCANCEL_U);
  65. BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hMemDc, 0, 0, SRCCOPY);
  66. DeleteDC(hMemDc);
  67. //绘制获取焦点时状态
  68. if (pdis->itemState & ODS_FOCUS)
  69. {
  70. hMemDc = CreateCompatibleDC(hdc);
  71. SelectObject(hMemDc, pdis->CtlID == IDOK ? hBitmapOK_F : hBitmapCANCEL_F);
  72. BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hMemDc, 0, 0, SRCCOPY);
  73. DeleteDC(hMemDc);
  74. }
  75. //绘制下压状态
  76. if (pdis->itemState & ODS_SELECTED)
  77. {
  78. hMemDc = CreateCompatibleDC(hdc);
  79. SelectObject(hMemDc, pdis->CtlID == IDOK ? hBitmapOK_D : hBitmapCANCEL_D);
  80. BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hMemDc, 0, 0, SRCCOPY);
  81. DeleteDC(hMemDc);
  82. }
  83. RestoreDC(hdc, -1);
  84. }
  85. return (TRUE);
  86. }
  87. return (FALSE);
  88. }

在WM_INITDIALOG函数中加载相关资源和设置按钮大小

在WM_DRAWITEM完成主要绘制工作,获得WM_DRAWITEM消息时获得绘制的结构体,这个结构体包括当前要绘制的按钮的ID、状态等,我们主要的工作就是将对应状态的按钮贴上相应的位图即可。
效果如下
WM_DRAWITEM消息控件重绘是最常用的重绘技巧,在网上常见的别人封装好的自定义控件都是这样的原理。

3.控件重绘(控件子类化)

子类化是借鉴C++的面向对象中的继承和重载的思想,基本意思就是如果子类对消息处理了的话对应C++的重载,这时候父类就没办法再处理这个消息,除非人为的将消息传递给父类,所有的消息先流经子类再到父类,当然这一过程需要子类的配合,具体意思我们用代码来说明。

同样是达到上一节WM_DRAWITEM绘制的按钮效果
我们用控件子类化完成这一效果,贴出部分代码,完整代码请下载演示文件
对话框过程函数
  1. WNDPROC btnOkOldProc, btnCancelOldProc;
  2. BOOL CALLBACK SubclassProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
  3. {
  4. static HWND    hwndOk;
  5. static HWND    hwndCanel;
  6. switch(message)
  7. {
  8. case WM_INITDIALOG:
  9. {
  10. hwndOk = GetDlgItem(hDlg, IDOK);
  11. hwndCanel = GetDlgItem(hDlg, IDCANCEL);
  12. //窗口子类化
  13. btnOkOldProc = SetWindowLong(hwndOk, GWL_WNDPROC, (LONG)BtnProc);
  14. btnCancelOldProc = SetWindowLong(hwndCanel, GWL_WNDPROC, (LONG)BtnProc);
  15. }
  16. return (TRUE);
  17. case WM_CLOSE:
  18. {
  19. EndDialog(hDlg,0);
  20. }
  21. return (TRUE);
  22. case WM_COMMAND:
  23. switch (LOWORD(wParam))
  24. {
  25. case IDCANCEL:
  26. {
  27. SendMessage(hDlg, WM_CLOSE, 0, 0);
  28. }
  29. return (TRUE);
  30. case IDOK:
  31. {
  32. }
  33. return (TRUE);
  34. }
  35. return (FALSE);
  36. }
  37. return (FALSE);
  38. }
按钮过程函数(子类)
  1. typedef enum tagBUTTONSTATE
  2. {
  3. BTNSTATE_DEFAULT=0,
  4. BTNSTATE_FOCUS,
  5. BTNSTATE_SELECTED
  6. }BUTTONSTATE;
  7. LRESULT CALLBACK  BtnProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
  8. {
  9. HDC hdc;
  10. HDC hMemDc;
  11. PAINTSTRUCT ps;
  12. static int      id;
  13. static HINSTANCE hInstance;
  14. static HBITMAP hBitmapOK_D;
  15. static HBITMAP hBitmapOK_U;
  16. static HBITMAP hBitmapOK_F;
  17. static HBITMAP hBitmapCANCEL_D;
  18. static HBITMAP hBitmapCANCEL_U;
  19. static HBITMAP hBitmapCANCEL_F;
  20. static BITMAP  bm;
  21. static BOOL    bOnce = TRUE;
  22. static BUTTONSTATE btnOkState=BTNSTATE_FOCUS;
  23. static BUTTONSTATE btnCancelState=BTNSTATE_DEFAULT;
  24. id = GetWindowLong(hwnd, GWL_ID);
  25. //初次进入函数加载资源,模拟WM_CREATE
  26. if (TRUE == bOnce)
  27. {
  28. hInstance = GetWindowLong(hwnd, GWL_HINSTANCE);
  29. hBitmapOK_D = LoadBitmap(hInstance, TEXT("image1d"));
  30. hBitmapOK_U = LoadBitmap(hInstance, TEXT("image1u"));
  31. hBitmapOK_F = LoadBitmap(hInstance, TEXT("image1f"));
  32. hBitmapCANCEL_D = LoadBitmap(hInstance, TEXT("image2d"));
  33. hBitmapCANCEL_U = LoadBitmap(hInstance, TEXT("image2u"));
  34. hBitmapCANCEL_F = LoadBitmap(hInstance, TEXT("image2f"));
  35. GetObject(hBitmapCANCEL_D, sizeof(BITMAP), (PTSTR)&bm);
  36. SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, bm.bmWidth, bm.bmHeight, SWP_NOZORDER | SWP_NOMOVE);
  37. bOnce = FALSE;
  38. }
  39. switch (message)
  40. {
  41. case WM_CREATE:
  42. //注意这个消息不会进入
  43. return (0);
  44. case WM_PAINT:
  45. hdc = BeginPaint (hwnd, &ps);
  46. hMemDc = CreateCompatibleDC(hdc);
  47. //绘制不同状态下的按钮样式
  48. if (btnOkState == BTNSTATE_DEFAULT && id == IDOK)
  49. {
  50. SelectObject(hMemDc, hBitmapOK_U);
  51. }
  52. if(btnCancelState == BTNSTATE_DEFAULT && id==IDCANCEL)
  53. {
  54. SelectObject(hMemDc, hBitmapCANCEL_U);
  55. }
  56. if (btnOkState == BTNSTATE_FOCUS && id==IDOK)
  57. {
  58. SelectObject(hMemDc, hBitmapOK_F);
  59. }
  60. if(btnCancelState == BTNSTATE_FOCUS && id==IDCANCEL)
  61. {
  62. SelectObject(hMemDc, hBitmapCANCEL_F);
  63. }
  64. if (btnOkState == BTNSTATE_SELECTED && id==IDOK)
  65. {
  66. SelectObject(hMemDc, hBitmapOK_D);
  67. }
  68. if(btnCancelState == BTNSTATE_SELECTED && id==IDCANCEL)
  69. {
  70. SelectObject(hMemDc, hBitmapCANCEL_D);
  71. }
  72. BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hMemDc, 0, 0, SRCCOPY);
  73. DeleteDC(hMemDc);
  74. EndPaint (hwnd, &ps);
  75. return (0);
  76. case WM_SETFOCUS:
  77. if (id==IDOK)
  78. {
  79. btnOkState = BTNSTATE_FOCUS;
  80. }
  81. else
  82. {
  83. btnCancelState = BTNSTATE_FOCUS;
  84. }
  85. return (0);
  86. case WM_KILLFOCUS:
  87. if (id==IDOK)
  88. {
  89. btnOkState = BTNSTATE_DEFAULT;
  90. }
  91. else
  92. {
  93. btnCancelState = BTNSTATE_DEFAULT;
  94. }
  95. return (0);
  96. case WM_KEYDOWN:
  97. if (wParam == VK_SPACE)
  98. {
  99. if (id==IDOK)
  100. {
  101. btnOkState = BTNSTATE_SELECTED;
  102. }
  103. else
  104. {
  105. btnCancelState = BTNSTATE_SELECTED;
  106. }
  107. InvalidateRect(hwnd, NULL, TRUE);
  108. }
  109. return (0);
  110. case WM_KEYUP:
  111. if (wParam == VK_SPACE)
  112. {
  113. if (id==IDOK)
  114. {
  115. btnOkState = BTNSTATE_FOCUS;
  116. }
  117. else
  118. {
  119. btnCancelState = BTNSTATE_FOCUS;
  120. }
  121. InvalidateRect(hwnd, NULL, TRUE);
  122. }
  123. return (0);
  124. case WM_LBUTTONDOWN:
  125. SetCapture(hwnd);
  126. if (id==IDOK)
  127. {
  128. btnOkState = BTNSTATE_SELECTED;
  129. }
  130. else
  131. {
  132. btnCancelState = BTNSTATE_SELECTED;
  133. }
  134. InvalidateRect(hwnd, NULL, TRUE);
  135. return (0);
  136. case WM_LBUTTONUP:
  137. if (id==IDOK)
  138. {
  139. btnOkState = BTNSTATE_FOCUS;
  140. }
  141. else
  142. {
  143. btnCancelState = BTNSTATE_FOCUS;
  144. }
  145. InvalidateRect(hwnd, NULL, TRUE);
  146. ReleaseCapture();
  147. return (0);
  148. case WM_DESTROY:
  149. DestroyWindow(hwnd);
  150. return (0);
  151. }
  152. return CallWindowProc (id == IDOK ? btnOkOldProc : btnCancelOldProc, hwnd, message, wParam, lParam);
  153. }

在以上代码,我们在对话框的WM_INITDIALOG消息中强制换掉按钮原有的内建窗体过程函数,使用我们自己的BtnProc过程函数。需要注意的是在我们换掉
按钮原有的内建窗体过程函数的时候按钮已经创建完成,所以如果我们在BtnProc的WM_CREATE设置断点,程序是不会进入的。和WM_DRAWITEM一样,我们需要按钮的不同状态时绘制,因为我们采用自己的BtnProc过程函数,所以我们只能自己来维护按钮的状态,在WM_PAINT函数中根据不同状态绘制不同样式的按钮,在其他消息中处理按钮的按下或弹起、获得焦点、或失去焦点等状态转变。

创建效果如下
我们基本上模拟了WM_DRAWITEM消息重绘效果:按Tab键切换焦点,按Space键按钮按下弹起(当然只是为了演示原理,会有一些Bug,你可以想办法完善他们)。在上诉代码中,我们在最后调用了原来的内建的窗体过程函数,我们处理了WM_PAINT、WM_KEYUP、WM_KEYDOWN等消息,这些消息都return

(0)直接返回了,即内建的窗体过程函数没有机会处理这些消息,其他的子类没有处理的消息都传给原来内建的窗体过程函数处理了,如果我们想原来的内建窗体过程函数也处理WM_PAINT,那么将return

(0)改成break即可。这就是我上面提到的子类化的实现必须依靠子类化窗体函数的配合,我们也可以将所有的消息都在子类中处理不回传给原来的内建窗口,但是这样的工作量太大,一般是不会这样做的。

另外,可以看到相比于WM_DRAWITEM消息重绘,子类化实现控件重绘工作量要大得多,当然这样的灵活性要更大。实际上,微软提供子类化的作用更多是为了重新定制子控件的行为,比如说要将一组相同按钮按下时发送一个自定义消息,这时候就可以将这些按钮的消息子类化都先流经一个子类化窗体过程函数,然后再调用内建的窗体过程函数。
总结来说,一般重绘控件样式使用WM_DRAWITEM消息,重新定制控件行为使用窗体子类化。
博客完整演示代码下载链接
毕竟现在来说如果不是为了实现一个自绘控件库的话,不会使用SDK自绘的方式,下一次我会讲一下MFC中自绘方式的具体流程和实例。

原创,转载请注明来自http://blog.csdn.net/wenzhou1219

深入Windows窗体原理及控件重绘技巧的更多相关文章

  1. Windows窗体原理及控件WM_DRAWITEM和子类化重绘演示文件

    http://download.csdn.net/detail/wenzhou1219/6783959

  2. ActiveX 控件重绘无效问题,用CClientDC 而不是CPaintDC

    ActiveX 控件重绘子控件时,用Invalid()会出现无效的情况即不会更新界面. OnPaint 方法里,是用的 CPaintDC,经测试无效,后换CClientDC,发现可以,百度查他们的区别 ...

  3. VC++中关于控件重绘函数/消息 OnPaint,OnDraw,OnDrawItem,DrawItem的区别

    而OnPaint()是CWnd的类成员,同时负责响应WM_PAINT消息. OnDraw()是CVIEW的成员函数,并且没有响应消息的功能.这就是为什么你用VC成的程序代码时,在视图类只有OnDraw ...

  4. 初识Windows窗体(包括各种控件,属性,方法)

    什么是Wind ows窗体? 顾名思义,win dows窗体就是将一些所必须的信息通过窗体的形式展示给客户看.例如:我们经常玩的QQ登陆界面,微信登陆界面,等等,都是以窗体的形式将信息展示给我们看的. ...

  5. Winfrom 减少控件重绘闪烁的方法

    Winform控件的双缓冲.控件的双缓冲属性是隐藏的,可以通过反射改变其属性值. lv.GetType().GetProperty("DoubleBuffered", Bindin ...

  6. c#控件重绘的问题

    1.当Panel有背景图像的时候,往Panel添加控件(带图像),画面会非常闪烁,所以,Panel尽量不要带背景图像 2.带背景图像可以参考designer.cs里面的写法... 添加Control之 ...

  7. WinForm GroupBox控件重绘外观

    private void groupBoxFun_Paint(PaintEventArgs e, GroupBox groupBox){ e.Graphics.Clear(groupBox.BackC ...

  8. TabControl控件重绘

    原文地址:http://www.codeproject.com/Articles/91387/Painting-Your-Own-Tabs-Second-Edition 在网上看到重绘TabContr ...

  9. WPF中不规则窗体与WindowsFormsHost控件的兼容问题完美解决方案

    首先先得瑟一下,有关WPF中不规则窗体与WindowsFormsHost控件不兼容的问题,网上给出的解决方案不能满足所有的情况,是有特定条件的,比如  WPF中不规则窗体与WebBrowser控件的兼 ...

随机推荐

  1. bzoj2208

    首先有向图的题目不难想到先tarjan缩点 一个强连通分量中的点的连通数显然是相等: 据说这样直接dfs就可以过了,但显然不够精益求精 万一给定的是一个完全的DAG图怎么办,dfs铁定超时: 首先想, ...

  2. PHP libxml RSHUTDOWN安全限制绕过漏洞(CVE-2012-1171)

    漏洞版本: PHP PHP 5.5.x 漏洞描述: BUGTRAQ ID: 65673 CVE(CAN) ID: CVE-2012-1171 PHP是一种HTML内嵌式的语言. PHP 5.x版本内的 ...

  3. 【转】VS2010/MFC编程入门之八(对话框:创建对话框类和添加控件变量)

    原文网址:http://www.jizhuomi.com/software/153.html 前两讲中鸡啄米为大家讲解了如何创建对话框资源.创建好对话框资源后要做的就是生成对话框类了.鸡啄米再声明下, ...

  4. Google Chrome中的高性能网络(二)

    Chrome Predictor的预测功能优化 Chrome会随着使用变得更快. 它这个特性是通过一个单例对象Predictor来实现的.这个对象在浏览器内核进程(Browser Kernel Pro ...

  5. 关于SQL\SQL Server的三值逻辑简析

    在SQL刚入门的时候,我们筛选为某列值为NULL的行,一般会采用如下的方式: SELECT * FROM Table AS T WHERE T.Col=NULL  www.2cto.com   而实际 ...

  6. c++11 lambda递归调用写法

    偶然想到要在函数内部使用lambda递归调用,以下是可行的写法,可参考 std::function<void(Node * container,const BlendFunc &blen ...

  7. bzoj 1975 [Sdoi2010]魔法猪学院(k短路)

    题目描述 iPig在假期来到了传说中的魔法猪学院,开始为期两个月的魔法猪训练.经过了一周理论知识和一周基本魔法的学习之后,iPig对猪世界的世界本原有了很多的了解:众所周知,世界是由元素构成的:元素与 ...

  8. 各种sensor名称统计

    gyroscopes 陀螺仪accelerometers 加速度计magnetometers 磁力计barometric pressure 气压remote pressure sensing 远程压力 ...

  9. PHP字符串替换函数strtr()

    strtr函数比str_replace函数的效率要高很多,strtr()的两种定义方式: strtr(string, from, to)和strtr(string, array)1.strtr区分大小 ...

  10. 如何把.rar文件隐藏在一个图片内

    首先假设我们要隐藏的.rar文件叫a.rar,图片叫a.jpg.先把他俩放到同一个目录下,然后通过“cmd”进入windows命令行,进入目标目录下,使用以下命令进行隐藏: copy/B  a.jpg ...