C++ CDialogEx losses ParentWnd when run from DLL - c++

I've got a situation.
There is a huge app (C++ MFC). I write a .dll module with a dockable pane.
Pane interface structure:
Pane -> CMFCToolBar
-> CSplitterWndEx -> CListCtrl
-> CDialogEx
That's how i create my DialogEx:
int CMyPane::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
///////////////////////////////////////////
////////// TAB CTRL ///////////////////////
///////////////////////////////////////////
const int dwResTabCtrlStyle = WS_CHILD | WS_VISIBLE | TCS_VERTICAL;// | LVS_SINGLESEL;
if(!m_SptitterWndEx.AddTabCtrl(0, 1, &m_tabCtrl, CMFCTabCtrl::STYLE_3D, CMFCBaseTabCtrl::LOCATION_TOP, CSize(10,10)))
return -1;
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
m_DialogEx.Create(CAccuracyResultPage::IDD, NULL);
}
m_DialogEx.SetParent(&m_tabCtrl);
if(!m_DialogEx.GetParent())
return -1;
str.LoadString( AfxGetStaticModuleState()->m_hCurrentResourceHandle, IDS_RESULT_TAB);
m_tabCtrl.AddTab(&m_DialogEx, str, 0);
AdjustLayout();
return 0;
}
I getting assert on CDialogEx::PreTranslateMessage. The reason is the when it gets it's parents
_AFXWIN_INLINE CWnd* CWnd::GetParent() const
{ ASSERT(::IsWindow(m_hWnd)); return CWnd::FromHandle(::GetParent(m_hWnd)); }
m_hWnd is not a Wnd. But the CDialog looks perfectly OK, it has m_pParentWnd, but it's not m_tabCtrl.
So my questions are: Why doesn't CDialogEx set its parent?! And how it can be dealed with?!

Where is your dialog template residing? Is it in the same dll? If not, then i think that's your problem.
My guess is, if your dialog template resides in a different dll then windows might search in the HWND=>CWnd map of the module state of that dll. If that happens then it wont be able to find the CWnd in the map and will create a temporary CWnd object and set it as the parent of the dialog.

Related

MFC: Communicate with View from Dialog

