Mehrere Views pro Dokument

Bisher haben wir nur den Standardfall betrachtet, dass ein Dokument genau eine Ansicht (View) besitzt. Das Doc/View Modell lässt es aber auch zu, dass ein Dokument mehrere Ansichten besitzen kann.

Wie mehrere Dokumente und deren Ansichten erstellt werden ist Thema des Kapitels MDI.

Im Folgenden werden wir nur den Fall betrachten, dass ein Dokument genau zwei Ansichten besitzt. Eine Erweiterung auf beliebig viele Ansichten ist dann nur noch reine Formsache.

Enthält ein Dokument mehrere Ansichten, so sind hierbei zwei Fälle zu unterscheiden:

  • alle Ansichten sind gleichzeitig sichtbar
  • es ist immer nur eine Ansicht sichtbar

Beginnen wir mit dem ersten Fall, dass alle Ansichten gleichzeitig sichtbar sind. Da Ansichten nicht für sich alleine existieren können ist es notwendig, dass pro Ansicht ein dazugehöriges Rahmenfenster erstellt wird. Und hierfür stellt die Dokumentenverwaltung CSingleDocTemplate die Methode CreateNewFrame(...) zur Verfügung. Dieser Methode wird u.a. als Parameter ein Zeiger auf das Dokument übergeben, für das ein neues Rahmenfenster einschließlich der Ansicht erstellt werden soll. Als Rückgabewert liefert die Methode einen CFrameWnd-Zeiger auf das erstellte Rahmenfenster. Dieses Rahmenfenster besitzt jetzt aber noch, wie jedes Standard-Rahmenfenster, ein Menü und das Schließ-Icon rechts oben in der Titelzeile. Da das neu erstellte Rahmenfenster aber nur zur Darstellung der Dokumentendaten dienen soll, und damit kein eigenes Menü und kein Schließ-Icon benötigt, wird im nächsten Schritt noch das Menü entfernt und das Schließ-Icon gesperrt. Stören Sie sich im nachher folgenden Listing nicht an den Aufrufen zum Entfernen und Modifizieren eines Menüs. Diese Befehle werden später bei der Behandlung der Menüs noch erklärt.

Nachdem das Menü entfernt und das Schließ-Icon gesperrt wurde ist der erste Schritt erledigt. Kommen wir nun zum zweiten Schritt, dem Erstellen einer zusätzlichen Ansicht. Obwohl das soeben erstellte Rahmenfenster schon eine Ansicht besitzt ist diese für unsere Zwecke nicht brauchbar da wir ja die Dokumentdaten in einer weiteren, eigenen OnDraw(...) Methode ausgeben wollen. Fügen wir deshalb über den Klassen-Assistenten zum Projekt eine neue MFC-Klasse hinzu die von der Basisklasse CView abgeleitet ist.

Im letzten Schritt muss 'nur' noch die erzeugte Ansicht mit dem bereits bestehenden Dokument und Rahmenfenster verbunden werden. Doch bevor wir das tun muss die bisherige Ansicht des neuen Rahmenfensters entfernt werden. Um die alte Ansicht zu erhalten wird die Methode GetActiveView(...) des Rahmenfensters aufgerufen, die einen CView-Zeiger darauf zurückliefert. Diese Ansicht wird dann mittels RemoveView(...) vom Dokument abgetrennt. Im Anschluss daran geht's ans Erstellen der neuen Ansicht. Dazu müssen der Klasse der neuen Ansicht noch Laufzeitinformationen hinzugefügt werden, so dass die Ansicht dynamisch durch die MFC erstellt werden kann. Dies wird durch Aufruf des Makros RUNTIME_CLASS(...) erreicht. Um die neuen Ansicht mit dem Dokument und Rahmenfenster zu verbinden, sind die Eigenschaften der Klasse CCreateContext  entsprechend auszufüllen. Diese Klasse wird dann an die CFrameWnd Methode CreateView(...) übergeben, die dann die Ansicht erstellt und mit mit dem Rahmenfenster und Dokument verbindet. Als Rückgabewert liefert die Methode einen Zeiger auf das neuen Ansichtsobjekt, das dann als aktuelle Ansicht des Rahmenfensters gesetzt werden muss.  Anschließend ist unbedingt die Methode RecalcLayout(...) des Rahmenfensters aufzurufen um die Größe der Ansicht an das Rahmenfenster anzupassen. Standardmäßig besitzt die Ansicht bis hierher noch die Größe (0/0)! Um die aktuellen Dokumentdaten in der Ansicht darzustellen, ist die Methode InitialUpdateFrame(...) aufzurufen. Am Ende muss noch das Fenster der alten Ansicht mittels DestroyWindow(...) entfernt werden.

 

