Question

In my MFC (Feature Pack) application one can dynamically create docking panes to display charts/tables etc.
However, I don't want to let the user open the same thing twice.

I create a pane like this:

// Create CMyDockablePane pPane
pPane->Create(...);
pPane->EnableDocking(CBRS_ALIGN_ANY);
// Create CRect rcPane
pPane->FloatPane(rcPane);

This seems to work fine.

This is how I tried to check if a pane already exists. A pane is identified by its type (class) and a parameter.

BOOL CanOpenPane(const type_info & paneType, const CMyParameter & parameter) const
{
    CMainFrame* pFrm = GetMainFrame();
    CDockingManager* pDockMan = pFrm->GetDockingManager();


    // Check if there already is a pane of the same type which also has the same parameter.
    bool canOpen = true;
    CObList panes;
    pDockMan->GetPaneList(panes);
    POSITION pos = panes.GetHeadPosition();
    while (pos)
    {
        CMyDockablePane* pPane = dynamic_cast<CMyDockablePane*>(panes.GetNext(pos));
        if (NULL == pPane) { continue; }

        if (paneType == typeid(*pPane) &&
                pPane->GetParameter() == parameter)
        {
            canOpen = false;
            break;
        }
    }


    return canOpen;
}

The problem with this is that when I close a pane, this is not recognized. The CDockingManager object still returns the pane in the GetPanes() call.

How can I tell the manager to not return panes that are closed?
or
How can I remove the pane from a pane list, when it's closed?


Update

I dived a bit deeper and found, that the CWnd objects are not actually closed, when clicking the 'x' button in the caption bar, but only their containers.
So the real problem seems to be to really close the panes.
I also changed the question to better reflect the problem.

Was it helpful?

Solution

As described in my update, the problem for the docking manager giving me closed panes, was that the panes were not actually closed. Only their containers were closed; the panes themselves were just hidden.

So to really close the panes I overrode the following methods in my CMDIFrameWndEx derived main frame class:

BOOL CMainFrame::OnCloseMiniFrame(CPaneFrameWnd* pWnd)
{
    if(0 == pWnd->GetPaneCount()) { return TRUE; } // No panes.. allow closing

    // Close all child panes of the miniframe that is about to be closed.
    //
    // Panes are placed inside a mini frame when they have the "floating" status.
    // Since I didn't find a way to iterate over the panes of a mini frame
    // (CMultiPaneFrameWnd can have several panes), we iterate over all panes
    // and close those whose parent frame is pWnd.

    CDockingManager* pDockMan = GetDockingManager();
    if(NULL != pDockMan)
    {
        CObList allPanes;
        pDockMan->GetPaneList(allPanes, TRUE, NULL, TRUE);

        for(POSITION pos = allPanes.GetHeadPosition(); pos != NULL;)
        {
            CDockablePane* pPane = dynamic_cast<CDockablePane*>(allPanes.GetNext(pos));
            if (NULL == pPane) { continue; }

            if(pWnd == pPane->GetParentMiniFrame())
            {
                pPane->PostMessage(WM_CLOSE); // Note: Post instead of Send
            }
        }

    }

    return TRUE; // Allow closing
}

And the second:

BOOL CMainFrame::OnCloseDockingPane(CDockablePane* pWnd)
{
    CObList paneList;

    // We can get CDockablePanes and CTabbedPanes here.
    // The tabbed panes contain dockable panes.
    CTabbedPane* pTabbed = dynamic_cast<CTabbedPane*>(pWnd);
    CDockablePane* pDockable = dynamic_cast<CDockablePane*>(pWnd);
    if(NULL != pTabbed)
    {
        pTabbed->GetPaneList(paneList);
    }
    else if(NULL != pDockable)
    {
        paneList.InsertAfter(paneList.GetHeadPosition(), pDockable);
    }

    // Whatever it was, we now have a list of dockable panes, which we will close.
    for(POSITION pos = paneList.GetHeadPosition(); NULL != pos;)
    {
        CDockablePane* pPane = dynamic_cast<CDockablePane*>(paneList.GetNext(pos));
        ASSERT(NULL != pPane);


        // Let the window disappear and then recalculate the layout.
        // Not doing this causes problems with panes grouped together in a tabbed pane.
        pPane->ShowWindow(SW_HIDE);
        RecalcLayout();

        // Really close the window so the docking manager also doesn't know of it anymore.
        pPane->Reset();
        pPane->PostMessage(WM_CLOSE); // Note: Post instead of Send
    }


    return TRUE; // Allow closing
}

OTHER TIPS

add to yor CMainFram a msg entry like the following :

ON_REGISTERED_MESSAGE(AFX_WM_ON_PRESS_CLOSE_BUTTON,OnClosePane)

OnClosePane look like this :

LRESULT CMainFrame::OnClosePane(WPARAM,LPARAM lp)
{
    CBasePane* pane = (CBasePane*)lp;
    int id = pane->GetDlgCtrlID();
    pane->ShowPane(FALSE, FALSE, FALSE);
    RemovePaneFromDockManager(pane,TRUE,TRUE,TRUE,NULL);
    AdjustDockingLayout();
    pane->PostMessage(WM_CLOSE);
    PostMessage(WM_RESETMEMBER,id,0);
    return (LRESULT)TRUE;//prevent close , we already close it
}

COUTION :

OnClosePane is called in middle of CBasePane::OnLButtonDown handler , destroying window will make your code assert , so you need to post message(WM_CLOSE) instead of sending it , this give CBasePane::OnLButtonDown handler a chance to finish executing while the pane hWnd still valid . and for the same resone i return True to prevent close because we already close it via WM_CLOSE which will destroy window as well.

WM_RESETMEMBER message is registered window message to reset pane member to null .

it implementation look like this :

LRESULT CMainFrame::OnResetMember(WPARAM wp,LPARAM)
{
    int id = (int)wp;
    switch(id)
    {
        case IDC_BIDBOND_TREE_PANE:
            m_pBBTreePane.reset((BBTreePane*)NULL);
            break;
        case IDC_REFTREE_PANE :
            m_pRefTreePane.reset((RefTreePane*)NULL);
            break;
        default :
            return (LRESULT)FALSE;//id warent found

    }
    return (LRESULT)TRUE;
}

you should msg map entry like one :

ON_REGISTERED_MESSAGE(WM_RESETMEMBER,OnResetMember)

and you should register message globally like this :

const UINT WM_RESETMEMBER = ::RegisterWindowMessage(_T("WM_RESETMEMBER"));

I'd expect a call to CDockingManager::RemovePaneFromDockManager when you are closing your pane to do the job.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top