0

I have an MFC based Windows desktop SDI application built with the Doc-View framework and which displays modern OpenGL based graphics in the MainFrame.

All the common frequently encountered/used OpenGL operations are managed by small library of dedicated classes (e.g Camera, PipelineManager, ShaderManager, Quaternion, etc).

In addition to the project default View class (MFCAPPView) I have created a second View class (SecondaryView).

I want to be able to switch/toggle the default project View class to the second View class while the application is running, via a Mainmenu option 'Switch View' (i.e. make the second View class to be the active view).

Both View classes have an OnCreate(), OnDraw() and OnDestroy().

The initialisation of the OpenGL (i.e. loading of shaders specific for the View, etc) is done in OnCreate().

int MFCAPPView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CView::OnCreate(lpCreateStruct) == -1)
    {
        return -1;
    }

    //initialize the screen, create OpenGL context based on arguments
    m_screen.Initialize(GetSafeHwnd(), 32, 3, 0, false);

    //create the shader manager
    m_shaderManager = std::make_unique<GLShaderManager>();

    m_shaderManager->CreateMainShader();
    m_shaderManager->CreateTextShader();
    m_shaderManager->CreateLightShader();
.
.
.
}

The OnDraw() takes the form:

void CMFCAPPView::OnDraw(CDC*)
{
    CMFCAPPDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);

    m_screen.ActivateContext();
.
.
.
}

And the OnDestroy() complete code is:

void CMFCAPPView::OnDestroy()
{
    //disable the rendering context  
    m_screen.DeactivateContext();

    //close down window
    m_screen.ShutDown();

    //call the parent class 'CView' to destroy the window
    CView::OnDestroy();
}

In the App class header I define:

    CView* SwitchView();
    CView* m_defaultView = NULL;
    CView* m_secondaryView = NULL;

The initial basic code I have for the 'Switch View' event handler is based on Microsoft sample code for Switching Function: link

CView* CMFCAPPApp::SwitchView()
{
    CView* pActiveView = ((CFrameWnd*)m_pMainWnd)->GetActiveView();

    CView* pNewView = NULL;
    if (pActiveView == m_defaultView)
    {
        dynamic_cast<CMFCAPPView*>(pActiveView)->OnDestroy();
        pActiveView = m_secondaryView;
        dynamic_cast<SecondaryView*>(pActiveView)->OnCreate();
        pNewView = m_secondaryView;
    }
    else
        pNewView = m_secondaryView;
.
.
.
}

When I try to use the 'Switch View' menu option nothing happens at all. At very least I would have expected that when the OnDestroy() is called that the default view OpenGL context would be deactivated and I would then see just a blank and black View.

What am I missing or not doing correctly?

