65.9K
CodeProject 正在变化。 阅读更多。
Home

在设计时绘制复杂的 ATL/ActiveX 控件

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

1999年12月1日

CPOL

6分钟阅读

viewsIcon

119309

downloadIcon

2306

一篇讨论在设计时绘制控件的文章

首先,让我们讨论问题,然后我将向您展示解决方案。

问题在于,如果您使用 ATL 创建控件,并且该控件具有子窗口(例如这里的 TreeView),它在运行时会正常工作。通常,它在设计时也会正常工作,但在某些容器中则不行。这是因为某些容器在设计时不会创建您的控件,它们仅实例化它们(通过 CoCreateInstance)并通过 IViewObject 接口进行绘制。它们直接调用 OnDraw(ATL_DRAWINFO& di) 并提供一个设备上下文供您绘制。这可能是一个图元文件、屏幕设备或任何其他设备。

如果调整控件的大小,它可能会在短时间内通过 IOLEObject::DoVerbInPlaceActive(...) 创建,并通过 IOLEObject::Close(...) 销毁。

MS VC++ 中的 ResourceEditor(对话框,见上图)是在设计时以这种方式显示控件的一个容器。

我将向您展示一些图表,以便您了解该怎么做。源代码在文档末尾的示例中。您有两种在设计时绘制控件的可能性(如 OnDraw 的图表中所示)。

OnCreate 控件的函数通过调用基类来创建基础控件。子窗口的创建应放在一个单独的函数(CreateChildWindows)中,这样您就可以在不创建控件的情况下创建它们。

CreateChildWindows CreateChildWindows 检查是否存在父窗口。如果不存在,它将使用 CreateHiddenWindow 创建一个。如果子窗口不存在,它们将被创建(在新的父窗口上)或我们将在窗口上调用 SetParent。窗口可能已经存在,因为有时控件会被原地激活(例如调整大小),在这种情况下我们只会设置一个新的父窗口。

CreateHiddenWindow

如果已经存在一个“HiddenWindow”,我们就返回它,否则我们就创建一个新的,所有参数都设置为 0,窗口类为 _T("Static")。

对于第二个解决方案,我们还必须手动删除 WS_BORDER 和 WS_CAPTION 样式,因为它们是为顶级窗口自动设置的(我们使用 Get / SetWindowLong)。


Destruktor 我们必须在控件的析构函数中销毁“HiddenWindow”。

解决方案一

绘制子窗口,这些子窗口在重载的 WM_PAINT 消息处理程序中进行所有绘制,让系统不做任何事情(没有通用控件!)。

绘制代码应放在一个单独的函数中,例如 OnDraw(HDC hdc, RECT& rc),可以从外部调用。因此,WM_PAINT - 消息处理程序仅获取 DC 和客户端矩形并调用 OnDraw,我们也可以从控件的 OnDraw 中这样做。在 OnDraw 函数中,您不得调用任何返回窗口状态或属性的标准函数(如 DC 或 RECT!)!接下来,您可以调用默认的绘制例程(通过 DefWindowProc),因为窗口存在,但它是隐藏的!!!

OnDrawEinfach

如果控件在调用 OnDraw 时没有窗口(这一定是设计模式,此时 OnDraw 可能通过 IViewObject 调用,否则就会有窗口!)。我们测试窗口是否已存在(子窗口和“HiddenWindow”)。如果不存在,则调用 CreateChildWindows(0) 来创建它们。

然后我们遍历所有子窗口并调用 OnDraw 函数以及参数(我们必须提前计算矩形)。如果您需要任何用于绘制的成员,它们必须提前初始化!

