Buttons und Checkboxen

Nach dem Sie in der vorherigen Lektion die Grundlagen des Dialogs kennen gelernt haben, werden wir uns in den folgenden Lektionen dieses Kapitels mit den Controls (Steuerelemente) beschäftigen. Außerdem erfahren Sie nun, wie der Datenaustausch zwischen der Anwendung und einem Dialog vonstatten geht.

Beginnen werden wir mit den einfachsten Controls, den Buttons. Folgender Dialog wird im Verlaufe dieser Lektion entwickelt:

Button-Dialog

Checkboxen (links im Dialog dargestellt) dienen zur Einstellung von boolschen Parametern. Die Anwendung initialisiert vor dem Anzeigen des Dialogs die mit den Checkboxen verbundenen Variablen und kann dann nach dem Schließen des Dialogs die neuen Einstellungen abfragen. Wie das alles geschieht, das erfahren Sie gleich.

Radiobuttons (rechts im Dialog dargestellt) hingegen werden zur Auswahl einer Möglichkeit aus mehreren eingesetzt (1 aus n Auswahl). Auch diese Auswahl muss vor dem Anzeigen des Dialogs initialisiert werden. Nach dem Schließen des Dialogs kann die Anwendung dann die aktuelle Auswahl abfragen.

Das letzte, in dieser Lektion besprochene, Control ist der Button. Buttons veranlassen in der Regel sofortige Aktionen, die entweder im Dialog selbst oder aber in der Anwendung verarbeitet werden.

Die ebenfalls im Dialog vorhandenen Gruppenfelder sind im eigentliche Sinne keine Controls, sondern dienen nur zur optischen Hervorhebung von Gruppen im Dialog.

Im obigen Dialog werden wir im Verlaufe dieser Lektion zwei 'nette Kleinigkeiten' einbauen. Zum einen werden die Radiobuttons zur Farbauswahl über die Checkbox Syntax-Coloring gesteuert. Ist das Syntax-Coloring aktiv, kann eine der drei Farben ausgewählt werden. Wird das Syntax-Coloring dagegen gesperrt, so werden auch die Radiobuttons gesperrt, d.h. es ist dann keine Farbauswahl mehr möglich. Die zweite Besonderheit betrifft den Button Übernehmen. Wird dieser Button anklickt, so werden die gerade aktuellen Einstellung an die Anwendung übermittelt, die diese dann sofort auswerten kann ohne dass der Dialog geschlossen werden muss.

Aber fangen wir nun erst einmal mit der Erstellung des Dialogs an.

Erstellen Sie wieder ein neues SDI-Projekt und geben Sie diesem den Namen Buttons. Wechseln Sie anschließend zur Ressourcen-Ansicht und fügen Sie, wie in der letzten Lektion besprochen, einen neuen Dialog hinzu. Ändern Sie jetzt durch Ziehen am Dialograhmen zunächst die Größe des Dialogs, bis dieser etwa 210x150 Einheiten groß ist. Die aktuelle Größe wird unten rechts in der Statuszeile angezeigt.

Im nächsten Schritt werden wir die beiden Buttons Ok und Abbrechen an den unteren Dialogrand verschieben. Dazu markieren Sie bitte die beiden Buttons und wählen dann den Menüeintrag Layout-Schaltflächen anordnen-Unten aus. Danach sollte Dialog folgendes Aussehen besitzen:

Der Ausgangsdialog

Anschließend definieren wir die dem Dialog zugeordnete Klasse. Führen Sie dazu einen Doppelklick auf den Dialog aus und wählen Sie bei dem daraufhin eingeblendeten Dialog die Option Neue Klasse erstellen aus. Geben Sie der Dialogklasse den Namen CButtonDlg.

Die Dialogklasse des Dialogs

Um den Dialog nachher in der Anwendung anzuzeigen, fügen Sie dem Menü ein neues Popup-Menü Dialog mit dem Menüeintrag Anzeigen hinzu. Geben Sie dem Menüeintrag die ID ID_SHOWDLG.

Menü zum Anzeigen des Dialogs

Zum Schluss der Vorbereitungen muss noch die entsprechende Methode für die Bearbeitung des Menüeintrags definiert werden. Starten Sie den Klassen-Assistenten und stellen Sie als Klassenname die Ansichtsklasse CButtonsView ein. Suchen Sie anschließend im Feld Objekt-IDs die ID ID_SHOWDLG und führen Sie dann einen Doppelklick auf den Eintrag COMMAND im Feld Nachrichten aus.

Nachrichtenbearbeiter zur Anzeige des Dialogs

Übernehmen Sie den daraufhin vorgeschlagenen Namen OnShowdlg für die Methode zur Bearbeitung des Menüeintrags. Vom Klassen-Assistenten wird nun die Methode OnShowdlg(...) zum Ansichtsobjekt hinzugefügt, die Sie wie folgt noch erweitern um den Dialog modal anzuzeigen:

void CButtonsView::OnShowdlg()
{
    // TODO: Code für Befehlsbehandlungsroutine hier einfügen
    CButtonDlg    CMyDlg;
    int            nRetValue;

    // Dialog anzeigen
    nRetValue = CMyDlg.DoModal();
    // Returncode auswerten
    if (nRetValue == IDOK)
    {
        TRACE("Dialogdaten übernommen\n");
    }
    else
        TRACE("Dialog abgebrochen");
}

Binden Sie nun noch die Header-Datei CButtonDlg.h ein und übersetzen Sie dann das Projekt. Bei Auswahl des Menüeintrags Dialog-Anzeigen sollte dann der neu erstellte Dialog angezeigt werden.

So, nun können dem Dialog die Controls hinzufügen werden. Beginnen werden wir mit den Checkboxen. WINDOWS kennt vier Arten von Checkboxen: die 2-State Checkbox, die den Zustand markiert und nicht-markiert annehmen kann und die 3-State Checkbox, die zusätzlich noch den Zustand gesperrt besitzt. Außerdem kann jede dieser beiden Checkbox-Arten noch als Auto-Checkbox definiert werden oder nicht. Bei Auto-Checkboxen wird der Zustandswechsel (markiert, nicht markiert) von WINDOWS vorgenommen und die Anwendung erhält eine Nachricht nach dem der Zustandswechsel durchgeführt wurde. Bei Nicht-Auto-Checkboxen ist die Anwendung für den Zustandswechsel verantwortlich. Daher erhält die Anwendung in diesem Fall nur eine Nachricht zugesandt, die den angeforderten Zustandswechsel signalisiert. Die Checkbox selbst ist zu diesem Zeitpunkt noch nicht verändert. Wir werden im Beispiel gleich beide Fälle einbauen.

Fügen wir jetzt die vier Checkboxen und das sie umschließende Gruppenfeld zum Dialog hinzu.

Zum Einfügen von Gruppenfeldern und Checkboxen werden folgende Symbole innerhalb des Toolbars verwendet:

Gruppenfeld und Checkbox

