2

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.

David
  • 1,050
  • 1
  • 16
  • 31
  • 1
    Modal dialogs _capture_ the message pump. I think what you will need to do is use your dialog as non-modal. – lakeweb Oct 01 '19 at 19:33
  • 1
    If you use SendMessage() and not PostMessage() the message will get there irregardless. For MFC dialogs, they have their own message pump...they disable the main frame, run the MFC dialog as modeless with a message pump, and so simulate the Win32 way of doing modal dialogs. If you're using property sheets/pages, it might be a little different...been too long since I looked.You can derive your own CPropertySheet based class and have a member that points to the CView and initialize it before you call DoModal(). If you're in the same thread, you can call member funcs or SendMessage(). – Joseph Willcoxson Oct 01 '19 at 20:30
  • Hi @Joseph Willcoxson But the view can to nothing with the message as I understand it. So why send a message? Modals are self contained and collect state, usually with the data exchange mechanism. So, *OK* will say the data is valid. But the window can not update with the message pump blocked. It has been a while for my since I've been there also... – lakeweb Oct 01 '19 at 21:02
  • 1
    [This](https://stackoverflow.com/questions/26741841/how-does-a-modal-dialogs-message-pump-interact-with-the-main-application-messag) may help... – lakeweb Oct 01 '19 at 21:29
  • 1
    Why do you say the message pump is blocked? There is a message pump. Messages are pumped. Besides, there is also UpdateWindow() where WM_PAINT can be sent (not posted) directly to the window procedure. He should try it and verify it does or does not work... – Joseph Willcoxson Oct 01 '19 at 21:56
  • I will try modeless and get back to you (in about 15 hours or so). Although, looking at sample code from the MFC book, they are still calling back to the MainFrame then messaging the referenced view. I will still try. – David Oct 02 '19 at 03:44
  • Was hoping to check tonight - but this will have to be in a couple of days. – David Oct 02 '19 at 18:27
  • I have my modeless dialog working, but haven't fixed the callback yet. I think this should work. – David Oct 03 '19 at 14:20

2 Answers2

2

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)
Tom Tom
  • 1,127
  • 11
  • 25
  • This seems to be the simplest and cleanest way to do this. Thank you. – David Oct 09 '19 at 03:27
  • For beginners like me: 1) The two `WM_BUTTON1` can be any value, as long as those two values are the same. 2) `PostMessage` is non-blocking, it just "post" the message to the queue. But `SendMessage` is blocking, not return until the window procedure has processed the message. – starriet Sep 08 '22 at 13:23
0

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
      }
   }
}
Joseph Willcoxson
  • 5,853
  • 1
  • 15
  • 29
  • The CYourView aspect is interesting as it starts throwing up errors if I try to include my View. I assume just using a pointer to CView should work? I will try this after trying the modeless suggestions above. Thank you! – David Oct 02 '19 at 03:42