MFC: Adding a status bar to a CDialogEx that is resizable? - mfc

I thought it be nice to add a Status Bar with percentage and other information to a CDialogEx that is used for viewing an image. But it doesn't seem that you can simply use a CMFCStatusBar or a CStatusBar and have it just work.
I found various samples, but none of them have the statusbar outside the client area and moves as resized? The different methods simply create a statusbar and it ends up hidden under a horizontal scrollbar and if you resize the window, the statusbar is sitting there in the middle of the dialog.
Is there an easy way or full example of having a statusbar on a CDialogEx that can be resized like a normal window?

Is there an easy way or full example of having a statusbar on a CDialogEx that can be resized like a normal window?
Yes! Once you have created the status bar you can add it to the dynamic layout for resizing:
//This is where we actually draw it on the screen
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST,
ID_INDICATOR_MEETING_TYPE);
GetDynamicLayout()->AddItem(m_StatusBar.GetSafeHwnd(),
CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeHorizontal(100));
I have a status bar (not CMFCStatusBar as it will not work, but CStatusBar is OK) on two dialogs in my application.
When Dynamic Layout is not automatically enabled
Here is an updated example for when Dynamic Layout is not automatically enabled for you (CDialogEx with no controls):
BOOL CMyDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
if (!m_StatusBar.Create(this)) {
TRACE0("Failed to create status bar\n");
return -1;
}
m_StatusBar.SetIndicators(indicators, _countof(indicators));
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0);
EnableDynamicLayout();
auto pdlmanager=GetDynamicLayout();
if (pdlmanager) {
if (pdlmanager->Create(this)) {
pdlmanager->AddItem(m_StatusBar.GetSafeHwnd(), CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeHorizontal(100));
}
}
// return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
return TRUE;
}
Catering for horizontal scroll bars
NIf you have a horizontal scrollbar the StatusBar will end up above it; therefore you may have to create separate CWnd and add it to the dynamic layout (it would also be the nIDLeftOver of the RepositionBars()).
Here's how you can add the a "view" window for the contents so scrollbars can be contained within the view area:
BOOL CMyDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
if (!m_StatusBar.Create(this)) {
TRACE0("Failed to create status bar\n");
return -1;
}
m_StatusBar.SetIndicators(indicators, _countof(indicators));
CRect rc;
GetClientRect(&rc);
CString clsname=AfxRegisterWndClass(0);
m_ImageView.Create(clsname, _T(""), WS_CHILD | WS_VISIBLE, rc, this, IDC_MY_VIEW);
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, IDC_MY_VIEW);
EnableDynamicLayout();
auto pdlmanager=GetDynamicLayout();
if (pdlmanager) {
if (pdlmanager->Create(this)) {
pdlmanager->AddItem(m_StatusBar.GetSafeHwnd(), CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeHorizontal(100));
pdlmanager->AddItem(m_ImageView.GetSafeHwnd(), CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontalAndVertical(100, 100));
}
}
// return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
return TRUE;
}

Related

MFC OnMeasureItem & OnDrawItem in menu of MDI multidoc application