Fügen Sie nun vier Checkboxen in den Dialog ein, in dem Sie im Toolbar zunächst das Checkbox-Symbol anklicken. Fahren Sie dann mit der Maus an die Stelle im Dialog, an der die Checkbox platziert werden soll. Drücken Sie die linke Maustaste, ziehen dann den Rahmen bei gedrückter Maustaste auf die Größe, die die Checkbox erhalten soll, und lassen dann die Maustaste wieder los. Sie können die Position und Größe eines Controls selbstverständlich zu jedem Zeitpunkt korrigieren, in dem Sie es anklicken und dann an den Markern im Rahmen ziehen. Fügen Sie nun die Checkboxen wie unten angegeben in den Dialog ein. Die endgültige Größe und Position der Checkboxen werden wir nachher gleich noch festlegen.

Die 4 Checkboxen

Als nächstes werden die Eigenschaften der Checkboxen festgelegt. Dazu klicken Sie zunächst die oberste Checkbox mit der rechten Maustaste an und wählen aus dem dann eingeblendeten Kontextmenü den Eintrag Eigenschaften aus. Folgender Dialog wird dann eingeblendet:

Checkbox-Eigenschaften

Wie Sie sehen, hat auch jede Checkbox wieder eine ID und einen Titel (Beschriftung). IDs von Steuerelementen sollten laut ungeschriebenem Gesetz immer mit dem Präfix IDC_xxx beginnen. Geben Sie den vier Checkboxen folgende IDs und Titel; die anderen Eigenschaften übernehmen Sie bitte noch unverändert.

IDs und Beschriftungen der Checkboxen

Und jetzt kommt die Kosmetik an die Reihe. Zuerst bringen wir alle Checkboxen einmal auf die gleiche Größe. Dazu wird zunächst die Checkbox mit dem längsten Titel ausgewählt, in unserem Beispiel also die Checkbox IDC_TABSTOP. Verkleinern/vergrößern Sie die Checkbox nun so, dass der Titel darin gerade Platz findet. Anschließend markieren Sie alle Checkboxen in dem Sie sie nacheinander bei gedrückter SHIFT-Taste anklicken, wobei das zuletzt markierte Steuerelement als Referenzelement dient. Sollen also alle Checkboxen die gleiche Größe wie die IDC_TABSTOP Checkbox besitzen, so ist diese Checkbox als letztes zu markieren. Wenn Sie alle Checkboxen markiert haben, wählen Sie den Menüeintrag Layout-Gleiche Größe-Beides aus und alle markierten Checkboxen erhalten die gleiche Größe.

Als nächstes korrigieren wir die vertikalen Abstände der Checkboxen. Positionieren Sie dazu die obere Checkbox IDC_HBAR so, dass deren Y-Koordinate ungefähr 30 Einheit beträgt. Die Position des markierten Elements können Sie unten rechts neben der Größenangabe in der Statuszeile ablesen. Als nächstes legen Sie die Y-Position der untere Checkbox IDC_SYNCOLOR auf etwa 75 Einheiten. Die X-Position der Checkboxen brauchen Sie dabei nicht zu beachten. Anschließend markieren Sie wieder alle Checkboxen und wählen dann den Menüeintrag Layout-Gleichmäßig verteilen-Abwärts aus. Die Checkboxen werden jetzt zwischen der oberen und unteren Checkbox vertikal gleichmäßig verteilt.

Nun richten wir die Checkboxen vertikal so aus, dass deren linke Kanten untereinander liegen. Markieren Sie dazu wieder alle Checkboxen Auch hier dient das zuletzt markierte Steuerelement wieder als Referenzelement. Wenn Sie alle Checkboxen markiert haben, wählen Sie den Menüeintrag Layout-Ausrichten-Links aus und die Checkboxen werden entsprechend neu platziert.

Zum Schluss umrahmen wir die Checkboxen noch mit einem Gruppenfeld. Klicken Sie dazu das entsprechende Symbol im Toolbar an und setzen den Mauszeiger dann an die Position, die der linken oberen Ecke des Gruppenfeldes entspricht. Drücken Sie nun die linke Maustaste und ziehen Sie den Rahmen um die Checkboxen herum auf, wobei Sie unterhalb der Checkboxen noch etwas Platz lassen sollten um später einen Button einfügen zu können. Wenn der Rahmen die gewünschte Größe hat, lassen Sie die Maustaste wieder los. Um den Text des Gruppenfeldes zu ändern, rufen Sie über die rechte Maustaste dessen Eigenschaftsdialog auf und geben als Titel den Text Edit-Optionen ein. Die vorgeschlagene ID IDC_STATIC können Sie unverändert übernehmen, da wir während der Programmlaufes das Gruppenfeld nicht verändern wollen.

Nun sollte der Dialog etwa folgendes Layout besitzen:

Checkboxen mit Gruppenfeld

Alle bis jetzt eingefügten Checkboxen sind 2-State Auto-Checkboxen, d.h. sie können nur die beiden Zustände markiert/nicht-markiert annehmen und der Zustandswechsel wird von WINDOWS übernommen. Damit aber noch 'etwas Leben' ins Beispiel kommt, wollen wir den Zustandswechsel der Checkbox IDC_SYNCOLOR selbst in die Hand nehmen. Dazu ist zunächst der Stil Auto-Checkbox zu entfernen. Öffnen Sie Eigenschaftsdialog der Checkbox IDC_SYNCOLOR und wählen dort den Tabulator Formate aus. Entfernen Sie die Markierung bei Auto.

Eine Nicht-Auto-Checkbox

Um eine 3-State Checkbox zu erstellen, markieren Sie die Option Tri-State im Eigenschaftsdialog. Diese Option wird im Beispiel aber nicht verwendet.

Übersetzen und starten Sie das Programm jetzt wieder.

Wenn Sie das Programm gestartet haben, so haben Sie sicher bemerkt, dass lediglich die Auto-Checkboxen ihren Zustand wechseln. Bei der 'normalen' Checkbox Syntax Coloring muss der Zustandswechsel durch die Anwendung erfolgen. Bauen wir diese Funktionalität nun ein. Um eine Checkbox auf einen bestimmten Zustand zu setzen, wird die CButton-Methode SetCheck(...) aufgerufen. Sie erhält als Parameter den zu setzenden Zustand laut nachfolgender Tabelle:

Wert

Zustand

0 Checkbox nicht markiert
1 Checkbox markiert
2 Checkbox gesperrt

Beachten Sie bitte, dass der Wert '2' nur für 3-State Checkboxen erlaubt ist.

Genauso wie Sie den Zustand einer Checkbox setzen können, können Sie auch deren aktuellen Zustand auslesen. Rufen Sie dazu die CButton-Methode GetCheck(...) auf. Als Returnwert liefert die Methode je nach Zustand einen der oben angegebenen Werte.

Erweitern wir das Beispiel jetzt um die Steuerung der Zustandswechsel für die 'normale' Checkbox. Wie schon angesprochen, versenden Checkboxen beim Anklicken Nachrichten. Diese Nachrichten sind vom Typ WM_COMMAND mit dem Benachrichtigungscode BN_CLICKED. Sehen wir uns jetzt an, wie Sie für diese Nachricht mit dem Klassen-Assistenten einen Nachrichtenbearbeiter definieren.

