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