I want my Dialog to communicate with my existing view outside of an OK response (so using an apply or similar). I assume Messages are the best way to do this.
I'm sure there are not a lot of MFC questions these days, so I hope someone is able to help.
Creating a new project via the wizard, I add a dialog (let's say a CPropertySheet) that is spawned by the view.
MyPropertiesSheet ps(_T("MyPropertiesSheet"));
if (ps.DoModal() == IDOK) {
// I don't care about this section
}
At first, I assumed that when I click 'apply' I would be able to send a message to the view and have it do something (as it was spawned in the view); however, I cannot pass messages directly to the view.
From the Dialog I use:
GetParent()->SendMessage(WM_BUTTON1, 0, 0);
I can catch the message within my MainFrm (a CmainFrame) which will launch the specified Button1() function, but I cannot catch the message in the view using the same code (below).
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWndEx)
...
ON_MESSAGE(WM_BUTTON1, Button1)
END_MESSAGE_MAP()
It makes sense as I guess the View is a child of the MainFrm and the Dialog belongs to the MainFrm, not the View.
My Programming Windows with MFC (2nd ed), by Jeff Prosise, uses a custom OnCreate to get a reference to the View by creating it manually, but I really don't want to have to do this as it seems rather complex. I am sure I will end up creating a lot of problems that way. The default OnCreate seems to have no obvious reference to my view (included for example, but feel free to skip this).
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CMDIFrameWndEx::OnCreate(lpCreateStruct) == -1)
return -1;
BOOL bNameValid;
CMDITabInfo mdiTabParams;
mdiTabParams.m_style = CMFCTabCtrl::STYLE_3D_ONENOTE; // other styles available...
mdiTabParams.m_bActiveTabCloseButton = TRUE; // set to FALSE to place close button at right of tab area
mdiTabParams.m_bTabIcons = FALSE; // set to TRUE to enable document icons on MDI taba
mdiTabParams.m_bAutoColor = TRUE; // set to FALSE to disable auto-coloring of MDI tabs
mdiTabParams.m_bDocumentMenu = TRUE; // enable the document menu at the right edge of the tab area
EnableMDITabbedGroups(TRUE, mdiTabParams);
if (!m_wndMenuBar.Create(this))
{
TRACE0("Failed to create menubar\n");
return -1; // fail to create
}
m_wndMenuBar.SetPaneStyle(m_wndMenuBar.GetPaneStyle() | CBRS_SIZE_DYNAMIC | CBRS_TOOLTIPS | CBRS_FLYBY);
// prevent the menu bar from taking the focus on activation
CMFCPopupMenu::SetForceMenuFocus(FALSE);
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(theApp.m_bHiColorIcons ? IDR_MAINFRAME_256 : IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
CString strToolBarName;
bNameValid = strToolBarName.LoadString(IDS_TOOLBAR_STANDARD);
ASSERT(bNameValid);
m_wndToolBar.SetWindowText(strToolBarName);
CString strCustomize;
bNameValid = strCustomize.LoadString(IDS_TOOLBAR_CUSTOMIZE);
ASSERT(bNameValid);
m_wndToolBar.EnableCustomizeButton(TRUE, ID_VIEW_CUSTOMIZE, strCustomize);
// Allow user-defined toolbars operations:
InitUserToolbars(nullptr, uiFirstUserToolBarId, uiLastUserToolBarId);
if (!m_wndStatusBar.Create(this))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT));
// TODO: Delete these five lines if you don't want the toolbar and menubar to be dockable
m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY);
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockPane(&m_wndMenuBar);
DockPane(&m_wndToolBar);
// enable Visual Studio 2005 style docking window behavior
CDockingManager::SetDockingMode(DT_SMART);
// enable Visual Studio 2005 style docking window auto-hide behavior
EnableAutoHidePanes(CBRS_ALIGN_ANY);
// Load menu item image (not placed on any standard toolbars):
CMFCToolBar::AddToolBarForImageCollection(IDR_MENU_IMAGES, theApp.m_bHiColorIcons ? IDB_MENU_IMAGES_24 : 0);
// create docking windows
if (!CreateDockingWindows())
{
TRACE0("Failed to create docking windows\n");
return -1;
}
m_wndFileView.EnableDocking(CBRS_ALIGN_ANY);
m_wndClassView.EnableDocking(CBRS_ALIGN_ANY);
DockPane(&m_wndFileView);
CDockablePane* pTabbedBar = nullptr;
m_wndClassView.AttachToTabWnd(&m_wndFileView, DM_SHOW, TRUE, &pTabbedBar);
m_wndOutput.EnableDocking(CBRS_ALIGN_ANY);
DockPane(&m_wndOutput);
m_wndProperties.EnableDocking(CBRS_ALIGN_ANY);
DockPane(&m_wndProperties);
// set the visual manager and style based on persisted value
OnApplicationLook(theApp.m_nAppLook);
// Enable enhanced windows management dialog
EnableWindowsDialog(ID_WINDOW_MANAGER, ID_WINDOW_MANAGER, TRUE);
// Enable toolbar and docking window menu replacement
EnablePaneMenu(TRUE, ID_VIEW_CUSTOMIZE, strCustomize, ID_VIEW_TOOLBAR);
// enable quick (Alt+drag) toolbar customization
CMFCToolBar::EnableQuickCustomization();
if (CMFCToolBar::GetUserImages() == nullptr)
{
// load user-defined toolbar images
if (m_UserImages.Load(_T(".\\UserImages.bmp")))
{
CMFCToolBar::SetUserImages(&m_UserImages);
}
}
// enable menu personalization (most-recently used commands)
// TODO: define your own basic commands, ensuring that each pulldown menu has at least one basic command.
CList<UINT, UINT> lstBasicCommands;
lstBasicCommands.AddTail(ID_FILE_NEW);
lstBasicCommands.AddTail(ID_FILE_OPEN);
lstBasicCommands.AddTail(ID_FILE_SAVE);
lstBasicCommands.AddTail(ID_FILE_PRINT);
lstBasicCommands.AddTail(ID_APP_EXIT);
lstBasicCommands.AddTail(ID_EDIT_CUT);
lstBasicCommands.AddTail(ID_EDIT_PASTE);
lstBasicCommands.AddTail(ID_EDIT_UNDO);
lstBasicCommands.AddTail(ID_APP_ABOUT);
lstBasicCommands.AddTail(ID_VIEW_STATUS_BAR);
lstBasicCommands.AddTail(ID_VIEW_TOOLBAR);
lstBasicCommands.AddTail(ID_VIEW_APPLOOK_OFF_2003);
lstBasicCommands.AddTail(ID_VIEW_APPLOOK_VS_2005);
lstBasicCommands.AddTail(ID_VIEW_APPLOOK_OFF_2007_BLUE);
lstBasicCommands.AddTail(ID_VIEW_APPLOOK_OFF_2007_SILVER);
lstBasicCommands.AddTail(ID_VIEW_APPLOOK_OFF_2007_BLACK);
lstBasicCommands.AddTail(ID_VIEW_APPLOOK_OFF_2007_AQUA);
lstBasicCommands.AddTail(ID_VIEW_APPLOOK_WINDOWS_7);
lstBasicCommands.AddTail(ID_SORTING_SORTALPHABETIC);
lstBasicCommands.AddTail(ID_SORTING_SORTBYTYPE);
lstBasicCommands.AddTail(ID_SORTING_SORTBYACCESS);
lstBasicCommands.AddTail(ID_SORTING_GROUPBYTYPE);
CMFCToolBar::SetBasicCommands(lstBasicCommands);
// Switch the order of document name and application name on the window title bar. This
// improves the usability of the taskbar because the document name is visible with the thumbnail.
ModifyStyle(0, FWS_PREFIXTITLE);
return 0;
}
I assume there must be a way to get a handle to my View from MainFrm.
I've tried:
auto pView = GetActiveView();
if (pView == NULL) {
std::string error = "Unable to get Active View\n";
TRACE(error.c_str());
}
else {
pView->SendMessage(WM_BUTTON1, 0, 0);
}
but this is returning NULL (so I can't use that to send a message).
I'm not even sure I need this, but I am interested in why this is not working and why I can't get a handle to my View from the MainFrm.
For a simple solution, I would post your command WM_BUTTON1 with WM_COMMAND. Then the command is routed the MFC way MSDN (
MDI: Main frame, active child frame, active view, active document, application).
No need to handle and forward it in CMainframe. It does automatically for you.
In your CDialog:
AfxGetMainWnd()->PostMessage(WM_COMMAND, WM_BUTTON1, 0);
Add your handler in your CView
ON_COMMAND(WM_BUTTON1, &CMyView::OnButton)
No guarantees...from memory mostly. I haven't used CMDIFrameWndEx, only CMDIFrameWnd, but I assume it should work for the derived Ex variant...seemed like that was your main frame class.
// pseudo code
CMDIFrameWndEx* pFrame = DYNAMIC_DOWNCAST(CMDIFrameWndEx, AfxGetMainWnd()); // doesn't always work for OLE servers
if (pFrame)
{
CMDIChileWnd* pMDIChild = pFrame->MDIGetActive();
if (pMDIChild)
{
CYourView* pYourView = DYNAMIC_DOWNCAST(CYourView, pMDIChild->GetActiveView());
if (pYourView)
{
// do something
}
}
}

CMFCTabCtrl ActiveTab on CMDIChildWndEx::OnMDIActivate and the MenuBar issue

As the title propose, I have an CMDIChildWndEx application(on VS2017, Windows 10 x64). On the ChildFrame, CMyView creates: (A) CMFCTabCtrl (Id=1), 2 CView derived classes: Lets say (B) CViewDerivedA object (Id=2) and (C) CViewDerivedB object (Id=3). The parent of A-C is the parent of CMyView. CMyView adds CDerivedViewA object as tab-0, and CViewDerivedB object as tab-1. CViewDerivedA handle MenuA of the menubar . But, when I open a MDI document, the menu is not enabled, until I switch to tab-1 & back to tab-0. I try the following code, but SetFocus() doesn't work:
// An application sends the WM_MDIACTIVATE message to a multiple-document interface (MDI)
// client window to instruct the client window to activate a different MDI child window.
void CMyChildFrame::OnMDIActivate(BOOL bActivate, CWnd* pActivateWnd, CWnd* pDeactivateWnd)
{
CMDIChildWndEx::OnMDIActivate(bActivate, pActivateWnd, pDeactivateWnd);
if (bActivate)
{
CMFCTabCtrl *pTabCtrl = (CMFCTabCtrl*) GetDlgItem(1);
if (pTabCtrl->GetActiveTab() == 0) // 0 - Silhouette tab, 1 - Hit List tab
{
// CWnd * pWnd = GetDlgItem(2);
// pWnd->SetFocus();
pTabCtrl->SetActiveTab(1);
pTabCtrl->SetActiveTab(0);
}
}
}
In any case, this solution seems to me not "clean", a "workaround". >>>> I assume that the proper way is to tell the pTabCtrl (Id=1) to SetFocus() on the active tab, as it does when I SetActiveTab() next & back. What is the way to make it properly?
I find the solution:
void CMyChildFrame::OnMDIActivate(BOOL bActivate, CWnd* pActivateWnd, CWnd* pDeactivateWnd)
{
CMDIChildWndEx::OnMDIActivate(bActivate, pActivateWnd, pDeactivateWnd);
if (bActivate)
{
CView * pView = (CView*)((CMFCTabCtrl*)GetDlgItem(1))->GetActiveWnd();
SetActiveView(pView);
}
}

Create multiple CDockablePanes onto an CDialog

I'm trying to do exactly the same as the article describes here:
"C++ MFC Feature Pack --> Create multiple CDockablePanes onto an CDialog"
I followed his procedure and now am able to undock and move the CDockablePane, but get the same crash when dock it back. In his own answer, he said he created the dummywnd by himself so MFC skipped the creation and the call to GetTopLevelFrame(). And this is where I got confused, how do I create the dummywnd exactly?
My second question is how do I exchange data between the CMyFrame and CDialog?
The person who asked and answered the question seems to be inactive for years and unreachable. Could anyone please help or have any ideas?
Thanks,
Edit:
I break the program and traced back exactly as the other author described. The dummy window mentioned above is actually in afxdragframeimpl.cpp:
void CMFCDragFrameImpl::MoveDragFrame(BOOL bForceMove)
where it creates:
m_pWndDummy = new CDummyDockablePane;
and calls:
m_pWndDummy->CreateEx(0, _T(""), AFXGetTopLevelFrame(m_pDraggedWnd), CRect(0, 0, 0, 0), FALSE, AFX_DUMMY_WND_ID, WS_CHILD);
And yes, I'm trying to create a CFrameWndEx as child window in my dialog, and then add child CDockablePane in that CFrameWndEx.
Basically I have a MFC dialogA with some controls, and within this dialogA I need some tear-off tabsXYZ, and I need to add some controls within each of the tear-off tabsXYZ. So this means each tear-off tabsXYZ is actually one child dialogB. So this comes to where I try to use CDockablePanes (CPaneDialog actually) within the dialogA.
BOOL CMyDlg::OnInitDialog()
{
CRect wndRect;
GetWindowRect(wndRect);
m_pFrame = new CMyFrame();
m_pFrame->Create(NULL, NULL, WS_CHILD | WS_VISIBLE | WS_BORDER, wndRect, this);
m_pFrame->MoveWindow(wndRect);
CDialog::OnInitDialog();
...
}
I don't recommend the above code where frame window is placed in a dialog, because CFrameWndEx does all sorts of strange things, it's easy to break this code. Surprisingly, it works fine on VS2015, I couldn't duplicate any crash. But the window's behavior is still odd.
It's better to make a new frame window, and place a child dialog in frame. For example:
class CMyFrame : public CFrameWndEx
{
CDialog m_dialog;
int OnCreate(LPCREATESTRUCT lpCreateStruct)
{
CFrameWndEx::OnCreate(lpCreateStruct);
m_dialog.Create(IDD_CHILD1, this);
CRect rc;
m_dialog.GetClientRect(&rc);
m_dialog.SetWindowPos(NULL, 0, 0, rc.right, rc.bottom, SWP_SHOWWINDOW);
return 1;
}
DECLARE_MESSAGE_MAP()
};
You open the window as follows:
void CMyMainFrame::OnButton()
{
CMyFrame *frame = new CMyFrame;
frame->LoadFrame(IDR_MAINFRAME,
WS_POPUPWINDOW | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU, this);
frame->SetMenu(0);
frame->ShowWindow(SW_SHOW);
}
You can also create a child dialog and put that inside a docking pane. For example:
class CMyFrame : public CFrameWndEx
{
CDockablePane m_DockWnd;
CDialog m_dialog;
int OnCreate(LPCREATESTRUCT lpCreateStruct)
{
CFrameWndEx::OnCreate(lpCreateStruct);
CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));
CDockingManager::SetDockingMode(DT_SMART);
EnableAutoHidePanes(CBRS_ALIGN_ANY);
m_DockWnd.Create(_T("Test"), this, CRect(0, 0, 200, 200), TRUE, 0,
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN |
CBRS_LEFT | CBRS_FLOAT_MULTI);
m_dialog.Create(IDD_PAGE1, &m_DockWnd);
CRect rdialog;
m_dialog.GetClientRect(&rdialog);
m_dialog.SetWindowPos(NULL, 0, 0, rdialog.Width(), rdialog.Height(), SWP_SHOWWINDOW);
m_DockWnd.SetMinSize(rdialog.Size());
m_DockWnd.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockPane(&m_DockWnd);
return 0;
}
...
};
Also remember, if your main window is also CFrameWndEx then you make something like the following calls in InitInstance:
SetRegistryKey(_T("MyCompany\\MyApp"));
SetRegistryBase(_T("MainFrame"));
When you open a new frame window you must change the registry base with
SetRegistryBase(_T("CMyFrame"));
Then change it back to SetRegistryBase(_T("MainFrame")) when you exit CMyFrame