Öffnen Sie zunächst den Klassen-Assistenten. Stellen Sie dann im Feld Klassenname die Dialogklasse CButtonDlg ein, da der Nachrichtenbearbeiter für die Checkbox innerhalb der Dialogklasse platziert werden muss. Anschließend suchen in der Liste Objekt-IDs die ID IDC_SYNCOLOR und klicken diese an. Im Feld Nachrichten wird daraufhin u.a. die Nachricht BN_CLICKED aufgelistet.

Nachrichtenbearbeiter für eine Checkbox

Führen Sie einen Doppelklick auf diese Nachricht aus und übernehmen Sie den vom Klassen-Assistenten vorgeschlagenen Namen OnSyncolor für den Nachrichtenbearbeiter.

Damit wäre zunächst der Nachrichtenbearbeiter definiert. Um in der Anwendung den Zustandswechsel der Checkbox durchführen zu können, muss zuerst der aktuelle Zustand ausgelesen und abhängig davon (und eventl. weiteren Bedingungen) der neue Zustand gesetzt werden. Die beiden hierfür notwendigen Methoden GetCheck(...) und SetCheck(...) der Klasse CButton haben Sie ja bereits kennen gelernt. Doch damit bleibt eine wichtige Frage noch offen: wie erhält man zu einem Control das dazugehörige C++ Objekt um das Control beeinflussen zu können? Die Lösung der Problems naht in Form der CWnd Methode GetDlgItem(...). Als Parameter erhält die Methode die ID, für die ein entsprechendes C++ Objekt erstellt werden soll. Und als Returnwert liefert die Methode einen CWnd Zeiger auf das mit dem Control verbunden Objekt. Da alle Controls von CWnd abgeleitet sind, erhalten Sie einen Basisklassen auf das Control, den Sie dann in der Regel noch in den 'richtigen' Datentyp konvertieren müssen. In unserem Fall also in einen CButton Zeiger.

So, jetzt dürfen Sie wieder rann! Versuchen Sie einmal die Methode OnSyncolor(...) so zu vervollständigen, dass die Checkbox bei jedem Anklicken ihren Zustand wechselt.

Lösung zur Steuerung einer Nicht-Auto-Checkbox

void CButtonDlg::OnSyncolor()
{
    // TODO: Code für die Behandlungsroutine der Steuerelement-....
    // CButton Zeiger auf Dialogobjekt holen
    CButton *pCBtn = static_cast<CButton*>(GetDlgItem(IDC_SYNCOLOR));
    ASSERT(pCBtn);
    // Falls Checkbox nicht markiert
    if (pCBtn->GetCheck() == 0)
    {
        // Checkbox markieren
        pCBtn->SetCheck(1);
    }
    else
    {
        // Checkbox nicht markieren
        pCBtn->SetCheck(0);
    }

}

Dürfte eigentlich nicht besonders schwierig gewesen sein, oder? Sollten Sie bei der IF-Abfrage keine geschweiften Klammern für die einzelnen Zweige stehen haben, so fügen Sie diese nun ein. Wir werden später die Methode noch etwas erweitern.

Ende der Lösung

Übersetzen und starten Sie das Programm nun erneut. Die Checkbox IDC_SYNCOLOR sollte dann, dank Ihrer eingefügten Routine, gleich funktionieren wie die Auto-Checkboxen.

Damit hätten wir die Checkboxen fast fertig behandelt. Was jetzt nur noch fehlt ist der Datenaustausch zwischen der Anwendung und den Checkboxen. Während Buttons in der Regel nur direkte Aktionen auslösen, so dienen die meisten der anderen Controls hauptsächlich dazu, irgend welche Einstellungen bzw. Vorgaben vom Anwender abzufragen und dann in der Anwendung zu verarbeiten. Hierzu muss zum einen die Möglichkeit bestehen, die Controls mit definierten Werten zu initialisieren und zum anderen beim Schließen des Dialogs, natürlich nur wenn der OK-Button angeklickt wurde, auch die aktuell eingestellten Werte in der Anwendung wieder auszulesen. Die MFC verwendet für diesen Datenaustausch den DDX- (dialog data exchange) und DDV-(dialog data validation) Mechanismus. Dieser Mechanismus verwendet für den Datenaustausch zwischen der Anwendung und dem Dialog public Membervariablen in der Dialogklasse. Vor der Darstellung des Dialogs werden die den einzelnen Controls zugeordneten Membervariablen einfach auf ihren Initialwert gesetzt. Die Controls selbst werden dann über den DDX-Mechanismus entsprechend dieser Vorgabe dargestellt. Nach dem Schließen des Dialogs können die public Membervariablen wieder ausgelesen werden um die aktuellen Einstellungen abzufragen. Um diesen Datenaustausch zu implementieren wird wiederum der Klassen-Assistent eingesetzt. Bauen wird jetzt den DDX-Mechanismus für die Checkboxen in unser Beispiel ein.

Öffnen Sie den Klassen-Assistent und wählen dort den Tabulator Membervariablen aus. Falls noch nicht automatisch erfolgt, wählen Sie in der Listbox Klassenname die Klasse CButtonsDlg aus. Die anschließend zu definierende Membervariable wird damit der Dialogklasse hinzugefügt.

Membervariablen für den Datenaustauch definieren

Im Feld Steuerelement-IDs sehen Sie die IDs, die Sie Ihren Controls zugewiesen haben. Um jetzt für die Checkbox IDC_HBAR eine DDX-Variable zu definieren, klicken Sie zunächst die ID IDC_HBAR an und anschließend den Button Variable hinzufügen.... Daraufhin wird folgender Dialog geöffnet:

DDX-Variable für eine Checkbox definieren

Über diesen Dialog legen Sie zunächst den Namen der DDX-Variablen fest. Geben Sie hier bitte den im Bild angegebenen Namen m_bHBar ein. Im darunter stehenden Feld Kategorie können Sie noch spezifizieren, ob die Membervariable zum Datenaustausch verwendet wird (Einstellung: Wert) oder ob ein dem Control entsprechendes C++ Objekt (Einstellung: Control) erstellt werden soll. Mit dem letzteren Fall werden wir uns in der nächsten Lektion befassen. Das letzte Feld Variablentyp legt den Datentyp der Membervariable fest. In unserem Fall ist hier nur der Typ BOOL einstellbar, da eine 2-State Checkbox Variable nur die Zustände markiert oder nicht-markiert annehmen kann. Klicken Sie den OK-Button an und es wird der Dialogklasse die public Membervariable hinzugefügt.

Wenn Sie eine Membervariable für eine 3-State Checkbox hinzufügen, so wird anstelle des Variablentyps BOOL der Typ int verwendet, da hier zusätzlich der Zustand gesperrt noch auftreten kann.

Fügen Sie jetzt auf die gleiche Art und Weise Membervariablen für die restlichen Checkboxen laut nachfolgender Tabelle hinzu:

DDX-Variablen für Checkboxen definieren