第二个解决方案

  • 绘制子窗口,这些子窗口调用它们的 WM_PAINT 消息处理程序以执行标准绘制(如树控件)。

    这一点会遇到问题,因为您无法将绘制代码放入一个单独的函数中,因为它不是您自己的。因此,您必须调用树控件的默认处理程序。我们可以使用 ComCtl32.dll 版本 5.80。早期版本也能做到,但并非所有控件都支持。

    我们将窗口绘制到位图,因此通常我们只需要将位图blit到容器设备(该位图是控件的一个成员)。只有当旧位图无效时(属性更改、大小更改...),我们才创建一个新位图。


    第二个解决方案(a,ComCtl32.dll 版本 >= 5.80)

    在此版本的 ComCtl32.dll 中,您可以调用 SendMessage(WM_PAINT, WPARAM(hdc), 0),窗口将绘制到给定的设备上下文。但要小心,它绘制到 (0 / 0),因此您必须移动视口(通过 SetViewportOrgEx),并且窗口不会绘制非客户端区域。这可以通过 DrawEdge(用于边框)完成。

    OnDrawFensterSendMessage 如果控件在调用 OnDraw 时没有窗口(这一定是设计模式,此时 OnDraw 可能通过 IViewObject 调用,否则就会有窗口!)。
    现在,我们检查是否必须重新创建位图(变量 m_bRecreateBitmap 和 m_sizeBmp)。如果不是,我们就进入 OnDraw 函数的 Blitting 部分。
    • 如果需要重新创建,我们检查子窗口是否已存在(子窗口和“HiddenWindow”)。如果不存在,则调用 CreateChildWindows(0) 来创建它们。
    • 销毁旧位图
    • 创建新位图(使用新大小,位于 di.prcBounds)
    • 计算所有窗口的偏移量,就像您在控件上创建它们一样,以便您可以将窗口绘制到正确的位置。
    • 现在通过以下方式将所有窗口绘制到它们的位置:
      • ::SetWindowPos(..., rcThisChild, ...)
      • ::SetViewportOrgEx(...)
      • ::SendMessage(hwndChild, WM_PAINT, (WPARAM) hdc, 0)
    • 接下来,绘制窗口的非客户端区域,例如边框、滚动条等。
      • ::SetViewportOrgEx(hdc, (0/0) of the control (save at start of OnDraw),...)
      • ::DrawEdge(...)
      • 将滚动条绘制为占位符(使用 FillRect 和 DrawEdge,不会有箭头,但没关系!)
    现在,将位图blit到容器设备(di.hdcDraw)。

    第二个解决方案(b,ComCtl32.dll 版本 < 5.80)

    如果您使用的是 ComCtl32.dll 的早期版本,则可能无法正常工作。在这种情况下,您需要显示 HiddenWindow 来将客户端矩形blit到位图。然后您可以隐藏窗口。这有点丑陋,因为窗口会跳到前面,然后在短时间内(约四分之一秒或半秒)隐藏自己。

    OnDrawFenster 如果控件在调用 OnDraw 时没有窗口(这一定是设计模式,此时 OnDraw 可能通过 IViewObject 调用,否则就会有窗口!)。
    现在,我们检查是否必须重新创建位图(变量 m_bRecreateBitmap 和 m_sizeBmp)。如果不是,我们就进入 OnDraw 函数的 Blitting 部分。
    • 如果需要重新创建,我们检查子窗口是否已存在(子窗口和“HiddenWindow”)。如果不存在,则调用 CreateChildWindows(0) 来创建它们。
    • 销毁旧位图
    • 创建新位图(使用新大小,位于 di.prcBounds)
    • 显示“HiddenWindow”(通过 ::SetWindowPos(..., HWND_TOP; ..., ... | SWP_SHOWWINDOW))
    • 为所有客户端窗口调用 ::SetWindowPos
    • 调用 UpdateWindow(m_hHiddenWindow) 以强制重绘(包括客户端区域)。
    • 将 HiddenWindow 的内容 BitBlt 到本地位图。
    • 隐藏 HiddenWindow。
    现在,将位图blit到容器设备(di.hdcDraw)。

    好了,我们完成了。现在是这个类的源代码(我已经用...替换了无意义的部分,代码是针对解决方案 2 a 的)。

    要测试此源代码,您只需创建一个新的 MFC 项目,其中包含一个对话框,并将新控件放在上面。然后您将看到我们的新绘制例程生效。如果您编译项目并运行它,您将看到正常的绘制例程。

    DrawingDesignmodeObj.h

    class ATL_NO_VTABLE CDesignTimePaintingObj : 
        public ...
    {
    public:
    
        /////////////////////////////////////////////////////////////////////////
        // ATL - Makros
        /////////////////////////////////////////////////////////////////////////
    
    ...
    
        /////////////////////////////////////////////////////////////////////////
        // protected functions and Members
        /////////////////////////////////////////////////////////////////////////
    protected:
        // the contained control (a tree control)
        CContainedWindow m_wndTree;        // window - object
    
        // variable for the OnSize Messagehandler
        bool m_bInitialised;            // Indicates, that the Childwindows are created,
                                        // so we can size them
    
        // variables for designtimepainting
        HWND m_hHiddenWnd;                // handle of the "HiddenWindow"
        HBITMAP m_hBmp;                    // handle of the Bitmap used for faster painting
        bool m_bRecreateBitmap;            // indicates, wether the bitmap must be 
                                           // recreated or not
        SIZE m_sizeBmp;                    // size of the actual bitmap
    
        /////////////////////////////////////////////////////////////////////////
        // helperfunctions (declared in DesignTimePaintingObj.cpp)
        /////////////////////////////////////////////////////////////////////////
        void SizeViews();
        void FillTree(void);
        BOOL CreateChildWindows(HWND hParent);
        void CreateHiddenWindow();
    
        /////////////////////////////////////////////////////////////////////////
        // public functions and members
        /////////////////////////////////////////////////////////////////////////
    public:
        /////////////////////////////////////////////////////////////////////////
        // standard functions (declared in DesignTimePaintingObj.cpp)
        /////////////////////////////////////////////////////////////////////////
        CDesignTimePaintingObj();
        ~CDesignTimePaintingObj();
        HRESULT OnDraw(ATL_DRAWINFO& di);
        virtual HWND Create(HWND hWndParent, RECT& rcPos, LPCTSTR szWindowName = NULL,
                            DWORD dwStyle = WS_CHILD | WS_VISIBLE, DWORD dwExStyle = 0,
                            UINT nID = 0);
    
        /////////////////////////////////////////////////////////////////////////
        // messagehandlers (declared in DesignTimePaintingObj.cpp)
        /////////////////////////////////////////////////////////////////////////
        LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
        LRESULT OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
    };

    DrawingDesignmodeObj.cpp

    ...
    
    /////////////////////////////////////////////////////////////////////////
    // standard functions
    /////////////////////////////////////////////////////////////////////////
    
    CDesignTimePaintingObj::CDesignTimePaintingObj() :    
                            m_wndTree(_T("SysTreeView32"), this, 1)
    {
        // This is only initialising, nothig important
        ...
    }
    
    CDesignTimePaintingObj::~CDesignTimePaintingObj()
    {
        // free all members
        ::DeleteObject(m_hBmp);
        ::DestroyWindow(m_hHiddenWnd);
    }
    
    // In the Create - function, we have to create the ContainedWindows via their
    // Create - functions
    HWND CDesignTimePaintingObj::Create(HWND hWndParent, RECT& rcPos, LPCTSTR szWindowName,
        DWORD dwStyle, DWORD dwExStyle, UINT nID)
    {
        m_bInitialised = false;
    
        HWND hWnd = CWindowImpl<CDesignTimePaintingObj>::Create(hWndParent, rcPos,
            szWindowName, dwStyle, dwExStyle, nID);
    
        if(hWnd != NULL)
        {
            CreateChildWindows(hWnd);
        }
    
        m_bInitialised = true;
        SizeViews();
    
        return hWnd;
    }
    
    // This function does nothing, if the control was instantiated and created.
    // But some containers instantiate controls
    // without creating them, and they draw them through the interface IViewObject::Draw.
    // So this is the interisting part of the project
    HRESULT CDesignTimePaintingObj::OnDraw(ATL_DRAWINFO& di)
    {
        RECT& rc = *(RECT*)di.prcBounds;
    
        // we are drawn throug IViewObject?
        if(m_hWnd == 0)
        {
            // was there a resize since the last painting?
            SIZE newSize = {abs(rc.right - rc.left), abs(rc.bottom - rc.top)};
    
            if((newSize.cx != m_sizeBmp.cx) || (newSize.cy != m_sizeBmp.cy))
            {
                m_bRecreateBitmap = true;
            }
    
            // should we recreate the Bitmap?
            if(m_bRecreateBitmap == true)
            {
                // First, create the Windows, so that they can draw themselves
                CreateChildWindows(0);
    
                if(m_hBmp != NULL)
                {
                    ::DeleteObject(m_hBmp);
                    m_hBmp = NULL;
                }
    
                m_bRecreateBitmap = false;
                m_sizeBmp.cx = newSize.cx;
                m_sizeBmp.cy = newSize.cy;
    
                // Create the Bitmap
                m_hBmp = ::CreateCompatibleBitmap(di.hdcDraw, m_sizeBmp.cx, m_sizeBmp.cy);
                if(m_hBmp == NULL)
                {
                    m_bRecreateBitmap = true;
                    return S_OK;
                }
    
                POINT pt;
                POINT poiOffset;
    
                // prepare painting the NonClientArea of the TreeView
                poiOffset.x = ::GetSystemMetrics(SM_CXEDGE) + 1;
                poiOffset.y = ::GetSystemMetrics(SM_CYEDGE) + 1;
    
                // Select the Bitmap as a drawing >Context
                HDC hBmpDC = ::CreateCompatibleDC(di.hdcDraw);
                HBITMAP hOldBmp = (HBITMAP) ::SelectObject(hBmpDC, m_hBmp);
    
                // Position the Windows in the Hidden one
                m_wndTree.SetWindowPos(HWND_TOP, &rc, SWP_NOZORDER);
    
                ::SetViewportOrgEx(hBmpDC, poiOffset.x, poiOffset.y, &pt);
                // force The temporary Window to Draw itself and copy it into the Bitmap
                m_wndTree.SendMessage(WM_PAINT, (WPARAM)hBmpDC, 0);
    
                ::SetViewportOrgEx(hBmpDC, pt.x, pt.y, NULL);
                ::DrawEdge(hBmpDC, &rc, EDGE_BUMP, BF_RECT);
    
                ::SelectObject(hBmpDC, hOldBmp);
                ::DeleteDC(hBmpDC);
            }
    
            // Copy the Bitmap to the Container
            HDC hBmpDC = ::CreateCompatibleDC(di.hdcDraw);
            HBITMAP hOldBmp = (HBITMAP) ::SelectObject(hBmpDC, m_hBmp);
            
            ::BitBlt(di.hdcDraw, rc.left, rc.top, m_sizeBmp.cx, m_sizeBmp.cy, hBmpDC, 0,
                0, SRCCOPY);
            ::SelectObject(hBmpDC, hOldBmp);
            ::DeleteDC(hBmpDC);
        }
    
        return S_OK;
    }
    
    
    /////////////////////////////////////////////////////////////////////////
    // messagehandlers
    /////////////////////////////////////////////////////////////////////////
    
    // set the focus to the childwindow (the code was created by the wizard)
    LRESULT CDesignTimePaintingObj::OnSetFocus(UINT uMsg, WPARAM wParam, LPARAM lParam,
        BOOL& bHandled)
    {
        LRESULT lRes = CComControl<CDesignTimePaintingObj>::OnSetFocus(uMsg, wParam, lParam,
           bHandled);
        if (m_bInPlaceActive)
        {
            DoVerbUIActivate(&m_rcPos,  NULL);
            if(!IsChild(::GetFocus()))
                m_wndTree.SetFocus();
        }
        return lRes;
    }
    
    // nothing important
    LRESULT CDesignTimePaintingObj::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam,
        BOOL& bHandled)
    {
        UNUSEDPARAM(uMsg);
        UNUSEDPARAM(wParam);
        UNUSEDPARAM(lParam);
        UNUSEDPARAM(bHandled);
    
        SizeViews();
        return 0;
    }
    
    
    /////////////////////////////////////////////////////////////////////////
    // helperfunctions
    /////////////////////////////////////////////////////////////////////////
    
    // Create the childwindow on the given window. If no window is specified by
    // hParent (if it's NULL)
    // we create our "HiddenWindow" and put the childs on this one.
    BOOL CDesignTimePaintingObj::CreateChildWindows(HWND hParent)
    {
        // should we create our "HiddenWindow"
        if(hParent == 0)
        {
            CreateHiddenWindow();
            hParent = m_hHiddenWnd;
        }
    
        // If the Windows already exist, only set a new parent
        if(m_wndTree.m_hWnd != NULL)
        {
            m_wndTree.SetParent(hParent);
        }
        else
        {
            RECT rc = {0,0,0,0};
    
            // only create the window
            UINT styles = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | TVS_HASLINES |
                TVS_HASBUTTONS | TVS_LINESATROOT | WS_BORDER;
            m_wndTree.Create(hParent, rc, NULL, styles,  WS_EX_CLIENTEDGE);
    
            // this is only for this example so you can see something
            FillTree();
        }
        return TRUE;
    }
    
    // Creates our HiddenWindow which acts as a parentwindow, which will never be seen.
    void CDesignTimePaintingObj::CreateHiddenWindow()
    {
        // if no HiddenWindow exists, create on
        if(m_hHiddenWnd == NULL)
        {
            long lStyle;
    
            m_hHiddenWnd = ::CreateWindow(_T("STATIC"), _T(" "), 0, 0, 0, 0, 0, 0, 0,
                0, NULL);
            
            // remove all Borders, captions and other nonsens
            lStyle = ::GetWindowLong(m_hHiddenWnd, GWL_STYLE);
            lStyle &= ~(WS_BORDER | WS_CAPTION);
            ::SetWindowLong(m_hHiddenWnd, GWL_STYLE, lStyle);
        }
    }
    
    // only resize the childwindows
    void CDesignTimePaintingObj::SizeViews()
    {
        if(m_bInitialised == true)
        {
            RECT rc;
            GetClientRect(&rc);
    
            m_wndTree.SetWindowPos(HWND_TOP, &rc, SWP_NOZORDER | SWP_DRAWFRAME);
        }
    }
    
    // I put some items in the tree, so you can see something, not important
    void CDesignTimePaintingObj::FillTree()
    {
        ...(putting stuff in the tree here)...
    }

    我认为在控件上放置更多窗口应该没有问题,您只需要在 OnDraw 中绘制更多窗口。

    玩得开心
    Gerolf

  • © . All rights reserved.