MFC自绘-WzdDialog窗口类

MFC窗口需要绑定窗口资源或者重载OnCreate()动态添加,为了窗口类的独立性,因此采用绑定窗口资源的方式让生成的窗口类继承WzdDialog类的方式进行自绘。
头文件:

#include "WzdImage.h"
#include "WzdButton.h"

class CWzdDialog : public CDialog
{
    DECLARE_DYNAMIC(CWzdDialog)

public:
    CWzdDialog(UINT nIDTemplate, CWnd* pParent = NULL);   // 标准构造函数
    virtual ~CWzdDialog();

    // 加载背景图片
    bool LoadBackImg(LPCTSTR pszResourcePath);
    bool LoadBackImg(HINSTANCE hInstance, LPCTSTR pszResourceName);

    // 设置透明度
    void SetAlphaDepth(unsigned int nAlphaDepth);

protected:
    CWzdImage m_backImg;           // 窗口背景
    CWzdButton m_btClose;          // 关闭按钮
    CWzdButton m_btMax;            // 最大化按钮
    CWzdButton m_btMin;            // 最小化按钮

    bool m_isInitButton; // 是否已经初始化按钮
    bool m_isZoomed;     // 是否已经最大化
    bool m_bExtrude;     // 能否拉伸
    unsigned int m_alphaDepth;  // 透明度

    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持

    // 提供接口给继承窗口类进行额外的绘制
    virtual void OnClientDraw(CDC*pDC,int nWidth,int nHeight){}

    // 重写函数
    virtual BOOL OnInitDialog();
    virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);

    // 消息响应
    afx_msg void OnPaint();
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnNcLButtonDown(UINT nHitTest, CPoint point);
    afx_msg LRESULT OnNcHitTest(CPoint point);
    afx_msg BOOL OnEraseBkgnd(CDC* pDC);

    DECLARE_MESSAGE_MAP()
};
};

首先需要WzdImage类进行一些图片绘制,所以包含WzdImage.h。需要关闭、最大化(恢复)、最小化按钮,所以需要WzdButton.h,这个按钮类下一篇再讲。
WzdDialog窗口类继承了CDialog类,构造函数需要提供窗口资源ID(nIDTemplate)。
同样有两种加载图片的方式:图片路劲或资源名称。
窗口需要有3个标准按钮:关闭、最大化(恢复)、最小化。
3个按钮是否初始化,保证窗口正常;是否最大化,进行窗口缩放;能否拉伸,窗口能否拉大缩小。
OnClientDraw(),支持继承类自己绘制其他东西。
SetAlphaDepth(),支持窗口透明度,默认为不透明(255)。

从构造函数开始,简单初始化一些变量

CWzdDialog::CWzdDialog(UINT nIDTemplate, CWnd* pParent /*=NULL*/)
: CDialog(nIDTemplate, pParent)
, m_isInitButton(false)
, m_isZoomed(false)
, m_bExtrude(true)
, m_alphaDepth(255)
{
}

窗口初始化函数: 改变窗口旧样式,变为无标题栏无边框的窗口,其中WS_EX_LAYERED支持窗口透明。初始化按钮。

BOOL CWzdDialog::OnInitDialog()
{
    CDialog::OnInitDialog();

    DWORD dwStyle = GetStyle();
    DWORD dwNewStyle = WS_OVERLAPPED | WS_VISIBLE | WS_SYSMENU | WS_MINIMIZEBOX |
                       WS_MAXIMIZEBOX | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
    dwNewStyle &= dwStyle;
    SetWindowLong(m_hWnd, GWL_STYLE, dwNewStyle);

    DWORD dwExStyle = GetExStyle();
    DWORD dwNewExStyle = WS_EX_LEFT | WS_EX_LTRREADING | WS_EX_RIGHTSCROLLBAR;
    dwNewExStyle &= dwExStyle;
    SetWindowLong(m_hWnd, GWL_EXSTYLE, dwNewExStyle | WS_EX_LAYERED);

    // 默认开始不透明
    SetLayeredWindowAttributes(0, m_alphaDepth, LWA_ALPHA);

    // 初始化按钮
    CRect rcControl(0,0,0,0);
    m_btClose.Create(NULL,WS_CHILD | WS_VISIBLE, rcControl, this, IDCANCEL);
    m_btClose.SetButtonImage(_T(".//res//close.png"));
    m_btMax.Create(NULL, WS_CHILD | WS_VISIBLE, rcControl, this, IDC_BTN_MAX);
    m_btMax.SetButtonImage(_T(".//res//maxsize.png"));
    m_btMin.Create(NULL,WS_CHILD | WS_VISIBLE, rcControl, this, IDC_BTN_MIN);
    m_btMin.SetButtonImage(_T(".//res//minsize.png"));
    m_isInitButton = true;

    return TRUE;  // return TRUE unless you set the focus to a control
    // 异常: OCX 属性页应返回 FALSE
}