BOOL CMultiViewApp::InitInstance()
{
    ....
    // Das einzige Fenster ist initialisiert und kann jetzt ...
    m_pMainWnd->ShowWindow(SW_SHOW);
    m_pMainWnd->UpdateWindow();

    // Zweiten Frame erstellen
    CDocument *pDoc = ((CFrameWnd*)m_pMainWnd)->GetActiveDocument();
    CFrameWnd *pCSecondFrame = pDocTemplate->CreateNewFrame(pDoc,NULL);
    // Menu aus zweiten Frame entfernen
    pCSecondFrame->SetMenu(NULL);
    // Sperre Schliessen-Menupunkt und Schliessen-Icon!
    CMenu* pSysMenu = pCSecondFrame->GetSystemMenu(FALSE);
    pSysMenu->ModifyMenu(SC_CLOSE,MF_BYCOMMAND|MF_DISABLED,SC_CLOSE);
    // Zweiten Frame darstellen
    pDocTemplate->InitialUpdateFrame(pCSecondFrame,pDoc,TRUE);
    // Zeiger auf Standard-View holen den CreateNewFrame(...) erzeugt hat
    CView *pCOldView = pCSecondFrame->GetActiveView();

    // View vom Dokument trennen
    pDoc->RemoveView(pCOldView);

    // Zweiten, eigenen View erstellen
    CRuntimeClass *pSecondView = RUNTIME_CLASS(CSecView);

    // CreateContext ausfuellen um Dokument/Frame/View zu verbinden
    CCreateContext CActContext;
    CActContext.m_pNewViewClass = pSecondView;
    CActContext.m_pCurrentDoc = pDoc;
    CActContext.m_pCurrentFrame = pCSecondFrame;
    // Nun zweiten View mit Frame verbinden
    CView *pNewView = static_cast<CView*>(pCSecondFrame->CreateView(&CActContext));
    // Zweiten View zum aktiven View im zweiten Frame machen
    pCSecondFrame->SetActiveView(pNewView);
    // Unbedingt Groesse des Views nun berechnen sonst besitzt das
    // View die Groesse (0/0)!
    pCSecondFrame->RecalcLayout();
    // Kompletten Frame nun aktualisieren
    pDocTemplate->InitialUpdateFrame(pCSecondFrame,pDoc,TRUE);
    // Altes View jetzt entfernen
    pCOldView->DestroyWindow();

    return TRUE;
}

Das Beispiel zu diesem Thema finden Sie unter 04DocView\MultiView. Im Beispiel werden jedes Mal wenn mit der linken Maustaste ins erste Rahmenfenster geklickt wird, die Dokumentdaten verändert und anschließend über die Ansichten im Debugfenster ausgegeben.

Wenden wir uns jetzt dem zweiten Fall zu, dass jeweils nur eine Ansicht aktiv ist. Zum Wechseln der Ansichten wird der Rahmenfenster-Klasse zunächst eine Methode SwitchView(...) hinzugefügt. Zusätzlich benötigt das Rahmenfenster noch zwei CView-Zeiger zum Abspeichern der Objektzeiger auf die Ansichten (im Beispiel m_pCFirstView und m_pCSecondView) sowie eine weitere Variable die die aktuell angezeigt Ansicht markiert (im Beispiel die bool-Variable m_bFirstViewActive).

Innerhalb der Methode SwitchView(...) holen wir uns wieder über GetActiveView(...) den Zeiger auf die aktuelle Ansicht und speichern diesen beim ersten Aufruf der Methode im Objektzeiger für die erste Ansicht ab. Anschließend muss noch das zweite Ansichtsobjekt erstellt werden. Die Erstellung der zweiten Ansichtobjekts (nicht WINDOWS-Fenster!) erfolgt über die MFC dynamisch durch den Aufruf der CreateObject(...) Methode der Klasse CRuntimeClass. Um die Ansicht selbst (d.h. das WINDOWS-Fenster) zu erstellen wird die Methode Create(...) des Ansichtobjekts aufgerufen. Zu beachten ist hierbei, dass Ansichten untergeordnete Fenster des Rahmenfensters sind und die aktive Ansicht immer die ID AFX_IDW_PANE_FIRST besitzen muss. Da IDs aber untergeordnete Fenster eindeutig bezeichnen müssen, wird der neuen Ansicht die ID AFX_IDW_PANE_FIRST+1 zugewiesen.

