如何关闭动态创建的CDockablePane窗口?

发布于 2024-08-02 11:36:04 字数 1396 浏览 9 评论 0原文

在我的 MFC(功能包)应用程序中,可以动态创建停靠窗格来显示图表/表格等。
但是,我不想让用户打开同一个东西两次。

我创建了一个像这样的窗格:

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

这似乎工作正常。

这就是我尝试检查窗格是否已存在的方法。窗格由其类型(类)和参数来标识。

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;
}

问题在于,当我关闭窗格时,它无法被识别。 CDockingManager 对象仍然在 GetPanes() 调用中返回窗格。

如何告诉经理不要返回已关闭的窗格?

当窗格关闭时,如何从窗格列表中删除窗格?


更新

我深入研究后发现,当单击标题栏中的“x”按钮时,CWnd 对象实际上并未关闭,但是只有他们的容器。
所以真正的问题似乎是真正关闭窗格。
我还改变了问题以更好地反映问题。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

稍尽春風 2024-08-09 11:36:04

正如我的更新中所述,对接管理器给我关闭的窗格的问题是窗格实际上并未关闭。只是他们的容器是封闭的;窗格本身只是被隐藏起来。

因此,为了真正关闭窗格,我在 CMDIFrameWndEx 派生的主框架类中重写了以下方法:

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
}

第二个:

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
}

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
}
眼泪淡了忧伤 2024-08-09 11:36:04

将如下所示的消息条目添加到您的 CMainFram 中:

ON_REGISTERED_MESSAGE(AFX_WM_ON_PRESS_CLOSE_BUTTON,OnClosePane)

OnClosePane 如下所示:

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
}

注意:

OnClosePane 在 CBasePane::OnLButtonDown 处理程序中间调用,销毁窗口
将使您的代码断言,因此您需要发布消息(WM_CLOSE)而不是发送它,这使 CBasePane::OnLButtonDown 处理程序有机会在窗格 hWnd 仍然有效时完成执行。
出于同样的原因,我返回 True 以防止关闭,因为我们已经通过关闭它
WM_CLOSE 这也会破坏窗口。

WM_RESETMEMBER 消息是注册窗口消息,用于将窗格成员重置为 null 。

它的实现看起来像这样:

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;
}

你应该像这样的 msg 映射条目:

ON_REGISTERED_MESSAGE(WM_RESETMEMBER,OnResetMember)

并且你应该像这样全局注册消息:

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

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"));
花伊自在美 2024-08-09 11:36:04

当您关闭窗格来完成这项工作。

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

悲欢浪云 2024-08-09 11:36:04

在mfc文档中,它说不要使用showwindow,所以使用showpane来显示窗格

in the mfc document,it said that don't use showwindow,so use showpane to show the pane

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文