Hiermit haben wir der Dialogklasse die public Membervariablen für die Checkboxen zugefügt. Um nun eine Voreinstellung für die Checkboxen vorzunehmen, müssen die Membervariablen vor dem Darstellen des Dialogs nur noch entsprechend gesetzt werden. Wird der Dialog dann geschlossen, so enthalten die Membervariablen den aktuellen Zustand der Checkboxen beim Verlassen des Dialogs. So einfach geht's. Bauen wir dies nun auch gleich ins Beispiel ein.

Da die aktuellen Einstellungen auch dann noch gültig sein sollen, wenn der Dialog nicht mehr angezeigt wird, müssen Sie in der Regel innerhalb der Anwendung ebenfalls entsprechende Membervariablen bereitstellen um die aktuellen Einstellung für die Anwendung abzuspeichern. Fügen Sie daher zur Klasse des Ansichtsobjektes die Membervariablen m_bHBar, m_bVBar, m_bTabStops und m_bSynColor, alle vom Typ BOOL, hinzu. Die Namen der Membervariablen wurden hier gleich gewählt wie die innerhalb des Dialogs. Sie könnten, wenn Sie wollen, aber auch ganz andere Namen verwenden. Sie müssen dann nur immer die richtige Zuordnung der Variablen beachten.

Diese neu hinzugefügten Membervariablen werden im Konstruktor des Ansichtsobjektes initialisiert. Die hier angegebenen Werte erscheinen dann nachher beim ersten Anzeigen des Dialogs als Vorgaben. Erweitern Sie den Konstruktor der Ansichtsklasse wie folgt:

CButtonsView::CButtonsView()
{
    // ZU ERLEDIGEN: Hier Code zur Konstruktion einfügen,
    m_bHBar = m_bVBar = TRUE;
    m_bTabStops = m_bSynColor = FALSE;
}

Anschließend müssen in der Methode OnShowdlg(...) vor dem Anzeigen des Dialogs die public Membervariablen des Dialogs mit den Membervariablen der Anwendung initialisiert werden. Wurde der Dialog mit OK geschlossen, so muss der umgekehrte Vorgang erfolgen, d.h. die Werte der Membervariablen des Dialogs werden in die Membervariablen der Anwendung umkopiert.

Versuchen Sie jetzt wieder einmal selbst, den entsprechenden Datenaustausch in der Methode OnShowdlg(...) des Ansichtsobjektes zu implementieren. Beachten Sie dabei aber, dass die aktuellen Dialogdaten nur dann übernommen werden dürfen, wenn der Dialog mit dem OK Button geschlossen wurde.

Lösung zum Datenaustausch mit dem Dialog

void CButtonsView::OnShowdlg()
{
    // TODO: Code für Befehlsbehandlungsroutine hier einfügen
    CButtonDlg  CMyDlg;
    int         nRetValue;

    // Dialog-Controls initialisieren
    CMyDlg.m_bHBar = m_bHBar;
    CMyDlg.m_bVBar = m_bVBar;
    CMyDlg.m_bTabStops = m_bTabStops;
    CMyDlg.m_bSynColor = m_bSynColor;
    // Dialog anzeigen
    nRetValue = CMyDlg.DoModal();
    // Returncode auswerten
    if (nRetValue == IDOK)
    {
        TRACE("Dialogdaten übernommen\n");
        // Daten aus Dialog uebernehmen
        m_bHBar = CMyDlg.m_bHBar;
        m_bVBar = CMyDlg.m_bVBar;
        m_bTabStops = CMyDlg.m_bTabStops;
        m_bSynColor = CMyDlg.m_bSynColor;
    }
    else
        TRACE("Dialog abgebrochen");
}

Dürfte eigentlich nicht besonders schwierig gewesen sein, oder?

Ende der Lösung

Übersetzen und starten Sie das Programm nun.

Die Checkboxen im Dialog sollten nun entsprechend den Vorgaben anzeigt werden. Wenn Sie diese Einstellungen verändern und den Dialog mit OK schließen und dann wieder öffnen, so werden die zuletzt vorgenommen Einstellungen angezeigt. Beim Abbruch des Dialogs sollten alle Einstellung erhalten bleiben.

Verlassen wir damit vorerst die Checkboxen und wenden uns den Radiobuttons zu. Während Checkboxen in der Regel dazu dienen, eine beliebige Anzahl von Optionen gleichzeitig auszuwählen, werden Radiobuttons immer dann eingesetzt, wenn von einer Anzahl von Optionen immer nur eine gültig sein kann. Da ein Dialog verschiedene logisch zusammengehörige Radiobuttons besitzen kann, ist beim Anlegen der Radiobuttons einiges zu beachten.

Aber gehen wir auch hier wieder Schritt für Schritt vor. Fügen wir unserem Beispiel jetzt zuerst ein paar Radiobuttons hinzu.

Um einem Dialog einen Radiobutton hinzuzufügen, ist das unten markierte Symbol im Toolbar des Dialogeditors anzuklicken und danach der Radiobutton entsprechend zu positionieren.

Radiobutton (Optionsfeld)

Legen Sie nun rechts im Dialog (siehe auch Bild am Anfang der Lektion) drei Radiobuttons mit den angegebenen Beschriftungen und IDs an. Fügen Sie dann noch ein Textfeld unterhalb der Radiobuttons hinzu und umrahmen Sie die neuen Controls mit einem Gruppenfeld. Beachten Sie bitte, dass auch das Textfeld hier eine eindeutige ID erhält da wir nachher dessen Text per Programm verändern werden.

Radiobuttons des Dialogs

Durch das Gruppenfeld wird optisch hervorgehoben, welche Radiobuttons logisch zusammengehören. Doch woher weiß WINDOWS wenn mehrere Radiobutton-Gruppen im Dialog vorhanden sind, welche Radiobuttons logisch zusammengehören und somit sich gegenseitig ausschließen. Dazu müssen wir uns zunächst mit einer neuen Eigenschaft der Dialoge befassen, der Tabulator-Reihenfolge. Bestimmt ist Ihnen auch schon einmal aufgefallen, dass Sie in einem Dialog mit der TAB-Taste von einem Control zum anderen springen können. Die Reihenfolge, in der die Controls dabei durchlaufen werden, wird beim Entwurf des Dialogs festgelegt.

Wählen Sie nun einmal den Menüeintrag Layout-Tabulator Reihenfolge aus. Die Anzeige des Dialogs wird sich daraufhin wie folgt ändern:

Die TAB-Reihenfolge

Sollten bei Ihnen anderen Zahlen an den einzelnen Controls stehen so macht dies im Augenblick nichts aus. Wir werden nachher gleich noch die notwendigen Anpassungen vornehmen.

Die Zahlen an den Controls geben die Reihenfolge an, in der die Controls durchlaufen werden wenn die TAB-Taste gedrückt wird und das Control die Eigenschaft Tabstopp besitzt. Um für ein Control die Eigenschaft Tabstopp zu setzen, markieren im Eigenschaftsdialog einfach diese Eigenschaft.

Die Tab-Stop Eigenschaft

Setzen Sie die Eigenschaft Tabstopp auch nur bei den Controls, die auch tatsächlich Benutzereingaben verarbeiten können. So macht es wenig Sinn, einen Gruppenfeld oder ein Textfeld mit dieser Eigenschaft auszustatten.

