Zurueck zum Inhaltsverzeichnis
7.1 Dokumente und Ansichten
Während der Begriff Ansichten (View)
einen Hinweis auf die 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 Schreiben und Lesen der
Daten in Dateien sorgt: Dies ist 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.
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 verstehen, zwischen MFC-Rahmenprogramm, das der Assistent erstellt, und Ihrem eigenen Programmcode klar zu trennen.
Wir starten wie gewohnt mit "Datei /
Neu/
Projekte / MFC-Anwendungs-Assistent(exe)".
Wählen Sie als Projektname "Test_",
damit Sie die Namensanhänge, die der Assistent den zu erstellenden
Klassen verpasst,
möglichst gut erkennen können.
In Schritt 1 entscheiden Sie für
"Einzelnes
Dokument (SDI)" und lassen das Häkchen bei
"Unterstützung der
Dokument-/Ansichts-Architektur"
gesetzt.
Schritt 2 und 3 akzeptieren Sie ohne Änderung.
In Schritt 4 wählen Sie alles außer 3D-Steuerelemente ab.
Abb. 7.2: Schritt 4 bei der Erstellung des Doc-/View-Beispiels
Ändern Sie unter "Weitere Optionen" die Eingaben wie folgt ab:
Abb. 7.3: Unterfenster bei Schritt 4 "Weitere Optionen"
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.
Abb. 7.4: Die vom Assistent erstellten Klassen
In Schritt 6 zeigt der Assistent dann die zu erstellenden Klassen:
Sie erkennen die vier Grundpfeiler, auf denen die Anwendung steht:
Ansicht (View),
Daten (Doc),
Fenster
(MainFrame)
und
Anwendung (App).
Es ist wichtig, dass Sie sich diese Unterteilung gut einprägen, damit Sie beim Programmieren die Übersicht behalten.
Abb. 7.5: Die kompilierte und ausgeführte Anwendung
Wenn Sie das Ganze nun kompilieren und
ausführen, erhalten Sie obiges Fenster.
Schauen Sie sich die Menüs genauer
an. Unter "Datei" finden Sie die wesentlichen Möglichkeiten in
Bezug
auf unsere sav-Dateien:
Neu - Beenden
-
Öffnen einer
vorhandenen Datei - Speichern/Überschreiben einer Datei
Der Menüpunkt "Bearbeiten" ist
z.Z.
nicht interessant.
Unter "?" finden Sie die bekannte
About-Dialogbox.
Damit steht das Fundament. Jetzt liegt es an uns, eigene Ideen in die Tat umzusetzen.
Früher 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
mit
vier diagonalen Linien.
Damit erhält man schnell und einfach
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 auch in unseren sav-Dateien landen.
Quadrate und 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
... class CTest_Doc : public CDocument { ... // Überladungen // Vom Klassenassistenten generierte Überladungen virtueller Funktionen //{{AFX_VIRTUAL(CTest_Doc) public: virtual BOOL OnNewDocument(); virtual void Serialize(CArchive& ar); //}}AFX_VIRTUAL //
Implementierung
|
Wir werden diese Member-Variablen der Dokumentenklasse nun an zwei Stellen initialisieren:
Im Konstruktor der Dokumentenklasse,
In der Member-Funktion "OnNewDocument"
der Dokumentenklasse:
///////////////
// CTest_Doc Konstruktion/Destruktion CTest_Doc::CTest_Doc()
Laenge
= 200;
CTest_Doc::~CTest_Doc() {} BOOL
CTest_Doc::OnNewDocument()
Laenge
= 200;
return TRUE;
|
Diese Zahlen sind unsere konkreten Daten |
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, um die Anzeige der Daten zu realisieren. Die
grafische
Anzeige erfolgt in der Member-Funktion OnDraw(), die uns bisher
folgendes
bietet:
void CTest_View::OnDraw(CDC*
pDC)
{ CTest_Doc* pDoc = GetDocument(); ASSERT_VALID(pDoc); } |
Wichtig sind die beiden Zeiger pDC
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 zeichen 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 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.
Aus den gleichen Daten könnten wir z.B. auch mit TextOut() eine Tabelle erzeugen. Das ist gemeint, wenn man davon spricht, dass ein Dokument mehrere Ansichten haben kann. Sie kennen dies z.B. auch aus MS Excel, wenn man Daten als Punkt-, Linien-, Balken- oder Tortengrafik darstellen kann. Die Ausgangstabelle ist das Dokument, während die Grafiken die "Views" sind.
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)
|
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 Daten speichern, sagen wir unter dem Dateinamen "Wuerfel1.sav".
Abb. 7.7: Das Ergebnis der Serialisierung, die Datei "Wuerfel.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 auch zusammengesetzte
Daten wie CPoint einfach hin- und
herschieben
kann (dies erinnert Sie sicher an die C++-Streams cin und cout).
Zum Abschluß 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 + W drücken; der
Klassen-Assistent
erscheint
2) Klassenname auf CTest_View abändern
3) Unter Nachrichten 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(...):
///////////////
// CTest_View Nachrichten-Handler void
CTest_View::OnKeyDown(UINT nChar,
UINT nRepCnt, UINT 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)
{ // TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen und/oder Standard aufrufen
CTest_Doc*
pDoc = GetDocument();
switch(
nChar )
pDoc->SetModifiedFlag();
CView::OnKeyDown(nChar, nRepCnt,
nFlags);
|
Sie sehen, dass wir uns wieder den
Zeiger
auf die Dokumentklasse besorgen 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 mußten wir ihm einfach nacheifern.
Nachdem wir Daten der Dokumentklasse
verändern,
teilen wir dies auch dieser Klasse mit.
Dies erfolgt mittels pDoc->SetModifiedFlag();
Die Aktualisierung der Ansichtsklasse
erfolgt mittels Invalidate();
Das Setzen des "ModifiedFlags"
führt
dazu, dass die Anwendung uns bei Veränderungen (hier mit den
Pfeiltasten)
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.
7.2 Single Document Interface (SDI)
Nachdem Sie nun das Doc/View-Modell mit der
Trennung
von Daten und Ansicht verstanden haben, wenden wir uns nun
verstärkt
dem Rahmenfenster, den Ansichten und der Anwendung zu. Beginnen wir mit
dem sogenannten Single Document Interface (SDI) (Gegensatz:
Multiple
Document Interface, MDI).
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 Schritt 1. Dort wählen Sie nicht MDI, sondern Einzelnes Dokument (SDI) aus. Genau genommen ist es merkwürdig, dass MDI voreingestellt ist, denn MicroSoft 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.
Beachten Sie den Schritt 4 des Assistenten:
Dort sind verschiedene Zutaten bereits eingestellt: Symbolleiste, Statusleiste, Drucken plus Druckvorschau, 3D-Steuerelemente. Lassen Sie diese Punkte ausgewählt, weil wir uns damit noch beschäftigen wollen. Interessant ist die Schaltfläche "Weitere Optionen". Wenn Sie dieses Feld anwählen, können Sie eine Vorauswahl für die Fensterstile (Window Styles) treffen und sogar ein geteiltes Fenster verwenden:
Wenn Sie diese Vorgaben in diesem oft
übersprungenen
Nebenzweig des Schrittes 4 akzeptieren, sieht ihre Funktion CMainFrame::PreCreateWindow(...)
vereinfacht wie folgt aus:
BOOL
CMainFrame::PreCreateWindow(CREATESTRUCT&
cs)
{ if( !CFrameWnd::PreCreateWindow(cs) ) return FALSE; return TRUE; } |
Hier wird also nur die Funktion der Oberklasse CFrameWnd::PreCreateWindow(...) ausgeführt.
Wenn Sie die Minimieren- und
Maximieren-Schaltfläche
mittels Assistent abwählen würden, dann sähe das
Ergebnis
wie folgt aus:
BOOL
CMainFrame::PreCreateWindow(CREATESTRUCT&
cs)
{ if( !CFrameWnd::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 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:
FWS_ADDTOTITLE:
FWS_ADDTOTITLE | FWS_PREFIXTITLE:
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 bei der Funktion OnNewDocument():
BOOL
CSDI001Doc::OnNewDocument()
{ if (!CDocument::OnNewDocument()) return FALSE; //
ZU ERLEDIGEN: Hier Code zur Reinitialisierung einfügen
SetTitle("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.
Was steht eigentlich in CFrameWnd::PreCreateWindow(...)? MFC erlaubt den Blick in die cpp's. Man muß nur wissen, wo man was findet. Hinweise findet man z.B. im Anhang des Buches "MFC Internals" von George Shepherd und Scot Wingo. Also öffnen wir die Motorhaube:
"MFC under the hood" - Die
Implementation
von CFrameWnd::PreCreateWindow(...) in winfrm.cpp:
BOOL
CFrameWnd::PreCreateWindow(CREATESTRUCT&
cs)
{ if (cs.lpszClass == NULL) { VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG)); cs.lpszClass = _afxWndFrameOrView; // COLOR_WINDOW background } if ((cs.style & FWS_ADDTOTITLE) && afxData.bWin4) cs.style |= FWS_PREFIXTITLE; if (afxData.bWin4) cs.dwExStyle |= WS_EX_CLIENTEDGE; return
TRUE;
|
afxData.bWin4:
Windows
95 (oder höher)
Nach diesem Kurzausflug zu CMainFrame::PreCreateWindow(...), die vor dem Erstellen des Rahmenfensters ausgeführt wird, belassen wir es bei diesem Standard, den uns die Basisklasse CFrameWnd serviert.Nach dem Erstellen und Ausführen der EXE-Datei sehen Sie folgende Anwendung auf ihrem Bildschirm (hier ist das Fenster verkleinert):
Der Assistent hat seine Arbeit geleistet. Sie
haben
aber vielleicht mehr oder etwas anderes als Sie wirklich brauchen oder
wollen.
Schauen wir uns die optische Erscheinung
zunächst
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)
7.2.2 Elemente des Rahmenfensters - Grundlagen
Nun suchen wir in unserer Anwendung nach diesen Elementen:
Beginnen wir mit der Klassendefinition
unserer von CFrameWnd abgeleiteten Klasse CMainFrame.
Dort entdecken wir unsere Statusleiste
und Symbolleiste in folgenden Member-Variablen:
CStatusBar m_wndStatusBar;
CToolBar
m_wndToolBar;
Darüber hinaus gibt es dort auch folgende Struktur für unser Rahmenfenster:
CREATESTRUCT cs.
class
CMainFrame : public
CFrameWnd
{ protected: // Nur aus Serialisierung erzeugen CMainFrame(); DECLARE_DYNCREATE(CMainFrame) //
Attribute
//
Operationen
//
Überladungen
//
Implementierung
#ifdef
_DEBUG
protected:
// Eingebundene
Elemente der Steuerleiste
//
Generierte Message-Map-Funktionen
|
Schauen wir uns diese Klasse noch
einmal
von allem für uns Überflüssigen bereinigt an:
class CMainFrame : public
CFrameWnd
{ protected: CMainFrame(); DECLARE_DYNCREATE(CMainFrame) public:
public:
protected:
protected:
|
Das sieht doch gleich viel
übersichtlicher
aus. 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 wieder. Das sind C++-Standardelemente einer Klasse. Was gibt es
Spezifisches?
Da finden wir zwei 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;
// ZU
ERLEDIGEN: Ändern Sie hier die Fensterklasse oder das
Erscheinungsbild,
indem Sie
return TRUE;
|
Wir gehen davon aus, das es klappt.
Interessanter
ist die Strukur cs vom Typ CREATESTRUCT. Was steht denn da alles
drinnen?
Also 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 (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; if
(!m_wndToolBar.CreateEx(this,
TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
if
(!m_wndStatusBar.Create(this)
||
//
ZU ERLEDIGEN: Löschen Sie diese drei Zeilen, wenn Sie nicht
wollen,
dass die Symbolleiste
return
0;
|
Das ist ja ein richtiger Brocken. Sie
sehen
der Assistent arbeitet fleißig für uns. Zunächst
bereinigen
und vereinfachen wir wieder, damit wir einen klaren Blick haben:
int
CMainFrame::OnCreate(LPCREATESTRUCT
lpCreateStruct)
{ if ( CFrameWnd::OnCreate(lpCreateStruct) == -1 ) return -1; if ( !m_wndToolBar.CreateEx( ... || !m_wndToolBar.LoadToolBar(IDR_MAINFRAME) ) return -1; // Fehler Toolbar if ( !m_wndStatusBar.Create( (this) || !m_wndStatusBar.SetIndicators(...) ) return -1; // Fehler Statusbar //
ZU ERLEDIGEN: Löschen Sie diese drei Zeilen, wenn Sie nicht
wollen,
dass die Symbolleiste andockbar ist.
return
0;
|
So sieht das doch gleich viel klarer aus. Nun erkennt man die Erstellung der Symbolleiste, der Statusleiste und den Anweisungsblock, der sich mit der "Andockbarkeit" der Symbolleiste beschäftigt. Hier ist also der richtige Platz, um Veränderungen an diesen beiden Elementen vorzunehmen.
Beginnen wir sofort mit einfachen Experimenten:
1) Wir wollen keine andockbare
Symbolleiste:
Das ist leicht, da müssen wir einfach
die drei Zeilen, wie angegeben, durch einen Kommentar (im C-Stil)
streichen.
Kommentieren Sie auf jeden Fall aus, also nicht wirklich wegstreichen,
es sei denn Sie kennen die drei Zeilen auswendig.
int
CMainFrame::OnCreate(LPCREATESTRUCT
lpCreateStruct)
{ ... //
ZU ERLEDIGEN: Löschen Sie diese drei Zeilen, wenn Sie nicht
wollen,
dass die Symbolleiste
return
0;
|
... und schon ist es vorbei mit der vagabundierenden Symbolleiste. Jetzt hängt sie fest.
2) Wir wollen gar keine Symbolleiste:
Wieder einfach. Dann wird eben auch die
Erzeugung gestrichen.
int
CMainFrame::OnCreate(LPCREATESTRUCT
lpCreateStruct)
{ if (CFrameWnd::OnCreate(lpCreateStruct) == -1) return -1; /*
if
(!m_wndStatusBar.Create(this)
|| !m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
//
ZU ERLEDIGEN: Löschen Sie diese drei Zeilen, wenn Sie nicht
wollen,
dass die Symbolleiste
return
0;
|
... und weg ist dieser "Schnickschnack".
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) || !m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT))) { TRACE0("Statusleiste konnte nicht erstellt werden\n"); return -1; // Fehler bei Erstellung } */ |
... da ist doch gleich viel mehr Platz für unsere "Views".
4) Jetzt wollen wir plötzlich nichts mehr, keine Symbolleiste, keine Statusleiste und vor allem kein Menü:
Die ersten beiden Wünsche sind mit
dem bisherigen Wissen leicht zu erfüllen, das brauchen wir hier
gar
nicht mehr zu zeigen. Aber wie geht das mit dem Menü? Wo war das
doch
gleich? Das handle auf das Menü war cs.hMenu. Also weg
damit!
Die richtige Funktion ist
CMainFrame::PreCreateWindow(...):
BOOL
CMainFrame::PreCreateWindow(CREATESTRUCT&
cs)
{ if ( cs.hMenu != NULL ) { ::DestroyMenu( cs.hMenu ); // Geladenes Menü entfernen cs.hMenu = NULL; // Rahmenfenster hat kein Menü } if(
!CFrameWnd::PreCreateWindow(cs)
) return FALSE;
|
... und das klappt ja prächtig.
Nun sieht das richtig primitiv aus! Ist natürlich kein Wunder, wenn wir alles Zubehör außer Kraft setzen.
Wollen Sie das Menü wieder haben,
müssen Sie nur den Zwischenblock wieder per Kommentar deaktivieren.
BOOL
CMainFrame::PreCreateWindow(CREATESTRUCT&
cs)
{ /* if ( cs.hMenu != NULL ) { ::DestroyMenu( cs.hMenu ); // Bereits geladenes Menü entfernen cs.hMenu = NULL; // Hauptfenster hat kein Menü } */ if(
!CFrameWnd::PreCreateWindow(cs)
) return FALSE;
|
5) 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;
|
... und schon erscheint das Fenster in der gewünschten Größe. Tool- und Statusleiste sind auch wieder aktiviert. Aber das können Sie ja nach Belieben ändern. Wie Sie sehen, ist unser Fenster für den rechten Teil der Statusleiste mit cs.cx = 300 sogar zu kurz. Aber man kann es durch Ziehen mit der Maus auf die notwendige Größe verändern.
Von den fünf Bestandteilen
1. Systemmenü
2. Menü
3. Symbolleiste
4. Client-Bereich
5. Statusleiste
haben wir bereits 2, 3 und 5 aufgespürt.
Gehen wir zu Punkt 1: Wie findet man
das
Systemmenü (heute auch Fenstermenü genannt)?
Wir möchten z.B. das Schließen
per Mausklick auf "X" unterbinden. Die Benutzer sollen mit unserer
Anwendung
arbeiten und diese nicht einfach "weg klicken". Wie erreichen wir dies?
Hierzu muß man dieses "X" bzw. das "Schließen" im
Systemmenü
desaktivieren. Der richtige Ort für diese Missetat ist die
Funktion
CMainFrame::OnCreate(...).
Dort erstellt man immerhin auch andere Verzierungen unseres
Rahmenfensters
wie Symbol- und Statusleiste:
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_wndStatusBar.Create(this)
|| !m_wndStatusBar.SetIndicators(indicators,
//
ZU ERLEDIGEN: Löschen Sie diese drei Zeilen, wenn Sie nicht
wollen,
dass die Symbolleiste andockbar ist.
CMenu*
pSystemMenu = GetSystemMenu(FALSE);
return
0;
|
Na, wer sagt's denn, geht doch. Jetzt ist das "X" nicht mehr so einladend, und nicht jeder kennt "Alt+F4", oder doch?
Diese Anweisung schauen wir uns genauer an:
CMenu*
pSystemMenu = GetSystemMenu(FALSE);
pSystemMenu->EnableMenuItem(
SC_CLOSE, MF_GRAYED);
In der ersten Zeile beschaffen wir uns einen Zeiger auf das Systemenü-Objekt unseres Rahmenfensters mittels
CMenu* CWnd::GetSystemMenu ( BOOL bRevert ) const
Wir müssen den Parameter auf FALSE
setzen, damit wir einen Zeiger auf eine manipulierbare Kopie von CMenu
erhalten.
Diesen Zeiger nennen wir in unserem Fall
pSystemMenu. Damit können wir verschiedene Member-Funktionen der
Klasse
CMenu auf unser Objekt anwenden. Im aktuellen Beispiel haben wir
UINT CMenu::EnableMenuItem( UINT nIDEnableItem, UINT nEnable )
verwendet.
Der erste Parameter gibt die ID (default) oder Position des Menüpunktes an, und der zweite Parameter den erwünschten Zustand. Nun benötigen wir nur noch Werte für die entsprechenden Konstanten zu unserem Verständnis:
UINT nIDEnableItem:
einige Beispiele (SC steht für
system
command):
SC_CLOSE:
"X"
SC_MAXIMIZE: Maximiert
das Fenster
SC_MINIMIZE. Minimiert
das Fenster
UINT nEnable
wichtige Parameter:
MF_BYCOMMAND: zeigt an, dass
der erste Parameter durch sein ID angezeigt wird (default)
MF_BYPOSITION: zeigt an, dass der
erste Parameter durch seine Position angezeigt wird
MF_DISABLED: Desaktiviert
den Menüpunkt (Farbe des Strings: normal)
MF_ENABLED:
Aktiviert
den Menüpunkt
MF_GRAYED:
Desaktiviert den Menüpunkt (Farbe des Strings: hellgrau)
Der Menüpunkt "Schließen" existiert nicht nur als einladendes "X", sondern zusätzlich etwas versteckt als String auf Platz 6 auch im Menü (ganz links), das durch Anklicken des MFC-Icons erreicht wird.
Wenn Sie MF_DISABLED anstelle MF_GRAYED verwenden, wird zwar das "X" heller dargestellt, aber "Schließen" immer noch als normaler String angezeigt. Nur, wenn Sie MF_GRAYED wählen, sind "X" und "Schließen" in hellgrauer Frabe dargestellt und desaktiviert.
Wollen Sie im ersten Parameter die Positionsnummer anstelle der ID angeben, dann wählen Sie die 6 als ersten Parameter in Verbindung mit MF_BYPOSITION als zweiten Parameter:
pSystemMenu->EnableMenuItem( 6,
MF_BYPOSITION
|
MF_GRAYED);
Die Minimize- und Maximize-Box
können
Sie über die Windows-Styles entfernen. Aber das kennen Sie ja
schon
von oben:
BOOL
CMainFrame::PreCreateWindow(CREATESTRUCT&
cs)
{ /* if (cs.hMenu!=NULL) { ::DestroyMenu(cs.hMenu); // Bereits geladenes Menü entfernen cs.hMenu = NULL; // Hauptfenster hat kein Menü } */ cs.cx =
300;
cs.style
&= ~WS_MINIMIZEBOX;
if(
!CFrameWnd::PreCreateWindow(cs)
) return FALSE;
|
Damit sind dann auch die
Menüeintrage
"Minimieren" und "Maximieren" auf hellgrau und inaktiv gesetzt.
Sie sehen, dass man mit dieser Funktion
CMainFrame::PrecreateWindow(...) umfangreiche
Einstellungsmöglichkeiten
hat.
Nachfolgend noch ein kleines Beispiel
für
eine SDI-Anwendung, die ein Rahmenfenster ohne
Minimieren-/Maximieren-Schaltflächen
und ohne einen in der Größe veränderbaren Rahmen
erzeugt.
Das Fenster wird auf n % des Bildschirms eingestellt und zentriert:
BOOL
CMainFrame::PreCreateWindow(CREATESTRUCT&
cs)
{ cs.style = WS_OVERLAPPED | WS_BORDER | WS_SYSMENU;
const double n = 33; //
n:
Prozent der Fenstergröße
return
CFrameWnd::PreCreateWindow(cs);
|
Sie haben nun einen 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. Zierrat wie Ressourcen, Menüs, Symbolleisten
und Statusleiste sind keine Eckpfeiler. Sie hängen vielmehr wie
"liebliche
Erker" an unserem massiven Rohbau, und mit fortschreitender
Entwicklung
des Betriebssystems MS Windows muß der GUI-Programmierer sich
wahrscheinlich
verstärkt mit diesen Feinheiten beschäftigen. Beginnen wir
mit
Symbol- und Statusleisten.
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.