(Update, see original question below)
After doing a bit of digging, I'm basically trying to understand the following; In the context of an MDI application, if a menu (which is associated with a specific CChildWnd) has an MF_OWNERDRAW, why are the ON_WM_MEASUREITEM and ON_WM_DRAWITEM events send to the CMainWnd instead of the CChildWnd?
In my InitInstance, the document template is registered and the associated menu is modified to add the MF_OWNERDRAW:
BOOL CMyApp::InitInstance()
{
// ...
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_CHILDFRAME,
RUNTIME_CLASS(CFooDoc),
RUNTIME_CLASS(CFooWnd),
RUNTIME_CLASS(CFooView)
);
if (pDocTemplate->m_hMenuShared != NULL) {
CMenu* pMenu = CMenu::FromHandle(pDocTemplate->m_hMenuShared);
// Add MF_ONWERDRAW to the items that need it.
pMenu->ModifyMenu([item_id], MF_BYCOMMAND | MF_OWNERDRAW, [item_id]);
}
AddDocTemplate(pDocTemplate);
// ...
}
So, once the document template is registered, the menu associated with the document/frame is modified to add the MF_ONWERDRAW flag to each of the required items (the color selection items in my case).
However, why are the OnMeasureItem and OnDrawItem events send to the CMainWnd and not the CFooWnd? And how can I direct the events to the CFooWnd instead?
The reason I'am asking, if I have 5 different types of documents in my MDI application, each needing custom menus, then the CMainWnd basically becomes a mess of message handling. The logical place for the custom menu logic is in the CChildWnd, not the CMainWnd.
Original question:
I'm doing some work on a very old application (MFC 4.2) and I'm running into a problem with drawing in a menu item.
The original application has a menu to select a color and it actually draws the colors in the menu when opened so it easier for the user to select the color.
The behavior for this implemented in CMainWnd using the OnMeasureItem and the OnDrawItem.
class CMainWnd : public CMDIFrameWnd
{
DECLARE_DYNCREATE(CMainWnd)
protected:
afx_msg void OnMeasureItem(int, LPMEASUREITEMSTRUCT);
afx_msg void OnDrawItem(int, LPDRAWITEMSTRUCT);
DECLARE_MESSAGE_MAP()
};
Then, in the implementation (omitted bits and pieces for brevity):
BEGIN_MESSAGE_MAP(CMainWnd, CMDIFrameWnd)
ON_WM_MEASUREITEM()
ON_WM_DRAWITEM()
END_MESSAGE_MAP()
void CMainWnd::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis)
{
lpmis->itemWidth = ::GetSystemMetrics(SM_CYMENU) * 4;
lpmis->itemHeight = ::GetSystemMetrics(SM_CYMENU) * 1;
}
void CMainWnd::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis)
{
CDC dc;
dc.Attach(lpdis->hDC);
CBrush* pBrush;
// draw the hover/selection rectangle
pBrush = new CBrush(::GetSysColor((lpdis->itemState & ODS_SELECTED) ? COLOR_HIGHLIGHT :
COLOR_MENU));
dc.FrameRect(&(lpdis->rcItem), pBrush);
delete pBrush;
// load a checkbox icon into a bitmap
BITMAP bm;
CBitmap bitmap;
bitmap.LoadOEMBitmap(OBM_CHECK);
bitmap.GetObject(sizeof(bm), &bm);
// if color/item selected then draw the checkbox
if (lpdis->itemState & ODS_CHECKED) {
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
CBitmap* pOldBitmap = dcMem.SelectObject(&bitmap);
dc.BitBlt(
lpdis->rcItem.left + 4,
lpdis->rcItem.top + (((lpdis->rcItem.bottom - lpdis->rcItem.top) - bm.bmHeight) / bm.bmWidth,
bm.bmHeight,
&dcMem,
0,
0,
SRCCOPY
);
dcMem.SelectObject(pOldBitmap);
}
// draw the actual color bar
pBrush = new CBrush(CPaintDoc::m_crColors[lpdis->itemID - ID_COLOR_BLACK]);
CRect rect = lpdis->rcItem;
rect.DeflateRect(6, 4);
rect.left += bm.bmWidth;
dc.FillRect(rect, pBrush);
delete pBrush;
dc.Detach();
}
What the OnDrawItem does is; it draws a horizontal color bar with a color, prefixed by a check icon if that color is selected and the menu item being hovered over is highlighted by a box being drawn around it.
However, since I'm turning this application into a Multidoc application and I don't really feel that this logic should be in the CMainWnd (since none of the other documents will have this type of menu), but that it should be part of the CChildWnd (which inherits from CMDIChildWnd).
But when I move this logic to that class, when I run the application, I get following message in the console logger:
Warning: unknown WM_MEASUREITEM for menu item 0x0082.
And none of the custom menu behavior seems to work.
so, the question is; How can move the custom behavior of a menu into the frame class of an MDI document rather than having it located in the application main frame?
I figured out a work around. Not ideal but I can understand that this is a quirk in the framework, i.e. the menu seems to be part of the MainWnd so from a technical point of view, that is where the ON_WM_MEASUREITEM and ON_WM_DRAWITEM would be handled.
Anyhow, my work around. Basically capture the events in the MainWnd and then delegate the behaviour to the ChildWnd. The trick here (I guess) is to figure out what ChildWnd to delegate to since in an MDI application there can be any number of different ChildWnd's (each with their own Document and View types).
The work around:
void CMainWnd::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis)
{
CMDIChildWnd* pActiveWnd = MDIGetActive();
if(pActiveWnd && pActiveWnd->IsWindowVisible())
{
if(pActiveWnd->IsKindOf(RUNTIME_CLASS(CMyChildWnd))) {
CMyChildWnd* pMyChildWnd = (CMyChildWnd*)pActiveWnd;
CMyChildWnd->DoMeasureItem(nIDCtl, lpmis);
}
}
}
void CMainWnd::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis)
{
CMDIChildWnd* pActiveWnd = MDIGetActive();
if(pActiveWnd && pActiveWnd->IsWindowVisible())
{
if(pActiveWnd->IsKindOf(RUNTIME_CLASS(CMyChildWnd))) {
CMyChildWnd* pMyChildWnd = (CMyChildWnd*)pActiveWnd;
CMyChildWnd->DoDrawItem(nIDCtl, lpdis);
}
}
}
Pretty straight forward, in the context of the MainWnd, get a pointer to the active MDI ChildWnd, check if it is active, then check the type by using IsKindOf and RUNTIME_CLASS and if so, voila, delegate the behavior to the ChildWnd. To DoMeasureItem and the DoDrawItem are just public methods implemented on the ChildWnd (see question for details).