Um die Tab-Reihenfolge festzulegen, klicken Sie (nachher in der Übung) die Controls in der Reihenfolge an, in der sie durchlaufen werden sollen. Haben Sie die Reihenfolge fertig spezifiziert, klicken Sie einfach mit der Maus neben den Dialog und die Dialogdarstellung erfolgt wieder in der gewohnten Weise. Wollen Sie eine bereits bestehende Reihenfolge nicht komplett neu aufbauen sondern nur leicht abändern, so können Sie auch dies tun. Halten Sie dazu die SHIFT-Taste gedrückt während Sie das Control anklicken, von dem ab Sie die Reihenfolge ändern wollen. Der Dialog-Editor nimmt dann die aktuelle Position in der Reihenfolge als Ausgangspunkt. Wollten Sie also z.B. im obigen Bild die Tab-Reihenfolge 5 und 6 vertauschen, so klicken Sie zuerst bei gedrückter SHIFT-Taste die Position 4 an und dann nacheinander die Controls Syntax Coloring und Tabs verwenden.

Doch die Tabulator-Reihenfolge alleine reicht noch nicht aus um verschiedene Radiobuttons in logischen Gruppen zusammenzufassen. Dies wird erst erst dadurch erreicht, dass der erste Radiobutton in der Gruppe, das ist der mit der niedrigsten Tab-Position, zusätzlich zur Tabstopp Eigenschaft noch die Eigenschaft Gruppe erhält. Hiermit wird der erste Button in einer Gruppe festgelegt. Alle weiteren Radiobuttons der Gruppe müssen unmittelbar aufeinanderfolgende Tab-Positionen besitzen. Um die Buttongruppe abzuschließen muss dass nächste, auf den letzten Button in der Gruppe folgende Control ebenfalls die Eigenschaft Gruppe erhalten.

Gruppieren wir nun unsere Radiobuttons. Legen Sie dazu zunächst wie oben dargestellt die Tabulator-Reihenfolge fest, in dem Sie im Menü Layout den Menüpunkt Tabulator-Reihenfolge auswählen. Klicken Sie dann die Controls in der angegebenen Reihenfolge an.

Fügen dann noch den Controls mit den Tab-Positionen 1-6 und 8 die Eigenschaft Tabstopp hinzu.

Als nächstes müssen die Radiobuttons zu einer logischen Gruppen zusammenfassen. Setzen Sie zunächst beim Button IDC_RED (Tab-Position 8) die Eigenschaft Gruppe. Damit haben wir den Beginn der Gruppe festgelegt. Die Buttons an der Tab-Position 9 und 10 sollen nun zu dieser Gruppe hinzugefügt werden. Um die Gruppe abzuschließen, muss dem Control an der nachfolgenden Position, also der Position 11, ebenfalls die Eigenschaft Gruppe hinzugefügt werden. Und damit ist die Gruppe fertig definiert. Ganz einfach, wenn man's weiß.

Sie können nun Ihren Dialog testen, in dem Sie im Menü Layout den Menüpunkt Testen auswählen. Der Dialog wird daraufhin dargestellt und Sie könnten nun z.B. auch die Tab-Reihenfolge verfolgen. Wenn Sie einen der Radiobuttons in der Gruppe anklicken, so sollte der andere nicht mehr markiert sein.

Nachdem die Buttongruppe festgelegt ist, kann es an die Implementierung der Button-Funktionen gehen.

Die nachfolgenden Schritte sind nur dann notwendig, wenn Sie innerhalb eines Dialog unmittelbar auf die Auswahl eines Radiobuttons reagieren müssen. Für den Datenaustausch der Anwendung mit dem Dialog sind keine Button-Funktionen notwendig. Wie der Datenaustausch bei Radiobuttons funktioniert wird nachher gleich beschrieben.

Radiobuttons senden, genauso wie die bereits behandelten Checkboxen, beim Anklicken eine BN_CLICKED Nachricht an den übergeordneten Dialog. Über den Klassen-Assistent könnten jetzt die entsprechenden Nachrichtenbearbeiter für die einzelnen Radiobuttons zur Dialogklasse hinzugefügt werden. Wir werden aber eine neue Variante für die Nachrichtenbearbeitung uns ansehen. Da alle drei Radiobuttons beim Anklicken die aktuelle Farbauswahl im Textfeld IDC_COLOR ausgeben sollen, werden wir einen Nachrichtenbearbeiter für alle Radiobuttons definieren. Dazu ist es aber notwendig, dass die IDs der Radiobuttons unmittelbar aufeinander folgen.

Um den Wert einer Control-ID explizit vorzugeben, öffnen Sie dessen Eigenschaftsdialog. Im Feld ID geben dann nach der ID ein Gleichheitszeichen folgt vom nummerischen Wert ein. Das war's auch schon. Im Bild unten besitzt die ID IDC_RED damit den Wert 2000.

Wert der ID explizit vorgeben

Ändern Sie jetzt die Werte der IDs IDC_RED, IDC_GREEN und IDC_BLUE so ab, dass diese die Werte 2000, 2001 und 2002 besitzen.

Nachdem Sie die IDs geordnet haben, kann's ans Definieren des Nachrichtenbearbeiters gehen. Um für mehrere IDs einen Nachrichtenbearbeiter festzulegen, wird das Makro ON_COMMAND_RANGE(...) eingesetzt. Als Parameter erhält das Makro die Start- und End-ID sowie den Namen des Nachrichtenbearbeiters. Der Nachrichtenbearbeiter selbst muss vom Typ afx_msg void DoSomething(UINT wID) sein. Im Parameter wID erhält die Methode die ID des Controls, das die Nachricht ausgelöst hat.

Da der Klassen-Assistent keine Nachrichtenbereiche unterstützt, muss das Makro ON_COMMAND_RANGE(...) von Hand in die Nachrichtentabelle eingefügt werden. Erweitern Sie die Nachrichtentabelle wie folgt:
BEGIN_MESSAGE_MAP(CButtonDlg, CDialog)
    //{{AFX_MSG_MAP(CButtonDlg)
    ON_BN_CLICKED(IDC_SYNCOLOR, OnSyncolor)
    //}}AFX_MSG_MAP
    ON_COMMAND_RANGE(IDC_RED, IDC_BLUE, OnColorChange)
END_MESSAGE_MAP()

Fügen Sie anschließend der Dialogklasse die Methode OnColorChange(...) wie folgt hinzu.

void CButtonDlg::OnColorChange(UINT wID)
{
    CStatic *pCText = static_cast<CStatic*>(GetDlgItem(IDC_COLOR));
    switch (wID)
    {
    case IDC_RED:
        pCText->SetWindowText("Farbe: rot");
        break;
    case IDC_GREEN:
        pCText->SetWindowText("Farbe: grün");
        break;
    case IDC_BLUE:
        pCText->SetWindowText("Farbe: blau");
        break;
    }
}

Übersetzen und starten Sie das Programm nun. Wenn Sie nun einen der Radiobuttons anklicken, so wird im Textfeld IDC_COLOR die aktuell eingestellte Farbe ausgegeben.

