wtl界面3

WM_CONTEXTMENU处理右键环境菜单.
命令栏CCommandBarCtrl控件,有TrackPopupMenu方法,与win32TrackPopupMenuEx一样.

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继承,你可加窗口类并链入消息映射.CScrollWindowImplCScrollImpl和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支持打印CPrintJobStartPrintJob方法.必须传递支持实现打印每一页的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()
};
posted @   zjh6  阅读(24)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示