DavidH
  • 97
  • 6
  • Here is MS's sample for [Implement the Switching Function](https://learn.microsoft.com/en-us/cpp/mfc/adding-multiple-views-to-a-single-document?view=msvc-170#vcconswitchingfunctiona4) – Richard Critten Jun 08 '23 at 18:57
  • [What is a debugger and how can it help me diagnose problems?](https://stackoverflow.com/questions/25385173/what-is-a-debugger-and-how-can-it-help-me-diagnose-problems) – Jesper Juhl Jun 08 '23 at 19:40
  • Side note: Don't use `NULL` in modern C++, use [nullptr](https://en.cppreference.com/w/cpp/language/nullptr). – Jesper Juhl Jun 08 '23 at 19:41
  • Richard, The Event Handler code I posted is essentially the starting section of the MS code sample you linked to. BUT I modified it to deliberately include first a call the the 'OnDestroy()' to first deactivate the OpenGL context of the current active view first - and that does not appear to happen. So, even before any possible switching of Views takes place using this sample code, my question is why does that happen? – DavidH Jun 08 '23 at 20:15
  • IF I use the MS Switching Function sample code exactly as it is (apart from re-naming 'oldView' and 'newView' to the names of my own Views) it crashes at the line:- ```::SetWindowLong(pActiveView->m_hWnd, GWL_ID, ::GetWindowLong(pNewView->m_hWnd, GWL_ID));``` With the explanation: "Unhandled exception thrown: read access violation. pNewView was nullptr." – DavidH Jun 08 '23 at 20:27
  • 2
    The MS sample is based on the assumption that `m_pNewView` has already been created, both as a C++ class instance and as a window (`Create()`). Also, on that the app will be maintaining 2 views and switching between them. You don't need to destroy the initial view, although you can. Do not call handlers like `OnCreate()` or `OnDestroy()` yourself, instead call `Create()` or `DestroyWindow()`. So you have to create the new view, add it to the doc's list of views, switch to it and destroy the old one if you want. Check `CDocument`'s AddView(), RemoveView(), GetFirstViewPosition(), GetNextView(). – Constantine Georgiou Jun 09 '23 at 16:43
  • Thanks. I will investigate. So, on the basis of the suggestions you make here what changes should be made to the Switching Function event handler code? – DavidH Jun 09 '23 at 19:48
  • Do you want your app to be maintaining two views and switching between them, or create the new view and delete the old one? – Constantine Georgiou Jun 10 '23 at 19:29
  • Sth about replying to comments, in your reply you must quote the user (using @username) if you want him/her to receive a notification; the author of the post always receives a notification, so this is not necessary. More [here](https://stackoverflow.com/help/privileges/comment). – Constantine Georgiou Jun 10 '23 at 19:39
  • @ConstantineGeorgiou I will do whatever is easiest. My only aim is to be able to allow user to change the view that is being displayed. IF it is easiest to only maintain one view at a time then I don't mind having to create the old view again when the user switches back to it again. – DavidH Jun 10 '23 at 21:49

1 Answers1

0

You have correctly overridden the OnCreate() and OnDestroy() handlers, so as to perform resources allocation and cleanup, and call the base class implementation. You may want to add some TRACE() messages too, to watch the object's lifetime, better for both the new and the original view classes. I would even check the objects' destructors. For example:

CNewView::~CNewView()
{
    TRACE("NewView OBJECT Destroyed\n");
}

void CNewView::OnDestroy()
{
    TRACE(_T("Destroying NewView WINDOW\n"));

    //disable the rendering context  
    m_screen.DeactivateContext();

    //close down window
    m_screen.ShutDown();

    //call the parent class 'CView' to destroy the window
    CView::OnDestroy();

    TRACE(_T("NewView WINDOW Destroyed\n"));
}

The MS sample creates another view and adds it to the document's list of views and activates it. So it maintains two view objects and switches between them. The active view occupies all the frame window's client area, ie only one of the views is visible at a time. If this is OK to you, it is possible to keep only one view object and destroy the one that is not visible. This is what I will be doing in my code below. It can be changed back to keep two view objects with very minor code changes, no implementation is really simpler.

Some notes about the MS sample and the changes I made:

  • The sample first creates a new view object (class CNewView) and associates it with the document through a properly initialized CCreateContext object. The view window is created (Create()) and initialized (WM_INITIALUPDATE);
  • Then it switches the views. Before doing so it flips their window IDs (those GetWindowLong()/SetWindowLong() calls). It seems to be a framework requirement that the active view has an ID of AFX_IDW_PANE_FIRST and the rest AFX_IDW_PANE_FIRST+1, AFX_IDW_PANE_FIRST+2 and so on. As we will only be keeping one view object, we can skip this and just create a new view with an ID of AFX_IDW_PANE_FIRST.
  • The sample defines two object variables, m_pOldView and m_pNewView, which we won't be using.

I think the frame-window class is the best place to put the SwitchView() function. So the code could become:

void CMainFrame::SwitchView()
{
    CView* pCurView = GetActiveView(), *pNewView;

    // Create a new view object, different to the current one
    if (pCurView->IsKindOf(RUNTIME_CLASS(CMFCAPPView))) // C++ purists may freak-out with this... 
        pNewView = new CNewView();
    else pNewView = new CMFCAPPView();

    if (!pNewView)
    {
        AfxMessageBox(_T("View Object could not be Created!"));
        return;
    }

    // Create the new view window and associate it with the document
    CCreateContext newContext;
    newContext.m_pNewViewClass = NULL;
    newContext.m_pNewDocTemplate = NULL;
    newContext.m_pLastView = NULL;
    newContext.m_pCurrentFrame = NULL;
    newContext.m_pCurrentDoc = GetActiveDocument();
    pNewView->Create(NULL, _T("View"), WS_CHILD, CRect(0, 0, 0, 0), this, AFX_IDW_PANE_FIRST, &newContext);
    pNewView->SendMessage(WM_INITIALUPDATE, 0, 0);

    // Switch to the new view and invalidate it
    pCurView->ShowWindow(SW_HIDE);
    pNewView->ShowWindow(SW_SHOW);
    SetActiveView(pNewView);
    RecalcLayout();
    pNewView->Invalidate(); // Will trigger a repaint

    // Remove the old view from the doc'c list of views and destroy it
    // Not sure if all statements below are needed, please test
    GetActiveDocument()->RemoveView(pCurView);
    pCurView->DestroyWindow();
    delete pCurView;
}

Most of this was taken from the MS sample and modified slightly. Haven't really tested this in VS, some of the last three commands may not be needed, please debug to find out.

Constantine Georgiou
  • 2,412
  • 1
  • 13
  • 17