doc - oth-regensburg.de

Werbung
Lösungen Übung 12
1. Aufgabe
Voraussetzung: Es existiert eine dialogfeldbasierende Anwendung, die folgendes Fenster erzeugt:
Zum Bearbeiten eines einzelnen Elements des Dialogfensters sind folgende Schritte notwendig:
- Man klickt das Element mit der Maus (linke Taste) an.
- Das Studio bestätigt die Aktivierung, indem es um das Element ein blaugepunktetes Rechteck zieht.
Die Eckpunkte und Seitenmittelpunkte sind fett blau markiert.
- Jetzt kann man dieses Element bearbeiten.
- Mit der Maus kann man es verschieben und seine Größe ändern.
- Mit der DELETE-Taste kann man es ganz entfernen.
- Man kann die ENTER-Taste drücken und erhält ein Eigenschaftenfenster (Properties), um weitere
Eigenschaften zu verändern. Mit einem weiteren ENTER schließt man dieses Hilfsfenster wieder.
1. Änderungen und Test
Ändere im Dialog(haupt)fenster die Überschrift (Caption) und füge die Minimize- und Maximize-Box
hinzu (befindet sich auf einer der Registerkarten des Properties-Fensters).
Entferne den Abbrechen-Button.
Entferne den Static Text "ZU ERLEDIGEN: ....."
Ändere nicht den OK-Button - auch nicht den Text!!!
Ändere die Größe des Dialog(haupt)fensters.
Übersetzen und Testen!
Beim Test wird folgendes Fenster angezeigt:
1
2. Änderung
a) Einfügen der Beschriftung „Breite“, des Eingabefelds dafür und der Beschriftung „in cm“.
Die einzelnen Elemente werden mit der Maus aus der Toolbox ungefähr an die passende Stelle im
Dialogfenster gezogen.
Danach öffne das Properties-Window und fügen passende Daten / Optionen hinzu.
Breite und in cm sind Static Text: es ist nichts weiter zu verändern.
Das Eingabekästchen ist eine EditBox. Sie hat (siehe Properties-Window) den identifier
IDC_EDIT1. Diesen Namen in IDC_BREITE ändern.
b) Weitere Schritte sind:
Einfügen der drei Elemente ein, die zur Länge gehören. Die zugehörige ID sollte IDC_LAENGE
heißen.
Einfügen des Rahmens mit der Überschrift "Ergebnisse" : dieses Element ist eine GroupBox
Einfügen vom Button "Berechnung durchführen" und ändern die ID in IDC_BERECHNEN.
Einfügen einer EditBox für das Ergebnis ein, ändern die ID in IDC_FLAECHE und markieren diese Box
in Edit properties - styles als Read-only
Einfügen der Texte für "Fläche" und "in cm 2 “ ein.
Übersetzen zur Kontrolle bzgl. fehlerfreier Ausführung.
3. Rückblick: Was wurde bisher entwickelt?
- Erzeugt wurde ein Projekt Ueb1201, zu diesem Projekt gehören 2 classes:
-- Die class CUeb1201App repräsentiert das Anwendungsprogramm (APP = application). Dazu gibt es
eine globale Variable CDemo1App theApp ;
-- Diese Variable wird am Anfang des Programmlaufs erzeugt (ihr constructor wird ausgeführt), sie
stellt für das Betriebssystem den Zugangspunkt zum Programm dar.
-- Während der Erzeugung dieser Variablen wird auch ein Fenster der zugehörigen Fensterklasse
erzeugt (siehe unten).
- Entwicklung einer Dialog Box (Dialogfenster), die alle nötigen Elemente enthält.
-- Die zugehörige class (Typ) ist CUeb1201Dlg.
-- Ein Fenster dieses Typs wird bei der Erzeugung von theApp erzeugt.
-- Das Fenster hat die ID IDD_UEB1201_DIALOG (IDD_ steht für ID-Dialog).
-- Das Fenster hat die 3 üblichen Controls von WINDOWS-Fenstern, nämlich Minimize-Box, MaximizeBox und Cancel-Box.
- Füllen der Dialog Box mit anwendungsspezifischen Controls. Jede dieser Controls hat eine
umgangssprachliche Bezeichnung, einen Typ (eine class), und eine ID :
Beschreibung
class = type
ID
2
7 Texte
Rahmen Ergebnisse
Eingabebox Breite
Eingabebox Länge
Ausgabebox Fläche
Button Berechnung
Button OK
CStatic
CStatic
CEdit
CEdit
CEdit
CButton
CButton
<interessiert nicht>
<interessiert nicht>
IDC_BREITE
IDC_LAENGE
IDC_FLAECHE
IDC_BERECHNEN
IDC_OK
4. Hinzufügen von Variablen
a) Das, was der Benutzer in eine der beiden Eingabeboxen tippt, muß im Programm (in einer
Variablen) ankommen. Dies geht in zwei Stufen:
- Der Benutzer tippt mehrere Zeichen, also einen string. In der MFC gibt es dafür die class CString.
- Die Bedeutung dieses string ist jedoch ein numerischer Wert, eine Größe vom Typ double.
Die Variablen werden “member variables” der “class CUeb1201Dlg”:
Eingabe Box
Breite
Länge
ID
IDC_BREITE
IDC_LAENGE
Edit-Variable
m_strBreite
m_strLaenge
Ziel-Variable
m_dBreite
m_dLaenge
Beachte die Schreibweise von „member variables“. Der Name beginnt stets mit „m_“ gefolgt von einer
Kurzbezeichnung des Typs, damit man ohne viel Blättern stets den Typ einer Variablen erkennen
kann. Dann erst folgt der eigentliche Name. „str“ steht für string, für die class CString, und d steht für
double.
- Definition von „member variables“ für den Dialog.
-- Für die beiden direkt mit der Eingabebox verbundenen Variablen sind folgende Daten zu bestimmen:
- die Namen m_strBreite bzw. m_strLaenge;
- den Datentyp CString;
- die ID der zugehörigen Eingabebox: IDC_BREITE bzw. IDC_LAENGE.
-- Diese Variablen werden m. H. des ClassWizard definiert. Der ClassWizard (deutsch: MFCKlassen-Assistent) wird aufgerufen über die Menupunkte View ClassWizard oder den shortcut
Ctrl+W.
-- Im ClassWizard wird die Registerkarte „member variables“ gewählt, die Zeilen für Breite bzw.
Länge und fügen dann jeweils eine passende Member Variable hinzu:
3
Schließen des MFC-Klassenassistenten
Für Variablen, die nicht direkt mit Controls verbunden sind, gilt: Ihr Bezug zu Control-Elementen ist nur
gedanklich, also für den Compiler oder das Studio nicht erkennbar.
Der Bezug wird durch ähnliche Namen ausgedrückt (m_dBreite bzw. m_dLaenge).
Der Typ ist jeweils double.
Diese Variablen werden folgendermaßen definiert:
Im linken Arbeitsbereich des Studios Wahl der Registerkarte Class und Expansion der class
CUeb1201DLG. Neben vielen unbekannten Größen sieht man unten die beiden neuen data members
m_strBreite und m_strLaenge.
Klicken mit der rechten Maustaste auf die class-Bezeichnung (ganz oben) CUeb1201DLG. Aus dem
sich daraufhin öffnenden Fenster Wahl von "member-Variable hinzufügen".
Eingabe von dem Typ double, dem Namen m_dBreite bzw. m_dLaenge ein und lassen den Status
public.
5. Der Programmablauf unter Windows
Jedes WINDOWS-Programm ist eine Ansammlung kleinerer Fenster ("child windows") wie EditBoxes,
Buttons etc.
WINDOWS übernimmt komplett die Kommunikation mit der Tastatur und der Maus.
4
Auf der Tastatur getippte Zeichen beziehen sich stets auf eines dieser "child windows": man sagt,
dieses Fenster habe den focus. Man erkennt den focus in einem Edit-Fenster am senkrechten Strich
(dem WINDOWS-cursor), bei einem Button an der doppelten Umrandung.
Man kann den focus m.H. der Maus oder m.H. der Tabulatortasten wechseln.
Jedes Ereignis wie ein Tastendruck, ein Ziehen der Maus, ein Mausklick löst in WINDOWS ein
Ereignis aus: WINDOWS notiert,
-- wo das Ereignis ausgelöst wurde (z.B. über dem Button mit der id IDC_BERECHNEN)
-- was passiert ist (z.B. linke Maustaste wurde gedrückt = BN_CLICKED)
Anschließend schaut WINDOWS in einer Tabelle nach, zu welchem Ereignis (wo;was) welches
Unterprogramm aufgerufen werden soll.
Ein Teil dieser Ereignisse und zugehörigen Unterprogramme ist bereits definiert. Man kann sie im
ClassWizard auf der Registerkarte "message tables" (Nachrichtenzuordnungstabellen) ansehen. Im
Bild sieht man z.B., daß für das Objekt CUeb1201Dlg und das Ereignis WM_INITDIALOG
(Initialisierung eines Dialogs) die Ausführung der Routine OnInitDialog vorgesehen ist.
Beispiel 1: Im Programm können bereits Daten in die EditBoxen eingeben werden. Drückt man dann
jedoch die ENTER-Taste, so wird das gesamte Programm beendet (Bitte erst einmal ausprobieren!)
Der Grund dafür ist, daß das Drücken der ENTER-Taste die message ONOK auslöst, die eigentlich
zum OK-Button gehört. Der OK-Button beendet jedoch das Programm.
Falls dies verhindert werden soll, muß man eine eigene Routine dazu schreiben, die nichts tut. Man
ruft den ClassWizard auf, gibt das was (BN_CLICKED = ENTER) und wo (IDOK = OK-Button) an und
wählt nacheinander "Funktion hinzufügen" und "Code bearbeiten".
5
Nach Bestätigung des Namensvorschlags OnOK des MFC-Klassenassistenten, kann über „Code
bearbeiten“ folgende Routine gesehen werden:
void CUeb1201Dlg::OnOK()
{
// TODO: Zusätzliche Prüfung hier einfügen
CDialog::OnOK();
}
Die Routine CUeb1201Dlg::OnOK() ruft die vordefinierte Routine CDialog::OnOK(); auf, die ihrerseits
den Dialog beendet. Aus diesem Aufruf wird ein Kommentar:
void CUeb1201Dlg::OnOK()
{
// TODO: Zusätzliche Prüfung hier einfügen
// CDialog::OnOK();
}
Danach wird noch einmal der OK-Button in unserem Dialogfenster bearbeitet. Seine id wird auf
IDC_BEENDEN gesetzt und seine Beschriftung auf "Beenden".
Auf die gleiche Art werden mit dem ClassWizard Routinen für folgende Ereignisse eingefügt, deren
Code nachher bearbeiten werden wird:
Objekt-ID (wo)
IDC_BREITE
IDC_LAENGE
IDC_BERECHNEN
IDC_BEENDEN
Nachricht / message (was)
EN_KILLFOCUS
EN_KILLFOCUS
BN_CLICKED
BN_CLICKED
6. Routinen mit Anweisungen füllen
6
Name der zug. Routine
OnKillFocusBreite
OnKillFocusLaenge
OnBerechnen
OnBeenden
Die Routinen können entweder vom ClassWizard aus ("Code bearbeiten") oder vom Class View aus
(die Routine doppleklicken) in den Editor geholt werden.
Die Routine void CUeb1201Dlg::OnKillfocusBreite() wird aufgerufen, wenn die zugehörige EditBox den
Focus verliert, die Eingabe in dieses child-Window also beendet ist. Leider sind die Daten dann noch
nicht in den zugehörigen Variablen, sondern noch unter Kontrolle von WINDOWS.
void CUeb1201Dlg::OnKillfocusBreite()
{
// TODO: Code für die Behandlungsroutine der Steuerelement//Benachrichtigung hier einfügen
}
void CUeb1201Dlg::OnKillfocusBreite()
{
// TODO: Code für die Behandlungsroutine der Steuerelement//Benachrichtigung hier einfügen
GetDlgItemText ( IDC_BREITE , m_strBreite ) ;
// Eingabestring bei WINDOWS abholen
m_dBreite = atof ( m_strBreite ) ;
// numerischen Wert ermitteln
m_dBreite = ( (long int) ( 10.0 * m_dBreite ) ) / 10.0 ;
// auf 1 Stelle nach dem Komma abschneiden
m_strBreite . Format ( "%2.1f" , m_dBreite ) ;
// abgeschnittenen Wert editieren
SetDlgItemText ( IDC_BREITE , m_strBreite ) ;
// und in die EditBox stellen
}
Die entsprechende Routine void CUeb1201Dlg::OnKillfocusLaenge() ist:
void CUeb1201Dlg::OnKillfocusLaenge()
{
// TODO: Code für die Behandlungsroutine der Steuerelement// Benachrichtigung hier einfügen
GetDlgItemText ( IDC_LAENGE , m_strLaenge ) ;
// Eingabestring bei WINDOWS abholen
m_dLaenge = atof ( m_strLaenge ) ;
// numerischen Wert ermitteln
m_dLaenge = ( (long int) ( 10.0 * m_dLaenge ) ) / 10.0 ;
// auf 1 Stelle nach dem Komma abschneiden
m_strBreite . Format ( "%2.1f" , m_dLaenge ) ;
// abgeschnittenen Wert editieren
SetDlgItemText ( IDC_LAENGE , m_strLaenge ) ;
// und in die EditBox stellen
}
In die Routine void CUeb1201Dlg::OnBeenden() fügt man die Anweisungen ein, die in void
CUeb1201Dlg::OnOK() vorgefunden und Kommentar wurden.
In die Routine void
Ausgabe eingefügt:
CUeb1201Dlg::OnBerechnen() wird die eigentliche Berechnung und
void CUeb1201Dlg::OnBerechnen()
{
// TODO: Code für die Behandlungsroutine der Steuerelement// Benachrichtigung hier einfügen
double flaeche = m_dBreite * m_dLaenge;
// Fläche berechnen
CString help ;
help . Format ( "%4.2f" , flaeche ) ;
// Fläche editieren
SetDlgItemText ( IDC_FLAECHE , help ) ;
// und in die EditBox stellen
}
7
Jetzt kann das Programm abschließend übersetzt und ausgeführt werden. Es sollte (abgesehen von
einigen kleinen Macken) seine Aufgabe erfüllen.
2. Aufgabe
1. Standardaktion für <RETURN> setzen
Das vorgegebene Programm reagiert auf zwei Aktionen in der gleichen Weise:
Das Drücken der <RETURN>-Taste löst das Signal OK aus.
Das Anklicken des OK-Buttons (der jetzt der Beenden-Button ist) löst ebenfalls
das Signal OK aus.
Woher kommt diese Gemeinsamkeit? Beim Betrachten des
Eigenschaften-Fenster des Buttons Beenden und dort die Registerkarte Formate sieht man: Der
Button ist als Standardschaltfläche markiert. Beseitige diese Markierung, und dann sollte das
Programm auf <RETURN> nicht mehr reagieren.
2. Elemente ausrichten
Häufig möchte man, daß mehrere Elemente (child-windows, controls) auf gleicher Höhe oder
untereinander stehen oder auch gleich hoch, gleich breit oder insgesamt gleich groß sind. Alle diese
Wünsche sind im Developer Studio leicht zu erfüllen:
Als erstes aktiviert man nacheinander alle Elemente, die unter diesem Aspekt zusammengehören. Das
1. Element aktiviert man wie üblich mit einem (linken) Mausklick, bei den weiteren Elementen muß
man dabei zusätzlich die Control-Taste (auf deutsch: Strg=Steuerung) niedergedrückt halten. Wichtig:
alle Elemente richten sich nach dem zuletzt aktivierten Element, welches auch als aktives Element
hervorgehoben ist.
Man kann auch ganze Gruppen von Elementen verschieben, ohne ihre Abstände untereinander zu
verändern. Dazu schreibt man ein Rechteck um die Elemente (es geht nur ein Rechteck), indem man
bei gedrückter linker Maustaste die Maus vom linken oberen Eckpunkt zum rechten unteren Eckpunkt
zieht. Das hört sich kompliziert an, ist aber einfach: man sieht, wie das Rechteck entsteht. Nach dem
Loslassen der Maustaste sind alle inneren Elemente markiert, und bei gedrückter linker Maustaste
(oder mit den Cursortasten) kann man das ganze Ensemble verschieben. Am besten: ausprobieren!
3. Reihenfolge der Felder festlegen
Mit der Tabulator-Taste kann man die einzelnen Elemente des Dialogs durchlaufen. Die Reihenfolge
der einzelnen Elemente kann man im Menupunkt Layout - Tab Order festlegen. Man ruft den
Menupunkt auf und klickt die einzelnen Elemente mit der Maus an. Man beendet diese Arbeit mit der
ENTER- oder der ESC-Taste.
4. Der Focus und zugehörige Messages
Stets ist genau eins der child-windows virtuell mit der Tastatur verbunden. Ist dieses Fenster ein
Eingabefenster, so enthält es den Cursor und alle Tastendrücke gehen als Characters in dieses
Fenster. Ist es ein Button, so ist dieser Button i.a. durch eine doppelte Linie gekennzeichnet und er
reagiert eigentlich nur auf die ENTER-Taste (was gleich einem Mausklick auf diesen Button ist).
In beiden Fällen sagt man, das betreffende Element habe den Focus. WINDOWS generiert nun zwei
messages in Bezug auf den Focus eines jeden Elements:
Die message EN_SETFOCUS wird erzeugt, wenn ein Element den Focus erhält.
Die message EN_KILLFOCUS wird erzeugt, wenn ein Element den Focus abgibt
Bei beiden messages ist es egal, wodurch diese Aktion ausgelöst wurde: der Benutzer kann sich
der Tabulatortasten bedient haben, er kann den Focus auch mit der Maus bewegt haben.
Will man, daß das Programm auf diese messages reagiert, so muß man für jedes Fenster und jede
message m.H. des ClassWizard ein Unterprogramm erzeugen.
Dabei schlägt der ClassWizard passende Namen vor, die man i.a. akzeptiert.
8
Beispiel:
Element
Identifier
Breite
Länge
IDC_BREITE
IDC_LAENGE
Routine
für Routinr
EN_SETFOCUS
EN_KILLFOCUS
OnSetfocusBreite
OnKillfokusBreite
OnSetFocusLaenge
OnKillfokusLaenge
für
Hier gibt es eine Möglichkeit, das Problem der nachträglich geänderten Eingabewerte zu lösen.
Jedesmal, wenn Länge oder Breite den Focus erhält, wird das (alte) Rechenergebnis gelöscht.
void CUeb1201Dlg::OnSetfocusBreite()
{
// TODO: Code für die Behandlungsroutine der Steuerelement// Benachrichtigung hier einfügen
SetDlgItemText ( IDC_FLAECHE , "" ) ;
}
void CUeb1201Dlg::OnSetfocusLaenge()
{
// TODO: Code für die Behandlungsroutine der Steuerelement// Benachrichtigung hier einfügen
SetDlgItemText ( IDC_FLAECHE , "" ) ;
}
5. Den Focus zurücksetzen (Brandmauertechnik)
In klassischen prozeduralen Programmiersprachen benutzt man eine nachprüfende Schleife und
zwingt den Benutzer, solange Daten einzugeben, bis die Eingabe korrekt ist. Hier verwendet man eine
ähnliche Technik. In der zugehörigen Killfocus-Routine wird geprüft, ob die Eingabe korrekt ist. Ist sie
es nicht, so wird der Focus zurück auf dasselbe Fenster gesetzt. Der zugehörige Code lautet:
void CUeb1201Dlg::OnKillfocusLaenge()
{
// TODO: Code für die Behandlungsroutine der Steuerelement// Benachrichtigung hier einfügen
GetDlgItemText ( IDC_LAENGE , m_strLaenge ) ;
// Eingabestring bei WINDOWS abholen
m_dLaenge = atof ( m_strLaenge ) ;
// numerischen Wert ermitteln
m_dLaenge = ( (long int) ( 10.0 * m_dLaenge ) ) / 10.0 ;
// auf 1 Stelle nach dem Komma abschneiden
if ( m_dBreite <= 0.0 )
{
MessageBox ( "Die Breite muß größer als Null sein" , "FEHLER" ) ;
CWnd * pF = (CWnd *) GetDlgItem ( IDC_BREITE ) ;
pF -> SetFocus () ;
}
else
{
m_strBreite . Format ( "%2.1f" , m_dBreite ) ;
// abgeschnittenen Wert editieren
SetDlgItemText ( IDC_BREITE , m_strBreite ) ;
// und in die EditBox stellen
}
}
Die Routine SetFocus(); ist eine „member-function“ der Class CWnd. Alle Elemente (Controls) gehören
Classes an, die von dieser Class hergeleitet sind. Daher „erbt“ man diese Routine und kann sie
jederzeit benutzen. Um sie aufrufen zu können, braucht man jedoch einen Zeiger auf das ControlElement, das den Focus erhalten soll. Man definieret also einen Zeiger pF vom Typ CWnd*, den Wert
für den Zeiger liefert uns der Aufruf der Routine GetDlgItem:
CWnd * pF = (CWnd *) GetDlgItem ( IDC_BREITE ) ;
Der dann folgende Aufruf pF -> SetFocus () ; bewirkt, daß der Focus auf das aktive Fenster hier also das Fenster mit der ID IDC_BREITE - zurückgesetzt wird.
9
Wird diese Methode auf beide Eingabefenster angewendet, erhält man jedoch ein verwirrendes
Ergebnis (bitte ausprobieren, es schildert sich in Worten so schlecht!).. Gibt man z.B. den Wert -3.4 für
die Breite ein und klickt dann mit der Maus auf das Eingabefeld für die Länge, so erhält man
nacheinander erst die
Meldung "Die Länge muß größer als Null sein" (offenbar wurde durch den Mausklick auch bereits ein
EN_KILLFOCUS-Ereignis von WINDOWS ausgelöst), danach die Meldung "Die Breite muß größer als
Null sein", und danach hat das Eingabefenster der Breite (wo unser ursprünglicher Fehler liegt) wieder
den Focus.
Es ist zu befürchten, daß der normale Benutzer eines solchen Programm die wundersame Logik von
WINDOWS nicht zu schätzen weiß.
7. Fehlermeldung ausgeben
Eine Fehlermeldung kann so ausgegeben werden:
MessageBox ( "Die Breite muß größer als Null sein" , "FEHLER" ) ;
Der Aufruf erzeugt eine Meldung, die mit einem Mausklick auf den OK-Button quittiert werden muß.
Die Routine MessageBox hat wahlweise 1, 2 oder 3 Parameter. Der dritte Parameter bewirkt, daß in
der Messagebox noch ein Icon erscheint. Zugelassene dritte Parameter sind:
MB_ICONHAND
MB_ICONQUESTION
MB_ICONEXCLAMATION
MB_ICONINFORMATION
10
3. Aufgabe
Zur Erstellung des Dialogfensters dienen folgende Elemente:
Bezeichnung der Elemente
Text
Eingabefeld = Edit Box
Gruppenfeld = Group Box
Schaltfläche = Button
Verwendet für
IDC_STATIC
IDC_LAENGE, IDC_BREITE, IDC_FLAECHE
IDC_STATIC
IDC_EINGABE,
IDC_BERECHNEN,
IDC_BEENDEN
Im gesamten Programm soll eine Schrift mit festem Zeichenabstand (z.B. Courier New) benutzt
werden.
Folgende Routinen sollten erzeugt sein:
void Cueb1203Dlg::OnOK()
{
// CDialog::OnOK();
}
void Cueb1203Dlg::OnBeenden()
{
CDialog::OnOK();
}
void Cueb1203Dlg::OnEingabe()
{
MessageBox ("Eingabe-Button gedrückt") ;
}
void CUeb1203Dlg::OnBerechnen()
{
MessageBox ("Berechnen-Button gedrückt") ;
}
Anschließend erzeugt man die „data members“ der Class CUeb1203Dlg. Die „members“, die direkt zu
Fensterelementen (controls) gehören (also m_strBreite, m_strLaenge und m_strFlaeche vom
Typ CString), erzeugt man mit dem Klassenassistenten.
Weitere „data members“, die natürlich gedanklich (aber eben nicht für WINDOWS) auch mit den Edit
Boxes für Länge, Breite und Fläche verbunden sind (also m_dBreite, m_dLaenge, m_dFlaeche),
erzeugt man, indem man in der Class View die Class CUeb1203Dlg mit der rechten Maustaste anklickt
und dem angebotenen Menu folgt.
Als Teil des Programmstarts wird die Routine CUeb1203App::InitInstance() aufgerufen. In dieser
Routine kann festgestellt werden:
CUeb1203Dlg dlg;
.....
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{ .....
}
else if (nResponse == IDCANCEL)
{ .....
}
Als erstes wird dort ein Objekt (eine Variable) des Namens dlg vom Typ CDemoDlg definiert. Dann
wird mit dieser Variablen die function DoModal(); ausgeführt: dlg.DoModal();
Man beachte, daß dies der Aufruf der (von der Class CDialog ererbten) member function DoModal()
mit dem aktuellen Objekt dlg ist. Dieser Aufruf bewirkt bereits die gesamte Ausführung des Dialogs.
Nach Beendigung des Dialogs liefert der Aufruf einen Wert vom Typ int, der sagt, wie der Dialog
beendet wurde: entweder durch einen Klick auf die OK-Schaltfläche (die jetzt die Beenden-
11
Schaltfläche ist) oder durch einen Klick auf die Abbrechen-Schaltfläche (die gelöscht ist, die aber
durch das Kreuz rechts oben im Fenster und durch die ESCAPE-Taste noch virtuell vorhanden ist).
Erstellen des Eingabe-Dialogfensters: Ein Dialogfenster ist eine Resource. Mit Einfügen->Resource
öffnet man ein Auswahlfenster. Dort wird Dialog gewählt und ein neues Dialogfenster erhalten, das
bearbeitet werden kann:
Ändern der ID dieser Resource in IDD_EINGABE und die Überschrift in „Eingabe der Rechteckmaße“.
Danach muß man, dieser Resource eine neue Class (mit dem Klassen-Assistenten) zuordnen.
Ausbau des Eingabe-Dialogfensters: Die nötigen Elemente werden hinzugefügt:
Der Eingabedialog soll nur durch Anklicken der OK-Schaltfläche beendet werden:
void CEingabe::OnOK()
{
CWnd * pF ;
GetDlgItemText ( IDC_E_BREITE, m_strEBreite ) ;
m_dEBreite = atof ( m_strEBreite ) ;
m_dEBreite = double ( int ( 10.0 * m_dEBreite ) ) / 10.0 ;
GetDlgItemText ( IDC_E_LAENGE, m_strELaenge ) ;
m_dELaenge = atof ( m_strELaenge ) ;
m_dELaenge = double ( int ( 10.0 * m_dELaenge ) ) / 10.0 ;
if ( m_dEBreite <= 0.0 )
{
MessageBox ( "Die Breite muß größer als 0.0 sein" ,
"EINGABEFEHLER" ) ;
pF = GetDlgItem ( IDC_E_BREITE ) ; pF -> SetFocus () ;
return ;
}
if ( m_dELaenge <= 0.0 )
{
MessageBox ( "Die Länge muß größer als 0.0 sein" ,
"EINGABEFEHLER" ) ;
pF = GetDlgItem ( IDC_E_LAENGE ) ; pF -> SetFocus () ;
12
return ;
}
CDialog::OnOK();
}
Aufruf des Eingabe-Dialogfensters, wenn der Benutzer die Eingabe-Schaltfläche anklickt:
void CUeb1203Dlg::OnEingabe()
{
CEingabe dlg ;
int ergebnis ;
ergebnis = dlg . DoModal () ;
// Ausführen des Eingabedialogs
if ( ergebnis == IDOK )
{
m_dBreite = dlg.m_dEBreite ; m_dLaenge = dlg.m_dELaenge ;
m_strBreite . Format ( "%2.1f" , m_dBreite ) ;
m_strLaenge . Format ( "%2.1f" , m_dLaenge ) ;
SetDlgItemText ( IDC_BREITE , m_strBreite ) ;
SetDlgItemText ( IDC_LAENGE , m_strLaenge ) ;
SetDlgItemText ( IDC_FLAECHE , "" ) ;
}
if ( ergebnis == IDCANCEL )
{
MessageBox ( "Eingabe abgebrochen" , "Zur Information" ) ;
// nichts weiter tun
}
}
Nach der unerfindlichen Weisheit der Microsoft-Leute muß man folgende Zeile in die Datei
Ueb1202Dlg.cpp von Hand einfügen:
#include "Eingabe.h"
Man fügt diese Zeile (wie üblich) am besten ganz oben in der Datei ein, hinter den anderen #includes.
Aktivieren und Deaktivieren von Elementen:
Die Anweisungen zum Berechnen sind:
void CUeb1203Dlg::OnBerechnen()
{
m_dFlaeche = m_dBreite * m_dLaenge ;
m_strFlaeche . Format ( "%3.2f" , m_dFlaeche ) ;
SetDlgItemText ( IDC_FLAECHE , m_strFlaeche ) ;
}
Gleich nach den Programmstart ist es sinnlos, daß der Benutzer die Berechnen-Schaltfläche anklickt,
schhließlich muß er erst einmal Daten eingeben. Folgende Regeln gelten deshalb für das Programm:
Am Anfang soll die Beenden-Schaltfläche deaktiviert sein.
Nach jeder korrekten Eingabe von Daten soll sie aktiviert werden.
Nach jeder abgebrochenen Eingabe von Daten soll sie deaktiviert werden, um erst wieder ein
korrekte Dateneingabe zu erzwingen.
Die Routine zum Aktivieren und Deaktivieren von Steuuerelementen (Controls) ist EnableWindow:
diese Routine tut beides, mit einem Parameter TRUE aktiviert sie ein Element, mit einem Parameter
FALSE deaktiviert sie das Element. Für den Aufruf muß ein Zeiger (pointer) auf das Fensterelement
zur Verfügung stehen. Diesen pointer erhält man durch
CWnd * pF = (CWnd *) GetDlgItem ( IDC_BERECHNEN ) ;
Danach lauten die Aufrufe pF -> EnableWindow ( FALSE ); oder pF -> EnableWindow (
TRUE );
13
Den Anfangszustand der Berechnen-Schaltfläche stellt man durch einen Aufruf in der Routine
CUeb1203DLG::OnInitDlg() her. Diese Routine wird einmal am Anfang der Dialog-Ausführung
aufgerufen. Am Ende der Routine finden wir einen Hinweis, daß dort eigene Anweisungen hinzugefügt
werden dürfen:
BOOL CUeb1203Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
..........
// ZU ERLEDIGEN: Hier zusätzliche Initialisierung einfügen
CWnd * pF = (CWnd *) GetDlgItem ( IDC_BERECHNEN ) ;
pF -> EnableWindow ( FALSE ) ;
return TRUE; // Geben Sie TRUE zurück, außer ein Steuerelement
// soll den Fokus erhalten
}
Die geänderte Routine OnEingabe lautet dann
void CUeb1203Dlg::OnEingabe()
{
CEingabe dlg ;
int ergebnis ;
CWnd * pF = (CWnd *) GetDlgItem ( IDC_BERECHNEN ) ;
ergebnis = dlg . DoModal () ;
if ( ergebnis == IDOK )
{
m_dBreite = dlg.m_dEBreite ; m_dLaenge = dlg.m_dELaenge ;
m_strBreite . Format ( "%2.1f" , m_dBreite ) ;
m_strLaenge . Format ( "%2.1f" , m_dLaenge ) ;
SetDlgItemText ( IDC_BREITE , m_strBreite ) ;
SetDlgItemText ( IDC_LAENGE , m_strLaenge ) ;
SetDlgItemText ( IDC_FLAECHE , "" ) ;
pF -> EnableWindow ( TRUE ) ;
}
if ( ergebnis == IDCANCEL )
{
pF -> EnableWindow ( FALSE ) ;
}
}
14
4. Aufgabe
a) Drehfelder (Spinbuttons)
Drehfelder (englisch: spinbuttons) werden häufig in den Fällen angewendet, in denen man dem
Benutzer die Möglichkeit geben möchte, einen Wert nur geringfügig m.H. der Maus zu ändern.
Beispiel: eine Größe habe den Wert 5.6, der Benutzer möchte ihn auf 5.8 ändern. Nun soll er nicht die
Zahl mit der Tastatur ändern müssen, er soll die Möglichkeit haben, durch zwei Mausklicks von 5.6
über 5.7 (1. Mausklick) auf 5.8 (2. Mausklick) zu ändern.
1. Einfügen wir mit Hilfe der ToolBox von zwei Drehfeldern in den Eingabedialog ein. Die zugehörigen
Ids werden IDC_SPIN_LAENGE und IDC_SPIN_BREITE genannt.
Spinbuttons sind immer int-Zähler. Sie haben einen festen Zählraum: dies ist ein (mathematisches)
Intervall von ganzen Zahlen. Beim Start des Dialogs muß dieses Intervall und den Anfangswert des
Zählers setzen. Bekannt ist: die Routine OnInitDialog() kann für Initialisierungen benutzt werden. In der
Liste der member function der Class CEingabe ist diese Funktion noch nicht vorhanden ist. Wie und
wo ist diese Routine einzufügen?
Zur Informieren Doppelklicken in der Class View auf das Symbol CDemoDlg: dort gibt es schließlich
schon eine Routine OnInitDialog. Dieser Doppelklick öffnet die Datei DemoDlg.h und führt dort in die
Definition der Class CDemoDlg. Etwas weiter unten in der Class Definition sieht man die Definition des
Prototyps von OnInitDialog. Sie steht eingeklammert von grünem Text, in dem man so seltsame Worte
wie AFX_MSG sieht: innerhalb dieser grünen Klammern stehen Informationen über Daten und
Functions, die vom Klassen-Assistenten (Class Wizard) verwaltet werden.
// Implementierung
protected:
HICON m_hIcon;
// Generierte Message-Map-Funktionen
//{{AFX_MSG(CUeb1203Dlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
virtual void OnOK();
afx_msg void OnBeenden();
afx_msg void OnEingabe();
afx_msg void OnBerechnen();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
2. Aufruf des Class Wizzard. Zur Object_ID CEingabe und zur Nachricht WM_INITDIALOG kann eine
Funktion hinzugefügt und anschließend bearbeitet werden.
Die eingefügte Funktion hat folgende Form:
BOOL CEingabe::OnInitDialog()
{
CDialog::OnInitDialog();
// TODO: Zusätzliche Initialisierung hier einfügen
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX-Eigenschaftenseiten sollten FALSE zurückgeben
}
Inhaltliche Festlegung der Drehfelder: Für Länge und Breite sind jeweils Werte von 1.0 bis 10.0
zugelassen, die auf eine Stelle hinter dem Dezimalpunkt angegeben werden können. Da die
Drehfelder nur ganzzahlilge Werte zulassen, geht das int-Intervall also von 10 bis 100. Der
Anfangswert soll sowohl für die Breite als auch für die Länge der Wert 5.0 sein. Alle diese
Informationenwerden werden in CEingabe::OnInitDialog() hinter dem // TODO eingefügt:
BOOL CEingabe::OnInitDialog()
{
15
CDialog::OnInitDialog();
// TODO: Zusätzliche Initialisierung
m_dEBreite = m_dELaenge = 5.0 ;
CString help ;
help . Format ( "%2.1f" , m_dEBreite )
SetDlgItemText ( IDC_E_BREITE , help )
SetDlgItemText ( IDC_E_LAENGE , help )
hier einfügen
;
;
;
CSpinButtonCtrl * pSpin =
( CSpinButtonCtrl * ) GetDlgItem ( IDC_SPIN_BREITE ) ;
pSpin -> SetRange ( 10 , 100 ) ;
pSpin -> SetPos ( 50 ) ;
pSpin = ( CSpinButtonCtrl * ) GetDlgItem ( IDC_SPIN_LAENGE ) ;
pSpin -> SetRange ( 10 , 100 ) ;
pSpin -> SetPos ( 50 ) ;
return TRUE;
// return TRUE unless you set the focus to a control
// EXCEPTION: OCX-Eigenschaftenseiten sollten FALSE
zurückgeben
}
3. Drehfelder und zugehörige Fenster („buddies“)
Problem: der Wert, den ein Drehfeld hat, ist erst einmal nicht zu sehen. Diesen Wert ist sichtbar zu
machen. Deshalb gehört zu jedem Drehfeld ein zugehöriges Fenster, welches im MS-Jargon einfach
'buddy' (englisch für Freund, Kumpel) heißt. Die buddies der Drehfelder sind die Eingabefelder für
Länge und Breite. Es gibt in der MFC eine Möglichkeit, die Zuordnung Drehfeld <--> buddy
automatisch vorzunehmen, sofern der buddy den direkten Drehfeldwert anzeigen soll. Wir erläutern
diese Möglichkeit Behandelt wird der allgemeinenere Fall, daß der buddy-Wert aus dem Drehfeldwert
errechnet werden muß.
Dann gibt es folgende Situation:
Ändert der Benutzer den Drehfeldwert m.H. der Maus, so muß der buddy-Wert entsprechend geändert
werden.
Gibt der Benutzer den buddy-Wert direkt von der Tastatur her ein, so muß der Drehfeldwert
entsprechend gesetzt werden.
Fall 1: Der Benutzer gibt den buddy-Wert direkt ein
Fall 2: Der Benutzer spielt am Drehknopf
Hier kommt wieder einmal die wunderliche Logik von Microsoft zu Vorschein. Man würde nach
Gelernten erwarten, daß man zu IDC_SPIN_BREITE und einer Nachricht EN_KILLFOCUS
entsprechende OnKill...-Routine definieren könnte. Aber weit gefehlt! Alle Drehfelder und
sonstigen Fenster mit einem vertikalen Schiebebalken erzeugen genau 1 Nachricht, die
gesamten Eingabefenster zugeordnet ist. Man sieht dies im Class Wizard:
16
dem
eine
alle
dem
void CEingabe::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// TODO: Code für die Behandlungsroutine für Nachrichten hier
// einfügen und/oder Standard aufrufen
CDialog::OnVScroll(nSBCode, nPos, pScrollBar);
}
Die drei Parameter von OnVScroll haben folgende Bedeutung:
Die drei Parameter von OnVScroll haben folgende Bedeutung:
nSBCode: ein ScrollBar-Code, der hier im Moment nicht weiter interessiert
nPos: hier (in der Anwendung eines Drehfeldes) ist es der neu eingestellte Wert des Drehfeldes.
pScrollBar: ist ein Zeiger (pointer) auf das Element, dessen ScrollBar (Schiebebalken) bewegt
wurde. Mit Hilfe dieses Zeigers kann das Drehfeld, welches benutzt wurde, identifiziert werden.
Der Aufruf von CDialog::OnVScroll(nSBCode, nPos, pScrollBar); ist nur da, damit etwas passiert, falls
keine Anweisungen eingefügt werden: Daraus wird ein Kommentar. Danach werden (einem Lehrbuch
folgend) folgende Anweisungen eingefügt:
b) Die Listbox erhält die ID IDC_PROTOKOLL
Ausschnitte aus den Routinen OnInitDialog() und ONBerechnen()
BOOL CDemoDlg::OnInitDialog()
{
.......
// ListBox löschen
CListBox * pLB = (CListBox*) GetDlgItem ( IDC_PROTOKOLL ) ;
pLB -> ResetContent( );
return TRUE;
// Geben Sie TRUE zurück, außer ein Steuerelement
// soll den Fokus erhalten
}
17
void CDemoDlg::OnBerechnen()
{
CString help ;
m_dFlaeche = m_dBreite * m_dLaenge ;
m_strFlaeche . Format ( "%8.2f" , m_dFlaeche ) ;
SetDlgItemText ( IDC_FLAECHE , m_strFlaeche ) ;
help = m_strBreite + " x " + m_strLaenge + " = " + m_strFlaeche ;
CListBox * pLB = (CListBox*) GetDlgItem ( IDC_PROTOKOLL ) ;
pLB -> InsertString ( -1 , help ) ;
}
Der erste Parameter von InsertString (die -1) besagt, daß die Zeile am Ende der vorhandenen Zeilen
eingefügt werden soll. Ist dieser Wert >= Null, so gibt er die Stelle an, an der die Zeile eingefügt
werden soll (wie immer in C werden Elemente ab 0 gezählt).
b)
18
5. Aufgabe
Deaktivieren der Wirkung der RETURN-Taste in der Routine CUeb1204Dlg::OnOK(), benenne die
"OK"-Schaltfläche
in
"Beenden"
um
und
erstellen
passend
dazu
die
Routine
CUeb1204Dlg::OnBeenden() .
Übersetzen und testen des Programms. Falls das Programm über die "Beenden"-Schaltfläche sicher
beendet werden kann, entferne das Cancel-Kreuz rechts oben im Programmfenster. Dies geschieht
durch Deaktivieren im Eigenschaften-Dialog des Hauptfenstersdie Check Box "Systemmenu".
Bearbeite die Dialog-Resource und füge die in der Abbildung gezeigten Elemente ein (es handelt sich
nur um Ihnen bekannte Elemente):
Das große Fenster mit all den leckeren Sachen ist ein Listenfeld (List Box) mit der IDC_ZUTATEN. Sie
wurde bereits durch Anweisungen in CUeb1204Dlg::OnInitDialog() initialisiert. (Beachten Sie
den Hinweis in der Quelle, wo diese zusätzlichen Anweisungen eingefügt werden dürfen.
Das andere große Fenster ist für die Anzeige der fertigen Bestellung gedacht. Es ist ebenfalls ein
Listenfeld und hat die IDC_BESTELLUNG.
Das kleine Fenster unter "Bestellung zum" ist eine Edit Box mit der IDC_DATUM.
Die vier Schaltflächen wurden erst einmal alle auf gleiche Größe gebracht, danach mit Layout ->
Gleichmäßig verteilen so schön angeordnet.
Die Routine OnBenden wurde bereits geschrieben. Die anderen drei Routinen werden erzeugt
eingefügt als einziger Befehl ein Aufruf der MessageBox ein:
MessageBox ("Diese Routine ist in Arbeit" ) ;
Zur Wahl des Getränks dient ein Kombinationsfeld, eine Combo Box mit der IDC_GETRAENK ein.
Die Combo Box enthält schon am Anfang rechts einen kleinen Knopf mit einem Pfeil nach unten
enthält. Durch Anklicken dieses Pfeils kann man später die Combo Box "aufklappen". Beim Arbeiten
im Developer Studio kann man dieses Aufklappen simulieren und im aufgeklappten Zustand die Größe
der aufgeklappten Combo Box verändern.
Verändere die Größe so, wie in der Abbildungen gezeigt
19
Im Gegensatz zur List Box kann man bei der Combo Box die Werte schon im Editor des Developer
Studios eingeben. Man wählt dazu im Eigenschaften-Dialog die Registerkarte "Daten".
Gibt man nacheinander "Kaffee", "Tee", u.s.w. ein, dann stößt man auf ein Problem: sobald "Kaffee"
getippt wurde und die RETURN-Taste gedrückt wurde, wird der Eigenschaften-Dialog geschlossen.
Das ist vollkommen normal, denn man schließt immer den Eigenschaften-Dialog durch einen Druck
auf die RETURN-Taste. Hier hilft ein kleiner Trick: man beendet jede Datenzeile mit Ctrl-RETURN.
Zum Schluß wäht man im Eigenschaften-Dialog noch die Registerkarte Formate und stellen dort im
Typ Dropdown-Liste. Danach wird übersetzt und das Verhalten der verschiedenen Elemente
angesehen:
Bei der Combo Box sieht man, daß wegen der Datenfülle automatisch ein Schiebebalken eingefügt
wurde. Fernerhin stellt man fest, daß die Einträge sortiert sind. Das kann eigentlich nur am
Eigenschaften-Dialog liegen. Man sieht dort nach und bemerkt, daß die "Sortieren" - Check Box
aktiviert ist. Im übrigen benimmt sich die Combo Box schon genau so, wie man es will: man kann nur
ein Getränk auswählen.
Die List Box für die Zutaten benimmt sich aber nicht so, wie es vorgesehen ist: man kann immer nur
eine Zutat anklicken. Klickt man eine weitere Zutat an, wird die erste gelöscht - und das ist für ein
Frühstück wirklich zu wenig! Ändere daher die Einstellungen im Eigenschaften-Dialog der List Box. In
der Registerkarte Formate setzt man die Auswahl auf Mehrfach.
Bisher wurde die List Box nur in der Form benutzt, daß man mit InsertString Daten hineingetan hatte.
Jetzt besteht beim Getränk und bei den Zutaten das Problem, daß man Daten aus den Boxes
auslesen muß. Dies muß in der Routine CUeb1204Dlg::OnZusammenstellen() getan werden.
Vorher fügt man mit dem Class Wizard (Registerkarte Member-Variablen) passend zur Edit Box mit
IDC_DATUM eine member variable m_strBDatum hinzu.
void CUeb1204Dlg::OnZusammenstellen()
{
// TODO: Code für die Behandlungsroutine der Steuerelement// Benachrichtigung hier einfügen
// MessageBox("Diese Routine ist in Arbeit");
CListBox * p_Best = (CListBox * ) GetDlgItem ( IDC_BESTELLUNG ) ;
p_Best -> ResetContent () ;
20
// Bestelldatum verarbeiten
GetDlgItemText ( IDC_DATUM , m_strBDatum ) ;
p_Best -> InsertString ( -1 , "Ihre Bestellung für " + m_strBDatum ) ;
// Getränk aus der Combo Box abholen
CComboBox * pCB = (CComboBox *) GetDlgItem ( IDC_GETRAENK ) ;
int getraenk_no = pCB -> GetCurSel () ;
CString getraenk ;
if ( getraenk_no >= 0 )
// nur dann wurde etwas aus der Liste angeklickt
pCB -> GetLBText( getraenk_no , getraenk ) ;
else
getraenk = "nicht gewählt" ;
p_Best -> InsertString ( -1 , "Getränk: " + getraenk ) ;
// Menge der Zutaten aus der List Box abholen
CListBox * p_Zut = (CListBox *) GetDlgItem ( IDC_ZUTATEN ) ;
int i , anzahl_zutaten, * p_index ;
CString zutat ;
anzahl_zutaten = p_Zut -> GetSelCount () ;
if ( anzahl_zutaten > 0 )
{
p_Best -> InsertString ( -1 ,
"Zum Frühstück erhalten Sie weiterhin" ) ;
p_index = (int *) malloc ( anzahl_zutaten * sizeof ( int ) ) ;
p_Zut -> GetSelItems ( anzahl_zutaten , p_index ) ;
for ( i = 0 ; i < anzahl_zutaten ; i ++ )
{
p_Zut -> GetText( *(p_index+i), zutat ) ;
p_Best -> InsertString ( -1 , zutat ) ;
}
free ( p_index ) ;
}
else
p_Best -> InsertString ( -1 ,
"Sie haben keine weiteren Wünsche angegeben" ) ;
}
Da aus der Menge der Zutaten eine Untermenge ausgewählt wurde und die Mächtigkeit (Anzahl der
Elemente) der Untermenge vorab unbekannt ist, werden die Daten in mehreren Schritten abgeholt:
Als erstes wird mit GetSelCount() = "Get Selection Count" die Anzahl der angeklickten Elemente
festgestellt. Ist diese Anzahl gleich Null, ist man schnell fertig (else-Teil der Fallunterscheidung).
Danach muß man die Indices der angeklickten Zutaten abholen: als Vorbereitung darauf läßt man sich
mit malloc Speicherplatz vom heap für ein int-array der passenden Größe geben.
Dann holt man mit GetSelItems() = "Get Selected Items" den int-array in den reservierten
Speicherplatz.
Zum Schluß durchläuft man den array mit einer Zählschleife: jedes array-element ( *(p_index+i) )
enthält den Index einer ausgewählten Zutat, mit GetText() holt man den Text dieser Zutat in die
Variable zutat.
Danach gibt man den reservierten Speicherplatz wieder frei.
Erstellen einer Textdatei: Die Dateibehandlung unter Visual C++ unterscheidet sich kaum von der in C.
void Cueb1204Dlg::OnSpeichern()
{
CFileDialog file_dlg ( FALSE ) ;
// FALSE sagt File Save as
// 1. Parameter reicht - Rest hat default-Werte
int result = file_dlg . DoModal () ;
if ( result == IDOK )
{ CString file_name = file_dlg . GetPathName( ) ;
CStdioFile out_file ;
if ( out_file . Open( file_name , CFile::modeCreate | CFile::modeWrite
) )
{ CListBox * p_Best = (CListBox * ) GetDlgItem ( IDC_BESTELLUNG ) ;
int i, anzahl_zeilen = p_Best -> GetCount() ;
CString zeile ;
for ( i = 0 ; i < anzahl_zeilen ; i ++ )
{ p_Best -> GetText ( i , zeile ) ;
21
out_file . WriteString ( zeile + "\n" ) ;
}
out_file . Close () ;
MessageBox ("Ihre Bestellung wurde gespeichert!",
"Hotel Visual - Wir danken für Ihre Bestellung",
MB_ICONINFORMATION );
}
else
MessageBox ("Fehler beim Open - Speichern wurde abgebrochen" ) ;
}
else
MessageBox ("Speichern wurde abgebrochen" ,
"Hotel Visual" , MB_ICONERROR ) ;
}
Es wird eine Variable file_dlg definiert, die benutzt werden soll, um den externen Dateinamen
(vollständigen Pfadname) in einem Dialog mit dem Benutzer zu ermitteln. Der Parameter FALSE
bewirkt einen "File Save as"-Dialog, ein Parameter TRUE würde einen "File Open"-Dialog bewirken.
Die anschließende Ausführung mit DoModal() besorgt vom Benutzer nur den Pfadnamen der Datei nichts weiter. Wenn der Dialog zur Bestimmung des Pfadnamens vom Benutzer nicht abgebrochen
wurde (result == IDOK), holt man sich den Pfadnamen in die Variable file_name und versucht
anschließend, die Datei zu öffnen:
Bei erfolgreichem Open werden alle Zeilen aus der List Box IDC_BESTELLUNG in die Datei kopiert.
Jeder Zeile muß ein "\n" angefügt werden, um in der Datei einen Zeilenwechsel zu erhalten.
Sauberes Terminieren des Programms: Ein sauberes Programm hat nicht nur die Aufgabe, irgendeine
Arbeit zu übernehmen. Es muß den Benutzer auch vor Fehlern schützen. Hier besteht z.B. die Gefahr,
daß der Benutzer eine Bestellung zusammenstellt und - im Glauben, es sei alles geschehen - das
Programm beendet, ohne die Bestellung abzusenden (im Beispiel: zu speichern). Daher sollte man
sich im Programm merken, ob das Speichern nötig ist bzw. ob es schon erfolgt ist. Dazu wird eine
Variable m_Bzu_speichern vom Typ BOOL benötigt. Sie wird
am Anfang auf FALSE gesetzt,
von der Routine CHotelDlg::OnZusammenstellen(), die eine Bestellung zusammenstellt, auf
TRUE gesetzt,
von der Routine CHotelDlg::OnSpeichern() nach dem erfolgreichen Speichern wieder auf
FALSE gesetzt,
vor dem Verlassen des Programms in CHotelDlg::OnBeenden() abgefragt.
void CUeb1204Dlg::OnBeenden()
{
// TODO: Code für die Behandlungsroutine der Steuerelement// Benachrichtigung hier einfügen
if ( m_Bzu_speichern )
{
int antwort = MessageBox ("Wollen Sie die Bestellung speichern ?" ,
"Aktuelle Bestellung nicht gespeichert" ,
MB_YESNOCANCEL ) ;
if ( antwort == IDYES )
// Benutzer wünscht zu speichern
{
OnSpeichern () ;
if ( m_Bzu_speichern ) // aber Speichern nicht erfolgreich
return ;
}
if ( antwort == IDCANCEL )
return ;
// if ( antwort == IDNO ) .... dann passiert nichts vor dem Beenden
}
CDialog::OnOK();
}
22
6. Aufgabe
1. Erstelle eine dialogbasierende Anwendung.
2. Lösche auf der Dialogseite IDD_Ueb1208_DIALOG die Tasten OK und Cancel. Positioniere zwei
neue Schaltflächen, eine zum Aufruf der Windows-Farbpalette und die andere als Exit-Taste.
3. Deklariere 2 „int“-Variable m_VorX, m_VorY und eine weitere Variable farbe vom Typ CORREF in
CUeb1208Dlg.h
4. Benötigt wird eine Nachrichtenfunktion OnLButtonDown() für die Nachricht WM_LBUTTONDOWN.
void CUeb1208Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen
// und/oder Standard aufrufen
// Ermittlung der Position des Maus-Cursor
m_VorX = point.x;
m_VorY = point.y;
CDialog::OnLButtonDown(nFlags, point);
}
5. Benötigt wird eine Funktion OnMouseMove() für die Nachricht WM_MOUSEMOVE.
void CUeb1208Dlg::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen
// und/oder Standard aufrufen
if ((nFlags& MK_LBUTTON) == MK_LBUTTON)
{ // Der Parameter nFlags stellt fest,
// welche Maustaste benutzt wurde, falls
// die linke Maustaste gedrueckt wurde, soll der Zeichenvorgang beginnen
CPen * oldPen;
CClientDC dc(this);
// neuen Stift erzeugen
CPen neuPen(PS_SOLID,3,farbe);
oldPen = dc.SelectObject(&neuPen);
dc.MoveTo(m_VorX,m_VorY);
dc.LineTo(point.x,point.y);
m_VorX = point.x;
m_VorY = point.y;
// Stift wieder loeschen
dc.SelectObject(oldPen);
}
CDialog::OnMouseMove(nFlags, point);
}
6. Verbinden der Schaltfläche zum Aufruf der Farbpalette mit der Nachricht BN_CLICKED
void CUeb1208Dlg::OnPalette()
{
// TODO: Code für die Behandlungsroutine der Steuerelement// Benachrichtigung hier einfügen
CColorDialog dlgColor;
int iret = dlgColor.DoModal();
// Windows Farbpalette wird geladen
if (iret != IDCANCEL)
{
farbe = dlgColor.GetColor();
// gewaehlte Frage wird abgefragt
}
}
7. Programmieren der Exit-Schaltfläche
void CUeb1208Dlg::OnCancel()
{
23
// TODO: Zusätzlichen Bereinigungscode hier einfügen
OnOK();
}
24
Herunterladen