WTL 桌面开发入门
一. 基础控件
除了CWindowImpl和CDialogImpl 这种窗口外,其他的界面窗口可统称为控件。WTL提供了大量的基础窗口控件,主要在atlctrls.h中定义,它们都继承自CWindow类:
二. 对话框程序
WTL中的消息机制比MFC中的要灵活的多,下面通过一个简单的对话框程序,对子类化,消息反射,重绘以及双缓冲技术的使用进行说明。如果有WTL工程向导的话,可以直接使用向导创建模式对话框程序,如果没有的话,则可以根据以下步骤手动创建一个对话框工程:
1. 资源创建
需要两个Dialog资源,一个作为主窗口,一个作为弹窗:
2. 代码
三个文件:stdafx.h 公共引用;MainDlg.h 对话框定义及实现;main.cpp 程序入口
//stdafx.h
#pragma once
// Change these values to use different versions
#define WINVER 0x0601
#define _WIN32_WINNT 0x0601
#define _WIN32_IE 0x0700
#define _RICHEDIT_VER 0x0500#include <atlbase.h>
#include <atlapp.h>
extern CAppModule _Module;#include <atlwin.h>#if defined _M_IX86
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")
#elif defined _M_IA64
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"")
#elif defined _M_X64
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"")
#else
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#endif// MainDlg.h
#pragma once
#include <atlbase.h>
#include <atlwinx.h>
#include <atlcrack.h>
#include <atlctrls.h>
#include <atlframe.h>
#include "resource.h"class CODButtonImpl : public CWindowImpl<CODButtonImpl, CButton>,public COwnerDraw<CODButtonImpl>
{
public:BEGIN_MSG_MAP_EX(CODButtonImpl)REFLECTED_COMMAND_HANDLER_EX(IDC_CUSTOM_BUTTON, BN_DOUBLECLICKED, OnClicked)MESSAGE_HANDLER(WM_SETCURSOR, OnSetCursor)CHAIN_MSG_MAP_ALT(COwnerDraw<CODButtonImpl>, 1)DEFAULT_REFLECTION_HANDLER()END_MSG_MAP()CODButtonImpl(){m_bmp;}LRESULT OnSetCursor(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){bHandled = TRUE;//will decide the return value of CODButtonImpl::ProcessWindowMessagestatic HCURSOR hcur = LoadCursor(NULL, IDC_HAND);if (NULL != hcur){SetCursor(hcur);return TRUE;//will decide the value of CODButtonImpl::ProcessWindowMessage's param: lResult}else{SetMsgHandled(false);return FALSE;}}void DrawItem(LPDRAWITEMSTRUCT lpdis)// must be implemented, required by COwnerDraw.{//SetMsgHandled(FALSE);// if you don't how to process this msg, then set handled flag as FALSE to spread it to other work flow.//return;CDCHandle dc = lpdis->hDC;CDC dcMem;dcMem.CreateCompatibleDC(dc);dc.SaveDC();dcMem.SaveDC();// Draw the button's background, red if it has the focus, blue if not.if (lpdis->itemState & ODS_FOCUS)dc.FillSolidRect(&lpdis->rcItem, RGB(255, 0, 0));elsedc.FillSolidRect(&lpdis->rcItem, RGB(0, 0, 255));// Draw the bitmap in the top-left, or offset by 1 pixel if the button// is clicked.//dcMem.SelectBitmap(m_bmp);if (lpdis->itemState & ODS_SELECTED)dc.BitBlt(1, 1, 80, 80, dcMem, 0, 0, SRCCOPY);elsedc.BitBlt(0, 0, 80, 80, dcMem, 0, 0, SRCCOPY);dcMem.RestoreDC(-1);dc.RestoreDC(-1);}void OnClicked(WORD wNotifyCode, WORD wID, HWND hWndCtl){//assert(wNotifyCode == BN_DOUBLECLICKED && wID == IDC_CUSTOM_BUTTON && hWndCtl == m_hWnd)return;}
private:HBITMAP m_bmp;
};class COwerTree :public CWindowImpl<COwerTree, CTreeViewCtrl>,public CCustomDraw<COwerTree>
{BEGIN_MSG_MAP(COwerTree)REFLECTED_NOTIFY_CODE_HANDLER_EX(TVN_ITEMEXPANDING, OnItemExpanding)CHAIN_MSG_MAP_ALT(CCustomDraw<COwerTree>, 1)DEFAULT_REFLECTION_HANDLER()END_MSG_MAP()LRESULT OnItemExpanding(NMHDR* phdr){//SetMsgHandled(FALSE);//return FALSE;NMTREEVIEW* pnmtv = (NMTREEVIEW*)phdr;if (pnmtv->action & TVE_COLLAPSE)return TRUE;elsereturn FALSE;}DWORD OnPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD){OCM_NOTIFY - OCM__BASE;return CDRF_NOTIFYITEMDRAW;}DWORD OnItemPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD){NMTVCUSTOMDRAW* pnmtv = (NMTVCUSTOMDRAW*)lpNMCD;RECT rcItem = lpNMCD->rc;HDC hdcItem = lpNMCD->hdc;POINT ptItem;ptItem.x = rcItem.left + 1;ptItem.y = rcItem.top + 1;UINT uFlags;HTREEITEM hItem = HitTest(ptItem, &uFlags);if (!hItem) return CDRF_DODEFAULT;return CDRF_SKIPDEFAULT;}
};class CMainDlg : public CDialogImpl<CMainDlg>, public CDoubleBufferImpl<CMainDlg>
{
public:enum { IDD = IDD_MAINDLG }; //WM_PAINTBEGIN_MSG_MAP(CMainDlg)MESSAGE_HANDLER(WM_CLOSE, OnClose)MESSAGE_HANDLER(WM_DESTROY, OnDestroy)MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)COMMAND_ID_HANDLER(IDC_ABOUT, OnAppAbout)COMMAND_ID_HANDLER(IDOK, OnOK)COMMAND_ID_HANDLER(IDCANCEL, OnCancel)CHAIN_MSG_MAP(CDoubleBufferImpl<CMainDlg>)// responsible for handling WM_PAINT msg.REFLECT_NOTIFICATIONS()// send msg to child ctrl according to the hwnd parsed from wParam or lParam. ALT_MSG_MAP(1)MESSAGE_HANDLER(WM_SETCURSOR, OnSetCursor_OK)ALT_MSG_MAP(2)MESSAGE_HANDLER(WM_SETCURSOR, OnSetCursor_Exit)END_MSG_MAP()// Handler prototypes (uncomment arguments if needed):// LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)// LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)// LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)CMainDlg() :m_wndOKBtn(this, 1), m_wndExitBtn(this, 2) {}LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled){// center the dialog on the screenCenterWindow();m_wndOKBtn.SubclassWindow(GetDlgItem(IDOK));//subclass the button, which will change the winproc, unlike the attach method which won't change the winproc.m_wndExitBtn.SubclassWindow(GetDlgItem(IDCANCEL));//handle won't be destoryed when deconstruct, but once the handle is destoryed, unsubclass will be conducted automatically.m_ownBtn.SubclassWindow(GetDlgItem(IDC_CUSTOM_BUTTON));m_ownTree.SubclassWindow(GetDlgItem(IDC_TREE1));bHandled = TRUE;//will affect the return value of CMainDlg::ProcessWindowMessage, which is called in Winproc, so you can look up the Winproc to know about the behavior.return TRUE;//will affect the value of CMainDlg::ProcessWindowMessage's output param: lResult.}LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){//DestroyWindow();m_nExitCode = 0;::DestroyWindow(m_hWnd);bHandled = TRUE;return TRUE;}LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/){PostQuitMessage(m_nExitCode);return 0;}LRESULT OnAppAbout(WORD wNotifyCode, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/){if (wNotifyCode == BN_DOUBLECLICKED && wID == IDC_ABOUT)// you need change the button's Notify attribute value to True to make it support double click.{CSimpleDialog<IDD_ABOUTBOX, FALSE> dlg;dlg.DoModal();}else if (wNotifyCode == BN_CLICKED && wID == IDC_ABOUT){}return 0;}LRESULT OnOK(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/){// TODO: Add validation code //EndDialog(wID);m_nExitCode = wID;::DestroyWindow(m_hWnd);return 0;}LRESULT OnCancel(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/){//EndDialog(wID);m_nExitCode = wID;::DestroyWindow(m_hWnd);return 0;}LRESULT OnSetCursor_OK(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled){bHandled = TRUE;//will decide the return value of CContainedWindow::ProcessWindowMessagestatic HCURSOR hcur = LoadCursor(NULL, IDC_HAND);if (NULL != hcur){SetCursor(hcur);return TRUE;//will decide the value of CContainedWindow::ProcessWindowMessage's param: lResult}else{SetMsgHandled(false);return FALSE;}}LRESULT OnSetCursor_Exit(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {static HCURSOR hcur = LoadCursor(NULL, IDC_NO);bHandled = TRUE;if (NULL != hcur){SetCursor(hcur);return TRUE;}else{SetMsgHandled(false);return FALSE;}}void DoPaint(CDCHandle memdc)// Forcedly required by CDoubleBufferImpl, you can draw on memdc in this method{RECT rc;GetClientRect(&rc);memdc.FillSolidRect(&rc, RGB(255, 255, 255));// draw background.//memdc.TextOutW(10, 10, _T("double buffer test"));//draw text.//memdc.Ellipse(50, 50, 200, 200);//draw geometries.return;}
protected:int m_nExitCode = 0;CContainedWindow m_wndOKBtn, m_wndExitBtn;// CContainedWindow diverts msgs to another CMessageMap object provided in the construct function.CODButtonImpl m_ownBtn;COwerTree m_ownTree;
};// main.cpp#include "stdafx.h"
#include "MainDlg.h"CAppModule _Module;int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR /*lpstrCmdLine*/, int nCmdShow)
{HRESULT hRes = ::CoInitialize(NULL);ATLASSERT(SUCCEEDED(hRes));AtlInitCommonControls(ICC_WIN95_CLASSES); // add flags to support other controlshRes = _Module.Init(NULL, hInstance);ATLASSERT(SUCCEEDED(hRes));int nRet = 0;BOOL bModal = FALSE;if (bModal){CMainDlg dlgMain;nRet = (int)dlgMain.DoModal();//use ::DialogBox to create a modal dialog, which is different from MFC using ::CreateDialog to create Non-modal dialogs.}else{CMessageLoop theLoop;_Module.AddMessageLoop(&theLoop);CMainDlg dlgMain;if (dlgMain.Create(NULL) != NULL)// use ::CreateDialog to create Non-modal dialogs.{dlgMain.ShowWindow(nCmdShow);nRet = theLoop.Run();}_Module.RemoveMessageLoop();}_Module.Term();::CoUninitialize();return nRet;
}
3. 运行结果
看把鼠标移到按钮上方,观察效果,点击按钮看事件响应。
参考书籍:
Part I - ATL GUI Classes | MFC程序员的WTL指南
https://wizardforcel.gitbooks.io/wtl-for-mfc-programmer/content/1.html