Как закрыть динамически созданные окна CDockablePane?
-
20-09-2019 - |
Вопрос
В моем приложении MFC (Feature Pack) можно динамически создавать стыковочные панели для отображения диаграмм / таблиц и т.д.
Однако я не хочу позволять пользователю открывать одно и то же дважды.
Я создаю панель, подобную этой:
// 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().
Как я могу сказать менеджеру, чтобы он не возвращал закрытые панели?
или
Как я могу удалить панель из списка панелей, когда она закрыта?
Обновить
Я погрузился немного глубже и обнаружил, что объекты CWnd на самом деле закрываются не при нажатии кнопки "x" в строке заголовка, а только их контейнеры.
Таким образом, реальная проблема, по-видимому, заключается в том, чтобы действительно закрыть стекла.
Я также изменил вопрос, чтобы лучше отразить проблему.
Решение
Как описано в моем обновлении, проблема с диспетчером стыковки, предоставляющим мне закрытые панели, заключалась в том, что панели на самом деле не были закрыты.Только их контейнеры были закрыты;сами стекла были просто скрыты.
Итак, чтобы действительно закрыть панели, я переопределил следующие методы в моем 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
}
Другие советы
добавьте в ваш CMainFram запись msg, подобную следующей :
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, который также уничтожит window.
Сообщение 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 map как один :
ON_REGISTERED_MESSAGE(WM_RESETMEMBER,OnResetMember)
и вы должны зарегистрировать сообщение глобально следующим образом :
const UINT WM_RESETMEMBER = ::RegisterWindowMessage(_T("WM_RESETMEMBER"));
Я бы ожидал звонка, чтобы CDockingManager::Удалить панель из DockManager когда вы закрываете свою панель, чтобы выполнить задание.