Barres de contrôle ancrables (volets)

Presque toutes les applications MFC ont une barre d’outils et une barre d’état - des types spéciaux de la barre de contrôle ancrée en haut et en bas du cadre principal de l’application. Mais souvent, la logique d’application nécessite plus de barres d’informations ancrées à un côté du cadre, par exemple, il peut s’agir de la barre de propriétés ou de la barre de classes, de la barre d’aperçu, de la barre de sortie et bien d’autres. Les MFC classiques ont une bonne solution uniquement pour les barres d’outils et autres contrôles qui ne sont pas redimensionnables dynamiquement. Mais MFC Feature Pack dispose d’un gestionnaire d’amarrage avancé qui permet :

  • Redimensionner dynamiquement les barres ancrées.
  • Redock sur n’importe quel côté du cadre avec aperçu en direct.
  • Sérialiser l’état et la position des barres ancrées dans le registre.
  • Ancrer les barres sur le même côté du cadre et les rassembler dans le volet à onglets.

Tout ce dont vous avez besoin est d’hériter de votre barre d’ancrage de la classe CDockablePane.

Panneau d’ancrage sur le côté gauche du cadre.

// Main frame class declaration
class CMainFrame 
   : public CMDIFrameWndEx
{
    DECLARE_DYNAMIC(CMainFrame)

protected:
    // declare our pane
    CDockablePane m_wndPane;

    // .... other class memebers

public:
    CMainFrame();

protected:
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    DECLARE_MESSAGE_MAP()
};


// Main frame class implementation
IMPLEMENT_DYNAMIC(CMainFrame, CMDIFrameWndEx)

BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWndEx)
    ON_WM_CREATE()
END_MESSAGE_MAP()

CMainFrame::CMainFrame()
{
}

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CMDIFrameWndEx::OnCreate(lpCreateStruct) == -1)
        return -1;

    // enable frame docking to any sides
    EnableDocking(CBRS_ALIGN_ANY);

    // set the visual manager used to draw all user interface elements
    CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));

    // enable smart docking style window behavior
    CDockingManager::SetDockingMode(DT_SMART);


    // Other frame initialization code
    // ....


    // Creating the pane.
    // ID_VIEW_PANE_ID - pane ID, must be declared in resource.h
    // CRect(0, 0, 100, 100) - default pane size in floating state (pane is not docked to any frame sides).
    // CBRS_LEFT - default side for pane docking.
    if (!m_wndPane.Create(_T("My Pane"), this, CRect(0, 0, 100, 100), TRUE, ID_VIEW_PANE_ID, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | CBRS_LEFT | CBRS_FLOAT_MULTI)) {
        TRACE0("Failed to create Pane\n");
        return -1; // failed to create
    }
    // Enable docking and redocking pane to frame any sides (you can pass a combination of CBRS_ALIGN_ flags)
    m_wndPane.EnableDocking(CBRS_ALIGN_ANY);

    // Dock pane to the default (left) side of the frame.
    DockPane(&m_wndPane);

    return 0;
}

Ancrage des volets au cadre enfant.

Parfois, l’application doit avoir des volets ancrés non pas au cadre principal, mais au cadre enfant. Il s’agit généralement d’une application MDI. Dans le pack de fonctionnalités MFC, ce cadre enfant est hérité de la classe CMDIChildWndEx et en tant que cadre principal (hérité de CMDIFrameWndEx) possède tout le code requis pour un tel amarrage.

Mais il y a quelques astuces pour le cadre enfant. Et cet exemple les montre.

// Declare child frame
class CChildFrame : public CMDIChildWndEx
{
    DECLARE_DYNCREATE(CChildFrame)

protected:
    // declare our pane
    CDockablePane m_wndPane;

public:
    CChildFrame();

protected:
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
    
    // CMDIChildWndEx class haven't serialization for state of the docking manager,
    // so we need to realize it manually.
    //
    // Docking state serialization methods:
    virtual void SaveBarState(LPCTSTR lpszProfileName) const;
    virtual void LoadBarState(LPCTSTR lpszProfileName);

protected:
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnDestroy();

    DECLARE_MESSAGE_MAP()
};



// CChildFrame Implementation 

IMPLEMENT_DYNCREATE(CChildFrame, CMDIChildWndEx)

BEGIN_MESSAGE_MAP(CChildFrame, CMDIChildWndEx)
    ON_WM_CREATE()
    ON_WM_DESTROY()
END_MESSAGE_MAP()