Having trouble getting a handle to a Dockable Pane C++

I created a multi doc ribbon based MFC application through the MFC Wizard. Im trying to get a handle to m_wndFileView to update its view. I know there are several ways to do it but Im not understanding why the method Im using is not working. Soooo to start
class CMainFrame : public CMDIFrameWndEx
{
...
CFileView m_wndFileView;
CPropertiesWnd m_wndProperties;
...
}
class CFileView : public CDockablePane
{
...
protected:
CViewTree m_wndFileView;
...
};
class CPropertiesWnd : public CDockablePane
{
...
protected:
CMFCPropertyGridCtrl m_wndPropList;
...
};
The main frame is created from MAINAPPLICATION.cpp
// create main MDI Frame window
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame || !pMainFrame->LoadFrame(IDR_MAINFRAME))
{
delete pMainFrame;
return FALSE;
}
m_pMainWnd = pMainFrame;
// call DragAcceptFiles only if there's a suffix
// In an MDI app, this should occur immediately after setting m_pMainWnd
// Enable drag/drop open
m_pMainWnd->DragAcceptFiles();
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// Enable DDE Execute open
EnableShellOpen();
RegisterShellFileTypes(TRUE);
// Dispatch commands specified on the command line. Will return FALSE if
// app was launched with /RegServer, /Register, /Unregserver or /Unregister.
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// The main window has been initialized, so show and update it
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
MainFrm.cpp creates these two panes:
// Create file view
CString strFileView;
bNameValid = strFileView.LoadString(IDS_FILE_VIEW);
ASSERT(bNameValid);
if (!m_wndFileView.Create(strFileView, this, CRect(0, 0, 200, 200), TRUE, ID_VIEW_FILEVIEW, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | CBRS_LEFT| CBRS_FLOAT_MULTI))
{
TRACE0("Failed to create File View window\n");
return FALSE; // failed to create
}
// Create properties window
CString strPropertiesWnd;
bNameValid = strPropertiesWnd.LoadString(IDS_PROPERTIES_WND);
ASSERT(bNameValid);
if (!m_wndProperties.Create(strPropertiesWnd, this, CRect(0, 0, 200, 200), TRUE, ID_VIEW_PROPERTIESWND, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | CBRS_RIGHT | CBRS_FLOAT_MULTI))
{
TRACE0("Failed to create Properties window\n");
return FALSE; // failed to create
}
From MAINAPPLICATION.cpp, I can access the properties pane through
CWnd * pwnd = ((CWnd*)(AfxGetApp()->m_pMainWnd));
CPropertiesWnd * pPropertiesWnd = (CPropertiesWnd*)pwnd->GetDlgItem(ID_VIEW_PROPERTIESWND);
CMFCPropertyGridCtrl * m_wndPropList = (CMFCPropertyGridCtrl *)pPropertiesWnd->GetDlgItem(2);
but for some reason I cannot access the fileview pane with
CWnd * pwnd = ((CWnd*)(AfxGetApp()->m_pMainWnd));
CFileView * pFileViewWnd = (CFileView*)pwnd->GetDlgItem(ID_VIEW_FILEVIEW);
CViewTree * m_wndFileView= (CViewTree*)pFileViewWnd ->GetDlgItem(4);
the (CFileView*)pwnd->GetDlgItem(ID_VIEW_FILEVIEW); returns NULL
please help. This is driving me crazy. In the end I can modify the m_wndPropList but not the m_wndFileView because I cannot get a handle to pFileViewWnd. Two panes created in the same way cannot be accessed in the same way. Why? If more code is needed, let me know. Thanks.
You should create a inline getters in CMainFrame class like this:
CFileView& GetFileViewPane()
{
return m_wndFileView;
}
CPropertiesWnd& GetPropsPane()
{
return m_wndProperties;
}
After that you can access those windows just like this:
CMainFrame* pMainFrame = DYNAMIC_DOWNCAST(CMainFrame, AfxGetMainWnd());
if (pMainFrame && pMainFrame->GetSafeHwnd()) // sanity check
{
pMainFrame->GetFileViewPane().DoStuff();
}
So #MarkRansom really helped out with the spy++ idea. To get a handle, I had to do the following:
// get CWnd to main window
CWnd * pwnd = ((CWnd*)(AfxGetApp()->m_pMainWnd));
// from spy++ i found that the tabbed panes were in a window called
// "File View" so i found a child window with that title
HWND h = FindWindowExW(pwnd->GetSafeHwnd(), NULL, NULL, L"File View");
// casted it to a tabbedpane pointer
CTabbedPane * pFileViewWn = (CTabbedPane *)CWnd::FromHandle(h);
// mfc wizard did what seems to me as weird naming. to find the docking
// panes i did the same as above but there was no title to this window
HWND hh = FindWindowExW(pFileViewWn->GetSafeHwnd(), NULL, NULL, L"");
// casted that
CDockablePane* pTabbedBar = (CDockablePane*)CWnd::FromHandle(hh);
// was able to find my specific docking pane using a resource id
CFileView * pFileViewWnd = (CFileView*)pTabbedBar->GetDlgItem(ID_VIEW_FILEVIEW);
// was able to find the control i wanted to use using a resource id
CViewTree * m_wndFileView = (CViewTree *)pFileViewWnd->GetDlgItem(4);
HTREEITEM hRoot = m_wndFileView->GetRootItem();
m_wndFileView->InsertItem(name, 2, 2, hRoot);
Going through the code i would think that the logic would be
tabbed pane
File View Dockable Pane
File View controls
Class View Dockable Pane
Class View controls
but somehow another window slide its way in to make
tabbed pane
MYSTERY DOCKABLE WINDOW
File View Dockable Pane
File View controls
Class View Dockable Pane
Class View controls

