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
Related
So, I have a CDialog resource that I have had for a long time and I decided to add a statusbar to it. Here is the resource:
All controls fit nicely in the dialog. Now, at runtime this is what it looks like:
The tutorial I followed was here and for the most part it works. Here is my setup code:
///////////////////////////////
m_StatusBar.Create(this); //We create the status bar
m_StatusBar.SetIndicators(indicators, 2); //Set the number of panes
CRect rect;
GetClientRect(&rect);
//Size the two panes
m_StatusBar.SetPaneInfo(0, ID_INDICATOR_DATE,
SBPS_NORMAL, 200);
m_StatusBar.SetPaneInfo(1, ID_INDICATOR_MEETING_TYPE, SBPS_STRETCH, 0);
//This is where we actually draw it on the screen
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST,
ID_INDICATOR_DATE);
GetDynamicLayout()->AddItem(m_StatusBar.GetSafeHwnd(),
CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeHorizontal(100));
///////////////////////////////
I have tried without WindowsBlinds and the issues are still there.
So my issues are:
1/ The controls are overlapping the status bar. How do I accurately set these controls in the resource editor so this issue won't happen? How should it be resolved? Hit n miss?
2/ My dialog supports resizing by using the dynamic layouts and it has the OBM_SIZE in the bottom right:
void CResizingDialog::InitialiseResizeIcon(CBitmap& rBmpResize, CStatic& rLblResize, CWnd* pDialog)
{
CRect rcIcon, rcClient;
if (pDialog != nullptr)
{
rBmpResize.LoadOEMBitmap(OBM_SIZE);
rLblResize.Create(nullptr, WS_CHILD | WS_VISIBLE | SS_BITMAP,
CRect(0, 0, 16, 16), pDialog, IDC_STATIC_RESIZE);
rLblResize.SetBitmap(rBmpResize);
pDialog->GetClientRect(rcClient);
rLblResize.GetClientRect(rcIcon);
rLblResize.SetWindowPos(&CWnd::wndTop,
rcClient.right - rcIcon.Width(),
rcClient.bottom - rcIcon.Height(), 0, 0, SWP_NOSIZE);
CMFCDynamicLayout *pDynamicLayout = pDialog->GetDynamicLayout();
if (pDynamicLayout != nullptr)
{
CMFCDynamicLayout::MoveSettings moveSettings = CMFCDynamicLayout::MoveHorizontalAndVertical(100, 100);
CMFCDynamicLayout::SizeSettings sizeSettings = CMFCDynamicLayout::SizeNone();
pDynamicLayout->AddItem(rLblResize.GetSafeHwnd(), moveSettings, sizeSettings);
}
}
}
How do I avoid the issue that you can see there now in the bottom right?
Update
It looked like I should use CreateEx And use this style SBARS_SIZEGRIP. Then stop creating my own resize icon. I assume the two grippers will look the same. So this might be one of the answers.
I tried using the above flag but unfortunately I can't use it:
This gripper is not consistent with the other one I am using so I need to keep my original one instead.
Update 2
I now realise that the gripper is always created anyway, so I had two grippers there! I have now derived my own statusbar class and switched off the default gripper:
BOOL CCreateReportStatusBar::PreCreateWindow(CREATESTRUCT& cs)
{
BOOL bRet = CStatusBar::PreCreateWindow(cs);
cs.style &= ~SBARS_SIZEGRIP;
return bRet;
}
So now I only have the one gripper. But my two issues still remain.
Update 3
I stumbled over this. In theory if I override this DrawGripper method I should be able to render my own gripper instead. Doesn't work. The method is never called.
Update 4
I decided not to fight the system. I have let the status bardraw the themes gripper and I have adjusted my resizing dialog class to also draw the themes gripper. So all is good.
I'm writing an MFC dialog with multiple controls. I have currently have a CWnd that is placed on the right half of the dialog. Upon clicking an edit button, the child CWnd is resized to take up a larger portion of the dialog.
However, now when I try to resize the window, the child CWnd jumps back to where it was originally. I cannot seem to figure out how to keep it in it's new position when resizing.
Relevant code:
OnInit() {
//the grouper rectangle
CRect rectHTMLGrouper;
m_grpHTMLbox.GetWindowRect(&rectHTMLGrouper);
ScreenToClient(&rectHTMLGrouper);
//the new rectangle to use for positioning
CRect rectHtml;
rectHtml.left = rectHTMLGrouper.left + PREVIEW_EDITOR_LEFT;
rectHtml.right = rectHTMLGrouper.right - PREVIEW_EDITOR_RIGHT;
rectHtml.top = rectHTMLGrouper.top + PREVIEW_EDITOR_TOP;
rectHtml.bottom = rectHTMLGrouper.bottom - PREVIEW_EDITOR_BOTTOM;
//this inits my editor and sets the position
m_wHtmlEditor.CreateHtmlEditor(rectHTMLGrouper, this, WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN);
//CodeJock - XTREMEToolkit Call for SetResize Logic
SetResize(m_wHtmlEditor.GetDlgCtrlID(), LEFT_PANE_RESIZE, 0, 1, 1);
m_wHtmlEditor.SetWindowPos(&CWnd::wndTop, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOMOVE);
}
OnEditMode() {
//enlarge the editor to take up the full dialog
CRect parentClientRect;
m_wHtmlEditor.GetParent()->GetClientRect(&parentClientRect);
m_wHtmlEditor.SetWindowPos(&CWnd::wndTop, parentClientRect.left + edgePadding, parentClientRect.top + editorTopPadding, parentClientRect.right - (edgePadding * 2), parentClientRect.bottom - bottomPadding, SWP_NOOWNERZORDER);
return;
}
Upon clicking an edit button, the child CWnd is resized to take up a
larger portion of the dialog.
You have to handle that same resize in your OnSize() (ON_WM_SIZE()) message handler (using some kind of BOOL member to keep track of the child window's status).
OnSize() is called repeatedly while resizing the dialog.
Example:
// .h
BOOL m_bIsEditMode;
// .cpp
// keep track of m_bIsEditMode
void CMyDlg::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
if (m_bIsEditMode) {
//enlarge the editor to take up the full dialog
m_wHtmlEditor.MoveWindow (0, 0, cx, cy);
}
}
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.
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
I've would like to have 2 sets of tabs on my CScrollView -derived class, so I put 2 CMFCTabCtrs side by side. The tab controls are filled with dialogs (currently same class, but different objects for test purpose, later different dialogs for each tab control).
The problem is while the controls themselves are placed besides each other as desired. the tabs or dialogs from the second tab control appear in area of the first tab controls, thus overlapping with it.
on the picture above the dialog on the left (currently minimalistic for testing) actually belongs to the control on the right. the dialogs inside of tab are childs of the view.
Resource of the dialog:
IDD_COMBATANTDLG DIALOGEX 0, 0, 320, 331
STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE
FONT 8, "MS Shell Dlg", 0, 0, 0x0
BEGIN
GROUPBOX "Flotte",IDC_STATIC,19,18,192,175,BS_FLAT
LTEXT "Static",IDC_STATIC,91,94,19,8
EDITTEXT IDC_EDIT1,136,118,40,14,ES_AUTOHSCROLL
END
declarations:
class SimDataInput : public CScrollView
{
protected: // create from serialization only
SimDataInput();
DECLARE_DYNCREATE(SimDataInput)
// Attributes
private:
CMFCTabCtrl combatant_tabs[2];//tabs
std::vector<CombatantDlg*> p2combatdlg[2];//stores pointers to dialog
const CStringW attdef[2] = { L"Angreifer ", L"Verteidiger " };//standard labels for respective tab controls
............
from OnInitialUpdate():
void SimDataInput::OnInitialUpdate()
{
CScrollView::OnInitialUpdate();
for (auto i = 0; i < 2;i++)
{
CStringW label ( attdef[i]);
RECT pos = RECT{ i * 400, 0, 400 + i * 400, 500 };
combatant_tabs[i].Create(CMFCTabCtrl::STYLE_3D, pos, this,10000+i, CMFCTabCtrl::LOCATION_TOP,TRUE);//create tab
combatant_tabs[i].EnableTabSwap(FALSE);
combatant_tabs[i].EnableActiveTabCloseButton();
//combatant_tabs[sim::ATT].EnableTabDocumentsMenu(TRUE);
p2combatdlg[i].reserve(16);
p2combatdlg[i].emplace_back();//create pointer
p2combatdlg[i].back() = new CombatantDlg(this);//initialize pointer to the new dialog
p2combatdlg[i].back()->Create(IDD_COMBATANTDLG, this);//create dialog
//p2combatdlg[i].back()->SetWindowPos(&combatant_tabs[i], 400, 0, 0, 0, SWP_NOZORDER | SWP_SHOWWINDOW);//tried this -doesn't help
label.AppendFormat(L"%d",1);
combatant_tabs[i].InsertTab(p2combatdlg[i].back(),label , 0, -1, FALSE);//insert the first tab
}
.......
next function inserts new tabs after pushing respective button
void SimDataInput::OnCombatant(UINT nID)//used by ON_COMMAND_RANGE for both tab controls
{
nID -= ID_ATTACKER;
if (combatant_tabs[nID].GetTabsNum() == 16)
return;
CStringW label ( attdef[nID]);
p2combatdlg[nID].emplace_back();
label.AppendFormat(L"%d", p2combatdlg[nID].size());
p2combatdlg[nID].back() = new CombatantDlg(this);
p2combatdlg[nID].back()->Create(IDD_COMBATANTDLG, this);
combatant_tabs[nID].InsertTab(p2combatdlg[nID].back(), label , combatant_tabs[nID].GetTabsNum(), -1, FALSE);
combatant_tabs[nID].SetActiveTab(combatant_tabs[nID].GetTabsNum() - 1);
// TODO: Add your command handler code here
}
After all, making the CMFCTabCtrls parents to the contained dialog corrected the behaviour. I had to store the pointer to the former parentsd insidethe dialogs to make the access easier