Click here to Skip to main content
Click here to Skip to main content

Tagged as

Views in a Sizeable Docking Control Bar (CSizingControlBar)

, 14 Mar 2000
Rate this:
Please Sign up or sign in to vote.
A fairly simple way to incoporate views into sizing control bars
  • Download demo project - 23 Kb
  • Download source files - 47 Kb

    I like Cristi Posea's implementation of sizing docking control bars and decided to use them in a project I am working on. Unfortunately, I really needed to use them with views, and set about investigating this. I read in one of the comments that a few people have tried using a frame window to aid the MFC framework in order to use views. I decided to see how hard it would be. For what I need it for it turns out to be relatively easy.
    Note I'm not describing how to deal with multiple views on multiple documents and how to link these together and manage them. This is a complex topic and highly application dependant and I feel it is better left to the particular application to resolve. (The example does allow the views to be linked to different documents).
    What I am explaining is a way of incorporating a view into a sizing control bar object.

    Here are the steps:

    Step 1

    I created a new CViewBar class that inherits from one of the CSizingControlBar classes. (#define TViewBarBase to the base class you need). This class encapsulates the frame window so you don't have to know it even exists. (A frame window is just needed to support MFC, it doesn't matter what kind.) The creation and sizing are handled within the class.
    Also taken care of, is specification of the view class in the create member function. I just let MFC do all the work by passing the class type through to it. That way you don't have to derive eight billion different classes just to use a docking view. This leads to my next step.

    // ViewBar.h: interface for the CViewBar class.
    //
    //////////////////////////////////////////////////////////////////////
    
    #ifndef VIEWBAR_H
    #define VIEWBAR_H
    
    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    
    #include "sizecbar.h"
    #include "scbarg.h"
    #include "scbarcf.h"
    
    // You must #define this for viewbar to compile properly
    #define TViewBarBase CSizingControlBarCF
    
    class CViewBar : public TViewBarBase
    {
        DECLARE_DYNAMIC(CViewBar);
    public:
        CViewBar();
        virtual ~CViewBar();
        virtual BOOL Create(
            CWnd* pParentWnd,           // mandatory
            CRuntimeClass *pViewClass,  // mandatory
            CCreateContext *pContext = NULL,
            LPCTSTR lpszWindowName = NULL,
            DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_TOP,
            UINT nID = AFX_IDW_PANE_FIRST);
    
    protected:
        CFrameWnd *m_pFrameWnd;
        CCreateContext m_Context;
    
    // Overrides
        // ClassWizard generated virtual function overrides
        //{{AFX_VIRTUAL(CViewBar)
        public:
        //}}AFX_VIRTUAL
    
        //{{AFX_MSG(CViewBar)
        afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
        afx_msg void OnSize(UINT nType, int cx, int cy);
        //}}AFX_MSG
        DECLARE_MESSAGE_MAP()
    };
    
    #endif
    
    
    // ViewBar.cpp: implementation of the CViewBar class.
    //
    //////////////////////////////////////////////////////////////////////
    
    #include "stdafx.h"
    #include "ViewBar.h"
    
    #ifdef _DEBUG
    #undef THIS_FILE
    static char THIS_FILE[]=__FILE__;
    #define new DEBUG_NEW
    #endif
    
    IMPLEMENT_DYNAMIC(CViewBar, TViewBarBase);
    
    //////////////////////////////////////////////////////////////////////
    // Construction/Destruction
    //////////////////////////////////////////////////////////////////////
    
    CViewBar::CViewBar()
    {
        ZeroMemory(&m_Context, sizeof(m_Context));
        // Create a frame object.
        CRuntimeClass *pRuntimeClass = RUNTIME_CLASS(CFrameWnd);
        CObject* pObject = pRuntimeClass->CreateObject();
        ASSERT( pObject->IsKindOf( RUNTIME_CLASS( CFrameWnd ) ) );
        m_pFrameWnd = (CFrameWnd *)pObject;
    }
    
    
    CViewBar::~CViewBar()
    {
    }
    
    
    BEGIN_MESSAGE_MAP(CViewBar, TViewBarBase)
        //{{AFX_MSG_MAP(CRegBar)
        ON_WM_CREATE()
        ON_WM_SIZE()
        //}}AFX_MSG_MAP
    END_MESSAGE_MAP()
    
    //////////////////////////////////////////////////////////////////////
    // CViewBar message handlers
    
    /* ---------------------------------------------------------------
            Overridden to set the view class before calling the
        base create function.
            For an SDI application the main frame window is created
        when the document template is created and a valid
        CreateContext is passed through (hurray!). Meaning there is
        no problem creating the ViewBar in the frame window create.
        However, for an MDI application the main frame window is
        constructed outside the document creation, so the
        CreateContext isn't present. In this case you can either
        create and setup a CreateContext object with the desired
        characteristics (doc template, frame window, etc.) and pass
        it, or let the CViewBar::Create() create a CreateContext
        with only the runtime class of the view, and the main frame
        window set.
    --------------------------------------------------------------- */
    BOOL CViewBar::Create(
        CWnd* pParentWnd,
        CRuntimeClass *pViewClass,
        CCreateContext *pContext,
        LPCTSTR lpszWindowName,
        DWORD dwStyle,
        UINT nID)
    {
        ASSERT(pViewClass != NULL);
        ASSERT(pViewClass->IsDerivedFrom(RUNTIME_CLASS(CWnd)));
        if (pContext)
            memcpy(&m_Context, pContext, sizeof(m_Context));
        else {
            CFrameWnd *fw = (CFrameWnd *)AfxGetMainWnd();
            if (fw) {
                m_Context.m_pCurrentDoc = fw->GetActiveDocument();
                m_Context.m_pCurrentFrame = fw;
            }
        }
        m_Context.m_pNewViewClass = pViewClass;
        return TViewBarBase::Create(
            lpszWindowName,
            pParentWnd,
            nID,
            dwStyle);
    }
    
    
    /* ---------------------------------------------------------------
            Create the frame window associated with the view bar.
    --------------------------------------------------------------- */
    int CViewBar::OnCreate(LPCREATESTRUCT lpCreateStruct) 
    {
        if (TViewBarBase::OnCreate(lpCreateStruct) == -1)
            return -1;
    
        if (!m_pFrameWnd->Create(NULL, NULL,
            WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE,
            CRect(0,0,0,0), this, NULL, 0,
            &m_Context))
            return -1;
        return 0;
    }
    
    
    /* ---------------------------------------------------------------
            Must remember to move the frame window as well...
    --------------------------------------------------------------- */
    void CViewBar::OnSize(UINT nType, int cx, int cy) 
    {
        CRect rc;
    
        TViewBarBase::OnSize(nType, cx, cy);
        GetClientRect(rc);
        m_pFrameWnd->MoveWindow(rc);
    }
    

    An aside:
    Ho, humm. Don't you wish there were some way to represent the direct base class rather than having to specify it explicitly ?
    Something like 'base::' would be handy. You could write code more generically. Sure, you could use templates but that is more complex than I like.

    Step 2

    How to create the view: Do the obvious thing and pass it through to MFC and let it do all the work. View information can be passed to MFC using the CCreateContext object. This is passed to many of the window creation functions in MFC. MFC uses this when creating a view. Unfortunately, this create context is not passed in the original sizing control bar create, meaning a little subterfuge is required (unless you want to modify the original class). Consequently, a CCreateContext object is maintained in the ViewBar class simply for the sake of not modifying the base class.
    It's needed to be passed when frame window Create() is called. If you don't like this, you can modify the CSizingControlBar::Create function to accept a CCreateContext. However, since it was possible to avoid, I avoided it. This way my class is decoupled from the original, meaning if the original author decides to do an update I'll have a much easier time updating my software. And I, like all good programmers am incredibly lazy.

    To create a ViewBar in you app use code like the following: (see the sample)

        sTitle.Format(_T("My Bar %d"), i + 1);
        if (!m_wndMyBars[i].Create(this,
            RUNTIME_CLASS (CAForm),
            (CCreateContext *)(lpCreateStruct->lpCreateParams),
            sTitle))//AFX_IDW_CONTROLBAR_FIRST + 33 + i))
        {
            TRACE0("Failed to create ViewBar\n");
            return -1;      // fail to create
        }
    

    Aside from the create function, it can be used exactly like the existing sizing control bar classes. You need only pass the RUNTIME_CLASS of the view you want to create.

    Step 3

    At this point the modifications to support views seemed to work, except for one niggly issue - menu access when the view is floating. (Even accelerator keys work!) For various reasons this is tricky to implement.
    Ultimately you have to save and restore which window had the focus before the menu was activated. This is needed in case the user hits the escape key. (Why Windows / MFC doesn't do this automatically is beyond me. Where else would you set it ?) Which window is active after a menu command is completed is entirely application dependant.
    To activate the main menu from the view while it's floating, override the PreTranslateMessage() function of the view and set the focus back to the main window when a menu keystroke is detected. Like this:

    /* ---------------------------------------------------------------
            Change the input focus to the main window when a menu
        key is selected.
    --------------------------------------------------------------- */
    BOOL CAForm::PreTranslateMessage(MSG* pMsg) 
    {
        if (pMsg->message == WM_SYSKEYDOWN && pMsg->wParam == VK_MENU
            && ((CViewBar *)(GetParent()->GetParent()))->IsFloating())
        {
            ((CMainFrame *)AfxGetMainWnd())->m_hWndBeforeMenu =
                ::GetFocus();
            AfxGetMainWnd()->SetFocus();
        }
        return CFormView::PreTranslateMessage(pMsg);
    }
    

    Don't try and put this line of code into the PreTranslateMessage() for the control bar class, it would be nice if you could, but that doesn't work. Somewhere along the way the SYSKEY messages get eaten up.
    I didn't bother to derive a special class to handle this operation for several reasons:

    1. Which view class do I derive from ? It would be silly to mirror the existing MFC view classes accomodate only a single line of code.
    2. You are probably deriving your own view class anyway, so it's relatively easy to place this code in the class.
    3. You might not care about being able to access the main menu when the view is floating.

    *Note: you can't use CWnd pointer to track the focus and restore it with GetFocus() and SetFocus(). You must use ::Setfocus(), ::GetFocus() and track the Windows handle.

    Put the following code in the CMainFrame class for the app. It restores the window that was active before escape was pressed.
    Also, in a similar fashion, you will need to add code to any menu command handlers that you want to return to a floating window if it was active.

    /* ---------------------------------------------------------------
            If the last key pressed before the menu is exited was
        the escape key, then set the focus back to the window that
        was active before the menu was activated.
        *   This message is not available in class wizard as a
        frame window message. *
    --------------------------------------------------------------- */
    
    void CMainFrame::OnExitMenuLoop( BOOL bIsTrackPopupMenu )
    {
        if (((CSCBDemoApp *)AfxGetApp())->m_nLastKey == VK_ESCAPE) {
            if (m_hWndBeforeMenu) {
                ::SetFocus(m_hWndBeforeMenu);
            }
        }
        m_hWndBeforeMenu = NULL;
        CWnd::OnExitMenuLoop(bIsTrackPopupMenu);
    }
    

    Notes

    • This function is not avaiable for a frame window through AppWizard.
    • Once you add this function to the class AppWizard freaks if you try to add more virtual functions or message handlers. So add it once you're done everything else. (At least that what happens in VC6.0 - some error about parsing...)
    • If you're paranoid you'll think this was done on purpose.

    Add the following code to track keystrokes for the app. (Needed to detect escape key). Add a UINT member var to the app class, m_nLastKey.

    BOOL CSCBDemoApp::ProcessMessageFilter(int code, LPMSG lpMsg) 
    {
        if (lpMsg->message == WM_KEYDOWN)
            m_nLastKey = lpMsg->wParam;
        return CWinApp::ProcessMessageFilter(code, lpMsg);
    }
    

    With this all done, there is still a slight anomaly in the way focus operates. Unfortunately a side effect of this mechanism is it automatically activates the window associated with the main frame window. If you have code that identifies the window as active (on GotFocus() for instance) this code will be activated, although the main menu is active. (Hitting escape deactivates this window again because escape will return focus to the original window).
    Note that I think the menu handling is ugly as it is now, but it works for me. If there is a better way of doing this (I'm sure there is) I'd like to hear about it.

    Other Notes:
    You might think you can get away without using a frame window. I tried this and it almost works. It works for a regular form view but when I tried it with a CEditView derived class *poof* the edit view no longer registered keystrokes.

    I've done most of my testing in an SDI application, although the SCBDemo does seem to work (an MDI app). Undoubtedly there will be problems or issues that I've missed.

    Notes on the Example:
    Data for the document is actually stored in the edit view for convenience. If you have multiple views, ideally the data should be stored in the document object, not a view.
    The example is provided to simply illustrate that it works. It is not intended to be a sophisticated app. Handling multiple views and multiple types of views for multiple documents is quite complex, and there are a lot of issues involved.
    The attach to document button on the ViewBar attaches to the currently active document. If you wanted to be fancy you could provide a dropdown list like on the file new.

    Conclusion:

    This provides a fairly simple way to incoporate views into sizing control bars. Menu actuation when the bar is floating could be improved upon.

  • 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

    Share

    About the Author

    Rob Finch

    United States United States
    No Biography provided

    Comments and Discussions

     
    GeneralOnInitialUpdate() missed when floating PinmemberAlastor66619-Feb-04 9:49 
    As far as other messages from the main frame. Does somebody know graceful descision to this problem?
    GeneralRe: OnInitialUpdate() missed when floating PinmemberPaul S. Vickery14-Apr-04 3:56 
    GeneralRe: OnInitialUpdate() missed when floating PinmemberDiogenes Lazzarini11-Apr-06 8:20 

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

    Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

    | Advertise | Privacy | Terms of Use | Mobile
    Web01 | 2.8.141220.1 | Last Updated 15 Mar 2000
    Article Copyright 2000 by Rob Finch
    Everything else Copyright © CodeProject, 1999-2014
    Layout: fixed | fluid