MFC Allow use to enable/disable floating toolbar via menu command

Added Update 2 on 3/29/2022
My MFC app has the menubar, toolbar, and status bar all working correctly. I am at the point I'm adding creature features...one that really annoys me is the choice of permanently locking them or allow them to float at runtime...I've looked high and low and I've yet to see an example where a user can allow this feature to be dynamically changed once the app is up and running. The first function is from the app which works correctly. NOTE: I have the items allowing docking commented out as this was my test code..I know that those are the lines that enable or disable docking at run time....-> // enable docking
int CMainFrame::OnCreate(LPCREATESTRUCT pptCreate)
{
CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerVS2005));
if ( -1 == CMDIFrameWndEx::OnCreate(pptCreate) )
return -1;
// create menu bar
if ( !m_wndMenuBar.Create(this) )
return -1;
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);
// set the visual manager and style based on persisted value
theApp.m_nAppLook = theApp.GetInt(_T("ApplicationLook"), ID_VIEW_APPLOOK_VS_2008);
OnApplicationLook(theApp.m_nAppLook);
// create tool bar
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(IDR_MAINFRAME) )
return -1;
// create status bar
if ( !m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators(miIndicators, sizeof(miIndicators)/sizeof(UINT)) )
return -1;
// enable docking
//m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY);
//m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
m_wndToolBar.EnableToolTips(TRUE);
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);
// create docking windows
if (!CreateDockingWindows())
{
TRACE0("Failed to create docking windows\n");
return -1;
}
m_wndOutput.EnableDocking(CBRS_ALIGN_ANY);
DockPane(&m_wndOutput);
return 0;
}
So I've created a menu item that toggles a flag to turn "docking" on and off. The variable switches correctly.. but the command execution of turning the dock "3 dots" on the left side of the menubar and toolbar always remain...i.e. never goes from Float to Dock mode and vice versa. The logic of the toggle works as I can see in my debugging status window the entry points of TRUE and FALSE are seen each time I click the menu. So I guess the question is, can these items be dynamically turned from FLOAT to DOCK without destroying the window and trying to reinit it which would be a visual disaster?
void CMainFrame::UserDockingBarsOption()
{
CMainFrame* pMainFrame = (CMainFrame*)AfxGetMainWnd();
pMainFrame->m_wndOutput.AddStringDebugTab(_T("Debug: MainFrame--CMainFrame::UserDockingBarsOption()"));
if (UserDockingFlag == TRUE)
{
m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY);
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
UserDockingFlag = FALSE;
pMainFrame->m_wndOutput.AddStringDebugTab(_T("Debug: MainFrame--Inside True"));
}
else
{
m_wndMenuBar.EnableDocking(FALSE);
m_wndToolBar.EnableDocking(FALSE);
EnableDocking(FALSE);
UserDockingFlag = TRUE;
pMainFrame->m_wndOutput.AddStringDebugTab(_T("Debug: MainFrame--Inside False"));
}
}
Update 1:
So like my other posts, I'm back after a family matter. I revisited this and because I'm using:
CMFCMenuBar m_wndMenuBar;
CMFCToolBar m_wndToolBar;
and the only member I can find is IsFloat as a question, not an execute. So it was mentioned I can float or dock it via:
https://learn.microsoft.com/en-us/cpp/mfc/docking-and-floating-toolbars?view=msvc-160
Every example that I see is done within the OnCreate which does not help me as I can't recall it after it's already the child is already constructed. So my thought was to invalidate the toolbar, destroy the toolbar, recreate the tool bar with the user switch of dock or float, and then reorganize the toolbars. I have "most" of this working except for the actual switch control aspect to rebuild the menu with the user option of floating the toolbar or docking it.
What I did in my property sheet is created two buttons for two seperate functions to see if I can get each piece of this to work. One button is destroy the toolbar, the other button is create i.e. recreate the toobar....and both of those "work".
This is all being done in the MainFrame.cpp for simplicity of the member variables, I may move it later if I can get it working.
There are the two functions:
Destroy Function:
void CMainFrame::OnToolBarDestroy()
{
m_wndToolBar.Invalidate();
m_wndToolBar.DestroyWindow();
RecalcLayout();
//OnToolBarCreate();
//m_wndToolBar.ShowPane(FALSE, FALSE, FALSE); // Hide toolbar
}
Recreate Toolbar outside of OnCreate
int CMainFrame::OnToolBarCreate()
{
// TODO: Add your implementation code here.
if (m_wndToolBar)
{
m_wndOutput.AddStringStatusTab(_T("Error: Icon toolbar is already active, action cancelled"));
m_wndOutput.AddStringDebugTab(_T("Debug: MainFrame--Error: Icon toolbar is already active, action cancelled"));
return -1;
}
// Create ToolBar toolbar
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(IDR_MAINFRAME))
{
TRACE0("Failed to Create Dialog ToolBar\n");
return -1;
}
CRect rcClientOld;
CRect rcClientNew;
GetClientRect(rcClientOld);
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0, reposQuery, rcClientNew);
//m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
//DockPane(&m_wndToolBar);
RecalcLayout();
//m_wndToolBar.ShowPane(TRUE, FALSE, FALSE); // Show toolbar
return -1;
}
So what is commented out that would make it "work" assuming following the OnCreate code order up to this point:
//m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
//DockPane(&m_wndToolBar);
What happening is the successful destroy, and successful rebuild of the menu.
What I need for switch control is to either have the line active for floating or commented out for docked which I have also done in OnCreate but since this is for the rebuilt menu, I need this to follow that method switching in or out this line: //m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); ...but when I do....the toolbar gets all garbled up / missing all icons and I have to nuke the registry keys for the workspace to "try again"...
Any ideas on how I can make this work? I feel like I'm This close to getting it...I'm just unsure what I may be missing here to get across he goal line.
Update 2
So I got the toolbar to come out of docked to float with modified code below without restarting the app. But there are issues, it seems to be drawing "ghost" bars underneath. If I double click the 3 dots, it will detach and the bar will float....GREAT! But, it leave a "mirror" behind....if I double click the floating menu, it redocks to the frame.
I know I'm close, I'm just missing a piece to finish this off. I've added the code below and a few screenshots of wha I'm seeing.....the point is I is "working", but I'm missing something...can anyone help?
OnDestroy method updated:
int CMainFrame::OnToolBarDestroy()
{
//if (!m_wndToolBar)
//{
// m_wndOutput.AddStringStatusTab(_T("Error: Icon toolbar is already removed, action cancelled"));
// m_wndOutput.AddStringDebugTab(_T("Debug: MainFrame--Error: Icon toolbar is already removed, action cancelled"));
// return -1;
//}
//m_wndToolBar.Invalidate();
//m_wndMenuBar.DestroyWindow();
m_wndToolBar.DestroyWindow();
//m_wndToolBar.AdjustDockingLayout();
//RecalcLayout();
//OnToolBarCreate();
//return 0;
CRect rcClientOld;
CRect rcClientNew;
GetClientRect(rcClientOld);
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0, reposQuery, rcClientNew);
//m_wndToolBar.ShowPane(FALSE, FALSE, TRUE); // Hide toolbar
RecalcLayout();
return 0;
}
OnCreate method updated:
int CMainFrame::OnToolBarCreate()
{
enter code here// TODO: Add your implementation code here.
if (m_wndToolBar)
{
m_wndOutput.AddStringStatusTab(_T("Error: Icon toolbar is already active, action cancelled"));
m_wndOutput.AddStringDebugTab(_T("Debug: MainFrame--Error: Icon toolbar is already active, action cancelled"));
return -1;
}
CMFCPopupMenu::SetForceMenuFocus(FALSE);
//CMDIChildWndEx::m_bEnableFloatingBars = TRUE;
// Create ToolBar toolbar
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_ALIGN_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to Create Dialog ToolBar\n");
return -1;
}
m_wndMenuBar.EnableDocking(FALSE);
m_wndToolBar.EnableDocking(FALSE);
m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY);
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockPane(&m_wndMenuBar);
DockPane(&m_wndToolBar);
// DockPaneLeftOf(&m_wndToolBar);
CDockingManager::SetDockingMode(DT_SMART);
EnableAutoHidePanes(CBRS_ALIGN_ANY);
CRect rcClientOld;
CRect rcClientNew;
GetClientRect(rcClientOld);
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0, reposQuery, rcClientNew);
m_wndOutput.AddStringStatusTab(_T("I'm created 1 times"));
//m_wndToolBar.ResetAll();
RecalcLayout();
//m_wndToolBar.ShowPane(TRUE, FALSE, TRUE); // Show toolbar
return 0;
}
The images below:
Start from OnCreate with the panes properly "docked" mode
Destroyed ToolBar
ToolBar is switched from dock to float mode with Update 2 code. Ghost bars shown in the image. Trying to solve that issue.
Soooooo....anyone have any ideas on how I can solve this riddle?
Thanks!
Chris

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