Einen kleinen Schönheitsfehler hat unser Dialog noch. Wenn Sie den Dialog neu anzeigen, so wird keiner der Radiobuttons eine Markierung aufweisen. Dies ist aber nicht zulässig, da ein Radiobutton innerhalb einer logischen Gruppe immer selektiert sein muss. Wir müssen nun noch einen Weg finden, die Radiobuttons zu initialisieren. Dazu wird dem ersten Button der Gruppe über den Klassen-Assistenten wieder eine entsprechende public Membervariable der Dialogklasse zugeordnet. Wird diese Membervariable vor dem Aufruf des Dialogs mit dem Wert '0' initialisiert, so wird der erste Radiobutton der Gruppe markiert. Entsprechend wird der zweite Radiobutton markiert, wenn der Membervariable der Wert '1' zugewiesen wird, usw.. Beachten Sie bitte, dass Sie nur dem ersten Button innerhalb einer Gruppe eine Variable zuordnen müssen. Über den DDX-Mechanismus wird dann je nach Wert der entsprechende Radiobutton in der Gruppe gesetzt.

Initialisieren wir die Radiobuttons so, dass der erste Radiobutton markiert ist. Weisen Sie, wie nachfolgend angegeben, dem Radiobutton IDC_RED die Membervariable m_nSynColor zu.

DDX für Radiobuttons

Zum Schluss muss die Membervariable noch vor der Anzeige des Dialogs in der Methode OnShowdlg(...) des Ansichtsobjektes entsprechend initialisiert werden. Nachdem der Dialog geschlossen wurde können Sie durch auslesen des Wertes abprüfen, welcher Radiobutton markiert war. Fügen Sie zur Ansichtsklasse die Membervariable m_nSynColor hinzu und initialisieren Sie diese im Dialog wie folgt:

CButtonsView::CButtonsView()
{
    // ZU ERLEDIGEN: Hier Code zur Konstruktion einfügen,
    m_bHBar = m_bVBar = TRUE;
    m_bTabStops = m_bSynColor = FALSE;
    m_nSynColor = 0;
}

Anschließend erweitern Sie den folgenden Code der Methode OnShowdlg(...) noch.

void CButtonsView::OnShowdlg()
{
    // Dialog-Controls initialisieren
    ....
    CMyDlg.m_bSynColor = m_bSynColor;
    CMyDlg.m_nSynColor = m_nSynColor;
    // Dialog anzeigen
    nRetValue = CMyDlg.DoModal();
    // Returncode auswerten
    if (nRetValue == IDOK)
    {
        ....
        m_bSynColor = CMyDlg.m_bSynColor;
        m_nSynColor = CMyDlg.m_nSynColor;
    }
    else
        TRACE("Dialog abgebrochen");
}

Übersetzen und starten Sie das Beispiel wieder.

Sie doch schon recht gut jetzt aus, oder? Bis auf .... Nun, das Textfeld IDC_COLOR zeigt beim Anzeigen des Dialogs immer noch nicht die aktuell eingestellte Farbe an sondern erst nachdem Sie einen der Radiobuttons angeklickt haben. Was also hier noch fehlt ist die Initialisierung des Textfeldes. Wir könnten jetzt zwar auch wieder für das Textfeld eine Membervariable für den DDX-Mechanismus definieren und diese dann im Ansichtsobjekt entsprechend setzen. Da jedoch der Inhalt des Textfeldes eigentlich nur innerhalb für den Dialog von Interesse ist (die Anwendung erhält die aktuelle Farbe ja über die Einstellung der Radiobuttons mitgeteilt), werden wir einen anderen Weg gehen. Nachdem ein Dialog erstellt und dessen Controls initialisiert wurden und bevor er das erstemal dargestellt wird, wird die Methode OnInitDialog(...) aufgerufen. Diese Methode eignet sich damit hervorragend für Initialisierungsaufgaben.

Versuchen Sie jetzt zuerst einmal selbst die Methode OnInitDialog(...) zu implementieren. Wie gesagt, die Methode soll die aktuell eingestellte Farbe im Textfeld IDC_COLOR wie folgt ausgeben: Farbe: xxxx.

Lösung zur Initialisierung des Textfeldes

BOOL CButtonDlg::OnInitDialog()
{
    CDialog::OnInitDialog();
   
    // TODO: Zusätzliche Initialisierung hier einfügen
    // Textfarbe im Ausgabefeld setzen
    CStatic *pCText = static_cast<CStatic*>(GetDlgItem(IDC_COLOR));
    switch (m_nSynColor)
    {
    case 0:
        pCText->SetWindowText("Farbe: rot");
        break;
    case 1:
        pCText->SetWindowText("Farbe: grün");
        break;
    case 2:
        pCText->SetWindowText("Farbe: blau");
        break;
    }   
    return TRUE; // return TRUE unless you set the focus to a control
    // EXCEPTION: OCX-Eigenschaftenseiten sollten FALSE zurückgeben
}

Das Schwierigste an dieser Übung dürfte das Auffinden der Methode OnInitDialog(...) gewesen sein. Sie finden Sie im Klassen-Assistent unter den WINDOWS Nachrichten. Der Code der Methode entspricht sonst weitgehend dem der Methode OnColorChange(...).

Ende der Lösung

Übersetzen und starten Sie das Beispiel nun und Sie werden einen funktionsfähigen Dialog haben.

Damit Sie sehen, dass alle Einstellungen im Ansichtsobjekt auch richtig ankommen, erweitern Sie dessen OnDraw(...) wie folgt:

void CButtonsView::OnDraw(CDC* pDC)
{
    CButtonsDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    // ZU ERLEDIGEN: Hier Code zum Zeichnen der ursprünglichen Daten hinzufügen
    static COLORREF AvailColors[3] = {RGB(255,0,0), RGB(0,255,0), RGB(0,0,255)};

    pDC->SetBkMode(TRANSPARENT);
    if (m_bSynColor)
    {
        pDC->SetTextColor(AvailColors[m_nSynColor]);
        pDC->TextOut(10,10,"Syntax Coloring ein");
    }
    else
    {
        pDC->SetTextColor(RGB(0,0,0));
        pDC->TextOut(10,10,"Syntax Coloring aus");
    }
   
    if (m_bHBar)
        pDC->TextOut(10,30,"Horizontale Leiste ein");
    else
        pDC->TextOut(10,30,"Horizontale Leiste aus");

    if (m_bVBar)
        pDC->TextOut(10,50,"Vertikale Leiste ein");
    else
        pDC->TextOut(10,50,"Vertikale Leiste aus");

    if (m_bTabStops)
        pDC->TextOut(10,70,"TabStops ein");
    else
        pDC->TextOut(10,70,"TabStops aus");
}

Damit OnDraw(...) auch ausgeführt wird wenn der Dialog geschlossen wird, müssen Sie in der Methode OnShowdlg(...) noch die Methode Invalidate(...) aufrufen.

void CButtonsView::OnShowdlg()
{
    ....
    if (nRetValue == IDOK)
    {
        ....
        Invalidate();
    }
    else
        TRACE("Dialog abgebrochen");
}

