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