Customising a dynamic layout at runtime based on control visibility

I have a multi-purpose CDialog that supports resizing. It can display content in 3 variations.
Variation 1:
Variation 2:
Variation 3:
The dialogue controls are using the dynamic layout settings from the resource editor.
Variation 1 is fine and need no changes.
Variation 2 does not display the combo and date button. As a result I would like the "Text will ..." label to be down at the bottom and the "edit" box to be taller.
Variation 3 has a similar issue where the date button should move to the bottom and the edit box be taller.
Can this be achieved by changing the dynamic layout in code?
Update
I tried this in OnInitDialog:
if (!m_bShowWeekCombo)
{
CRect rctCombo;
m_cbWeek.GetWindowRect(rctCombo);
ScreenToClient(rctCombo);
CRect rctNote;
m_staticInfo.GetWindowRect(rctNote);
ScreenToClient(rctNote);
m_staticInfo.MoveWindow(rctCombo.left, rctCombo.top, rctNote.Width(), rctNote.Height());
}
At first I thought it was working:
The note is now at the bottom. But as soon as I resize the window:
The note has reverted to the original position.
I know I have this answer to a similar issue but do I really have to re-build the whole layout?
Update 2
if (!m_bShowWeekCombo)
{
CRect rctEdit;
m_editText.GetWindowRect(rctEdit);
ScreenToClient(rctEdit);
CRect rctCombo;
m_cbWeek.GetWindowRect(rctCombo);
ScreenToClient(rctCombo);
CRect rctNote;
m_staticInfo.GetWindowRect(rctNote);
ScreenToClient(rctNote);
//m_staticInfo.MoveWindow(rctCombo.left, rctCombo.top, rctNote.Width(), rctNote.Height());
m_staticInfo.SetWindowPos(NULL, rctCombo.left, rctCombo.top, 0, 0,
SWP_NOSIZE | SWP_NOZORDER);
m_editText.SetWindowPos(NULL, 0, 0, rctEdit.Width(), rctEdit.Height() + (rctCombo.top - rctNote.top),
SWP_NOMOVE | SWP_NOZORDER);
if (m_pDynamicLayout)
{
if (!m_pDynamicLayout->HasItem(m_staticInfo.m_hWnd))
{
m_pDynamicLayout->AddItem(m_staticInfo.m_hWnd,
CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeHorizontal(100));
}
else
{
TRACE(L"item already has dynamic move/size\n");
}
if (!m_pDynamicLayout->HasItem(m_editText.m_hWnd))
{
m_pDynamicLayout->AddItem(m_editText.m_hWnd,
CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontalAndVertical(100, 100));
}
else
{
TRACE(L"item already has dynamic move/size\n");
}
}
}
When I try the above the control width is the original width, even though the dialog had restored to wider dialog width.
CMFCDynamicLayout reads the dialog resource, it stores the coordinates for the child controls as well as their dynamic resize/move properties.
This is all done in CDialog::OnInitDialog. If you move the child control, example, m_staticInfo then CMFCDynamicLayout doesn't know you moved/resized the control. So upon the next dialog resize request, CMFCDynamicLayout uses the old values.
You can add dynamic resize/move for all controls expcept m_staticInfo and other controls which you intend to move manually. Then add m_staticInfo separately:
BOOL CMyDialog::OnInitDialog()
{
CDialog::OnInitDialog();
CRect rctCombo;
m_cbWeek.GetWindowRect(rctCombo);
ScreenToClient(rctCombo);
m_staticInfo.SetWindowPos(NULL, rctCombo.left, rctCombo.top, 0, 0,
SWP_NOSIZE | SWP_NOZORDER);
if(m_pDynamicLayout)
{
if(!m_pDynamicLayout->HasItem(m_staticInfo.m_hWnd))
{
m_pDynamicLayout->AddItem(m_staticInfo.m_hWnd,
CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeNone());
}
else
{
TRACE(L"item already has dynamic move/size\n");
AfxDebugBreak(0);
}
}
return 1;
}
Internally, MFC calls LoadDynamicLayoutResource(m_lpszTemplateName) to initialize dynamic size/move. But documentation says not to use this method directly.
Clarification
If you are using a dialog that supports resizing then you must remember to calculate the new width and height when you move the control to the new position. You would then use one of the appropriate Size calls. For example:
// The EDIT control height now needs increasing
iNewEditHeight = rctButton.top - iTextMarginY - rctEdit.top;
m_editText.SetWindowPos(nullptr, 0, 0, iNewWidth, iNewEditHeight, SWP_NOMOVE | SWP_NOZORDER);
It is up to you to workout how you want your control initially re-sized.
Then, in OnInitDialog I called a new method:
void CEditTextDlg::SetupDynamicLayout()
{
if (m_pDynamicLayout != nullptr)
{
m_pDynamicLayout->AddItem(IDC_BUTTON_INSERT_DATE,
CMFCDynamicLayout::MoveHorizontalAndVertical(100, 100), CMFCDynamicLayout::SizeNone());
m_pDynamicLayout->AddItem(IDC_STATIC_INFO,
CMFCDynamicLayout::MoveVertical(100), CMFCDynamicLayout::SizeHorizontal(100));
m_pDynamicLayout->AddItem(IDC_EDIT_TEXT,
CMFCDynamicLayout::MoveNone(), CMFCDynamicLayout::SizeHorizontalAndVertical(100, 100));
}
}
If you don't set the width correctly when using SetWindowPos and only use SizeNone() it will not resize correctly.

Creating a custom MessageBox class for MFC

I'm trying to create a Modeless Messagebox class in MFC 6.0. The end result should look pretty similar to this:
I'm trying to add an icon with the following, but it doesn't seem to work.
HICON hIcon = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_ERROR));
SetIcon(hIcon, FALSE);
Also, is there a way to set the background color of the top part of a dialog to a different color from the rest of the dialog?
There is some design issues here, for this type of interface the program should save automatically. If user is deleting the file then confirm it with modeless dialog. This way data is not lost if user is impatient. Anyway, you can just override OnPaint etc. and draw the icon
void CMyDlg::OnPaint()
{
CDialog::OnPaint();
//draw white rectangle on top-half of dialog
//draw lt-gray rectangle at bottom-half, GetSysColor(COLOR_BTNFACE)
HICON hIcon = LoadIcon(NULL, IDI_ERROR);
dc.DrawIcon(x, y, hIcon);
DestroyIcon(hIcon);
}
BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)
{
return 1;
}
You might also need to override OnCtlColor