CMFCCaptionMenuButton alternative?

I need to create a caption bar button for a CDockablePane which will call up a menu with various options. I tried to use CMFCCaptionMenuButton and the button and menu show up but the message map methods for the menu ids don't fire. The MFC documentation states that CMFCCaptionMenuButton is meant for internal infrastructure and not really for your code.
So assuming that is what my problem is should I be using a CMFCCaptionBarButton and then making a separate popup menu? Has anyone made a similar caption bar based menu in MFC before?
Here's some slimmed down code snippets in case I just made a stupid mistake in hooking up the events:
BEGIN_MESSAGE_MAP(CDockPane, CDockablePane)
ON_COMMAND(ID_MORPH_BROWSER, OnMorphBrowser)
END_MESSAGE_MAP()
void CDockPane::OnPressButtons(UINT nHit)
{
// only for custom button handling don't call base
// close, maximize, and pin will be handled by default
switch (nHit)
{
case ID_MORPHTEST:
{
CMorphMenuButton* pButton = dynamic_cast<CMorphMenuButton*>(m_arrButtons.GetAt(m_morphIndex));
pButton->ShowMenu(this);
break;
}
}
}
void CDockPane::SetCaptionButtons()
{
CDockablePane::SetCaptionButtons(); // for close, pin etc
m_morphIndex = m_arrButtons.Add(new CMorphMenuButton(ID_MORPHTEST));
}
void CDockPane::OnMorphBrowser()
{
// do stuff on menu item click
}
Edit: Removed previous code no longer in use
Now that the sound of crickets chirping has dwindled in the background I guess I'll post the workaround I currently have in place:
Instead of inheriting and extending CMFCCaptionMenuButton I build my class by extending CMFCCaptionButton. I then create a menu and provide a ShowMenu method to be explicitly called when handling the custom button events as well as overriding GetIconID to return a particular system icon for the button for each menu added to the caption bar ending up with something like this for the example outlined in the question:
#pragma once
// CMorphMenuButton command target
class CMorphMenuButton : public CMFCCaptionButton
{
public:
CMorphMenuButton(UINT nHit);
virtual ~CMorphMenuButton();
virtual CMenuImages::IMAGES_IDS GetIconID (BOOL bHorz, BOOL bMaximized) const;
void ShowMenu(CWnd* pWnd);
private:
CMenu m_dockMenu;
CMenu* m_subMenu;
};
// MorphMenuButton.cpp : implementation file
//
#include "stdafx.h"
#include "MorphMenuButton.h"
// CMorphMenuButton
CMorphMenuButton::CMorphMenuButton(UINT nHit)
: CMFCCaptionButton(nHit)
{
SetMiniFrameButton(); // already defaulted?
m_dockMenu.LoadMenu(IDR_DOCKPANE); // resource ID for dock pane menus
}
CMorphMenuButton::~CMorphMenuButton()
{
m_dockMenu.DestroyMenu();
}
CMenuImages::IMAGES_IDS CMorphMenuButton::GetIconID(BOOL bHorz, BOOL bMaximized) const
{
return CMenuImages::IdArrowForward;
}
void CMorphMenuButton::ShowMenu(CWnd* pWnd)
{
CRect windowRect, buttonRect;
pWnd->GetWindowRect(&windowRect);
buttonRect = GetRect();
CPoint menuPos(windowRect.left + buttonRect.right, windowRect.top + buttonRect.bottom);
m_subMenu = m_dockMenu.GetSubMenu(0);
if (!m_subMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, menuPos.x, menuPos.y, pWnd))
{
DWORD id = GetLastError();
wchar_t errMsg[256];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, id, 0, errMsg, sizeof(errMsg), 0);
MessageBox(0, errMsg, L"Error", MB_OK);
}
}
The setting of caption bar buttons and handling of click events for both buttons and menus are the same as defined in the question and this works.