wtl图形界面
第一部分介绍WTL框架窗口结构,编写基于WTL的SDI,MDI和多线程SDI的程序,及WTL的辅助类及封装DDX,看向导及例程.
第二部分讲WTL命令栏,封装通用控件,及消息路由,及通用对话框,属性页/属性表,打印支持,以及滚动窗口
WTL的基础–ATL
ATL本来用来支持COM组件和OLE属性页框架.封装了所有基本窗口函数,包括创建和管理窗口/对话框,窗口函数,消息路由,窗口子类化,超类化和消息链等.
对话框/窗口依赖其根,根/容器依赖C窗口.外有个C消息映射.
wtl使创建sdi更容易:CFrameWindowImpl.
其从CFrameWindowImplBase继承,继承标框架的所有功能.
序号 | 功能 |
|---|---|
1 | 工具栏,菜单栏,状态栏,钢筋栏(容器) |
2 | 基本视图处理:视图与框架同步大小 |
3 | 命令栏菜单 |
4 | 键盘快捷键; |
5 | 工具栏按钮的工具提示; |
6 | 在状态栏显示帮助字符串; |
7 | 显示框架图标 |
相同资源ID可在字符串表中用来作为框架标题,菜单资源,快捷键表,图标和工具条资源等,用用通用资源ID找到了资源,就自动加载.只是工具条资源要手动加载.
处理WM_SIZE(重置工具条和状态条大小)和WM_DESTROY(调用PostQuitMessage)等.
atlapp.h定义了从CComModule继承的CAppModule.
atlframe.h定义CFrameWindowImpl类.
ATL允许每个线程有个消息队列.CCommandBarCtrl封装命令栏.先父窗口为参创建命令栏,再附加菜单,再加载图像传递资源,其为位图工具栏,然后图与菜标对应.
视图是包含窗柄的窗口.ATL/WTL提供大量窗口类可作为视图.可封装窗口和通用控件为类来作为视图.把CBitmapView变为CEdit就是文本编辑器.
// 继承C++方法为来继承CAxWindow功能
class CHtmlView : public CWindowImpl<CHtmlView, CAxWindow>
{
public:
// 继承Windows方法来预处理Windows消息,两个继承
DECLARE_WND_SUPERCLASS(NULL, CAxWindow::GetWndClassName())
BEGIN_MSG_MAP(CHtmlView)
...
END_MSG_MAP()
CHtmlView()
{
AtlAxWinInit(); // 初始化控件宿主(在atlhost.h定义)
}
LRESULT CMainFrame::OnCreate(UINT, WPARAM, LPARAM, BOOL&)
{
// 创建该视图(m_view 的类型是CHtmlView)
m_hWndClient = m_view.Create(m_hWnd, rcDefault, "http://www.microsoft.com",
WS_CHILD | WS_VISIBLE, WS_EX_CLIENTEDGE);
return 0;//打开页面
}
};
c++/窗口就是超类化.
CRecentDocumentList提供全功能最近文档.用文件菜单句柄初化它.
LRESULT CMainFrame::OnCreate(UINT, WPARAM, LPARAM, BOOL&)
{
...
m_mru.SetMenuHandle(GetMenu().GetSubMenu(0));
m_mru.ReadFromRegistry(_T("Software\\MyCompany\\MyCoolApp"));
m_mru.SetMaxEntries(16);
...
}
打开文件,加至列表,用宏来处理区间,取文档名,移到最上,从中删除.写至注册表.
多线程SDI.每个线程都有独立消息队列,能处理和分发消息.线管很重要.
顶级窗口及子窗口运行在线程,UI线程有自己消息队列,这样每个顶级窗口,就像独立应用.
设计线管:每线程一窗口,保存线程数,保持命令参数和初化窗口大小.向导里面有.由MsgWaitForMultipleObjects来等待线程对象.有WM_USER时就产生新线程
用WM_USER来代替特定消息,代码只用主线程来创建界面线程,主线程不创建窗口,因而不处理事件,所以不会有消息冲突.在线管中创建框架.新菜单提交用户信息来产生新线程.
LRESULT CMainFrame::OnFileNewWindow(WORD, WORD, HWND, BOOL&)
{
::PostThreadMessage(_Module.m_dwMainThreadID, WM_USER, 0, 0L);
return 0;
}
多线程一般是单例.
创建多子窗口
提供CFramewindowImplBase类来创建mdi程序.
WM_MDI系列消息.
子窗口,从CMDIChildWindowImpl<CChildFrame>继承.
将bHandled置为假,由基类处理窗消.在OnFinalMessage消息中删除自己,释放资源.
分隔窗口在atlsplit.h中定义,由CSplitterImpl提供.还应从CWindowImpl/CMessageMap继承,以处理消息.
注意消除调整大小时的抖动,处理WM_ERASEBKGND时,屏蔽了默认消息处理,默认用背景刷删背景,带来抖动.返回非0,表示不再调用默认.
CSplitterWindowImpl类处理了背景和大小消息,
template <class T, bool t_bVertical = true, class TBase = CWindow, class TWinTraits = CControlWinTraits>
class ATL_NO_VTABLE CSplitterWindowImpl :
public CWindowImpl<T,TBase,TWinTraits>,
public CSplitterImpl<CSplitterWindowImpl<T,t_bVertical,TBase,TWinTraits>,t_bVertical>
{
public:
DECLARE_WND_CLASS_EX(NULL,CS_DBLCLKS,COLOR_WINDOW)
typedef CSplitterWindowImpl<T,t_bVertical,TBase,TWinTraits>thisClass;
typedef CSplitterImpl<CSplitterWindowImpl<T,t_bVertical,TBase,TWinTraits>,t_bVertical>baseClass;
BEGIN_MSG_MAP(thisClass)//本类
MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
MESSAGE_HANDLER(WM_SIZE, OnSize)
CHAIN_MSG_MAP(baseClass)//链入消息
FORWARD_NOTIFICATIONS()//转发各种通知给父窗口
END_MSG_MAP()
LRESULT OnEraseBackground(UINT,WPARAM, LPARAM, BOOL&){return 1;}
LRESULT OnSize(UINT, WPARAM wParam, LPARAM, BOOL& bHandled)
{
if(wParam != SIZE_MINIMIZED)
SetSplitterRect();
bHandled = FALSE;
return 1;
}
};
可用分割器多层嵌套来分隔客户.也可包含句柄,然后动态切换视图.
atlgdi.h中封装gdi,有管理与不管理的版本.区别是管理版消灭所管对象.如CFontT带个极模板参来区分是否管理.
最重要的是CDCT对象,封装了240个方法,覆盖了GDI和WGL.
CPaintDC,CWindowDC,CClientDC和CEnhMetaFileDC类,从管理版CDC继承.
DC对象 | 说明 |
|---|---|
CPaintDC | 封装PS(画结构),BeginPaint/EndPaint之间,用来获取/释放DC |
CClientDC | 用GetDC和ReleaseDC来获取/释放DC,代表窗口客户区 |
CWindowDC | 代表整个窗口,用GetWindowDC和ReleaseDC对获取释放. |
CEnhMetaFileDC | 封装HENHMETAFILE构,用CreateEnhMetaFile()和DeleteEnhMetaFile()来处理元文件 |
类似这样:
class CPaintDC : public CDC
{
public:
HWND m_hWnd;PAINTSTRUCT m_ps;
CPaintDC(HWND hWnd){
m_hWnd = hWnd;
m_hDC = ::BeginPaint(hWnd, &m_ps);
}//先画
~CPaintDC(){
::EndPaint(m_hWnd, &m_ps);//结束画
Detach();//释放
}
};
如,管理位图:
class CBitmapView : public CWindowImpl<CBitmapView>
{
public:
CBitmapView()
{
m_bmp.LoadBitmap(MAKEINTRESOURCE(IDB_ATLWINDOWING));
}//加位图
BEGIN_MSG_MAP(CBitmapView)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
END_MSG_MAP()
LRESULT OnPaint(UINT, WPARAM, LPARAM, BOOL&)
{//画
CPaintDC dc(m_hWnd);
RECT rect;
GetClientRect(&rect);
CDC dcMem;
dcMem.CreateCompatibleDC(dc);
CBitmap bmpOld = dcMem.SelectBitmap(m_bmp);
BITMAP bm;
m_bmp.GetBitmap(&bm);
SIZE size = { bm.bmWidth, bm.bmHeight };
dc.BitBlt(rect.left, rect.top, size.cx, size.cy, dcMem, 0, 0, SRCCOPY);
// 清除
dcMem.SelectBitmap(bmpOld);
return 0;
}
private:
CBitmap m_bmp;
};
wtl,还提供了AtlGetStockBrush,AtlGetStockFont,AtlGetStockPalette,AtlGetStockPen,AtlLoadAccelerators,AtlLoadBitmap,AtlLoadBitmapImage,AtlLoadCursor和AtlLoadString等辅助函数
CString等最新的wtl已删.atl有.
DDX用来交换成员与窗口子控件数据.通过CWinDataExchange,用DoDataExchange来实现.
BEGIN_DDX_MAP(CMainDlg)
DDX_TEXT(IDC_STRING, m_sz)
END_DDX_MAP()
开始映射.初化时调用DoDataExchange()(手动调用)即可.
点击时,DoDataExchange(TRUE)同步.也可只传标识来同步单个控件.视图基于listbox,edit,listview,treeview,richedit等控件,
从资源中创建上下文菜单主窗口处理WM_CONTEXTMENU.命令栏CCommandBarCtrl有个TrackPopupMenu方法来显示环境菜单.与TrackPopupMenuEx(窗口接口)一样,使用前要加载菜单资源.CMenu和CMenuHandle.有Handle的要自己处理.
视图窗口来的环境消息,则在主框架中处理.
CScrollImpl和CScrollWindowImpl为滚动窗口实现.及CMapScrollImpl和CMapScrollWindowImpl,支持映射模式.为全屏窗口增加消息映射.支持映射模式允许你易用坐标系统和单位,来填充你内容.
你要:设置内容大小,绘画.你必须处理WM_PAINT.或你需要绘制窗口哪部分.置SetScrollSize,并实现DoPaint方法.用SetScrollSize()设置滚动区域大小.
改变内容时,调用SetScrollSize.
LRESULT CScrollImpl::OnPaint(UINT, WPARAM wParam, LPARAM, BOOL&)
{
T* pT = static_cast<T*>(this);//转
ATLASSERT(::IsWindow(pT->m_hWnd));
if(wParam != NULL)
{ // HDC是这时传入的.
CDCHandle dc = (HDC)wParam;
dc.SetViewportOrg(-m_ptOffset.x, -m_ptOffset.y);//置神器
pT->DoPaint(dc);//绘画
}
else
{
CPaintDC dc(pT->m_hWnd);
dc.SetViewportOrg(-m_ptOffset.x, -m_ptOffset.y);
pT->DoPaint(CDCHandle(dc));
}
return 0;
}
class CBitmapView : public CScrollWindowImpl<CBitmapView>
{//具体类
...
void DoPaint(CDCHandle dc)//参数
{
if(!m_bmp.IsNull())
{
CDC dcMem;
dcMem.CreateCompatibleDC(dc);
HBITMAP hBmpOld = dcMem.SelectBitmap(m_bmp);
dc.BitBlt(0, 0, m_size.cx, m_size.cy, dcMem, 0, 0, SRCCOPY);
dcMem.SelectBitmap(hBmpOld);
}
}
...
};
用CFileDialog来取文件.
WTL封装类名 | 相应地Win32函数名 |
|---|---|
CFileDialog | GetOpenFileName/GetSaveFileName |
CFolderDialog | SHBrowseForFolder |
CFontDialog | ChooseFont |
CRichEditFontDialog | ChooseFont |
CColorDialog | ChooseColor |
CPrintDialog | PrintDlg |
CPrintDialogEx | PrintDlgEx |
CPageSetupDialog | PageSetupDlg |
CFindReplaceDialog | FindText/ReplaceText |
需要更加定制化对话框时,要勾挂.一般用缺省值.用DoModal来生成通用.
你可从他们简单派生类,并实现消息映射,重载函数,或定制对话框行为
class CListBox : CWindow
{
public:
void ResetContent()
{
::SendMessage(m_hWnd, LB_RESETCONTENT, 0, 0L);
}
int InsertString(int nIndex, LPCTSTR lpszItem)
{
return (int)::SendMessage(m_hWnd, LB_INSERTSTRING, nIndex, (LPARAM)lpszItem);
}
int SetCurSel(int nSelect)
{
return (int)::SendMessage(m_hWnd, LB_SETCURSEL, nSelect, 0L);
}
int SetTopIndex(int nIndex)
{
return (int)::SendMessage(m_hWnd, LB_SETTOPINDEX, nIndex, 0L);
}
...
};
wtl封装控件:
WTL封装类名 | 相应Win32窗口类 |
|---|---|
CStatic | STATIC |
CButton | BUTTON |
CListBox | LISTBOX |
CComboBox | COMBOBOX |
CEdit | EDIT |
CScrollBar | SCROLLBAR |
CToolTipCtrl | tooltips_class32 |
CListViewCtrl | SysListView32 |
CTreeViewCtrl | SysTreeView32 |
CHeaderCtrl | SysHeader32 |
CToolBarCtrl | ToolbarWindow32 |
CStatusBarCtrl | msctls_statusbar32 |
CTabCtrl | SysTabControl32 |
CTrackBarCtrl | msctls_trackbar32 |
CUpDownCtrl | msctls_updown32 |
CProgressBarCtrl | msctls_progress32 |
CHotKeyCtrl | msctls_hotkey32 |
CAnimateCtrl | SysAnimate32 |
CRichEditCtrl | RichEdit20A/RichEdit20W |
CDragListBox | LISTBOX |
CReBarCtrl | ReBarWindow32 |
CComboBoxEx | ComboBoxEx32 |
CDateTimePickerCtrl | SysDateTimePick32 |
CMonthCalendarCtrl | SysMonthCal32 |
CIPAddressCtrl | SysIPAddress32 |
CPagerCtrl | SysPager |
具mru列表的列表框
class CMruList : public CWindowImpl<CMruList, CListBox>//这里继承mru与列表框.
{//CWindow基类不做任何事
public:
SIZE m_size;
CMruList(){
m_size.cx = 200;
m_size.cy = 150;
}
HWND Create(HWND hWndParent)
{
CWindowImpl<CMruList, CListBox>::Create(hWndParent, rcDefault, NULL,WS_POPUP | WS_THICKFRAME | WS_CLIPCHILDREN | WS_CLIPSIBLINGS |WS_VSCROLL | LBS_NOINTEGRALHEIGHT, WS_EX_CLIENTEDGE);
if(IsWindow())
SetFont(AtlGetStockFont(DEFAULT_GUI_FONT));
return m_hWnd;
}
BOOL BuildList(CRecentDocumentList& mru)
{
ATLASSERT(IsWindow());
ResetContent();
int nSize = mru.m_arrDocs.GetSize();
for(int i = 0; i < nSize; i++)
InsertString(0, mru.m_arrDocs[i].szDocName); // 文档名以相反的顺序存放在数组中
if(nSize > 0)
{
SetCurSel(0);
SetTopIndex(0);
}
return TRUE;
}
BOOL ShowList(int x, int y)
{
return SetWindowPos(NULL, x, y, m_size.cx, m_size.cy, SWP_NOZORDER | SWP_SHOWWINDOW);
}
void HideList()
{
RECT rect;
GetWindowRect(&rect);
m_size.cx = rect.right - rect.left;
m_size.cy = rect.bottom - rect.top;
ShowWindow(SW_HIDE);
}
void FireCommand()
{
int nSel = GetCurSel();
if(nSel != LB_ERR)
{
::SetFocus(GetParent()); // 将隐藏该窗口
::SendMessage(GetParent(), WM_COMMAND,
MAKEWPARAM((WORD)(ID_FILE_MRU_FIRST + nSel), LBN_DBLCLK), (LPARAM)m_hWnd);
}
}
BEGIN_MSG_MAP(CMruList)
MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown)
MESSAGE_HANDLER(WM_LBUTTONDBLCLK, OnLButtonDblClk)
MESSAGE_HANDLER(WM_KILLFOCUS, OnKillFocus)
MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
END_MSG_MAP()
LRESULT OnKeyDown(UINT, WPARAM wParam, LPARAM, BOOL& bHandled)
{
if(wParam == VK_RETURN)
FireCommand();
else
bHandled = FALSE;
return 0;
}
LRESULT OnLButtonDblClk(UINT, WPARAM, LPARAM, BOOL&)
{
FireCommand();
return 0;
}
LRESULT OnKillFocus(UINT, WPARAM, LPARAM, BOOL&)
{
HideList();
return 0;
}
LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL&)
{
LRESULT lRet = DefWindowProc(uMsg, wParam, lParam);
switch(lRet)
{
case HTLEFT:
case HTTOP:
case HTTOPLEFT:
case HTTOPRIGHT:
case HTBOTTOMLEFT:
lRet = HTCLIENT; //这里不允许重新调整大小
break;
default:
break;
}
return lRet;
}
};
C++继承同样作用于Win32超类,允许我们在之前处理LISTBOX类窗口消息或替代LISTBOX来处理它
打印与打印预览
CPrintJob提供一个StartPrintJob方法.后台打印,将调用默认打印设置.要传送支持IPrintJobInfo接口对象.
IPrintJobInfo是C++接口,由CPrintJob调用.wtl有CPrintJobInfo类,除了PrintPage都实现了.
你完全可在页基础定制打印,如,为不同页提供不同打印机.CDevModeT,CPrinterT,CPrinterDC和CPrinterInfo类来帮助打印.
创建CPrintJob对象,调用其StartPrintJob方法.传递CPrintJobInfo派生类,还有两个配置页面:CPrintDialog和CPageSetup.处理命令中:ID_FILE_PRINT和ID_FILE_PAGE_SETUP
LRESULT CMainFrame::OnFilePrint(WORD, WORD, HWND, BOOL&)
{
CPrintDialog dlg(FALSE);
dlg.m_pd.hDevMode = m_devmode.CopyToHDEVMODE();
dlg.m_pd.hDevNames = m_printer.CopyToHDEVNAMES();
dlg.m_pd.nMinPage = 1;
dlg.m_pd.nMaxPage = 1;
if(dlg.DoModal() == IDOK)
{
m_devmode.CopyFromHDEVMODE(dlg.m_pd.hDevMode);
m_printer.ClosePrinter();
m_printer.OpenPrinter(dlg.m_pd.hDevNames, m_devmode.m_pDevMode);
CPrintJob job;
job.StartPrintJob(false, m_printer, m_devmode.m_pDevMode, this, _T("BmpView Document"), 0, 0);
}
::GlobalFree(dlg.m_pd.hDevMode);
::GlobalFree(dlg.m_pd.hDevNames);
return 0;
}
LRESULT CMainFrame::OnFilePageSetup(WORD, WORD, HWND, BOOL&)
{
CPageSetupDialog dlg;
dlg.m_psd.hDevMode = m_devmode.CopyToHDEVMODE();
dlg.m_psd.hDevNames = m_printer.CopyToHDEVNAMES();
dlg.m_psd.rtMargin = m_rcMargin;
if(dlg.DoModal() == IDOK)
{
if(m_bPrintPreview)
TogglePrintPreview(); // 稍后...
m_devmode.CopyFromHDEVMODE(dlg.m_psd.hDevMode);
m_printer.ClosePrinter();
m_printer.OpenPrinter(dlg.m_psd.hDevNames, m_devmode.m_pDevMode);
m_rcMargin = dlg.m_psd.rtMargin;
}
::GlobalFree(dlg.m_psd.hDevMode);
::GlobalFree(dlg.m_psd.hDevNames);
return 0;
}
支持CPrintPreview,CPrintPreviewWindowImpl和CPrintPreviewWindow,处理消息增加更多特性.
CPrintJob不同线程后台处理.处理中,取消打印.后台打印,调用StartPrintJob后立即返回.类似协程了.属性有多个类别,可显示属性页.
属性对话框.一种HWND,一种模板窗口,一个使用类.
更新界面元素,来表明当前状态.
过滤消息是窗口间路由消息的便利方法.在翻译/分发消息前处理.这里处理消息很方便.
应用程序可管理消息循环/线程,实现多线程SDI应用时,很方便.
预翻译消息调用遍历本线程CMessageFilter类的实现列表.返回真,表示已预处理,不用再分发了.假,则再分发.主框架从CMessageFilter继承.主框架先问基类,再问视图是否处理.想在无模式对话框中IsDialogMessage,
class CMyModelessDlg : public CDialogImpl<CMyModelessDlg>,public CMessageFilter
{//对话框.
public:
CMyModelessDlg()
{
_Module.GetMessageLoop()->AddMessageFilter(this);
}
~CMyModelessDlg()
{
_Module.GetMessageLoop()->RemoveMessageFilter(this);
}//加,删
virtual BOOL PreTranslateMessage(MSG* pmsg)
{//预翻译
return IsDialogMessage(m_hWnd, pmsg);
}
...
};
过滤消息的最好方法是任何对象可过滤消息,他们可在空闲时加入和删除.消息队列为空时,就是空闲.
通过注册自己来过滤消息.
任何对象可注册自身为空闲处理,然后做喜欢之事.
更新界面.其宏映射指出要映射哪些界面,一个标识加更新的位置.
用WTL,在控件中代替WM_INITMENUPOPUP消息和/或自己空闲处理.1,登记.2,空闲时更新界面,状态改变时,才访问实际控件.
更新类支持方法:
BOOL UIEnable(int nID, BOOL bEnable, BOOL bForceUpdate = FALSE);
BOOL UISetCheck(int nID, int nCheck, BOOL bForceUpdate = FALSE);
BOOL UISetRadio(int nID, BOOL bRadio, BOOL bForceUpdate = FALSE);
BOOL UISetText(int nID, LPCTSTR lpstr, BOOL bForceUpdate = FALSE);
BOOL UIUpdateMenuBar(BOOL bForceUpdate = FALSE, BOOL bMain = FALSE);
BOOL UIUpdateToolBar(BOOL bForceUpdate = FALSE);
BOOL UIUpdateStatusBar(BOOL bForceUpdate = FALSE);
BOOL UIUpdateChildWindows(BOOL bForceUpdate = FALSE);
BOOL UISetState(int nID, DWORD dwState);
DWORD UIGetState(int nID);
消息处理函数中分解消息.
m_CmdBar.TrackPopupMenu(...,GET_X_LPARAM(lParam),GET_Y_LPARAM(lParam));
在atlcrack中有消息宏/分解宏/通知宏.放在消息映射中,必须用BEGIN_MSG_MAP_EX,其分解消息给处理函数,并指出是否处理.用SetMessageHandled(真/假)来指定是否处理某个函数.
class CMyWindow : public CWindowImpl<CMyWindow>
{
public:
BEGIN_MSG_MAP_EX(CMyWindow)
MSG_WM_CREATE(OnCreate)
MSG_WM_SIZE(OnSize)
END_MSG_MAP()
int OnCreate(LPCREATESTRUCT lpCreateStruct)
{
...
return 0;
}
void OnSize(UINT nType, CSize size)
{
if(nType != SIZE_MINIMIZED) ...
}//麻烦,就是要看宏的原型,来找(函数参数)
};
浙公网安备 33010602011771号