Sie sehen, es ist doch schon recht viel Arbeit notwendig um selbst einen solchen kleinen Dialog zu implementieren. Aber trösten Sie sich, ohne Unterstützung der MFC wäre der Aufwand noch um einiges größer!

Aber setzen wir noch eins darauf. Da die Farbauswahl auch nur dann Sinn macht, wenn das Syntax Coloring freigegeben ist, wollen wir nun die Radiobuttons in Abhängigkeit vom Zustand der Checkbox IDC_SYNCOLOR freigeben oder sperren. Wie schon mehrfach erwähnt, senden Checkboxen beim Anklicken BN_CLICKED Nachrichten an den Dialog. Und für die Checkbox IDC_SYNCOLOR haben wir vorher auch schon den entsprechenden Nachrichtenbearbeiter OnSyncolor(...) definiert. Was wir jetzt nur noch tun müssen, ist die Radiobuttons in dieser Methode entsprechend zu sperren bzw. wieder freizugeben. Um Controls freizugeben und zu sperren wird die CWnd Methode EnableWindow(...) aufgerufen. Wir können hier die CWnd Methode verwenden da Controls eigentlich sind anderes sind wie Fenster mit einem vordefinierten Verhalten.

Versuchen Sie jetzt wieder einmal selbst, die Methode OnSyncolor(...) entsprechend anzupassen. Beachten Sie dabei bitte, dass auch das Textfeld IDC_COLOR analog dazu freizugeben bzw. zu sperren ist. Und noch ein Hinweis: Sie können sich bei der Lösung dieser Aufgabe die Tatsache zu nutze machen, dass die IDs der Radiobuttons fortlaufend durchnummeriert sind.

Lösung zum Sperren der Radiobuttons

void CButtonDlg::OnSyncolor()
{
    // TODO: Code für die Behandlungsroutine der Steuerelement-....
    // CButton Zeiger auf Dialogobjekt holen
    CButton *pCBtn = static_cast<CButton*>(GetDlgItem(IDC_SYNCOLOR));
    // CStatic Zeiger auf Textfeld fuer Farbausgabe holen
    CStatic *pColorField = static_cast<CStatic*>(GetDlgItem(IDC_COLOR));

    ASSERT(pCBtn);
    // Falls Checkbox nicht markiert
    if (pCBtn->GetCheck() == 0)
    {
        // Checkbox markieren
        pCBtn->SetCheck(1);
        for (int iIndex=IDC_RED; iIndex<=IDC_BLUE; iIndex++)
        {
            CButton *pBtn = static_cast<CButton*>(GetDlgItem(iIndex));
            pBtn->EnableWindow(TRUE);
        }
        pColorField->EnableWindow(TRUE);
    }
    else
    {
        // Checkbox nicht markieren
        pCBtn->SetCheck(0);
        for (int iIndex=IDC_RED; iIndex<=IDC_BLUE; iIndex++)
        {
            CButton *pBtn = static_cast<CButton*>(GetDlgItem(iIndex));
            pBtn->EnableWindow(FALSE);
        }
        pColorField->EnableWindow(FALSE);
    }
}

Ende der Lösung

Übersetzen und starten Sie das Beispiel nun.

Was fällt Ihnen dabei auf? Wenn Sie sich den Dialog genau ansehen werden Sie am Anfang feststellen, dass das Syntax Coloring nicht markiert ist, die Radiobuttons trotzdem aber freigegeben sind. Was hier wiederum noch fehlt ist die die Initialisierung der Radiobuttons. Die Methode zum Initialisieren von Controls haben wir bereits vorhin eingebaut, nämlich die Methode OnInitDialog(...). Diese gilt es jetzt noch zu erweitern.

Fügen Sie folgenden Code zu OnInitDialog(...) Methode hinzu, damit die Radiobuttons (und das Textfeld) in Abhängigkeit vom Syntax Coloring immer richtig dargestellt werden:
BOOL CButtonDlg::OnInitDialog()
{
    ....
    switch (m_nSynColor)
    {
        ....
    }
    // Falls Syntax Coloring nicht gesetzt ist
    // Farbauswahl sperren!
    if (!m_bSynColor)
    {
        CButton *pSynColor = static_cast<CButton*>(GetDlgItem(IDC_SYNCOLOR));
        CStatic *pColorText = static_cast<CStatic*>(GetDlgItem(IDC_COLOR));
        for (int iIndex=IDC_RED; iIndex<=IDC_BLUE; iIndex++)
        {
            CButton *pBtn = static_cast<CButton*>(GetDlgItem(iIndex));
            pBtn->EnableWindow(FALSE);
        }
        pColorText->EnableWindow(FALSE);
    }
    return TRUE; // return TRUE unless you set the focus to a control
    // EXCEPTION: OCX-Eigenschaftenseiten sollten FALSE zurückgeben
}

Wenn Sie das Beispiel jetzt laufen lassen, sollte alles richtig funktionieren.

Und damit könnten wir eigentlich diese Lektion abschließen. Was ich Ihnen aber noch zeigen möchte ist, wie man eine Art 'Übernehmen-Button' implementieren kann. D.h. die aktuellen Einstellungen sollen beim Drücken eines Buttons an die Anwendung übertragen werden ohne dass der Dialog dazu geschlossen werden muss.

Fügen wir zuerst dem Dialog den hierfür einzusetzenden Button hinzu. Um einem Dialog einen 'normalen' Button hinzuzufügen, klicken Sie im Toolbar das folgende Symbol an:

(Normaler) Button

Platzieren Sie den Button unterhalb der Checkbox Syntax Coloring (siehe auch Abbildung am Anfang der Lektion) und weisen dem Button die ID IDC_NOW und die Beschriftung Übernehmen zu.

Button-Eigenschaften

Anschließend fügen Sie über den Klassen-Assistent wieder einen Nachrichtenbearbeiter für den Button zum Dialog hinzu. Übernehmen Sie den vom Assistenten vorgeschlagenen Namen OnNow(...) für die Methode.

Immer wenn der Anwender nun den Button anklickt, wird die neu hinzugefügte Methode OnNow(...) aufgerufen. Doch wie übertragen wir jetzt die aktuellen Einstellungen des Dialogs an das Ansichtsobjekt? Nun, da WINDOWS ein nachrichtenbasierendes System ist werden wir auch hierfür eine Nachricht einsetzen. Wir definieren uns für diesen Zwecke eine anwenderdefinierte Nachricht. Wenn Sie den Kurs bisher aufmerksam durchgearbeitet haben werden Sie wissen, dass der Bereich für anwenderdefinierte Nachrichten ab der Konstanten WM_APP beginnt.

Sie dürfen hier auf keinen Fall den Bereich ab WM_USER verwenden, da diese Nachrichten nur innerhalb einer Fensterklasse verwendet werden dürfen. Wir aber versenden Nachrichten zwischen zwei Fensterklassen, der Fensterklasse des Dialogs und der Fensterklasse des Ansichtsobjekts!