CChildFrame::CChildFrame()
{
    // Trick#1: Add this line for enable floating toolbars
    m_bEnableFloatingBars = TRUE;
}

BOOL CChildFrame::PreCreateWindow(CREATESTRUCT& cs)
{
    if( !CMDIChildWndEx::PreCreateWindow(cs) )
        return FALSE;

    cs.style = WS_CHILD | WS_VISIBLE | WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU
        | FWS_ADDTOTITLE | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_MAXIMIZE;

    // Trick#2: Add this line for remove the ugly client edge of the child frame.
    cs.dwExStyle &= (~WS_EX_CLIENTEDGE);

    return TRUE;
}

void CChildFrame::SaveBarState(LPCTSTR lpszProfileName) const
{
    const_cast<CChildFrame*>(this)->GetDockingManager()->SaveState(lpszProfileName);

    // Trick#3: we need to call serialization method of CMFCToolBar panes that may be docked to the child frame.
    CObList list;
    const_cast<CChildFrame*>(this)->GetDockingManager()->GetPaneList(list, FALSE, NULL, FALSE);
    if (list.GetCount() > 0) {
        POSITION pos = list.GetTailPosition();
        while (pos != NULL) {
            CMFCToolBar* pToolBar = DYNAMIC_DOWNCAST(CMFCToolBar, list.GetPrev(pos));
            if (pToolBar != nullptr) {
                pToolBar->SaveState(lpszProfileName);
            }
        }
    }
}

void CChildFrame::LoadBarState(LPCTSTR lpszProfileName)
{
    // Trick#3: we need to call serialization method of CMFCToolBar panes that may be docked to the child frame.
    CObList list;
    GetDockingManager()->GetPaneList(list, FALSE, NULL, FALSE);
    if (list.GetCount() > 0) {
        POSITION pos = list.GetTailPosition();
        while (pos != NULL) {
            CMFCToolBar* pToolBar = DYNAMIC_DOWNCAST(CMFCToolBar, list.GetPrev(pos));
            if (pToolBar != nullptr) {
                pToolBar->LoadState(lpszProfileName);
            }
        }
    }

    GetDockingManager()->LoadState(lpszProfileName);
    GetDockingManager()->SetDockState();
    GetDockingManager()->ShowDelayShowMiniFrames(TRUE);

    // Trick#4: MFC BUGFIX: force assigning the child frame docking manager to all miniframes (for details look at http://stackoverflow.com/q/39253843/987850).
    for (POSITION pos = GetDockingManager()->GetMiniFrames().GetHeadPosition(); pos != NULL;)
    {
        CWnd* pWndNext = (CWnd*)GetDockingManager()->GetMiniFrames().GetNext(pos);
        if (pWndNext != nullptr && pWndNext->IsKindOf(RUNTIME_CLASS(CPaneFrameWnd))) {
            STATIC_DOWNCAST(CPaneFrameWnd, pWndNext)->SetDockingManager(GetDockingManager());
        }
    }
}


int CChildFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    bool bRes = CMDIChildWndEx::OnCreate(lpCreateStruct) == 0;
    if (bRes)
    {
        // enable docking
        EnableDocking(CBRS_ALIGN_ANY);

        // enable Visual Studio 2005 style docking window behavior
        CDockingManager::SetDockingMode(DT_SMART);

        // Creating the pane.
        // ID_VIEW_PANE_ID - pane ID, must be declared in resource.h
        // CRect(0, 0, 100, 100) - default pane size in floating state (pane is not docked to any frame sides).
        // CBRS_LEFT - default side for pane docking.
        if (!m_wndPane.Create(_T("My Pane"), this, CRect(0, 0, 100, 100), TRUE, ID_VIEW_PANE_ID, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | CBRS_LEFT | CBRS_FLOAT_MULTI)) {
            TRACE0("Failed to create Pane\n");
            return -1; // failed to create
        }
        // Enable docking and redocking pane to frame any sides (you can pass a combination of CBRS_ALIGN_ flags)
        m_wndPane.EnableDocking(CBRS_ALIGN_ANY);

        // Dock pane to the default (left) side of the frame.
        DockPane(&m_wndPane);
    }

    // Loading dock manager state
    if (bRes) {
       LoadBarState(theApp.GetRegSectionPath(_T("ChildFrame")));
    }

    return bRes ? 0 : 1;
}

void CChildFrame::OnDestroy()
{
    // Save dock manager state
    SaveBarState(theApp.GetRegSectionPath(_T("ChildFrame")));

    CMDIChildWndEx::OnDestroy();
}