重载OnPaint(),用双缓冲来解决重画时的画面闪烁。双缓冲大体的思路是先将画面保存到一张图片中,最后才显示到桌面上。

void CWzdDialog::OnPaint()
{
    CPaintDC dc(this); // device context for painting

    CRect rcClient;
    CDC memDC; // 用于缓冲作图的内存DC
    CBitmap bmp; // 内存中承载临时图象的位图

    GetClientRect(&rcClient);
    memDC.CreateCompatibleDC(&dc);
    bmp.CreateCompatibleBitmap(&dc, rcClient.Width(), rcClient.Height());
    memDC.SelectObject(&bmp);   // 将位图选择进内存DC
    memDC.FillSolidRect(&rcClient, dc.GetBkColor());   // 按原来背景填充客户区
    m_backImg.DrawImage(&memDC, 0, 0, rcClient.Width(), rcClient.Height());
    // 将缓冲区内容绘画到界面
    dc.BitBlt(rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(), &memDC, 0, 0, SRCCOPY);

    //清理资源
    memDC.DeleteDC();
    bmp.DeleteObject();
}

同时,还要重载OnEraseBkgnd(),直接返回TRUE防止清除背景带来的闪烁。

BOOL CWzdDialog::OnEraseBkgnd(CDC* pDC)
{
    return TRUE;
    return CDialog::OnEraseBkgnd(pDC);
}

在初始化按钮的时候,按钮的位置都是0,需要动态改变按钮的位置,响应WM_SIZE消息,添加OnSize()函数。uFlags设定不改变按钮的一些状态,LockWindowUpdate()和UnlockWindowUpdate()让窗口在大小改变完成后才重画界面。DeferWindowPos()系列函数用来移动按钮位置。

void CWzdDialog::OnSize(UINT nType, int cx, int cy)
{
    CDialog::OnSize(nType, cx, cy);

    if (!m_isInitButton) return;

    const UINT uFlags = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOCOPYBITS | SWP_NOSIZE;
    LockWindowUpdate();    // 锁定屏幕

    // 移动控件
    HDWP hDwp = BeginDeferWindowPos(32);
    CRect rcButton;

    m_btClose.GetWindowRect(&rcButton);
    if (m_isZoomed) // 最大化时显示恢复样式,反之显示最大化样式
        m_btMax.SetButtonImage(_T(".//res//restore.png"));
    else
        m_btMax.SetButtonImage(_T(".//res//maxsize.png"));
    DeferWindowPos(hDwp, m_btClose, NULL, cx - rcButton.Width() - 2, 2, 1, 0, uFlags);
    DeferWindowPos(hDwp, m_btMax, NULL, cx - rcButton.Width() * 2 - 2, 2, 0, 0, uFlags);
    DeferWindowPos(hDwp, m_btMin, NULL, cx - rcButton.Width() * 3 - 2, 2, 0, 0, uFlags);

    EndDeferWindowPos(hDwp);

    // 重画界面
    Invalidate(FALSE);
    UpdateWindow();
    UnlockWindowUpdate();
}

按钮的显示完成了,接着就是响应。重载OnCommand函数

BOOL CWzdDialog::OnCommand(WPARAM wParam, LPARAM lParam)
{
    switch (LOWORD(wParam))
    {
    case IDC_BTN_MAX:   //最大化消息
        {
            static CRect rcClient(0,0,0,0);
            if (m_isZoomed)
            {
                m_isZoomed = false;
                MoveWindow(&rcClient);
            }
            else
            {
                GetWindowRect(&rcClient); // 保存现在的窗口位置
                CRect rc;
                SystemParametersInfo(SPI_GETWORKAREA, 0, &rc, 0);
                m_isZoomed = true;
                MoveWindow(&rc);
            }
            break;
        }
    case IDC_BTN_MIN:   //最小化消息
        {
            ShowWindow(SW_MINIMIZE);
            break;
        }
    }

    return CDialog::OnCommand(wParam, lParam);
}

接着实现窗口的拖动,重载OnLButtonDown()函数模拟左键点击拖动。

