Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Drawing complex ATL/ActiveX controls at designtime

0.00/5 (No votes)
30 Nov 1999 1  
An article that discusses drawing a control at designtime

First let's discuss the problem, then let me show you a solution.

The problem is, that if you create a control with ATL, and this control has childwindows (like the TreeView here), it will work fine in runmode. Normally it will also work fine in designmode, but in some containers it won't. That is because some containers don't create your control at designtime, they only instantiate them (via CoCreateInstance) and draw it through the interface IViewObject. They call OnDraw(ATL_DRAWINFO& di) directly and give you a devicecontext to draw on. That can be a metafile or a screen device, or some other device.

If you resize the control, it is possible that it will be created for a short time through IOLEObject::DoVerbInPlaceActive(...) and will be destroyed by IOLEObject::Close(...).

One container that shows controls at designtime this way is the ResourceEditor in MS VC++ (Dialogs, see picture above).

I'll show you some diagrams so you can see what to do. The sourcecode is in the example at the end of the document. You have two posibilities of drawing the control at designtime (shown in the diagramm for OnDraw):

OnCreate The function of the control creates the basecontrol by calling to the baseclass. The Creation of the Childwindows should be put to an extra function (CreateChildWindows) so you can create them without creating the control.

CreateChildWindows CreateChildWindows checks if there is a parentwindow. If not, it creates one with CreateHiddenWindow. If the childs doesn't exist they are now created (on the new parentwindow) or we call SetParent on the windows. It is possible that the windows exist because sometimes the control is put inplace active (p.e. resizing it) and in this case we will only set a new parent.

CreateHiddenWindow

If there already exists a "HiddenWindow", we return it, else we create a new one with all parameters set to 0 and the windowclass to _T("Static").

For the second solution, we also have to remove the WS_BORDER and WS_CAPTION style by hand, because they are set automatically for a toplevel window (we use Get / SetWindowLong).


Destruktor We have to destroy the "HiddenWindow" in the destructor of the controls.

First Solution

Drawing childwindows which do all drawing in an overloaded WM_PAINT message handler and let the system do nothing (no common controls!)

The painting code should be put to an extra function like OnDraw(HDC hdc, RECT& rc) that can be called from outer. So the WM_PAINT - messagehandler only gets the DC and the clientrect and calls OnDraw and we can do so from out OnDraw of the Control. In the OnDraw function, you must not call any standard functions that return states or properties of the window (like a DC or a RECT)! Next, you are allowed to call the default painting routines (via DefWindowProc), because the window exists, but is Hidden!!!

OnDrawEinfach

If the control dosn't have a window, when the OnDraw is called (this must be designmode, where the OnDraw could be called through IViewObject, elsewhere there would be a window!) We test, if the windows already exist (childs and "HiddenWindow"). If not, call CreateChildWindows(0) to create them.

Then we iterate through all childwindows and call the OnDraw functions with the parameters (we have to calculate the rects before). If you need any members for painting, they must be initialized before!

Second Solution

  • Drawing childwindows which call their WM_PAINT messagehandler in order to do standard painting (like a treecontrol)

    This is the point where you get to problems because you can't put the drawing code into an extra function because it is not yours. So you have to call the default handler of the treecontrol. We can use this with ComCtl32.dll in Version 5.80. Earlier versions are able to do so, but not with all controls.

    We draw the Windows to a Bitmap, so normally we only have to blit the Bitmap to the container device (this Bitmap is a member of the control). We only create a new bitmap, if the old one is invalid (properties change, size changes, ...)


    Second Solution (a, ComCtl32.dll version >= 5.80)

    In this version on ComCtl32.dll, you can call SendMessage(WM_PAINT, WPARAM(hdc), 0) and the window will draw to the given devicecontext. but be careful, it draws to (0 / 0), so you have to move the viewport (via SetViewportOrgEx) and the window won't draw the nonclientarea. This could be done by DrawEdge (for Borders).

    OnDrawFensterSendMessage If the control doesn't have a window, when the OnDraw is called (this must be designmode, where the OnDraw could be called through IViewObject, elsewhere there would be a window!).
    Now, we check if the bitmap must be recreated (variables m_bRecreateBitmap and m_sizeBmp). If not, we got the the Blitting part of the OnDraw function.
    • if recreating is neccassary, we check, if the childwindows already exist (childs and "HiddenWindow"). If not, call CreateChildWindows(0) to create them.
    • destroy the old bitmap
    • create the new one (with the new size, located in di.prcBounds)
    • calculate all offsets of the windows,like you create them on the control, so you can draw the windows to the correct position
    • now draw all windows to their positions via
      • ::SetWindowPos(..., rcThisChild, ...)
      • ::SetViewportOrgEx(...)
      • ::SendMessage(hwndChild, WM_PAINT, (WPARAM) hdc, 0)
    • next, draw the nonclientarea of the windows, like borders, Scrollbars, ...
      • ::SetViewportOrgEx(hdc, (0/0) of the control (save at start of OnDraw),...)
      • ::DrawEdge(...)
      • draw scrollbars as dummies (use FillRect and DrawEdge, there will be no arrows, but that doesn't matter!)
    Now, blit the Bitmap to the container device (di.hdcDraw).

    Second Solution (b, ComCtl32.dll version < 5.80)

    If you use an earlier version of ComCtl32.dll, it could be possible that this doesn't work. In this case, you need to show the HiddenWindow to Blit the clientrect to the bitmap. Then you can hide the window. This is a bit ugly, because a window jumps to the front and hides itself short time later (about fourth second, or a half).

    OnDrawFenster If the control doesn't have a window, when the OnDraw is called (this must be designmode, where the OnDraw could be called through IViewObject, elsewhere there would be a window!).
    Now, we check if the bitmap must be recreated (variables m_bRecreateBitmap and m_sizeBmp). If not, we got the the Blitting part of the OnDraw function.
    • if recreating is neccassary, we check, if the childwindows already exist (childs and "HiddenWindow"). If not, call CreateChildWindows(0) to create them.
    • destroy the old bitmap
    • create the new one (with the new size, located in di.prcBounds)
    • show the "HiddenWindow" (via ::SetWindowPos(..., HWND_TOP; ..., ... | SWP_SHOWWINDOW))
    • call ::SetWindowPos for all clientwindows
    • Call UpdateWindow(m_hHiddenWindow) to force repainting (including clientarea)
    • BitBlt from the HiddenWindow to the local bitmap
    • Hide the HiddenWindow
    Now, blit the Bitmap to the containerdevice (di.hdcDraw).

    That's it, we have finished. Now the sourcecode for this class (I replaced nonsense by ..., the code is for solution 2 a):

    To test this sourcecode, you only need to create a new MFC project with a dialog and put the new control on it. Then you see our new painting routines work. If you compile the project and start it, you see the normal painting routines.

    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)...
    }

    I think it will be no problem to put more windows on the controls, you only have to draw more windows in the OnDraw.

    Have much fun
    Gerolf

  • License

    This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

    A list of licenses authors might use can be found here