Sind alle Ansichtsobjekte bestimmt kann es an das Umschalten der Ansicht gehen. Dazu werden zunächst die IDs der beiden Ansichten ausgetauscht, d.h. die aktuelle Ansicht erhält danach immer die ID AFX_IDW_PANE_FIRST und die inaktive Ansicht die ID AFX_IDW_PANE_FIRST+1.

Jetzt müssen noch im Dokument ebenfalls die Ansichten ausgetauscht werden, damit das Dokument beim Aufruf seiner Methode UpdateAllViews(...) auch die aktuelle Ansicht findet. Dies geschieht durch den Aufruf der beiden Methoden AddView(...) und RemoveView(...) der Dokumentenklasse.

Zum Schluss muss die aktuelle Ansicht noch innerhalb des Rahmenfensters aktiviert und angezeigt werden. Die alte Ansicht wird hierbei nur verborgen, aber nicht zerstört. Beim nächsten Wechsel der Ansicht muss diese ja wieder angezeigt werden.

 

void CMainFrame::SwitchView()
{
    CView *pOldView;
    CView *pNewView;

    // Zeiger auf aktives View holen
    pOldView = GetActiveView();
    // Falls dies der erste Aufruf der Methode ist, ist das erste View
    // das aktive View und der Zeiger darauf muss noch gerettet werden
    if (m_pCFirstView == NULL)
        m_pCFirstView = pOldView;
    // Falls zweites View noch nicht erstellt
    if (m_pCSecondView == NULL)
    {
        CRuntimeClass *pCR = RUNTIME_CLASS(CSecondView);
        m_pCSecondView = static_cast<CView*>(pCR->CreateObject());
        // ID des Views ungleich AFX_IDW_PANE_FIRST setzen!
        m_pCSecondView->Create(NULL,NULL,AFX_WS_DEFAULT_VIEW,rectDefault,
                               this,AFX_IDW_PANE_FIRST+1,NULL);
    }
    // Neues aktives View bestimmen
    if (m_bFirstViewActive)
        pNewView = m_pCSecondView;
    else
        pNewView = m_pCFirstView;
    // Aktives View muss die ID AFX_IDW_PANE_FIRST besitzen
    // IDs deshalb austauschen
    int nChildId = pNewView->GetDlgCtrlID();
    pNewView->SetDlgCtrlID(AFX_IDW_PANE_FIRST);
    pOldView->SetDlgCtrlID(nChildId);

    // Zeiger auf Dokument holen
    CDocument *pDoc = pOldView->GetDocument();
    // View mit Dok verbinden
    pDoc->AddView(pNewView);
    pDoc->RemoveView(pOldView);
    // Aktives View dem Rahmenfenster mitteilen
    SetActiveView(pNewView);
    // Und Viewgroesse neu berechnen lassen
    RecalcLayout();
    // View anzeigen bzw. verbergen
    pNewView->ShowWindow(SW_SHOW);
    pOldView->ShowWindow(SW_HIDE);
    m_bFirstViewActive = !m_bFirstViewActive;
}

Selbstverständlich sind die der Rahmenfenster-Klasse hinzugefügten Eigenschaften noch im Konstruktor entsprechend zu initialisieren.

Auch hierfür finden Sie ein Beispiel unter 04DocView\MultiView2. Jedes Mal wenn Sie mit der linken Maustaste ins Fenster klicken werden die Ansichten umgeschaltet. Dazu enthalten die Ansichten Nachrichtenbearbeiter für die Bearbeitung der linken Maustaste. Im Debugfenster wird dann jeweils die aktuelle Ansicht in deren OnDraw(...) Methode ausgegeben.

Damit sind wir am Ende der Tipps&Tricks Reihe zum Doc/View Modell angekommen und es normal mit dem Kurs weiter.



Copyright © 2004

Senden Sie Emails mit Fragen oder Kommentaren zu dieser Website an: mailto:info@cpp-tutor.de
 Wolfgang Schröder, Lerchenweg 23, D-72805 Lichtenstein. Tel: +49 7129 6470