void CWzdDialog::OnLButtonDown(UINT nFlags, CPoint point)
{
    if (!m_isZoomed)
    {
        // 模拟左键点击拖动
        PostMessage(WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(point.x,point.y));
        return;
    }

    CDialog::OnLButtonDown(nFlags, point);
}

最后是拉动边框改变窗口大小,先重载OnNcHitTest()模拟鼠标移动到边框时,从而显示双箭头。BORDER_WIDTH时边框宽度的宏定义。

LRESULT CWzdDialog::OnNcHitTest(CPoint point)
{
    if (m_bExtrude && !m_isZoomed)
    {
        CRect rcWindow;
        GetWindowRect(&rcWindow);

        if ((point.x <= rcWindow.left+BORDER_WIDTH) && (point.y>BORDER_WIDTH) && (point.y<rcWindow.bottom-BORDER_WIDTH*2) )
            return HTLEFT;
        else if ((point.x >= rcWindow.right-BORDER_WIDTH) && (point.y>BORDER_WIDTH) && (point.y<rcWindow.bottom-BORDER_WIDTH*2) )
            return HTRIGHT;
        else if ((point.y <= rcWindow.top+BORDER_WIDTH) && (point.x>BORDER_WIDTH) && (point.x<rcWindow.right-BORDER_WIDTH*2))
            return HTTOP;
        else if ((point.y >= rcWindow.bottom-BORDER_WIDTH) && (point.x>BORDER_WIDTH) && (point.x<rcWindow.right-BORDER_WIDTH*2))
            return HTBOTTOM;
        else if ((point.x <= rcWindow.left+BORDER_WIDTH*2) && (point.y <= rcWindow.top+BORDER_WIDTH*2))
            return HTTOPLEFT;
        else if ((point.x >= rcWindow.right-BORDER_WIDTH*2) && (point.y <= rcWindow.top+BORDER_WIDTH*2))
            return HTTOPRIGHT;
        else if ((point.x <= rcWindow.left+BORDER_WIDTH*2) && (point.y >= rcWindow.bottom-BORDER_WIDTH*2))
            return HTBOTTOMLEFT;
        else if ((point.x >= rcWindow.right-BORDER_WIDTH*2) && (point.y >= rcWindow.bottom-BORDER_WIDTH*2))
            return HTBOTTOMRIGHT;
        else
            return CWnd::OnNcHitTest(point);
    }

    return CDialog::OnNcHitTest(point);
}

再模拟鼠标点击边框进行拖动,重载OnNcLButtonDown(),发送响应消息进行模拟。

void CWzdDialog::OnNcLButtonDown(UINT nHitTest, CPoint point)
{
    if(m_bExtrude)
    {
        if (nHitTest == HTTOP)
            SendMessage( WM_SYSCOMMAND, SC_SIZE | WMSZ_TOP, MAKELPARAM(point.x, point.y));
        else if (nHitTest == HTBOTTOM)
            SendMessage( WM_SYSCOMMAND, SC_SIZE | WMSZ_BOTTOM, MAKELPARAM(point.x, point.y));
        else if (nHitTest == HTLEFT)
            SendMessage( WM_SYSCOMMAND, SC_SIZE | WMSZ_LEFT, MAKELPARAM(point.x, point.y));
        else if (nHitTest == HTRIGHT)
            SendMessage( WM_SYSCOMMAND, SC_SIZE | WMSZ_RIGHT, MAKELPARAM(point.x, point.y));
        else if (nHitTest == HTTOPLEFT)
            SendMessage( WM_SYSCOMMAND, SC_SIZE | WMSZ_TOPLEFT, MAKELPARAM(point.x, point.y));
        else if (nHitTest == HTTOPRIGHT)
            SendMessage( WM_SYSCOMMAND, SC_SIZE | WMSZ_TOPRIGHT, MAKELPARAM(point.x, point.y));
        else if (nHitTest == HTBOTTOMLEFT)
            SendMessage( WM_SYSCOMMAND, SC_SIZE | WMSZ_BOTTOMLEFT, MAKELPARAM(point.x, point.y));
        else if (nHitTest == HTBOTTOMRIGHT)
            SendMessage(WM_SYSCOMMAND, SC_SIZE | WMSZ_BOTTOMRIGHT, MAKELPARAM(point.x, point.y));
        else if (nHitTest==HTCAPTION)
            SendMessage(WM_SYSCOMMAND, SC_MOVE | WMSZ_TOPLEFT, MAKELPARAM(point.x, point.y));
    }

    CDialog::OnNcLButtonDown(nHitTest, point);
}

猜你喜欢

转载自blog.csdn.net/wizardtoh/article/details/47956041