wtl界面3
WM_CONTEXTMENU
处理右键环境菜单.
命令栏CCommandBarCtrl
控件,有TrackPopupMenu
方法,与win32
的TrackPopupMenuEx
一样.
BOOL CCommandBarCtrlImpl::TrackPopupMenu(HMENU hMenu, UINT uFlags, int x, int y,LPTPMPARAMS lpParams = NULL)
使用前,加载菜单资源.有CMenu和CMenuHandle
两个类.
LRESULT CMainFrame::OnContextMenu(UINT, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if((HWND)wParam == m_view)
{
CMenu menu;//菜单.
menu.LoadMenu(IDR_CONTEXTMENU);
CMenuHandle menuPopup = menu.GetSubMenu(POPUP_MENU_POSITION);
m_CmdBar.TrackPopupMenu(menuPopup, TPM_RIGHTBUTTON | TPM_VERPOSANIMATION | TPM_VERTICAL,GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));//命令栏的
}
else
bHandled = FALSE;
return 0;
}
自己绘制窗口内容,需要写代码来支持滚动,
两个滚动类:CScrollImpl和CScrollWindowImpl
.从CScrollImpl
继承,你可加窗口类
并链入消息映射
.CScrollWindowImpl
从CScrollImpl和CWindowImpl
继承,提供全部滚动窗口实现.CMapScrollImpl和CMapScrollWindowImpl
还提供映射
模式.为全屏
加了消息映射.映射模式
主要方便整合坐标系
与最佳内容单元.
实现滚动基两样:置内容大小,绘画
.但你不必处理WM_PAINT
及相对当前滚动位置画哪部分
.而是SetScrollSize
并实现DoPaint
.由任意带CScrollImpl
重载的SetScrollSize
置大小.
void SetScrollSize(int xMin, int yMin, int xMax, int yMax, BOOL bRedraw = TRUE);
void SetScrollSize(RECT& rcScroll, BOOL bRedraw = TRUE);
void SetScrollSize(int cx, int cy, BOOL bRedraw = TRUE);
void SetScrollSize(SIZE size, BOOL bRedraw = NULL);
改变内容大小时,调用SetScrollSize
.
class CBitmapView : public CScrollWindowImpl<CBitmapView>
{
...
void SetBitmap(HBITMAP hBitmap)
{
if(!m_bmp.IsNull())
m_bmp.DeleteObject();
m_bmp = hBitmap;
if(!m_bmp.IsNull())
m_bmp.GetSize(m_size);
else
m_size.cx = m_size.cy = 1;
SetScrollOffset(0, 0, FALSE);
SetScrollSize(m_size);
}
...
};
绘制时,CScrollWindowImpl
处理了WM_PAINT
消息,并转向子类DoPaint
函数.用适当Win32视口
干DoPaint
,允许你在当前滚动位置绘制你的内容
.
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);//T类型.
}
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);
}
}
...
};
//这是视图窗口
用缺省值填充结构,不关心是通过结构参数还是直接访问.最后DoModal
生成对话框.
所有通用对话框类允许你从他们简单派生类,并实现消息映射,重载函数,或定制对话框的行为
.
LRESULT CMainFrame::OnFileOpen(WORD, WORD, HWND, BOOL&)
{
// 传入TRUE 作为特定参数来指示这是文件打开对话框,而文件保存对话框
CFileDialog dlg(TRUE, _T("bmp"), NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
_T("Bitmap Files (*.bmp)\0*.bmp\0All Files (*.*)\0*.*\0"), m_hWnd);//从指定类型.
// 所有模式通用对话框封装类都有DoModal方法
if(dlg.DoModal() == IDOK)
{
HBITMAP hBmp = (HBITMAP)::LoadImage(NULL, dlg.m_szFileName, IMAGE_BITMAP, 0, 0,
LR_DEFAULTCOLOR | LR_LOADFROMFILE);
m_view.SetBitmap(hBmp);//加载位图
}
return 0;
}
//封装列表框
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);
}
...
};
//构建
BOOL CMainFrame::BuildList(HWND hwndLB, CRecentDocumentList& mru)
{
CListBox lb(hwndLB);
lb.ResetContent();
int nSize = mru.m_arrDocs.GetSize();
for(int i = 0; i < nSize; i++)
lb.InsertString(-1, mru.m_arrDocs[i].szDocName);//列表框
if(nSize > 0)
{
lb.SetCurSel(0);
lb.SetTopIndex(0);//置索引
}
return TRUE;
}
选中时,发送WM_COMMAND
消息.
template <class T, class TBase = CWindow, class TWinTraits = CControlWinTraits>
class ATL_NO_VTABLE CWindowImpl : public CWindowImplBaseT< TBase, TWinTraits > {...};
//实现
TBase
可从无为的CWindow
类改为任意我们喜欢的类.C++继承
也能有效父类化win32
,允许我们在列表框
处理前处理/替换
列表框类消息.
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;
}
};
wtl
支持打印
是CPrintJob
的StartPrintJob
方法.必须传递支持实现打印每一页的IPrintJobInfo
接口对象.
// CPrintJob 使用的缺省回调
// 这不是一个COM接口
class ATL_NO_VTABLE IPrintJobInfo
{
public:
virtual void BeginPrintJob(HDC hDC)=0;
virtual void EndPrintJob(HDC hDC, bool bAborted)=0;
virtual void PrePrintPage(UINT nPage, HDC hDC)=0;
virtual bool PrintPage(UINT nPage, HDC hDC)=0;
virtual void PostPrintPage(UINT nPage, HDC hDC)=0;
virtual DEVMODE* GetNewDevModeForPage(UINT nLastPage, UINT nPage)=0;
virtual bool IsValidPage(UINT nPage);
};
CPrintJobInfo
重载了除PrintPage
外方法,你必须实现它.重载其他方法,来定制.然后调用重载的CPrintJobInfo
方法来打印.
如框架:
class CMainFrame : ..., public CPrintJobInfo
{
...
CPrinter m_printer;
CDevMode m_devmode;
RECT m_rcMargin;
};
//两个回调
bool CMainFrame::IsValidPage(UINT nPage)
{
return (nPage == 0); // 我们只有一页
}
bool CMainFrame::PrintPage(UINT nPage, HDC hDC)
{
if (nPage >= 1)
return false; // 我们只有一页
ATLASSERT(!m_view.m_bmp.IsNull());
...
// 打印
dc.StretchBlt(xOff, yOff, cxBlt, cyBlt, dcMem, 0, 0, cx, cy, SRCCOPY);
return true;
}
IsValidPage
是否有效,PrintPage
实际打印,拉伸位图
,最好允许用户指定打印机/页面布局
,CPrintDialog和CPageSetup
,先构造,再调用DoModal
见前面.CPrintPreview
基类,提供SetPrintPreviewInfo
方法.
CPrintPreview
为打印预览基类.其创建增强型元文件
.窗口中调用DoPaint
来显示.
CPrintPreviewWindow
为完整实现.
LRESULT CMainFrame::OnFilePrintPreview(WORD, WORD, HWND, BOOL&)
{
TogglePrintPreview();
return 0;
}
void CMainFrame::TogglePrintPreview()
{
if(m_bPrintPreview) // 关闭打印预览窗口
{
ATLASSERT(m_hWndClient == m_wndPreview.m_hWnd);//切换视图就完了.
m_hWndClient = m_view;
m_view.ShowWindow(SW_SHOW);
m_wndPreview.DestroyWindow();//消灭
}
else // 显示打印预览窗口
{
ATLASSERT(m_hWndClient == m_view.m_hWnd);
m_wndPreview.SetPrintPreviewInfo(m_printer, m_devmode.m_pDevMode,this, 0, 0);
m_wndPreview.SetPage(0);//设置
m_wndPreview.Create(m_hWnd, rcDefault, NULL, 0, WS_EX_CLIENTEDGE);//创建
m_view.ShowWindow(SW_HIDE);//显示
m_hWndClient = m_wndPreview;//客户区
}//一个时候只一个客户区
m_bPrintPreview = !m_bPrintPreview;
UpdateLayout();
}
CPrintJob
类允许不同线程后台打印
,处理打印中取消
.属性页
class CPageOne : public CPropertyPageImpl<CPageOne>
{
public:
enum { IDD = IDD_PROP_PAGE1 }; // 要求的,跟CDialogImpl一样
LPCTSTR m_lpstrFilePath;
CFileName m_filename;
CPageOne() : m_lpstrFilePath(NULL) { }
BEGIN_MSG_MAP(CPageOne)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
CHAIN_MSG_MAP(CPropertyPageImpl<CPageOne>)
END_MSG_MAP()
LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&);
};
class CPageTwo : public CPropertyPageImpl<CPageTwo>
{
public:
enum { IDD = IDD_PROP_PAGE2 };
LPCTSTR m_lpstrFilePath;
CBitmapHandle m_bmp;
CPageTwo() : m_lpstrFilePath(NULL){ }
BEGIN_MSG_MAP(CPageTwo)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
CHAIN_MSG_MAP(CPropertyPageImpl<CPageTwo>)
END_MSG_MAP()
LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&);
};
class CPageThree : public CPropertyPageImpl<CPageThree>
{
public:
enum { IDD = IDD_PROP_PAGE3 };
BEGIN_MSG_MAP(CPageThree)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
CHAIN_MSG_MAP(CPropertyPageImpl<CPageThree>)
END_MSG_MAP()
LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&);
};
class CBmpProperties : public CPropertySheetImpl<CBmpProperties>
{
public:
CPageOne m_page1;
CPageTwo m_page2;
CPageThree m_page3;
CBmpProperties()
{
m_psh.dwFlags |= PSH_NOAPPLYNOW;//关闭应用,在此操作属性表标记
AddPage(m_page1);
AddPage(m_page2);
AddPage(m_page3);//加至页列表
SetActivePage(1);//首页
SetTitle(_T("位图属性"));//属性表标题
}
void SetFileInfo(LPCTSTR lpstrFilePath, HBITMAP hBitmap);
BEGIN_MSG_MAP(CBmpProperties)
CHAIN_MSG_MAP(CPropertySheetImpl<CBmpProperties>)//基类
END_MSG_MAP()
};
LRESULT CMainFrame::OnViewProperties(WORD, WORD, HWND, BOOL&)
{
CBmpProperties prop;
if(lstrlen(m_szFilePath) > 0) // 我们有一个文件名
prop.SetFileInfo(m_szFilePath, NULL);
else // 然后必须是剪切板
prop.SetFileInfo(NULL, m_view.m_bmp.m_hBitmap);
prop.DoModal();//查看属性表时.
return 0;
}
显示位图,好界面,更新界面.消息过滤,是像流水线一个挨个处理.
int Run(LPTSTR = NULL, int nCmdShow = SW_SHOWDEFAULT)
{
CMessageLoop theLoop;
_Module.AddMessageLoop(&theLoop);//主模块
CMainFrame wndMain;
if(wndMain.CreateEx() == NULL)
return 0;
wndMain.ShowWindow(nCmdShow);
int nRet = theLoop.Run();//跑
_Module.RemoveMessageLoop();
return nRet;
}//消息循环
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR lpstrCmdLine, int nCmdShow)
{//主程序
INITCOMMONCONTROLSEX iccx;
iccx.dwSize = sizeof(iccx);
iccx.dwICC = ICC_COOL_CLASSES | ICC_BAR_CLASSES;
BOOL bRet = ::InitCommonControlsEx(&iccx);
bRet;//常见控件
ATLASSERT(bRet);
HRESULT hRes = _Module.Init(NULL, hInstance);
hRes;
ATLASSERT(SUCCEEDED(hRes));
int nRet = Run(lpstrCmdLine, nCmdShow);
_Module.Term();
return nRet;
}
主应用:
class CAppModule : public CComModule
{
public:
DWORD m_dwMainThreadID;
CSimpleMap<DWORD, CMessageLoop*>* m_pMsgLoopMap;//消息循环.
CSimpleArray<HWND>* m_pSettingChangeNotify;//设置通知
// 重载CComModule::Init 和Term
HRESULT Init(_ATL_OBJMAP_ENTRY* pObjMap, HINSTANCE hInstance, const GUID* pLibID = NULL);
void Term();
// 消息循环映射方法
BOOL AddMessageLoop(CMessageLoop* pMsgLoop);
BOOL RemoveMessageLoop();
CMessageLoop* GetMessageLoop(DWORD dwThreadID) const;
// 设置改变通知方法
BOOL AddSettingChangeNotify(HWND hWnd);
BOOL RemoveSettingChangeNotify(HWND hWnd);
//加设置改变/删设置改变通知
...
};
消息循环的跑
int CMessageLoop::Run()
{
BOOL bDoIdle = TRUE;
int nIdleCount = 0;
BOOL bRet;
for(;;)
{
while(!::PeekMessage(&m_msg, NULL, 0, 0, PM_NOREMOVE) && bDoIdle)
{
if(!OnIdle(nIdleCount++))
bDoIdle = FALSE;
}
bRet = ::GetMessage(&m_msg, NULL, 0, 0);
if(bRet == -1)
continue; // error, don't process
else if(!bRet)break; // WM_QUIT,退出
if(!PreTranslateMessage(&m_msg))
{//遍历登记在本线程CMessageFilter类的实现列表
::TranslateMessage(&m_msg);
::DispatchMessage(&m_msg);
}//分发
if(IsIdleMessage(&m_msg))
{
bDoIdle = TRUE;
nIdleCount = 0;
}
}
return (int)m_msg.wParam;
}//消息循环
CMessageFilter
接口:
class CMessageFilter
{
public:
virtual BOOL PreTranslateMessage(MSG* pMsg) = 0;
};
向导生成:
class CMainFrame : ..., public CMessageFilter
{
...
virtual BOOL PreTranslateMessage(MSG* pMsg)
{
if(CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg))return TRUE;
return m_view.PreTranslateMessage(pMsg);
}//先基类处理,再本视图处理.
};
空闲处理器:
class CIdleHandler
{
public:
virtual BOOL OnIdle() = 0;
};
BOOL CMessageLoop::AddIdleHandler(CIdleHandler* pIdleHandler)
{
return m_aIdleHandler.Add(pIdleHandler);
}
BOOL CMessageLoop::RemoveIdleHandler(CIdleHandler* pIdleHandler)
{
return m_aIdleHandler.Remove(pIdleHandler);
}
更新界面:
class CMainFrame : ..., public CUpdateUI<CMainFrame>
{
...
BEGIN_UPDATE_UI_MAP(CMainFrame)
UPDATE_ELEMENT(ID_FILE_PRINT, UPDUI_MENUPOPUP | UPDUI_TOOLBAR)
UPDATE_ELEMENT(ID_FILE_PRINT_PREVIEW, UPDUI_MENUPOPUP | UPDUI_TOOLBAR)
UPDATE_ELEMENT(ID_EDIT_COPY, UPDUI_MENUPOPUP | UPDUI_TOOLBAR)
UPDATE_ELEMENT(ID_EDIT_PASTE, UPDUI_MENUPOPUP | UPDUI_TOOLBAR)
UPDATE_ELEMENT(ID_EDIT_CLEAR, UPDUI_MENUPOPUP | UPDUI_TOOLBAR)
UPDATE_ELEMENT(ID_VIEW_TOOLBAR, UPDUI_MENUPOPUP)
UPDATE_ELEMENT(ID_VIEW_STATUS_BAR, UPDUI_MENUPOPUP)
UPDATE_ELEMENT(ID_VIEW_PROPERTIES, UPDUI_MENUPOPUP | UPDUI_TOOLBAR)
UPDATE_ELEMENT(ID_RECENT_BTN, UPDUI_TOOLBAR)
END_UPDATE_UI_MAP()
};
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现