Damit der Dialog aber seinen Erzeuger und damit den Empfänger dieser Nachricht auch kennt (das ist das Ansichtsobjekt), geben wir beim Erstellen des Dialogs einen Zeiger darauf mit.

Fügen Sie zur Dialogklasse zunächst die Membervariable m_pView vom Typ CWnd-Zeiger hinzu. In dieser Membervariable wird dann innerhalb des Konstruktor der Zeiger auf das Ansichtsobjekt abgelegt.
CButtonDlg::CButtonDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CButtonDlg::IDD, pParent)
{
    //{{AFX_DATA_INIT(CButtonDlg)
    m_bHBar = FALSE;
    m_bVBar = FALSE;
    m_bSynColor = FALSE;
    m_bTabStops = FALSE;
    m_nSynColor = -1;
    //}}AFX_DATA_INIT
    m_pView = pParent;
}

Nun muss noch die Erstellung des Dialogs etwas korrigiert werden, da der Konstruktor des Dialogs jetzt einen Zeiger auf den Empfänger der Nachricht erwartet.

void CButtonsView::OnShowdlg()
{
    // TODO: Code für Befehlsbehandlungsroutine hier einfügen
    CButtonDlg  CMyDlg(this);
    int         nRetValue;
    ....
}
Die im Konstruktor des Dialogs im Block AFX_DATA_INIT enthaltenen Anweisungen wurden vom Klassen-Assistenten hinzugefügt und dienen zur Initialisierung des Datentausches mit der Anwendung.

Da der Dialog nun den Empfänger der Nachricht kennt, kann's ans Versenden der Nachricht gehen.

Definieren Sie dazu zuerst die Konstante für die anwenderdefinierte Nachricht in der Header-Datei des Dialogs:
....
// ButtonDlg.h : Header-Datei
//

// Anwenderdefinierte Nachricht zur sofortigen Datenuebernahme
#define APPLYNOW WM_APP

/////////////////////////////////////////////////////////////
// Dialogfeld CButtonDlg

Anschließend kann diese Nachricht im Nachrichtenbearbeiter des Buttons OnNow(...) versandt werden. Da das Ansichtsobjekt nachher Zugriff auf die Dialogdaten benötigt um die aktuellen Einstellungen auszulesen, geben wir im LONG-Parameter der SendMessage(...) Methode einen Zeiger auf das Dialogobjekt mit.

void CButtonDlg::OnNow()
{
    // TODO: Code für die Behandlungsroutine der Steuerelement-....
    m_pView->SendMessage(APPLYNOW,0,(LONG)this);
}

Als nächstes muss diese vom Dialog kommende Nachricht im Ansichtsobjekt verarbeitet werden. Da der Klassen-Assistent keine anwenderdefinierten Nachrichten unterstützt, müssen Sie selbst diese Nachricht in die Nachrichtentabelle eintragen. Hierzu wird das bereits bekannte Makro ON_MESSAGE(...) verwendet.

BEGIN_MESSAGE_MAP(CButtonsView, CView)
    //{{AFX_MSG_MAP(CButtonsView)
    ON_COMMAND(ID_SHOWDLG, OnShowdlg)
    //}}AFX_MSG_MAP
    ON_MESSAGE(APPLYNOW,OnApplyNow)
END_MESSAGE_MAP()

Hiermit wird die anwenderdefinierte Nachricht APPLYNOW dem, noch zu implementierenden, Nachrichtenbearbeiter OnApplyNow(...) zugeordnet. Fügen Sie jetzt über den Klassen-Assistent den Nachrichtenbearbeiter OnApplyNow(...) hinzu und erweitern dessen Code wie folgt:

LONG CButtonsView::OnApplyNow (UINT, LONG lParam)
{
    CButtonDlg *pDlg = (CButtonDlg*)lParam;
    m_bHBar = pDlg->m_bHBar;
    m_bVBar = pDlg->m_bVBar;
    m_bTabStops = pDlg->m_bTabStops;
    m_bSynColor = pDlg->m_bSynColor;
    m_nSynColor = pDlg->m_nSynColor;
    Invalidate();
    return 0L;
}

Der LONG-Parameter enthält den Zeiger auf das Dialogobjekt, das die Nachricht versandt hat. Um an die Dialogdaten zu gelangen, muss dieser Zeiger zuerst wieder in den entsprechenden Dialogzeiger konvertiert werden.

Übersetzen und starten Sie das Programm nun. Was beobachten Sie?

Nun, vermutlich wird sich beim Anklicken des Buttons Übernehmen im Dialog im Ansichtsobjekt überhaupt nichts tun. Die erste Vermutung wird sein, dass die Methode OnApplyNow(...) des Ansichtsobjektes überhaupt nicht ausgeführt wird. Sie können jedoch einmal die Funktion MessageBeep(...) in die Methode einbauen und Sie werden feststellen, dass bei jedem Anklicken des Buttons ein Ton ausgegeben wird. Also wird OnApplyNow(...) auch aufgerufen. Also ganz so einfach war die Lösung doch wohl nicht. Bevor Sie nun Stunden mit der Fehlersuche verbringen, verrate ich Ihnen lieber die Lösung. Die Lösung liegt im Aufruf einer weiteren CDialog Methode UpdataData(...). Diese Methode ist dafür verantwortlich, dass zum einen beim Aufbau des Dialogs die DDX-Daten in die Controls übernommen werden und zum anderen auch dafür, dass die Control-Einstellungen wieder in die DDX-Daten zurück übertragen werden. Damit UpdateData(...) weiß, in welche Richtung der Datentransfer statt zu finden hat, erhält die Methode einen entsprechenden BOOL-Parameter. Standardmäßig wird UpdateData(...) von der bereits bekannten Methode OnInitDialog(...) aufgerufen wenn die Daten in die Controls transferiert werden sollen und von der Methode OnOk(...) um den aktuellen Zustand der Controls in den DDX-Daten abzuspeichern.

Bauen wir den Aufruf dieser Methode zum Abschluss dieser, doch etwas umfangreichen, Lektion nun ein:

Damit die Einstellung der Controls vor dem Absenden der anwenderdefinierten Nachricht in den DDX-Daten abgelegt werden, erweitern Sie die OnNow(...) des Dialogs noch um den erwähnten Aufruf der UpdateData(...) Methode:
void CButtonDlg::OnNow()
{
    // TODO: Code für die Behandlungsroutine der Steuerelement-....
    UpdateData(TRUE);
    m_pView->SendMessage(APPLYNOW,0,(LONG)this);
}

Damit ist das Beispiel endlich komplett. Sie finden es auch unter 08Dialoge\Buttons.

So viel zu den Buttons. Bei den Tipps&Tricks zu diesem Kapitel erfahren Sie dann noch, wie Sie farbige Buttons oder Buttons mit Grafiken erstellen können.

Und ansonsten geht's weiter mit der nächsten Gruppe der Controls, den Listboxen, Comboboxen und Editfeldern.



Copyright © 2004

Senden Sie Emails mit Fragen oder Kommentaren zu dieser Website an: mailto:info@cpp-tutor.de
 Wolfgang Schröder, Lerchenweg 23, D-72805 Lichtenstein. Tel: +49 7129 6470