7.1 Dokumente und Ansichten
Während der Begriff Ansichten (View)
einen Hinweis auf die grafische/bildhafte Darstellung von Daten gibt,
ist die Bezeichnung Dokumente (Doc)
nicht selbsterklärend. "Doc" enthält die Daten (Zahlen,
Texte,
...),
während "View" diese Daten bildlich
darstellt. "Doc" stellt auch einen Mechanismus zur Verfügung,
der für das Lesen und Schreiben der
Daten von und nach Dateien sorgt, die sogenannte "Serialisierung".
Dafür bietet "View" die
Möglichkeit,
die Ansicht der Daten nicht nur auf den Bildschirm,
sondern auch z.B. auf einen Drucker
auszugeben.
"View" stellt darüber hinaus den Kontakt mit den
Benutzereingaben her (Maus-,
Tastatureingaben,
...).
Für den Rahmen um "Doc" und "View"
sorgt eine Rahmenfensterklasse, z.B. CFrameWnd.
"Doc" beruht auf der Klasse CDocument
und "View" auf der von CWnd abgeleiteten Klasse CView.
Der Wirkmechanismus der Dokumentvorlagen
ist in der abstrakten MFC-Klasse CDocTemplate enthalten.
Diese Klasse wird nicht direkt benutzt,
sondern die davon abgeleiteten Klassen CSingleDocTemplate für SDI
und CMultiDocTemplate für MDI. Zusätzlich
benötigt man eine
von CWinApp abgeleitete Anwendungsklasse.
Nachfolgend ist die Klassenhierarchie (von
links nach rechts) für eine SDI-Anwendung dargestellt:
|
Anwendung | |||
CObject | CCmdTarget | CDocument | CMyDoc | |
CDocTemplate | CSingleDocTemplate | |||
CWinThread | CWinApp | CMyApp | ||
CWnd | CFrameWnd | CMyWnd | ||
CView | CMyView |
Abb. 7.1: Doc/View-Klassenhierarchie einer SDI-Anwendung
Wie Sie sehen, ist die von CObject abgeleitete Klasse CCmdTarget die gemeinsame Basisklasse der Doc/View-Architektur. Was bedeutet SDI? Diese Abkürzung steht für Single-Document Interface. Das Gegenteil ist MDI, das für Multiple-Document Interface steht. Seit der Einführung von Windows 95 werden Anwendungen allgemein als SDI betrieben, weil Windows seit dieser Zeit einen Dokument-zentrierten Blickwinkel verkörpert. Man legt z.B. für jedes Word-Dokument eine eigene Instanz an und stellt nicht mehrere Dokumente in einem Programm dar. Die heutigen Rechner erlauben diesen Luxus.
Damit aber genug der grauen Theorie. Wir werden an einem ersten Beispiel die Trennung von Daten (Doc) und Ansicht (View) ausprobieren. Wichtig ist, dass Sie es beherrschen, zwischen dem MFC-Rahmenprogramm, das der Assistent erstellt, und Ihrem eigenen Programmcode zu trennen.
Wir starten wie gewohnt mit Datei - Neu - Projekt - MFC-Anwendung. Wählen Sie als Projektname "Test_", damit Sie die Namensanhänge, die der Assistent den zu erstellenden Klassen verpasst, gut erkennen können.
Im Schritt "Anwendungstyp" entscheiden Sie für "Einfaches Dokument" und lassen das Häkchen bei "Unterstützung für die Dokument-/Ansicht-Architektur" gesetzt.
Den visuellen Stil
ändern wir ab auf Windows
7.
Abb. 7.2: Schritt "Anwendungstyp" bei der Erstellung des Doc-/View-Programms
Nun müssen wir eine Dateierweiterung wählen für unseren Dokumenten-Typ: Ändern Sie unter "Eigenschaften für Dokumentvorlagen" die Eingaben wie folgt ab:
Abb. 7.3: Unterfenster bei Schritt "Eigenschaften für Dokumentvorlagen"
Wichtig ist der Eintrag bei
Dateierweiterung.
Hier habe ich "sav" eingetragen.
Sie können jedoch auch andere
Bezeichnungen,
die auf das Speichern von Daten oder auf den Datentyp hinweisen,
benutzen.
Die anderen
Fenster übernehmen wir unverändert. Bei "Generierte Klassen" zeigt
Ihnen der MFC-Assistent folgende Übersicht:
Abb.
7.4: Die vom Assistent erstellten Klassen
Nun sehen Sie den Sinn des Unterstrichs nach Test. Das ist aber Geschmacksache. Sie erkennen die vier Grundpfeiler, auf denen die Anwendung steht:
Ansicht (View),
Daten (Doc),
Rahmenfenster
(MainFrame),
Anwendung (App).
Es ist wichtig, dass Sie sich diese
Unterteilung
gut einprägen, damit Sie beim Programmieren die Übersicht
behalten. Nach Kompilieren und Ausführen sehen Sie das Rahmenfenster
mit Systemmenü, Menüzeile, Symbolleiste und Statusleiste und natürlich
die "View" unseres Programms.
Abb. 7.5: Die kompilierte und ausgeführte Anwendung
Schauen Sie sich die Menüs genauer an. Unter "Datei" finden Sie die wesentlichen Möglichkeiten in Bezug auf unsere sav-Dateien:
Neu, Öffnen, Speichern, Speichern unter, Drucken, Seitenansicht, Druckeinrichtung, Zuletzt verwendete Datei, Beenden
Der Menüpunkt "Bearbeiten" ist
z.Z.
nicht interessant, bietet aber die üblichen Standards wie Rückgängig,
Ausschneiden, Kopieren, Einfügen.
Unter Ansicht
können Sie die Erscheinungsform der Anwendung bezüglich Symbolleiste,
Statuszeile und allgemeiner Erscheinung abändern.
Unter Hilfe - "?" finden Sie die bekannte About-Dialogbox (Infofeld).
Damit steht das Fundament. Jetzt liegt es an uns, eigene Ideen in die Tat umzusetzen.
Doc /
View
Nehmen wir ein Beispiel zum Üben. Als
Kind fand ich folgende Technik,
einen Würfel zu zeichnen, eindrucksvoll:
Man zeichnet ein erstes Quadrat,
dahinter
schräg versetzt ein zweites Quadrat, und verbindet die Eckpunkte
der beiden Quadrate mit
vier diagonalen Linien.
Damit erhält man einen perspektivisch
dargestellten Würfel.
Diese Idee setzen wir nun in folgendem Programm um:
Zunächst müssen wir überlegen, welche Daten hierbei anfallen, die dann für das konkrete Beispiel in den von uns so benannten sav-Dateien gespeichert werden.
Unsere Quadrate und der Würfel haben gleiche Seitenlängen. Die beiden Quadrate, die wir zeichnen wollen, haben beide einen linken oberen Eckpunkt, der jeweils eine x- und eine y-Koordinate besitzt. Damit müssen wir folgende Daten in unsere Dokumentklasse einfügen:
double Laenge
CPoint ErstesQuadrat
CPoint
ZweitesQuadrat
Die nächste Frage ist:
Wie deklarieren wir diese Daten-Variablen,
und wo werden sie initialisiert?
Die Deklaration erfolgt in der
Dokumentenklasse
CTest_Doc, also in der Header-Datei Test_Doc.h:
//
Test_Doc.h: Schnittstelle der Klasse CTest_Doc // #pragma once class CTest_Doc : public CDocument { protected: // Nur aus Serialisierung erstellen CTest_Doc(); DECLARE_DYNCREATE(CTest_Doc) // Attribute public: CPoint ZweitesQuadrat; CPoint ErstesQuadrat; int Laenge; // Vorgänge public: // Überschreibungen public: virtual BOOL OnNewDocument(); virtual void Serialize(CArchive& ar); #ifdef SHARED_HANDLERS virtual void InitializeSearchContent(); virtual void OnDrawThumbnail(CDC& dc, LPRECT lprcBounds); #endif // SHARED_HANDLERS // Implementierung public: virtual ~CTest_Doc(); ... |
Wir wechseln nun zu Test_Doc.cpp und initialisieren diese Member-Variablen der Dokumentenklasse an zwei Stellen:
1) Im Konstruktor der Dokumentenklasse (One-Time-Konstruktion),
2)
In der Member-Funktion "OnNewDocument()"
der Dokumentenklasse (Reinitialisierung):
//
CTest_Doc-Erstellung/Zerstörung CTest_Doc::CTest_Doc() { Laenge = 200; ErstesQuadrat.x = 100; ErstesQuadrat.y = 200; ZweitesQuadrat.x = 200; ZweitesQuadrat.y = 100; } CTest_Doc::~CTest_Doc() { } BOOL CTest_Doc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; // TODO: Hier Code zur Reinitialisierung einfügen // (SDI-Dokumente verwenden dieses Dokument) Laenge = 200; ErstesQuadrat.x = 100; ErstesQuadrat.y = 200; ZweitesQuadrat.x = 200; ZweitesQuadrat.y = 100; return TRUE; } |
Wenn Sie jetzt kompilieren / ausführen, sehen Sie noch keine Auswirkungen. Bisher haben wir nur Datenvariablen definiert und diese an den richtigen Stellen platziert. Wir haben jetzt noch folgende Schritte vor uns:
Daten anzeigen - Daten speichern / laden - Daten im Programm verändern
Dazu bewegen wir uns geistig von der
Dokumentenklasse
zur Ansichtsklasse, also zur Datei Test_View.cpp, um die Anzeige der
Daten zu realisieren.
Die
grafische
Anzeige erfolgt in der Member-Funktion CTest_View::OnDraw(), die uns
bisher
folgendes
bietet:
void CTest_View::OnDraw(CDC*
/*pDC*/) { CTest_Doc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: Code zum Zeichnen der systemeigenen Daten hinzufügen } |
Wichtig sind die beiden Zeiger pDC
(noch auskommentiert)
und pDoc. Der erste zeigt auf einen Device Context, der
beim
Neuzeichnen des Fensters eingesetzt wird, und der zweite zeigt auf die
Dokumentenklasse
CTest_Doc, um die Lücke zwischen View und Doc zu überwinden.
Insbesondere die Verwendung des Zeigers pDoc macht den Programmcode
etwas
schwerfällig, aber dies ist der Preis für die
Doc-/View-Architektur.
Wir zeichnen zunächst das erste Quadrat, anschließend das
zweite
Quadrat und zum Schluß die vier Verbindungslinien. Im
Programmcode
müssen wir für alle Zugriffe auf Member-Variablen der
Dokumentenklasse
den Zeiger pDoc einsetzen. Unser "Zeichengerät" ist pDC.
/////////////////////////////////////////////////////////////////////////////
// CTest_View Zeichnen void CTest_View::OnDraw(CDC*
pDC)
//Erstes
Quadrat zeichnen:
//Zweites
Quadrat zeichnen:
//Verbindungslinien
zeichnen:
pDC->MoveTo(pDoc->ErstesQuadrat.x
+ pDoc->Laenge, pDoc->ErstesQuadrat.y);
pDC->MoveTo(pDoc->ErstesQuadrat.x
+ pDoc->Laenge, pDoc->ErstesQuadrat.y + pDoc->Laenge);
pDC->MoveTo(pDoc->ErstesQuadrat.x,
pDoc->ErstesQuadrat.y + pDoc->Laenge); |
Wenn wir das Programm nun kompilieren und ausführen, erhalten wir das gewünschte Bild:
Abb. 7.6: Als View erhalten wir den perspektivischen Würfel
Halten wir hier zunächst inne. Die entscheidende Trennung von Dokument und Ansicht ist bereits erfolgt. Wir haben 5 Daten (Länge; X1, Y1; X2, Y2) an zwei Stellen (Konstruktor und OnNewDocument() ) in der Dokumentenklasse erzeugt und durch den Einsatz der Funktionen MoveTo() und LineTo() in der Member-Funktion OnDraw() der Ansichtsklasse obenstehende "View" erzeugt. Neu war die Verwendung des Zeigers auf unsere Dokumentenklasse CTest_Doc* pDoc. Wir haben die Attribute in der Doc-Klasse als public deklariert, damit wir einfacher zugreifen können. Definiert man diese als private, so müsste man noch public GET/SET-Funktionen für diese gekapselten Variablen schaffen. Hierauf habe ich hier verzichtet, möchte es aber im Sinne der OOP erwähnen.
Aus den gleichen Daten könnten wir z.B. auch mit TextOutW() eine Tabelle erzeugen. Das meint man, wenn man sagt, dass ein Dokument mehrere Ansichten (Views) haben kann. Sie kennen dies z.B. auch von MS Excel, wenn man Daten z.B. als Punkt-, Linien-, Balken- oder Tortengrafik darstellen kann. Die Ausgangstabelle ist das Dokument, während die Grafiken die "Views" sind.
Serialisierung
Nun kommt der nächste Schritt, das Schreiben und Lesen von Dokumentobjekten, die sogenannte "Serialisierung":
Sie haben sich sicher schon gefragt, wozu die Trennung in Doc und View eigentlich gut ist. Ein Vorteil ist die vom Assistenten bereit gestellte Funktion zur Serialisierung. Damit ist das Speichern und Laden unserer Daten im Zusammenhang mit unseren sav-Dateien gemeint.
Schauen wir uns zunächst die
Funktion
im "Rohzustand" an:
//
CTest_Doc-Serialisierung void CTest_Doc::Serialize(CArchive& ar) { if (ar.IsStoring()) { // TODO: Hier Code zum Speichern einfügen } else { // TODO: Hier Code zum Laden einfügen } } |
Gehen wir sofort praktisch weiter. Der
if-Zweig ist zuständig für das Speichern und der else-Zweig
für
das Laden.
Wir übergeben hier jeweils unsere
fünf Daten (Länge, ErstesQuadrat.X, ErstesQuadrat.Y,
ZweitesQuadrat.X,
ZweitesQuadrat.Y):
///////////////
// CTest_Doc Serialisierung void CTest_Doc::Serialize(CArchive& ar) { if (ar.IsStoring()) { ar << Laenge; ar << ErstesQuadrat; ar << ZweitesQuadrat; } else { ar >> Laenge; ar >> ErstesQuadrat; ar >> ZweitesQuadrat; } } |
Nach dem Ausführen des Programms werden wir nun unsere Würfel-Daten speichern, sagen wir unter dem Dateinamen "Wuerfel1.sav".
Abb. 7.7: Das Ergebnis der Serialisierung, die Datei "Wuerfel1.sav"
Öffnen wir die Datei mit dem Notizblock (Notepad) und dem Hex-Editor, dann finden wir folgendes:
Abb. 7.8: Die Datei "Wuerfel.sav" im Text-Editor
Abb. 7.9: Die Datei "Wuerfel.sav" im Hex-Editor
Zum Vergleich unsere Daten:
Laenge
= 200; // Hex: C8
ErstesQuadrat.x = 100; //
Hex: 64
ErstesQuadrat.y = 200; //
Hex: C8
ZweitesQuadrat.x = 200; //
Hex: C8
ZweitesQuadrat.y = 100; //
Hex: 64
Sie sehen, was hier geschieht. Unsere Daten werden "in Serie" in einer Datei abgelegt. Daher müssen wir logischerweise immer in der gleichen Variablen-Reihenfolge lesen wie schreiben. Das ist doch einfach, oder nicht? In der Klasse CArchive existieren die überladenen Operatoren << und >>, mit deren Hilfe man selbst zusammengesetzte Daten wie CPoint seriell laden bzw. speichern kann (dies erinnert Sie sicher an die C++-Streams cin und cout).
Zum Abschluss basteln wir uns noch eine Routine, mit deren Hilfe man das erste Quadrat verschieben kann. Hierzu benützen wir einfach die Pfeiltasten.
Fügen Sie mittels Klassen-Assistent eine Funktion für die Nachricht WM_KEYDOWN ein:
1) Strg+Shift+X,
der Klassen-Assistent
erscheint
2) Auswahl der Klasse CTest_View
3) Unter Meldungen WM_KEYDOWN
auswählen
und doppelt anklicken (fügt Funktion hinzu)
4) Auf "Code bearbeiten" drücken
Sie finden sich nun hier wieder in der
neuen Funktion CTest_View::OnKeyDown(...):
void CTest_View::OnKeyDown(UINT nChar, UINT
nRepCnt, UINT nFlags) { // TODO: Fügen Sie hier Ihren Meldungsbehandlungscode ein, und/oder benutzen Sie den Standard. CView::OnKeyDown(nChar, nRepCnt, nFlags); } |
Wir verwenden den Parameter UINT nChar,
um eine switch-Verzweigung mit entsprechenden breaks zu durchlaufen.
Pro Tastendruck schieben wir das erste
Quadrat um zehn Pixel in die entsprechende Richtung:
void
CTest_View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { CTest_Doc* pDoc = GetDocument(); ASSERT_VALID(pDoc); switch (nChar) { case 37: (pDoc->ErstesQuadrat.x) -= 10; break; case 38: (pDoc->ErstesQuadrat.y) -= 10; break; case 39: (pDoc->ErstesQuadrat.x) += 10; break; case 40: (pDoc->ErstesQuadrat.y) += 10; break; } pDoc->SetModifiedFlag(); Invalidate(); CView::OnKeyDown(nChar, nRepCnt, nFlags); } |
Sie sehen, dass wir uns wieder des Zeigers auf die Dokumentklasse pDoc bedienen müssen. In der Member-Funktion OnDraw der Ansichtsklasse hatte dies bereits der Assistent für uns erledigt:
void CTest_View::OnDraw(CDC* pDC)
{
CTest_Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
...
Hier haben wir einfach von ihm gelernt
und ebenfalls den Zeiger pDoc besorgt. Nachdem wir Daten der
Dokumentklasse
verändern,
teilen wir dies auch dieser Klasse mit.
Dies erfolgt mittels pDoc->SetModifiedFlag().
Die Aktualisierung der
Ansichtsklasse,
also das Neuzeichnen mit den geänderten Daten,
erfolgt mittels Invalidate().
Das Setzen des "ModifiedFlags"
führt
dazu, dass die Anwendung uns bei Veränderungen vor dem Beenden oder Neuladen nach dem
Speichern der aktuellen Daten befragt.
Abb.
7.10: Der Würfel bewegt sich und damit auch zwei der
fünf
Daten
Experimentieren Sie nun mit den
verschiedenen
Werten, speichern und öffnen Sie die Dateien. Schauen Sie sich die
Werte mit einem Text- oder Hex-Editor an, damit Sie sehen, dass die
Serialisierung
auch bestens funktioniert. Vielleicht fällt Ihnen eine andere
Steuerung ein, oder Sie bewegen auch das zweite Quadrat. Wichtig ist,
dass Sie zunächst das
Grundprinzip der Trennung von Daten und Ansicht und die Serialisierung
(Daten seriell speichern und lesen) gut verstehen.
Falls Ihnen
die Kapselung der Würfeldaten nicht ausreichend ist, bauen Sie diese
zur Übung um auf private und ergänzen Sie die entsprechenden public
Get-/Set-Methoden, damit Sie Zugang zu den Werten von View auf Doc
haben.
Sie haben
z.Z. drei Bereiche:
1) Daten im
Programmspeicher (Attribute der Doc-Klasse)
2) Daten in einem permanenten Archiv (Dateien Wuerfel1.sav, Wuerfel2.sav, ...)
3) Grafisch
oder textlich veranschaulichte Daten auf dem Bildschirm (oder später
Drucker)
7.2 Single Document Interface (SDI)
Nachdem Sie nun das Doc/View-Modell mit der
Trennung
von Daten und Ansicht in Aktion gesehen haben, wenden wir uns nun
detaillierter
dem Rahmenfenster, den Ansichten und der Anwendung zu. Wir bleiben bei
dem sogenannten Single Document Interface (SDI), da das
Multiple
Document Interface (MDI) heute nicht mehr empfohlen wird.
7.2.1 Erstellung der SDI-Anwendung, Rahmenfenster und Systemmenü
Erstellen Sie mit dem Anwendungsassistent eine neue SDI-Anwendung mit Namen "SDI001", übernehmen Sie alle Standardeinstellungen. Der einzige Schritt, in dem Sie aktiv eingreifen müssen, ist der Schritt "Anwendungstyp". Dort wählen Sie nicht "Mehrfaches Dokument", sondern "Einfaches Dokument" aus. Genau genommen ist es merkwürdig, dass MDI voreingestellt ist, denn MS rät Entwicklern inzwischen, von MDI Abstand zu nehmen, d.h. man soll jedes Dokument in einer eigenen Anwendung starten, denn für die heutigen Rechner mit ihren hervorragenden Multitasking-Fähigkeiten ist das kein technisches Problem mehr.
Als visuellen Stil würde ich Windows 7 wählen.
Dateierweiterung: dat
Beachten Sie die Schritte "Benutzeroberflächenfunktionen" und "Erweiterte Funktionen" des Assistenten:
Abb.
7.12: Erweiterte Funktionen wie Drucken/Druckvorschau,
ActiveX-Steuerelemente und erweiterte Framebereiche
Dort sind verschiedene Zutaten bereits
eingestellt: Symbolleiste, Statusleiste, Drucken plus
Druckvorschau,
ActiveX-Steuerelemente und erweiterte Framebereiche.
Lassen Sie diese Punkte ausgewählt, weil wir uns damit noch beschäftigen wollen. Bei Benutzeroebrflächenfunktionen hätten wir auch ein geteiltes Fenster wählen können.
Schauen Sie sich nun die Funktion CMainFrame::PreCreateWindow(...),
die Sie am schnellsten via Klassenansicht finden, wie folgt aus:
BOOL
CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWndEx::PreCreateWindow(cs) ) return FALSE; // TODO: Ändern Sie hier die Fensterklasse oder die Darstellung, indem Sie CREATESTRUCT cs modifizieren. return TRUE; } |
Hier wird also nur die Funktion der Elternklasse CFrameWndEx::PreCreateWindow(...) ausgeführt.
Wenn Sie die Minimieren- und
Maximieren-Schaltfläche
abwählen wollen, was eigentlich keinen Sinn macht, dann müssten Sie
diese Funktion wie folgt abändern:
BOOL
CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWndEx::PreCreateWindow(cs) ) return FALSE; cs.style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | FWS_ADDTOTITLE; return TRUE; } |
Der normalerweise vorgegebene
Window-Style
ist WS_OVERLAPPEDWINDOW:
WS_OVERLAPPEDWINDOW
kombiniert
folgende Window-Styles:
WS_OVERLAPPED, |
Sie sehen, dass bei Abwahl von
WS_MIMIMIZEBOX
und WS_MAXIMIZEBOX der verbleibende Rest dann cs.style zugewiesen wird.
WS_SYSMENU entspricht "Systemmenü"
und WS_THICKFRAME steht für "Breiter Rahmen".
In dieser Funktion können Sie also auch später noch Korrekturen vornehmen. Der Assistent trifft hier nur eine erste Wahl.
Neu ist für uns FWS_ADDTOTITLE.
Hier haben wir ein Beispiel eines Frame-Window
Style (FWS). Es gibt
noch
weitere:
Frame-Window Style (FWS) | Bedeutung |
FWS_ADDTOTITLE | Der Document-Titel wird dem Namen der Anwendung hinzugefügt. |
FWS_PREFIXTITLE | Funktioniert
zusammen
mit FWS_ADDTOTITLE. Document-Titel steht vor dem Namen der Anwendung. Dies ist die Standardeinstellung des MFC-Assistenten. |
FWS_SNAPTOBARS | Steuert die Größe des Rahmenfensters, das eine Steuerleiste ("control bar") als frei bewegliches ("floating") Fenster beherbergt. Das Rahmenfenster wird der Größe der Steuerleiste angepaßt. |
ohne FWS-Style: nur SDI001
mit FWS_ADDTOTITLE: SDI001 - Unbenannt
FWS_ADDTOTITLE | FWS_PREFIXTITLE:
Abb. 7.12 - 7.14: Verschiedene Styles für den Titel
Was soll eigentlich dieses
merkwürdige
"Unbenannt" im Titel? Bei Winword oder Excel heißt dies z.B.
"Dokument1"
bzw. "Mappe1". Wir wollen hier auch einen eigenen Begriff vorgeben. Wie
kann man diesen String verändern? Es handelt sich um den Namen für
das
Dokument. Also müssen wir auch dort ansetzen. Der richtige Platz
ist
in der Doc-Klasse CSDI001Doc bei der Funktion OnNewDocument():
BOOL CSDI001Doc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; // TODO: Hier Code zur Reinitialisierung einfügen // (SDI-Dokumente verwenden dieses Dokument) SetTitle(L"Neuer Titel"); return TRUE; } |
Die Titelleiste hat nun den von uns
vorgegebenen
String vor dem "- SDI001" eingefügt. Das "Unbenannt" sind wir also
los.
Hierzu haben wir die MFC-Funktion void
CDocument::SetTitle( LPCTSTR lpszTitle ) verwendet.
7.2.2 Elemente des Rahmenfensters - Grundlagen
Schauen wir uns die optische Erscheinung unserer Anwendung genauer an und suchen den Bezug im Sourcecode.
Sie können das Rahmenfenster von oben nach unten grob in fünf Zonen einteilen:
1. Systemmenü (auch Fenstermenü genannt)
2. Menü
3. Symbolleiste (Tool Bar)
4. Client-Bereich
5. Statusleiste (Status Bar)
Z usätzlich
haben wir noch die Möglichkeit folgende Elemente einzublenden:
Klassenansicht, Dateiansicht, Ausgabefenster und Eigenschaftsfenster.
Beginnen wir mit der Klassendefinition
unserer von CFrameWndEx abgeleiteten Klasse CMainFrame.
Dort entdecken wir folgende
Member-Variablen:
protected:
// Eingebettete Member der
Steuerleiste
CMFCMenuBar
m_wndMenuBar;
CMFCToolBar
m_wndToolBar;
CMFCStatusBar m_wndStatusBar;
CMFCToolBarImages m_UserImages;
CFileView
m_wndFileView;
CClassView
m_wndClassView;
COutputWnd
m_wndOutput;
CPropertiesWnd m_wndProperties;
Schauen wir uns diese Klasse noch
einmal
von allem für uns Überflüssigen bereinigt an:
class CMainFrame : public
CFrameWndEx { protected: CMainFrame(); DECLARE_DYNCREATE(CMainFrame) public: virtual ~CMainFrame(); protected: afx_msg int OnCreate(LPCREATESTRUCT
lpCreateStruct); |
Jetzt erkennen wir die Member dieser Rahmenfensterklasse: Da ist zunächst der Konstruktor CMainFrame() und der Destruktor ~CMainFrame(). Die eine Funktion sorgt für die Erzeugung unseres Objektes "Rahmenfenster", und die andere zerstört es. Das sind C++-Standardelemente einer Klasse. Was gibt es Spezifisches?
Wir finden wir weitere wichtige Funktionen:
virtual BOOL PreCreateWindow(CREATESTRUCT&
cs);
int OnCreate(LPCREATESTRUCT
lpCreateStruct);
Beginnen wir mit PreCreateWindow(...).
Zunächst gibt es dort einen Rückgabewert vom Typ BOOL. Ist
dieser
Wert FALSE, so ist die Erstellung des Rahmenfensters gescheitert. Das
wäre
es dann gewesen. Klappt die Erstellung, so ist der Wert TRUE.
Das wird in der Implementierung auch
abgefragt:
BOOL
CMainFrame::PreCreateWindow(CREATESTRUCT& cs) { if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; cs.style
= WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME |
FWS_ADDTOTITLE | FWS_PREFIXTITLE; //
unser style return TRUE; |
Wir gehen davon aus, das es klappt.
Warum auch nicht?
Interessanter ist die Strukur cs vom Typ CREATESTRUCT. Also markieren und F1, und schon wird online bei MSDN nachgeschaut:
typedef struct tagCREATESTRUCT
{
LPVOID lpCreateParams;
HANDLE hInstance;
HMENU hMenu;
HWND hwndParent;
int cy;
int cx;
int y;
int x;
LONG style;
LPCSTR lpszName;
LPCSTR lpszClass;
DWORD dwExStyle;
} CREATESTRUCT;
Wenn Sie sich schon mit der Erstellung
von Fenstern mittels WinAPI beschäftigt haben, sind Ihnen diese
Parameter
geläufig.
Die Fenstergeometrie und damit der
Client-Bereich
wird z.B. mittels der Ecke links oben ( x, y ), Breite ( cx ) und
Höhe
( cy ) festgelegt.
Das Handle hMenu steht für das
Menü
in unserem Rahmenfenster.
Dort finden sich also die Ansatzpunkte für Geometrie und Menü unseres Fensters.
Nun zur Implentierung der Funktion
OnCreate(...):
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWndEx::OnCreate(lpCreateStruct) == -1) return -1; BOOL bNameValid; if (!m_wndMenuBar.Create(this)) { TRACE0("Fehler beim Erstellen der Menüleiste.\n"); return -1; // Fehler beim Erstellen } m_wndMenuBar.SetPaneStyle(m_wndMenuBar.GetPaneStyle() | CBRS_SIZE_DYNAMIC | CBRS_TOOLTIPS | CBRS_FLYBY); // Verhindern, dass die Menüleiste beim Aktivieren den Fokus erhält 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("Fehler beim Erstellen der Symbolleiste.\n"); return -1; // Fehler beim Erstellen } 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); // Benutzerdefinierte Symbolleistenvorgänge zulassen: InitUserToolbars(NULL, uiFirstUserToolBarId, uiLastUserToolBarId); if (!m_wndStatusBar.Create(this)) { TRACE0("Fehler beim Erstellen der Statusleiste.\n"); return -1; // Fehler beim Erstellen } m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT)); // TODO: Löschen Sie diese fünf Zeilen, wenn Sie nicht möchten, dass die Symbolleiste und die Menüleiste andockbar sind m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY); m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); EnableDocking(CBRS_ALIGN_ANY); DockPane(&m_wndMenuBar); DockPane(&m_wndToolBar); // Andockfensterverhalten wie in Visual Studio 2005 aktivieren CDockingManager::SetDockingMode(DT_SMART); // Automatisches Ausblenden von Andockfenstern wie in Visual Studio 2005 aktivieren EnableAutoHidePanes(CBRS_ALIGN_ANY); // Menüelementbild laden (nicht auf Standardsymbolleisten platziert): CMFCToolBar::AddToolBarForImageCollection(IDR_MENU_IMAGES, theApp.m_bHiColorIcons ? IDB_MENU_IMAGES_24 : 0); // Andockfenster erstellen if (!CreateDockingWindows()) { TRACE0("Fehler beim Erstellen der Andockfenster.\n"); return -1; } m_wndFileView.EnableDocking(CBRS_ALIGN_ANY); m_wndClassView.EnableDocking(CBRS_ALIGN_ANY); DockPane(&m_wndFileView); CDockablePane* pTabbedBar = NULL; 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); // Visuellen Manager und Stil auf Basis eines persistenten Werts festlegen OnApplicationLook(theApp.m_nAppLook); // Umpositionieren des Menüs für Symbolleisten und Andockfenster aktivieren EnablePaneMenu(TRUE, ID_VIEW_CUSTOMIZE, strCustomize, ID_VIEW_TOOLBAR); // Schnelles Anpassen von Symbolleisten mit Alt+Ziehen aktivieren CMFCToolBar::EnableQuickCustomization(); if (CMFCToolBar::GetUserImages() == NULL) { // Benutzerdefinierte Symbolleistenbilder laden if (m_UserImages.Load(_T(".\\UserImages.bmp"))) { CMFCToolBar::SetUserImages(&m_UserImages); } } // Menüpersonalisierung aktivieren (zuletzt verwendete Befehle) // TODO: Definieren Sie eigene Basisbefehle, wobei jedes Pulldownmenü mindestens einen Basisbefehl enthalten muss. 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); return 0; } |
Das ist ja ein richtiger Brocken. Sie
sehen
der Assistent arbeitet fleissig für uns. Analysieren Sie diese Zeilen
in Ruhe und mittels MSDN. Experimentieren Sie damit und schauen Sie
sich die Auswirkungen an.
Beginnen wir sofort mit einfachen Experimenten:
1) Wir wollen keine andockbare Symbolleiste: Das ist leicht, da müssen wir einfach die fünf Zeilen, wie angegeben, auskommentieren. Kommentieren Sie auf jeden Fall aus, also nicht wirklich wegstreichen, es sei denn Sie kennen die Zeilen auswendig, und schon ist es vorbei mit der "vagabundierenden" Symbolleiste. Jetzt hängt sie fest.
2) Wir wollen gar keine Symbolleiste (Toolbar): Dann wird eben auch die Erzeugung und der damit zusammenhängende Code gestrichen (nur das Nötigste wurde auskommentiert).
int
CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CFrameWndEx::OnCreate(lpCreateStruct) == -1) return -1; // BOOL bNameValid; if (!m_wndMenuBar.Create(this)) { TRACE0("Fehler beim Erstellen der Menüleiste.\n"); return -1; // Fehler beim Erstellen } m_wndMenuBar.SetPaneStyle(m_wndMenuBar.GetPaneStyle() | CBRS_SIZE_DYNAMIC | CBRS_TOOLTIPS | CBRS_FLYBY); // Verhindern, dass die Menüleiste beim Aktivieren den Fokus erhält 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("Fehler beim Erstellen der Symbolleiste.\n"); return -1; // Fehler beim Erstellen } 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); // Benutzerdefinierte Symbolleistenvorgänge zulassen: InitUserToolbars(NULL, uiFirstUserToolBarId, uiLastUserToolBarId); */ if (!m_wndStatusBar.Create(this)) { TRACE0("Fehler beim Erstellen der Statusleiste.\n"); return -1; // Fehler beim Erstellen } m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT)); /* // TODO: Löschen Sie diese fünf Zeilen, wenn Sie nicht möchten, dass die Symbolleiste und die Menüleiste andockbar sind m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY); m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); EnableDocking(CBRS_ALIGN_ANY); DockPane(&m_wndMenuBar); DockPane(&m_wndToolBar); */ // Andockfensterverhalten wie in Visual Studio 2005 aktivieren CDockingManager::SetDockingMode(DT_SMART); // Automatisches Ausblenden von Andockfenstern wie in Visual Studio 2005 aktivieren EnableAutoHidePanes(CBRS_ALIGN_ANY); // Menüelementbild laden (nicht auf Standardsymbolleisten platziert): CMFCToolBar::AddToolBarForImageCollection(IDR_MENU_IMAGES, theApp.m_bHiColorIcons ? IDB_MENU_IMAGES_24 : 0); // Andockfenster erstellen if (!CreateDockingWindows()) { TRACE0("Fehler beim Erstellen der Andockfenster.\n"); return -1; } m_wndFileView.EnableDocking(CBRS_ALIGN_ANY); m_wndClassView.EnableDocking(CBRS_ALIGN_ANY); DockPane(&m_wndFileView); CDockablePane* pTabbedBar = NULL; 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); // Visuellen Manager und Stil auf Basis eines persistenten Werts festlegen OnApplicationLook(theApp.m_nAppLook); /* // Umpositionieren des Menüs für Symbolleisten und Andockfenster aktivieren EnablePaneMenu(TRUE, ID_VIEW_CUSTOMIZE, strCustomize, ID_VIEW_TOOLBAR); */ // Schnelles Anpassen von Symbolleisten mit Alt+Ziehen aktivieren CMFCToolBar::EnableQuickCustomization(); if (CMFCToolBar::GetUserImages() == NULL) { // Benutzerdefinierte Symbolleistenbilder laden if (m_UserImages.Load(_T(".\\UserImages.bmp"))) { CMFCToolBar::SetUserImages(&m_UserImages); } } // Menüpersonalisierung aktivieren (zuletzt verwendete Befehle) // TODO: Definieren Sie eigene Basisbefehle, wobei jedes Pulldownmenü mindestens einen Basisbefehl enthalten muss. 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); return 0; } |
3) Wir wollen keine Statusleiste, jedoch eine Symbolleiste:
Dazu "streichen" wir nur den
nachstehenden
Block. Die Teile für die Symbolleiste belassen wir aktiv.
/* if (!m_wndStatusBar.Create(this)) { TRACE0("Fehler beim Erstellen der Statusleiste.\n"); return -1; // Fehler beim Erstellen } m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT)); */ |
Da gibt es doch gleich viel mehr Platz für unsere "Views".
4) Nun wollen wir die Geometrie des
Fensters
beeinflussen:
Dazu ist
CMainFrame::PreCreateWindow(...)
wieder der richtige Ort:
BOOL
CMainFrame::PreCreateWindow(CREATESTRUCT&
cs) { /* ... */ cs.cx
= 300; // Breite if(
!CFrameWnd::PreCreateWindow(cs)
) return FALSE; cs.style =
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME |
FWS_ADDTOTITLE | FWS_PREFIXTITLE; return TRUE; |
... und schon erscheint das Fenster in der gewünschten Größe. Sie können es aber - wie bei Windows gewohnt - durch "Ziehen mit der Maus" in der Größe verändern.
Sie haben nun einen ersten groben
Überblick
über die Beeinflussung des "Rahmens", den MS Windows um unsere
"View"
spannt.
Machen Sie sich vor allem noch einmal
den elementaren Aufbau einer SDI-Anwendung aus Anwendung, Rahmen,
Dokument
und Ansicht klar.
Zutaten wie Ressourcen, Menüs,
Symbolleisten
und Statusleiste sind keine Eckpfeiler, aber sie hängen wie
"liebliche
Erker" an unserem massiven Rohbau und der heutige GUI-Programmierer
darf sich
verstärkt mit diesen Feinheiten beschäftigen, an die der
Windows-Benutzer gewöhnt wurde. Beginnen wir
mit
Symbol- und Statusleisten.
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Trennlinie der
Überarbeitung Version 6 nach Version 14
7.2.3 Symbolleiste
7.2.3.1 Standardsymbolleiste IDR_MAINFRAME
Warum heißt die Symbolleiste eigentlich Symbolleiste und nicht Werkzeugleiste? Wahrscheinlich, weil wir in dieser Leiste so viele schöne Symbole darstellen können. Diese Symbole stellen z.B. Werkzeuge und Hilfsmittel dar wie Papier, Ordner, Diskette, Schere, Drucker etc. Schauen wir uns in der Standardsymbolleiste das dritte Symbol "Diskette" an. Es steht für den Arbeitsvorgang "Speichern" In der heutigen Zeit wäre hier eine Festplatte sicher angebrachter. Wir wollen mit unserer Anwendung modern sein und daher dieses Symbol in ein "Festplatten"-Symbol umwandeln. Wie packen wir das an?
Bilder, Icons und Symbole sind Ressourcen. Daher werden wir dort auch fündig. Es gibt einen Ordner "Toolbar" und darin die Ressource IDR_MAINFRAME. In der nachstehenden Abbildung sehen Sie das erste Symbol dieser Leiste: ein Blatt Papier (mit Eselsohr!). Sie finden eine Auflösung von Breite = 16 Pixel mal Höhe = 15 Pixel vor. Übrigens hat die Symbolschaltfläche, also der für den Benutzer sichtbare Button, eine Auflösung von Breite = 24 Pixel mal Höhe = 22 Pixel. Da sind nun Ihre zeichnerichen Fähigkeiten auf engstem Raum gefordert.
Wir tauschen das dritte Symbol mutig durch eine Festplatte (hard disk) aus, nachfolgend mein spartanisches Ergebnis:
Reicht diese Veränderung schon
aus?
Was denken Sie?
Wir speichern, kompilieren, linken und
führen aus:
Es hat funktioniert! Sie sehen das geht einfach.
Sie können natürlich die Größe der Symbole im Ressourcen-Editor durch einfaches Ziehen mit der Maus einstellen und die grafische Verarbeitung mit einem anderen Zeichenprogramm (z.B. Paint) durchführen. Die Bitmap für die Standardsymbolleiste findet sich im Unterverzeichnis /res unter dem Namen toolbar.bmp.
Nun führen wir den
vollständigen
Bezug von der Ressource IDR_MAINFRAME zu unserem Programm
herbei.
Dazu lösen wir die Bedingung innerhalb der if-Kontrollstruktur
heraus
und lassen die beiden Negationen und das logische ODER weg. Als
Ergebnis
finden wir zwei aufeinander folgende Vorgänge:
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 ) |
Im ersten Schritt erzeugen wir eine Symbolleiste durch Anwendung der Member-Funktion CreateEx(...) auf unser Objekt m_wndToolBar der Klasse CToolBar, und im zweiten Schritt binden wir die Ressource Symbolleiste IDR_MAINFRAME mit der Member-Funktion LoadToolBar(...) an dieses Objekt an. Alles easy? Kompliziert wird das alles nur durch die Werte im dritten Parameter.
Betrachten wir die Funktion CreateEx(...) genau:
BOOL CToolBar::CreateEx
(
CWnd* pParentWnd,
DWORD dwCtrlStyle =
TBSTYLE_FLAT,
DWORD
dwStyle
= WS_CHILD | WS_VISIBLE | CBRS_ALIGN_TOP,
CRect rcBorders
= CRect(0, 0, 0, 0),
UINT
nID
= AFX_IDW_TOOLBAR
);
pParentWnd: Zeiger auf
das Elternfenster der Symbolleiste
dwCtrlStyle: Styles für
das eingebettete Objekt der Klasse CToolBarCtrl
dwStyle:
Styles der Symbolleiste
rcBorders:
Rahmen-Rechteck
für die Symbolleiste
nID:
Kindfenster-ID der Symbolleiste
Die Parameter 2, 3 und 4
bestimmen
das Aussehen und Verhalten unserer Symbolleiste. Ein weites
Betätigungsfeld
liegt vor uns.
Wir sehen auch, dass der Assistent kein
Standard-Objekt der Klasse CToolBar erzeugt, sondern seine eigene Note
bietet.
Eigentlich müssen wir doch nur den
ersten Parameter, d.h. den this-Zeiger auf das Objekt der Klasse
CMainFrame,
angeben.
Also testen wir sofort den eigentlichen
MFC-Standard aus:
int
CMainFrame::OnCreate(LPCREATESTRUCT
lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; /* if
(!m_wndToolBar.CreateEx(this) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) |
Also programmtechnisch sieht das viel schöner - sprich: klarer - aus.
Der "Griff" am linken Ende und die Tooltips sind jedoch weg.
Wir wenden uns dem dritten Parameter zu. Zunächst befassen wir uns mit dem Standard: WS_CHILD | WS_VISIBLE | CBRS_ALIGN_TOP
Neu ist CBRS_ALIGN_TOP. Es bedeutet, dass die Symbolleiste oben andocken darf. Gut, das ist nichts Aufregendes. Es sei denn, man läßt es weg und gibt nur WS_CHILD und WS_VISIBLE an. Dann fehlt gleich die ganze Symbolleiste. Keine Andockerlaubnis, keine Symbolleiste. Aha! Also weiter.
Wie ist das mit dem Griff (engl.
gripper)?
Wir fügen CBRS_GRIPPER hinzu,
... und weil wir experimentierfreudig
sind, geben wir die Andockerlaubnis diesmal unten:
if
(!m_wndToolBar.CreateEx(this,
TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_ALIGN_BOTTOM
| CBRS_GRIPPER ) || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
... |
Nun haben wir den gewohnten "Griff" (bei horizontaler Anordnung: links, bei vertikaler Anordnung: oben). Da macht das In-See-stechen (sprich: floaten) und das Andocken doch viel mehr Freude.
Schauen wir weiter. Da gibt es noch
eine
weitere Einstellung bezüglich der Größeneinstellung der
Symbolleiste:
CBRS_SIZE_DYNAMIC und CBRS_SIZE_FIXED.
Wir sind natürlich dynamisch, und fügen diesen Style hinzu. Was ist jetzt möglich?
Dynamisch genug? Sie können das Kindfenster sogar aus dem Bereich des Elternfensters hinaus bewegen? Nein, nein. Das ging auch vorher schon. Der "Zeilenumbruch" innerhalb der Symbolleiste ist die wahre Dynamik!
Keine Tooltips? Das ist für den heutigen Windows-Nutzer eher verblüffend. Dafür gibt es doch CBRS_TOOLTIPS. Bei unserem eigenen Bild für das Speichern müssen wir diesen Komfort bieten. Das nutzen wir auf jeden Fall, solange der Assistent selbst die Texte geschrieben hat:
Na sieht das nicht besser aus? Die Maus zeigt hierbei übrigens (auf dem Bild nicht sichtbar) auf das Speichern-Symbol.
Da gibt es noch CBRS_FLYBY. Was bedeutet dies? Wir fügen es mit dazu und vergleichen die beiden Bilder, wenn wir die Maustaste über das Speichern-Symbol halten:
Sehen Sie den Unterschied? Nein, dann achten Sie auf den Text in der Statuszeile. Der wird nun ständig aktualisiert, schon beim "Vorbeifliegen". Wir müssen nicht mehr "Klick" machen, sondern nur wie ein römischer Imperator unseren virtuell verlängerten Zeigefinger würdevoll (also bitte nicht zu schnell) über die Symbole gleiten lassen.
Sie sehen: Die Grundeinstellungen des MFC-Assistenten sind für die Praxis gut brauchbar, und der Benutzer ist es in dieser Form gewöhnt.
Also zurück zum Standard, den der
Assistent erzeugt :
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)) ... |
Dann sieht das auch alles wieder
ordentlich
aus!
Aber halt! Da war doch noch dieser vierte
Parameter: das Rechteck. Was nützt diese Einstellung?
Probieren wir es aus:
if
(!m_wndToolBar.CreateEx(this,
TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC, CRect(30, 15, 0, 0)) || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME)) ... |
Wir haben unsere Symbolleiste nun 30 Einheiten nach rechts und 15 Einheiten nach unten "geschoben". Optisch natürlich sehr unschön.
Mit CRect( 30, 15, 30, 15 ) können wir hier Symmetrie ins Spiel bringen:
Sie sehen, das dieses Rechteck als Rahmen um unsere Symbolleiste benutzt wird.
Probieren Sie verschiedene Einstellungen aus. Es funktionieren auch negative Zahlen, z.B. CRect( -2, -2, -2, -4 ):
Das sieht doch richtig schlank aus,
oder?
Vielleicht die richtige Einstellung für Anwendungen mit vielen
Symbolleisten.
7.2.3.2 Eigene Symbolleiste hinzufügen
Damit wir etwas mehr Übung mit
Symbolleisten
erzielen, fügen wir eine eigene zweite Symbolleiste zur
Standardsymbolleiste
hinzu.
Erstellen Sie eine völlig neue
SDI-Anwendung
namens "SDI_plus". Übernehmen Sie dabei alle Voreinstellungen.
Zunächst benötigen wir in der
Klasse CMainFrame ein weiteres Objekt der Klasse CToolBar.
Wir verwenden die Bezeichnung m_wndToolBar1:
class CMainFrame : public
CFrameWnd { ... ... protected: // Eingebundene Elemente der Steuerleiste CStatusBar m_wndStatusBar; CToolBar m_wndToolBar, m_wndToolBar1; ... }; |
Eine eigenen Symbolleiste benötigt auch eine eigene Ressource. Fügen Sie hierzu IDR_TOOLBAR1 (vorgegebener Name) ein:
Fangen Sie bitte nicht an zu "pinseln".
Wir wollen zunächst eine "nackte" Symbolleiste entwerfen, um die
Grundlagen
besser zu erkennen.
Zum Speichern müssen Sie vorher
zumindest
kurz mit dem Stift (in grau, damit kein schwarzer Punkt entsteht) in
das
Symbol klicken. Ansonsten geht diese Symbolleiste wieder verloren.
Bitte
beachten.
Jetzt haben wir ein Objekt der Klasse
CToolBar
und eine Toolbar-Ressource. Diese beiden Elemente fügen wir an der
bereits bekannten Stelle in CMainFrame::OnCreate(...) zusammen:
int CMainFrame::OnCreate(LPCREATESTRUCT
lpCreateStruct) { if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if
(!m_wndToolBar.CreateEx(this,
TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP if
(!m_wndToolBar1.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE |
CBRS_TOP ... //Andockerlaubnis
des Rahmenfensters //Standardsymbolleiste //Eigene
Symbolleiste Nr.1 return
0; |
Der eingefügte Code ist sozusagen eine Kopie der vom Assistenten vorgegebenen Zeilen, wobei wir m_wndToolBar gegen m_wndToolBar1 und IDR_MAINFRAME gegen IDR_TOOLBAR1 austauschen. Im unteren Teil haben wir umsortiert, damit die "Andockerlaubnisse/-befehle" für das Rahmenfenster und die beiden Symbolleisten klar getrennt sind.
Jetzt haben wir eine eigene "nackte"
Symbolleiste
hinzugefügt. Unsere Symbolleiste hat nur ein Symbol, nämlich
eine graue Fläche ohne Bild.
Wenn wir mit dem Mauszeiger über
dieses Symbol gehen, verschwindet der Text "Bereit" in der Statuszeile.
Dies passiert auch, wenn wir auf das Symbol klicken. Wir können
unsere
Symbolleiste auch am "Griff" packen und "floaten" lassen bzw. links,
rechts
oder unten andocken:
Diese Symbolleiste besitzt bisher nur
die
Grundfunktionen. Wir werden ihr nun etwas Leben einhauchen. Alles
eigene
braucht einen Namen. Wir wollen unserer Symbolleiste die
Überschrift
"Eigene Symbolleiste Nr.1" verpassen. Wie geht dies? Ganz einfach!
Unsere
Symbolleiste ist ein Fenster und verfügt daher über die
vererbten
Member-Funktion der MFC-Klasse CWnd. Also wenden wir dies sofort an:
//Standardsymbolleiste m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY); DockControlBar(&m_wndToolBar); m_wndToolBar.SetWindowText("Standardsymbolleiste"); //Eigene
Symbolleiste Nr.1 |
Die Standardsymbolleiste haben wir einfach mitgetauft. Den Namen sieht man nur im frei beweglichen Zustand:
Unsere Symbolleiste ist im "Urzustand"
so winzig, dass man noch nicht einmal den ersten Buchstaben unseres
Strings
richtig sieht.
Auf jeden Fall könnten wir jetzt
beginnen, eigene Funktionalitäten an diese zweite Symbolleiste
anbinden.
Da sich das Thema Symbolleisten anbietet, werden wir fünf Symbole
schaffen, die dann Funktionen ansteuern sollen, um die
Standardsymbolleiste
oben, unten, rechts und links anzudocken bzw. frei schwebend zu halten.
Der erste Schritt ist der Entwurf der fünf Symbole. Hier ist mein Entwurf, Sie sind natürlich frei in der künstlerischen Gestaltung:
Wenn wir jetzt kompilieren, erscheinen
diese Symbole nur grau in grau, da wir noch keine aktive Funktion
angebunden
haben.
Daher brauchen unsere Symbole nun
Bezeichnungen.
Ohne diese Namen, die in Windows immer stellvertretend für Zahlen
stehen, kommen wir nicht weiter. Also bitte munter IDs vergeben und
auch
gleich den Statuszeilentext generieren. Hier folgen meine IDs (STB
steht
bei mir für Standard Tool Bar) und
Statuszeilentexte
für die Symbole:
ID_STB_TOP | Standardsymbolleiste oben\nOben |
ID_STB_BOTTOM | Standardsymbolleiste unten\nUnten |
ID_STB_LEFT | Standardsymbolleiste links\nLinks |
ID_STB_RIGHT | Standardsymbolleiste rechts\nRechts |
ID_STB_FLOAT | Standardsymbolleiste frei\nFrei |
Nachdem wir die IDs vergeben haben, ordnen wir diesen 5 IDs in der Klasse CMainFrame Command-Nachrichten zu. Am Beispiel der Objekt-ID ID_STB_TOP wird dies hier gezeigt:
Als Name der Funktion belassen wir es beim Vorschlag von MSVC++: OnStbTop()
Der Funktionsrumpf wartet nun auf
unsere
Eingaben:
void
CMainFrame::OnStbTop() { // TODO: Code für Befehlsbehandlungsroutine hier einfügen } |
So sieht die Anwendung nach Anlegen der ersten Funktion aus:
Die Maus zeigt hier im Bild unsichtbar auf das erste Symbol. Man sieht den zugehörigen Statuszeilentext und den Tooltiptext.
Führen Sie diese Aktion für alle 5 IDs unserer Symbolleiste durch. Nun wollen wir alle Funktionen mit dem zugehörigen Sourcecode füllen. Also beginnen wir mit CMainFrame::OnStbFloat(). Wir wollen die Standardsymbolleiste in diesem Fall frei "floaten" lassen.
Welche Funktion gibt es hierfür? Wenn Sie diese nun selbst suchen, gibt es mehrere Möglichkeiten. Man kann z.B. einfach m_wndTollBar1 mit dem Punkt eintippen und sich alle möglichen Funktionen anschauen. Das führt hier jedoch zu einer unüberschaubaren Vielfalt, da wir die ganzen Funktionen der Klasse CWnd zur Verfügung haben. So wird das nichts.
Eine selektivere Möglichkeit besteht darin, in MSDN die Member-Funktionen der Klasse CToolBar anzuschauen. Da finden wir nur Attribute und Funktionen zur Konstruktion. Also schauen wir nach der Oberklasse CControlBar. Da finden wir CControlBar::EnableDocking(...). Das kann es aber auch nicht sein?! Aber da gibt es doch noch den Hinweis "see also ...". Da finden wir folgende Funktion:
CFrameWnd* CFrameWnd::FloatControlBar( CControlBar * pBar, CPoint point, DWORD dwStyle = CBRS_ALIGN_TOP );
Das könnte es doch sein?! Also
probieren
wir es aus:
void
CMainFrame::OnStbFloat() { CPoint pt( 100,100 ); ClientToScreen( &pt ); FloatControlBar( &m_wndToolBar, pt ); } |
Geklappt! Die Standardsymbolleiste löst sich auf unseren Befehl aus ihrem "Dock" und "floatet".
Der zweite Parameter in der Funktion
bezieht
sich übrigens auf Bildschirmkoordinaten. Wir wollen die 100,100
jedoch
als Client-Koordinaten verstanden wissen. Hierzu wandeln wir diese
Daten
zunächst in Client-Koordinaten um.
Das erledigt die Funktion CWnd::ClientToScreen(
LPPOINT lpPoint ).
Es gibt bei dieser Funktion auch noch einen dritten Parameter, der auf CBRS_ALIGN_TOP voreingestellt ist. Dieser Parameter beschreibt die Ausrichtung der freischwebenden Symbolleiste.
CFrameWnd* FloatControlBar( CControlBar * pBar, CPoint point, DWORD dwStyle = CBRS_ALIGN_TOP );
CBRS_ALIGN_TOP oder CBRS_ALIGN_BOTTOM
sorgen
für die hoizontale Ausrichtung.
CBRS_ALIGN_LEFT oder CBRS_ALIGN_RIGHT
sorgen für die vertikale Ausrichtung.
Was passiert, wenn man z.B.
CBRS_ALIGN_TOP
| CBRS_ALIGN_LEFT angibt? Da siegt die horizontale Ausrichtung!
Das war aber nur der erste Streich. Wir haben noch vier Symbole. Jetzt wollen wir wieder ins Dock mit der Auswahl Nordhafen, Südhafen, Westhafen und Osthafen (klingt irgendwie nach Monopoly, dort waren es aber Bahnhöfe).
Diesmal schauen wir gleich bei CFrameWnd-Funktionen nach und werden auch schnell fündig:
void CFrameWnd::DockControlBar( CControlBar * pBar, UINT nDockBarID = 0, LPCRECT lpRect = NULL );
Die Möglichkeiten für nDockBarID sind AFX_IDW_DOCKBAR_TOP, AFX_IDW_DOCKBAR_BOTTOM, AFX_IDW_DOCKBAR_LEFT und AFX_IDW_DOCKBAR_RIGHT.
Also probieren wir dies aus:
void
CMainFrame::OnStbTop() { DockControlBar( &m_wndToolBar, AFX_IDW_DOCKBAR_TOP ); } void
CMainFrame::OnStbRight() void
CMainFrame::OnStbBottom() void
CMainFrame::OnStbLeft() void
CMainFrame::OnStbFloat() |
... und es klappt auf Anhieb! Nun sind Sie gerüstet, eigene Symbolleisten zu erzeugen, "floaten" oder "docken" zu lassen und vor allem Funktionen anzubinden.
Falls Sie unbedingt Text unter die Symbole fügen wollen, dann gelingt dies mit den Funktionen CToolBar::SetButtonText(...) und CToolBar::SetSizes(...). Das macht die Symbolleisten jedoch raumgreifend. Sie haben doch schließlich die Tooltiptexte und die Texte in der Statusleiste. Daher mein Rat: Verzichten Sie lieber auf diesen Überfluß im "Klicki-Bunti-Browser-Stil", damit mehr Platz für die wesentlichen Dinge bleibt.
Ein Problem besteht oft bei der Anordnung von Symbolleisten nebeneinander. Die Lösung liegt in der Funktion
void CFrameWnd::DockControlBar( CControlBar * pBar, UINT nDockBarID = 0, LPCRECT lpRect = NULL );
und in der Anwendung des dritten Parameters. Durch Angabe eines Rechtecks simuliert man das Ziehen auf diese Fläche mit anschließendem Andocken.
Als erste Anregung finden Sie hier ein
Beispiel, in dem unsere eigene Symbolleiste rechts neben die
Standardsymbolleiste
gesetzt wird:
int
CMainFrame::OnCreate(LPCREATESTRUCT
lpCreateStruct) { ... ... ... EnableDocking(CBRS_ALIGN_ANY);
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
m_wndToolBar1.EnableDocking(CBRS_ALIGN_ANY);
return
0; BOOL
CMainFrame::PreCreateWindow(CREATESTRUCT&
cs)
if(
!CFrameWnd::PreCreateWindow(cs) ) return FALSE; |
Eine allgemeine Vorgehensweise finden
Sie
in MSDN bzw. in Kurzform hier:
http://www.codeproject.com/docking/toolbar_docking.asp?print=true
7.2.4 Statusleiste
Als Ausgangspunkt wählen wir eine ganz
normale
SDI-Anwendung, wie diese vom Assistenten erzeugt wird. Der Name sei
"SDI002".
In dieser Anwendung wird automatisch eine
Statusleiste
erzeugt. Sie kennen auch bereits den Ort der Erzeugung. Die Bausteine
müssen
wir jedoch an drei Stellen zusammen suchen:
class
CMainFrame : public CFrameWnd { ... CStatusBar m_wndStatusBar; |
//
MainFrm.cpp : Implementierung der Klasse CMainFrame
// ... static UINT indicators[] = { ID_SEPARATOR, ID_INDICATOR_CAPS, // Umschalt-Feststelltaste ID_INDICATOR_NUM, // Taste Num (Ziffernblockverriegelung) ID_INDICATOR_SCRL, // Taste Rollen (Bildlaufverriegelung) }; |
int
CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{ if ( !m_wndStatusBar.Create(this) || !m_wndStatusBar.SetIndicators( indicators, sizeof(indicators)/sizeof(UINT) ) ) { TRACE0("Statusleiste konnte nicht erstellt werden\n"); return -1; } |
Das Objekt "Statusleiste" ist eine Instanz der
Klasse
CStatusBar.
In der Klasse CMainFrame wird dieses Objekt als
Member-Variable definiert.
In der Datei MainFrm.cpp finden wir das globale
statische UINT-Array indicators.
In der Funktion CMainFrame::OnCreate(...) wird die
Statusleiste mittels CStatusBar::Create(...) erzeugt,
und die Funktion CStatusBar::SetIndicators(...)
ordnet das Array indicators der Statusleiste zu.
Wenn Sie sich die Anwendung genau anschauen, erkennen Sie noch einen Beitrag zur Statusleiste:
Der String "Bereit" steht zu Beginn ganz links in der Statusleiste.
Sie finden diesen Text in der String Table unter der Bezeichnung AFX_IDS_IDLEMESSAGE. Den String "Bereit" kann man dort auf individuelle Bedürfnisse anpassen.
Die Abkürzungen in den
Indikator-Bereichen
ganz rechts bedeuten:
UF: Umschalt-Feststelltaste
NUM: Ziffernblockverriegleung
RF: Rollen-Feststelltaste
(Bildlaufverriegleung)
Für japanische Tastaturen gibt es
zusätzlich die Kana-Taste ( ID_INDICATOR_KANA, Anzeige:"KANA" ).
Wir bauen dies zur Verdeutlichung in
unsere
Anwendung ein:
static UINT
indicators[]
=
{
ID_SEPARATOR,
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
ID_INDICATOR_KANA,
};
... und schon haben wir einen weiteren Indikator-Bereich.
Die Reihenfolge der Indikator-Bereiche
stimmt übrigens mit der Reihenfolge der Definition im Array
überein.
Für die Ausgabe der
Befehlsinformationen
ist der Eintrag ID_SEPARATOR zuständig.
Jede Statusleiste besteht aus sogenannten "panes" (engl. Scheibe). Das sind rechteckige Bereiche, in denen man Informationen wie z.B. Texte ausgeben kann. Die Zählung der "panes" beginnt links und startet bei 0. Der Text "Bereit" steht also in pane 0.
Wir wollen nun links von den rechts ausgerichteten Panes mit dem 3D-Look einen eigenen Bereich schaffen, in dem wir unsere eigenen Informationen ausgeben können. Zunächst benötigen wir eine ID. Diese fügen wir bei den Ressourcen als Textstring hinzu. Wir benutzen ID_INDICATOR_MYINFO und ordnen den String "Meine Info:__________" (10 Underscores nach dem Doppelpunkt) zu:
Dann fügen wir unsere ID dem
indicator[]
array an zweiter Stelle zu.
static UINT
indicators[]
= { ID_SEPARATOR, // Statusleistenanzeige ID_INDICATOR_MYINFO, ID_INDICATOR_CAPS, ID_INDICATOR_NUM, ID_INDICATOR_SCRL, ID_INDICATOR_KANA, }; |
Wenn Sie nun kompilieren, erhalten Sie sofort diesen neuen rechteckigen Bereich, sozusagen Ihr 3D-Look-Pane:
Wenn Sie nun den Text setzen wollen,
müssen
Sie folgende Schritte unternehmen. Zunächst wollen wir unseren
Text
aus Funktionen der View heraus verändern. Als Beispiel nehmen wir
den Links- und Rechtsklick mit der Maustaste. Also schaffen wir uns
zunächst
diese beiden Funktionen in unserer View-Klasse:
/////////////////////////////////////////////////////////////////////////////
// CSDI002View Nachrichten-Handler void
CSDI002View::OnLButtonDown(UINT
nFlags, CPoint point) CView::OnLButtonDown(nFlags,
point); void
CSDI002View::OnRButtonDown(UINT
nFlags, CPoint point) CView::OnRButtonDown(nFlags,
point); |
Was schreiben wir in diese Funktionen?
Wir brauchen einen Zeiger auf die private Member-Variable m_wndStatusBar der Klasse CMainFrame. Wie erhalten wir diesen?
Hierfür fügen wir mittels Assistent eine neue get-Funktion namens get_StatusBar() in der Klasse CMainFrame hinzu, die uns einen Zeiger auf diese private Member-Variable verschafft. Die Implementierung liefert einfach den Zeiger auf m_wndStatusBar zurück. Damit erhalten wir dann in unseren beiden neu hinzugefügten Member-Funktionen der View-Klasse (für Links- und Rechtsklick) Zugriff auf folgende Member-Funktion der Klasse CStatusBar:
BOOL CStatusBar::SetPaneText( int nIndex, LPCTSTR lpszNewText, BOOL bUpdate = TRUE )
Der erste Parameter ist der Index des
Pane,
den wir ansprechen wollen, in unserem Fall 1.
Der zweite Parameter ist der String, und
der dritte Parameter veranlaßt das Neuzeichnen.
Das Objekt der Klasse CMainFrame
erhalten
wir übrigens mittels CFrameWnd* CWnd::GetParentFrame().
//
MainFrm.h : Schnittstelle der Klasse CMainFrame ... class
CMainFrame : public
CFrameWnd |
//
MainFrm.cpp : Implementierung der Klasse CMainFrame
... /////////////////////////////////////////////////////////////////////////////
CStatusBar*
CMainFrame::get_StatusBar() |
//
SDI002View.cpp : Implementierung der Klasse CSDI002View
...
void
CSDI002View::OnLButtonDown(UINT
nFlags, CPoint point) CView::OnLButtonDown(nFlags,
point); void
CSDI002View::OnRButtonDown(UINT
nFlags, CPoint point) CView::OnRButtonDown(nFlags,
point); |
Hinweis zur OOP:
An diesem Beispiel sehen Sie recht gut,
wie man die Klassengrenze zwischen CMainFrame und C...View durch die
selbst
geschriebene get-Funktion auf saubere Weise überbrücken kann.
Wir hätten natürlich auch einfach m_wndStatusBar auf public
setzen
können. Dies soll man jedoch vermeiden. Member-Variablen
(Attribute)
sind privat! Dafür gibt es Member-Funktionen (Methoden).
Fügen
Sie in solchen Fällen auch keine friend-Anweisungen ein, sondern
behalten
Sie die gewollte Kapselung bei. Ansonsten macht OOP und MFC keinen Sinn.
Wenn Sie diese Hürden überwunden haben, erhalten Sie unseren eigenen Text in der Statuszeile:
7.3 Die Anwendung wird initialisiert
Die Funktion InitInstance() unserer
Anwendungsklasse
startet sozusagen unser SDI-Programm.
Dort findet sich eine ganze Sammlung von
Vorgängen,
die wenig miteinander zu schaffen haben.
Daher wirkt diese Funktion auf den ersten Blick
recht verwirrend. Die dort eingesetzten Anweisungen sind ebenfalls
wenig
selbsterklärend.
Nachfolgend zunächst eine etwas bereinigte
Form des Programms:
BOOL
CSDI001App::InitInstance() { AfxEnableControlContainer(); #ifdef
_AFXDLL
Enable3dControls(); //
MFC in DLLs SetRegistryKey(_T("Local AppWizard-Generated Applications")); LoadStdProfileSettings(); CSingleDocTemplate*
pDocTemplate; CCommandLineInfo
cmdInfo; m_pMainWnd->ShowWindow(SW_SHOW);
return
TRUE; |
AfxEnableControlContainer():
Schaltet die Unterstützung für
ActiveX-Steuerelemente ein. Ein ActiveX-Steuerelement-Container
unterstützt
ActiveX-Steuerelemente und kann diese in seine eigenen Fenster und
Dialogfelder
integrieren.
Sie haben diese Anweisung in Schritt 3 des Assistenten eingefügt:
Enable3dControls() bzw.
Enable3dControlsStatic():
Hierdurch wird das dreidimensionale
Aussehen
von Steuerlementen unterstützt. Man spricht von "3D Window
Controls".
Die Datei CTL3D32.DLL wird mit unserer Anwendung geladen. Das statische
Linken der MFC ist in der Standard-Version von MS VC++ noch nicht
möglich.
Entschieden haben Sie sich für diese Anweisung in Schritt 4 des
Assistenten:
SetRegistryKey(...):
Vielleicht ist es Ihnen noch garnicht
aufgefallen, dass unsere SDI-Anwendung sich in der Registry verewigt.
Unter HKEY_CURRENT_USER\Software\ finden
Sie den Eintrag "Local AppWizard-Generated Applications". Als
Unterschlüssel
finden sich dort verschiedene MFC-Programme, auch unser SDI001.
Wichtig ist dort wiederum der Unterschlüssel Recent File List.
Dort werden die zuletzt verwendeten Dateien unserer Anwendung abgelegt.
Üblich sind vier Files.
Der allgemeine Aufbau ist:
HKEY_CURRENT_USER\Software\
<company> \ <application> \ <section> \ <value>
Ersetzen Sie "Local AppWizard ..." durch den Namen Ihrer "company", und schon haben Sie der Registry Ihren persönlichen Stempel aufgedrückt.
Hier sehen Sie links den
Menüeintrag
einer zuletzt benutzten Datei. Rechts sehen Sie die Speicherung dieser
Information in der Registry.
Die Bezeichnung des Menüeintrages
ist übrigens ID_FILE_MRU_FILE1. Hierbei bedeutet MRU Most
Recently
Used.
LoadStdProfileSettings(UINT nMaxMRU
= _AFX_MRU_COUNT ):
Diese Funktion lädt die gespeicherten
Namen der zuletzt benutzten Files.
In der Datei afxwin.h findet man folgende
Festlegung für den Parameter:
#define _AFX_MRU_COUNT 4// default support for 4 entries in file MRU
Nun wissen Sie auch, warum
standardisiert
maximal vier MRU Files abgelegt werden. Wenn Sie mehr speichern wollen,
geben Sie selbst den entsprechenden Parameter nMaxMRU an. Wenn Sie LoadStdProfileSettings(0)
vorgeben, werden keine MRU Files gespeichert.
Den nächsten Anweisungsblock muß man als Ganzheit betrachten:
//
Dokumentvorlagen
registrieren
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new
CSingleDocTemplate
(
IDR_MAINFRAME,
RUNTIME_CLASS(
CSDI001Doc
),
RUNTIME_CLASS(
CMainFrame
), // Haupt-SDI-Rahmenfenster
RUNTIME_CLASS( CSDI001View
)
);
AddDocTemplate(pDocTemplate);
Man erzeugt hier mittels new auf dem Heap ein Objekt der MFC-Klasse CSingleDocTemplate. Die Adresse dieses Objektes übergeben wir an die Funktion AddDocTemplate(...). Diese fügt den Zeiger in ein Zeiger-Array ein. Eine Anwendung kann also mehrere Zeiger auf solche CDocTemplate besitzen.
Jetzt hat unsere Anwendung eine Aufzeichnung, in der die Adressen von Rahmenfenster, Dokument und Ansicht stehen. Das ist sozusagen die Auskunft, wenn Beziehungen zwischen den Einzelteilen gesucht werden. Stellen Sie sich das vor wie eine Familie: Vater, Mutter, Kinder. Der Familienname ist der Zeiger auf die Familie. Wenn ein Kind fragt: "Wer/Wo ist meine Mutter?", dann wird das Kind nach dem Familiennamen gefragt. Hat es diesen parat, dann erhält es eine Antwort.
Der "Familienname" hier ist
pDocTemplate.
Machen wir uns das etwas klarer. Typische Fragen nach anderen
"Familienmitgliedern"
sind z.B.:
CDocTemplate* CDocument::GetDocTemplate( ) const; |
Dokument
fragt: Wer ist mein CDocTemplate? (sozusagen Familienname) |
POSITION CDocument::GetFirstViewPosition( ) const; CView* CDocument::GetNextView( POSITION& ) const; |
Dokument
fragt: Wo ist meine erste Ansicht? Wer ist meine nächste Ansicht? |
CDocument* CView::GetDocument( ) const; |
Ansicht
fragt: Wer ist mein Dokument? |
CFrameWnd* CWnd::GetParentFrame( ) const; |
Ansicht
(oder Kind)
fragt: Wer ist mein Rahmenfenster? |
CView* CFrameWnd::GetActiveView( ) const; |
Rahmenfenster
fragt: Wer ist meine aktuelle Ansicht? |
CDocument* CFrameWnd::GetActiveDocument( ); |
Rahmenfenster
fragt: Wer ist das Dokument meiner aktuellen Ansicht? |
CWinApp* AfxGetApp( ); LPCTSTR AfxGetAppName( ); |
Alle
fragen: Wer ist unsere Anwendung? Wie heißt unsere Anwendung? |
Sie sehen: Es existieren zahlreiche Get-Funktionen, damit man sich überhaupt gegenseitig findet. Eine tolle "Familie".
Eine weitere Auskunft erteilt uns
CSingleDocTemplate
auch noch, nämlich über die Ressourcen unserer "Familie",
sozusagen
der Hausrat.
Sie wissen: AcceIerator-Tabellen, Icons,
Menüs, Symbolleisten, etc.
Der Name für den "Hausrat" (sprich: wichtige Ressourcen) ist IDR_MAINFRAME. Der Name signalisiert: Der Hausrat gehört zum Rahmenfenster. Sie sehen, wie oft diese Bezeichnung in unserer Ressourcenübersicht auftaucht. Auch der erste Eintrag in der Zeichenfolgentabelle (String Table) ist IDR_MAINFRAME:
Auch der nächste Anweisungsblock sollte als Ganzheit betrachtet werden:
CCommandLineInfo cmdInfo;
ParseCommandLine( cmdInfo
);
if ( !ProcessShellCommand( cmdInfo
) ) return FALSE;
Zunächst wird ein Objekt der MFC-Klasse CCommandLineInfo generiert. Diese Klasse ermöglicht die Auswertung der Parameter, die in der Kommandozeile nach dem Programmnamen übergeben werden.
ParseCommandLine(...) ruft für jeden übergebenen Parameter die Funktion
void CCommandLineInfo::ParseParam( LPCTSTR lpszParam, BOOL bFlag, BOOL bLast )
auf.
Die Funktion
BOOL CWinApp::ProcessShellCommand( CCommandLineInfo& rCmdInfo )
kann anschließend in
Abhängigkeit
der erkannten Parameter in der Kommandozeile folgende Aktionen
ausführen:
Parameter in Kommandozeile | Resultierende Aktion |
app | Neues Dokument |
app filename | Öffnet Datei als Dokument |
app /p filename | Druckt Datei auf Standard-Drucker |
app /pt filename printer driver port | Druckt Datei auf angegebenen Drucker |
app /Automation | statet als Automation-Server |
app /dde | wartet auf DDE-Befehle |
app /Embedding | bearbeitet ein eingebettetes OLE-Objekt |
Wenn Sie z.B. "SDI001.exe SchoenesBild.bmp" unter Start - Ausführen angeben,
entspricht dies app filename und unsere
Anwendung startet mit diesem Bild. Wirklich? Natürlich nicht, denn
wir haben keinerlei Funktionalität für die Bearbeitung von
bmp-Dateien
in unsere Anwendung implementiert. Aber prinzipiell könnte das so
ablaufen.
Die beiden letzen Anweisungen
m_pMainWnd->ShowWindow(
SW_SHOW
);
m_pMainWnd->UpdateWindow();
sind sicher schon bekannt. ShowWindow(...) zeigt das Rahmenfenster an, und UpdateWindow() erzwingt durch die Erzeugung der Nachricht WM_PAINT an der Nachrichtenschlange vorbei ein sofortiges Neuzeichnen des Client-Bereiches.
Bezüglich der möglichen Parameter bei der Fensteranzeige schauen Sie bitte bei der Funktion
BOOL CWnd::ShowWindow( int nCmdShow )
nach.
Den Abschluß bildet die
Rückgabe
des Wertes TRUE. Dies signalisiert die korrekte Initialisierung.