C++ und Microsoft Foundation Classes (MFC) mit MS VS Community 2015
Kapitel 2 -
Elemente im Dialogfeld

Dr. Erhard Henkes  (Stand: 24.07.2015)



Zurück zum Inhaltsverzeichnis

Zurück zum vorherigen Kapitel





Kapitel 2 - Elemente im Dialogfeld

2.1 Infofeld, Schaltfläche und Eingabefeld

Wenden wir uns dem einfachen Beispiel der Eingabe von zwei Zahlen, die miteinander multipliziert werden sollen, zu. In der klassischen ablauforientierten C-Programmierung hätten Sie dies mit den Ein- und Ausgabebefehlen "scanf" und "printf" erledigt. In C++ fand die Weiterentwicklung zu den Streams "cout" und "cin" statt. 

Wie ist der Ablauf in einem Windows-Programm?

In Windows verwendet man zur Eingabe der beiden Zahlen z.B. zwei Eingabefelder und für die Ausgabe ein schreibgeschütztes Ausgabefeld. Die Funktion der Verknüpfung der beiden Zahlen werden wir mittels eines Mausklicks auf einen Button erledigen. Als Rahmen für das Ganze benützen wir die bereits vertraute Dialogbasierende Anwendung, die ein Dialog-Fenster als Hauptfenster der Anwendung einsetzt.

Wir starten:

Wählen Sie im Menü Datei / Neu / Projekte / Visual C++ / MFC-Anwendung. Als Name für das Projekt wählen wir nicht das vorgeschlagene "MFCApplication4", sondern das aussagekräftigere "Multiplizieren".

Im Schritt "Anwendungstyp" wählen Sie "Auf Dialogfeldern basierend". Weiter.

Im Schritt "Benutzeroberrflächenfunktion" geben Sie für Dialogfeldtitel bitte ebenfalls "Multiplizieren" ein. Minmieren-/Maximieren-Schaltfläche wählen wir nicht aus.

Wir haben die Info im Systemmenü und die Unterstützung für ActiveX-Steuerelemente beibehalten. Dazu kommen wir später. Zunächst wollen wir zwei Eingaben multiplizieren. Sie landen direkt bei der Dialogfeld-Ressource, die wir nun mit Steuerelementen aus dem Dialog-Editor im Werkzeugkasten "bestücken" werden: 

Richten Sie nun das Dialogfeld aus. Mit Strg+T können Sie sich jeweils eine Probeansicht zeigen lassen und solange korrigieren, bis Ihnen das Dialogfeld gefällt
Jetzt sollte die Ressource in etwa wie folgt aussehen:

Abb. 2.1: Die Dialogfeld-Ressource unseres Projektes

Sie können die Datei "Multiplizieren.rc" mit dem Texteditor öffnen, um das Ressourcenskript im Bereich "Dialog" zu analysieren. Sie finden hier zwei Dialogfelder:

IDD_ABOUTBOX DIALOGEX 0, 0, 170, 62
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Info über Multiplizieren"
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
    ICON            IDR_MAINFRAME,IDC_STATIC,14,14,21,20
    LTEXT           "Multiplizieren, Version 1.0",IDC_STATIC,42,14,114,8,SS_NOPREFIX
    LTEXT           "Copyright (C) 2015",IDC_STATIC,42,26,114,8
    DEFPUSHBUTTON   "OK",IDOK,113,41,50,14,WS_GROUP
END

IDD_MULTIPLIZIEREN_DIALOG DIALOGEX 0, 0, 277, 113
STYLE DS_SETFONT | DS_FIXEDSYS | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
CAPTION "Multiplizieren"
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
    DEFPUSHBUTTON   "OK",IDOK,220,24,50,14
    PUSHBUTTON      "Abbrechen",IDCANCEL,220,39,50,14
    EDITTEXT        IDC_EDIT1,103,24,77,14,ES_AUTOHSCROLL
    EDITTEXT        IDC_EDIT2,103,43,77,14,ES_AUTOHSCROLL
    EDITTEXT        IDC_EDIT3,103,78,77,14,ES_AUTOHSCROLL
    LTEXT           "Eingabe 1",IDC_STATIC,33,27,33,8
    LTEXT           "Eingabe 2",IDC_STATIC,33,45,33,8
    LTEXT           "Ausgabe",IDC_STATIC,33,80,29,8
    PUSHBUTTON      "Ausgabe",IDC_BUTTON1,220,77,50,14
END

Anhand der Koordinaten können Sie überprüfen, ob die Elemente genau passen. Wenn nicht können Sie grafisch oder im Texteditor korrigierend eingreifen.

Nun speichern / kompilieren / starten (Strg + S, Strg + F5) Sie bitte, damit wir das bisher Erzeugte untersuchen können. Das Erscheinungsbild unserer Anwendung ist wie in Abb. 2.1. 

Bitte beachten Sie, daß in der Ressource (Abb. 2.1) das bunte MFC-Icon nicht angezeigt wird! Das finden Sie auch nicht im Ressourcenskript Multiplizieren.rc, sondern in Multiplizieren.ico. Wenn Sie in der ausgeführten Anwendung auf das Icon klicken, erscheint das Systemmenü, das nun auch zu unserer "AboutBox" (Infofeld) führt:

Abb. 2.2: Das Systemmenü beinhaltet den Menüpunkt "Info über ..."

Wählen Sie den Menüeintrag "Info über ..." aus, dann erscheint das zweite Dialogfeld, dessen textliche Definition Sie bereits im Ressourcenskript bei IDD_ABOUTBOX (s.o.) gefunden haben und die Sie dort auch ändern könnten:

Abb. 2.3: "Info über ..."-Box, wie sie vom MFC-Anwendungs-Assistenten erzeugt wird

Dieses Dialogfeld (eigentlich handelt es sich hier mehr um einen Monolog als einen Dialog, denn Sie können ja keine Eingaben machen) mußten Sie nicht selbst entwerfen. Der MFC-Anwendungsassistent erzeugt dies standardisiert. Sie können die Ressource IDD_ABOUTBOX natürlich verändern (entweder im Ressourcenskript oder im grafischen Ressourcen-Editor). Wenn Sie zum Aufruf dieses Info-Feldes den entsprechenden Menüpunkt wählen, erzeugen Sie eine Nachricht, die folgende Member-Funktion der Klasse CMultiplizierenDlg startet:

void CMultiplizierenDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
    if ((nID & 0xFFF0) == IDM_ABOUTBOX)
    {
        CAboutDlg dlgAbout;
        dlgAbout.DoModal();
    }
    else
    {
        CDialogEx::OnSysCommand(nID, lParam);
    }
}

Zum Verständnis wichtig sind folgende Zeilen:

    CAboutDlg dlgAbout;
    dlgAbout.DoModal();

Hier wird das Objekt dlgAbout der von der MFC-Fenster-Klasse CDialog abgeleiteten Klasse CAboutDlg erstellt. Die Funktion DoModal() zeigt dieses Dialogfenster dann auf dem Bildschirm, bis wir mit OK, "X" oder Alt + F4 dieses Fenster schließen.

An diesem Beispiel erkennen Sie im Code, wie ein Dialogfenster aus dem Menü eines anderen Fensters aufgerufen wird. Die modale Darstellung eines Fensters bedeutet, daß Sie zunächst auf dieses Fenster reagieren müssen, bevor Sie mit der Anwendung weiterfahren können.

Kehren wir nun zu unserer Anwendung zurück. Was natürlich noch nicht funktioniert, ist unsere Multiplikation. Wenn Sie auf den Knopf "Ausgabe" drücken, passiert nichts.

Sie können in alle drei Eingabefelder schreiben. Wenn Sie im grafischen Editor in einem Eingabefeld rechts klicken, dann kommen Sie zu den Eigenschaften.

Wir gehen jetzt schrittweise vor:

Im Ressourcenskript wurde hier ES_READONLY zugefügt. Die Zeile lautet nun:

EDITTEXT        IDC_EDIT3,103,78,77,14,ES_AUTOHSCROLL | ES_READONLY

void CMultiplizierenDlg::OnBnClickedButton1()
{
    // TODO: Fügen Sie hier Ihren Kontrollbehandlungscode für die Benachrichtigung ein.
}

Wir wechseln nun zum Klassen-Assistenten (Strg+Shift+X):

Für IDC_BUTTON1 benötigen wir momentan keine Variable, da wir ja bereits über unsere Funktion OnBnClickedButton1 verfügen, die wir nur noch mit Inhalt füllen müssen. Für die drei Eingabefelder benötigen wir jedoch Member-Variablen, die den Inhalt der Felder repräsentieren können.


Abb. 2.4: Der MFC-Klassen-Assistent hilft bei der Deklaration der Steuerelement-Member-Variablen



Abb. 2.5: Der "Member-Variable hinzufügen/löschen" - Dialog des MFC-Klassen-Assistenten

Lassen Sie sich hier Zeit. Denn Sie müssen nun eigene Eingaben machen, deren spätere Korrektur nicht leicht erfolgt, da der Assistent Ihre Eingaben an vielen Stellen verwendet. Geben Sie bitte den Variablennamen m_Eingabe1 ein. Unter Kategorie finden Sie "Value" und "Control ". Ändern Sie auf Value (Wert), da wir eine Variable brauchen, die eine Zahl speichern soll. 

In dem Listenfeld Variablentyp finden Sie eine reichhaltige Auswahl: CString, int, UINT, long, LONGLONG, DWORD, float, double, BYTE, short, BOOL, COleDateTime, COleCurrency.

Wählen Sie den Fließkomma-Typ "double" aus und geben Sie OK ein.

Achten Sie auf die Möglichkeit, (im unteren Bereich des Dialogs) eine Bereichsprüfung einzugeben. Bezüglich der Multiplikation macht dies keinen großen Sinn, wir wollen jedoch diese Möglichkeit nutzen, um später zu sehen, wie dies in Code-Form implementiert wird. Geben Sie daher einfach zwei Zahlen ein, die Ihnen zusagen. Wiederholen Sie die Prozedur jetzt für IDC_EDIT2 und IDC_EDIT3. Wählen Sie double m_Eingabe2 und double m_Ausgabe.


Abb. 2.6: Wir haben drei Member-Variablen hinzugefügt

void CMultiplizierenDlg::OnBnClickedButton1()
{
    UpdateData(TRUE);                        // Felder --> Variablen
    m_Ausgabe = m_Eingabe1 * m_Eingabe2;
    UpdateData(FALSE);                       // Variablen --> Felder
}

Prägen Sie sich diese Kombination gut ein:

UpdateData( TRUE ) transferiert Daten aus den Steuerelementen in die Variablen.
UpdateData( FALSE ) transferiert Daten aus den Variablen in die Steuerelemente.

Das ist schon alles. Nach dem Kompilieren können Sie die Anwendung hoffentlich erfolgreich testen:

Abb. 2.7: Die Anwendung funktioniert

Jetzt bleibt noch die Frage: Was ist der Beitrag der MFC? An welchen Stellen findet man den entsprechenden Programm-Code? Wir analysieren hierzu die Klassendefinition und die Member-Funktionen der Klasse CMultiplizierenDlg. Dies hilft nun auch, um zu sehen, ob alles wie gewünscht umgesetzt wurde:

In der Klassendefinition class CMultiplizierenDlg : public CDialogEx{...} findet man die Deklarationen der Member-Variablen für die drei Eingabefelder:

public:
    afx_msg void OnBnClickedButton1();
    double m_Eingabe1;
    double m_Eingabe2;
    double m_Ausgabe;

Im Konstruktor CMultiplizierenDlg::CMultiplizierenDlg(...) finden Sie die Initialisierung der drei Member-Variablen. Der Assistent hat die Variablen im C++ Konstruktorstil auf null gesetzt:

CMultiplizierenDlg::CMultiplizierenDlg(CWnd* pParent /*=NULL*/)
    : CDialogEx(IDD_MULTIPLIZIEREN_DIALOG, pParent)
    , m_Eingabe1(0)
    , m_Eingabe2(0)
    , m_Ausgabe(0)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

Die Member-Funktion CMultiplizierenDlg:: DoDataExchange(...) koordiniert den Datenaustausch unserer Member-Variablen mit den Dialogfeldelementen. DDX steht hier für Dialog Data Exchange und DDV für Dialog Data Validation. In der Funktion DDX_Text werden die ID des Eingabefeldes und die zugehörige Member-Variable verknüpft. In der Funktion DDV_MinMaxDouble wird die Member-Variable "validiert", das heißt auf die Einhaltung der Grenzwerte überprüft:

void CMultiplizierenDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Text(pDX, IDC_EDIT1, m_Eingabe1);
    DDX_Text(pDX, IDC_EDIT2, m_Eingabe2);
    DDX_Text(pDX, IDC_EDIT3, m_Ausgabe);
}

Auslöser für die Member-Funktion DoDataExchange ist die Member-Funktion UpdateData(). Beide Funktionen gehören übrigens zur MFC-Klasse CWnd. Die exakte Syntax für UpdateData lautet:

BOOL UpdateData( BOOL bSaveAndValidate = TRUE );

Wenn Sie mehr über DDX und DDV wissen möchten, dann schauen Sie in die "Technical Note 26"

Bei der Einrichtung der Member-Variablen bezüglich unserer Eingabefelder hatten Sie die Auswahl zwischen Value und Control. Während Variablen der Kategorie "Value" zum Speichern von Informationen gedacht sind, bietet die Kategorie "Control" die Möglichkeit, auf das entsprechende Steureelement einzuwirken.

Dies probieren wir sofort in der Praxis. Kehren Sie zu unserem Multiplikations-Dialog zurück. Öffnen Sie bitte mit Strg+Shift+X den Klassen-Assistenten und wählen Sie in der Ansicht  Membervariablen IDC_EDIT3 aus. In das sich öffnende Dialogfeld geben Sie nun folgendes ein:

Abb. 2.8: Ein Eingabefeld erhält eine Member-Variable der Kategorie Control

Zur Unterscheidung von der Value-Variablen m_Ausgabe fügen Sie das Präfix "ctl" (control) hinzu und erhalten m_ctlAusgabe. Sie sehen, dass ein Objekt des Typs CEdit erzeugt wurde. Nachdem Sie das Dialogfeld und den Klassen-Assistenten jeweils mit OK verabschiedet haben, findet man in der Klassendefinition jetzt die neue Member-Variable:

public:
    afx_msg void OnBnClickedButton1();
    double m_Eingabe1;
    double m_Eingabe2;
    double m_Ausgabe;    
    CEdit m_ctlAusgabe;

Wie wenden Sie dieses Objekt vom Typ CEdit an? Zunächst nutzen Sie es zur Visualisierung des Datenaustauschs. Fügen Sie in der Member-Funktion DoDataExchange folgendes ein:

void CMultiplizierenDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Text(pDX, IDC_EDIT1, m_Eingabe1);
    DDX_Text(pDX, IDC_EDIT2, m_Eingabe2);
    DDX_Text(pDX, IDC_EDIT3, m_Ausgabe);
    DDX_Control(pDX, IDC_EDIT3, m_ctlAusgabe);

    m_ctlAusgabe.ShowWindow(SW_MINIMIZE);
    m_ctlAusgabe.ShowWindow(SW_NORMAL);
}

Die Steuerelemente im Dialogfeld sind von der MFC-Klasse CWnd abgeleitet. Damit besitzen diese Elemente auch die entsprechenden Member-Funktionen. Die Funktion ShowWindow(...) kennen Sie bereits. Wenn Sie nun die Anwendung starten, können Sie den DDX/DDV-Vorgang auf spielerische Weise optisch darstellen, da das Fenster am unteren Rand des Dialogfeldes abgelegt (SW_MINIMIZE) und anschließend wieder hergestellt (SW_NORMAL) wird. Dieser Vorgang verläuft relativ langsam und kann daher leicht mitgezählt werden.

Wenn Sie nun die Schaltfläche "Ausgabe" betätigen, erkennen Sie, daß der DDX/DDV-Vorgang zweimal stattfindet.
Dies haben wir in der Funktion OnBnClickedButton1 durch zweifachen Aufruf von UpDateData programmiert.

void CMultiplizierenDlg::OnBnClickedButton1()
{
    UpdateData(TRUE);                        // Felder --> Variablen
    m_Ausgabe = m_Eingabe1 * m_Eingabe2;
    UpdateData(FALSE);                       // Variablen --> Felder
}

Verabschieden Sie den gesamten Dialog mit der OK-Schaltfläche, dann sehen Sie, dass der DDX/DDV-Vorgang einmal stattfindet.

Betätigt man "Abbrechen", dann erfolgt kein DDX/DDV-Vorgang.

Für diese Unterscheidung von IDOK und IDCANCEL sorgt MFC.

Mit solchen kleinen Tricks kann man Abläufe innerhalb eines Programmes sichtbar machen.

Wollen Sie sofort weitere spezifische Member-Funktionen zur Steuerung von Eingabefeldern sehen, dann schauen Sie im MSDN unter "CEdit Member Functions". Wie so oft, hat man eine reichhaltige Auswahl. Probieren Sie ruhig aus.

Nun wollen wir weitere typische Elemente des Dialogfeldes kennen lernen.


2.2 Statisches Textfeld (Text Static Control) und Timer

Das statische Textfeld (Text Static Control) wirkt auf den ersten Blick recht unscheinbar, ist jedoch ein wesentlicher Baustein in der Dialogfeld-Programmierung zur Information und Steuerung des Benutzers. Dieses Element ist ein sehr geeignetes Hilfsmittel zur Textausgabe. "Statisch" klingt, als ob man einen im Ressourcenskript vorgegebenen Text nicht mehr ändern könnte. Die Veränderbarkeit ist jedoch auch während des Programmablaufs möglich! Es steht nur dem Benutzer nicht zur direkten Manipulation zur Verfügung. 

Wir werden im nachfolgenden Beispiel zum Vergleich Texte auf folgende Arten ausgeben:

Als Programmieraufgabe nehmen wir die zeitgesteuerte Darstellung des klassischen ANSI-Codes, der die Beziehung zwischen den Integer-Zahlen 0 bis 255 und den entsprechenden Zeichen (characters) regelt. Bauen Sie mittels Anwendungs-Assistent eine dialogfeldbasierende Anwendung mit Namen ANSI auf. Sie benötigen im Dialogfeld zwei Eingabefelder (Edit Control), zwei statische Textfelder (Static Text) und eine Schaltfläche (Button).

Die von der MFC erzeugten Standards "OK", "Abbrechen" und das vorgegebene Textfeld (TODO ...) löschen Sie bitte. Auf Minimize-/Maximize-Button verzichten wir (also nicht auswählen). Nach dem Speichern, Kompilieren und Starten sollte das wie folgt aussehen:

Abb. 2.9: Ein Dialogfeld mit zwei Eingabefeldern, zwei statischen Textfeldern und einer Schaltfläche

Wir werden diese Anwendung in mehreren Schritten zum Leben erwecken, damit Sie die Übersicht behalten:

BEGIN
    EDITTEXT        IDC_EDIT1,43,26,101,14,ES_AUTOHSCROLL
    EDITTEXT        IDC_EDIT2,174,26,101,14,ES_AUTOHSCROLL
    LTEXT           "Statisch",IDC_STATIC1,43,59,101,14
    LTEXT           "Statisch",IDC_STATIC2,174,59,101,14
    PUSHBUTTON      "Button1",IDC_BUTTON1,45,123,231,14
END

Abb. 2.10: Die beiden Eingabefelder erhalten Member-Variablen, hier am Bsp. IDC_EDIT1 

Zunächst werden wir die beiden Eingabefelder zur Textausgabe einsetzen. Als Funktionsauslöser benützen wir die Schaltfläche:

void CANSIDlg::OnBnClickedButton1()
{
    c = 0; //Zählvariable auf null setzen
    SetTimer(1, 500, NULL); //Zeitgeber namens ID 1 starten, Zeitintervall: 0,5 Sekunden
}
Die Funktion OnTimer wird alle 500 Millisekunden ausgeführt. Wir setzen sie ein, um die Variable c von 0 bis 255 hoch zu zählen. Diese Variable soll im ersten Feld als String ausgegeben und im zweiten Feld in ANSI-Code umgewandelt werden. Geben Sie folgenden Programm-Code ein:

void CANSIDlg::OnTimer(UINT_PTR nIDEvent)
{
    c++; //Zählvariable hochsetzen
    CString strZahl, strAnsi;
    strZahl.Format(L"Zahl: %d", c);
    m_strEdit1 = strZahl;
    m_strEdit2 = (wchar_t)c;
    UpdateData(FALSE);

    CDialogEx::OnTimer(nIDEvent);
}

Das funktioniert jedoch noch nicht. Wir müssen die Variable c zuerst deklarieren. Wissen Sie noch wo und wie? Also dann:

Dies bewirkt:

in der Klassendefinition:

private:
    int c;

im Konstruktor:
CANSIDlg::CANSIDlg(CWnd* pParent /*=NULL*/)
    : CDialogEx(IDD_ANSI_DIALOG, pParent)
    , m_strEdit1(_T(""))
    , m_strEdit2(_T(""))
    , c(0)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

Wenn Sie jetzt starten, sollte die Anwendung auf Knopfdruck mit der Ausgabe des ANSI-Codes starten.

Abb. 2.11: Die Anwendung spult den ANSI-Code Timer-gesteuert ab.

Zunächst werden wir den Code in OnTimer(...) in seiner Funktion analysieren:

c++;

Dieser hübsche Befehl hat der Sprache C++ (C-plus-plus) den Namen gegeben. Das nachgestellte ++ bedeutet, daß die entsprechende Variable inkrementiert (erhöht) wird, in diesem Fall jeweils um die Zahl eins. Wir nutzen also die Nachricht WM_TIMER über OnTimer(...) zum Hochzählen von c (c steht für counter).

CString strZahl, strAnsi;
strZahl.Format(L"Zahl: %d", c);

Zwei Variablen der MFC-Klasse CString werden deklariert (heute bietet C++ natürlich eine eigene string-Klasse, aber wir können auch die MFC-Klasse nutzen). Wenn wir eine Zahl als solche in Textform darstellen wollen, können wir nicht den Zähler c vom Typ int der String-Variablen m_strEdit1 zuweisen, sondern benötigen zuerst die formatierte Umwandlung in einen String. Dies erledigt die überaus nützliche CString-Member-Funktion Format(...).

m_strEdit1 = strZahl;
m_strEdit2 = (wchar_t)c;
UpdateData( FALSE );

Jetzt können wir den formatierten String der Eingabefeld-Stringvariable m_strEdit1 zuweisen. In der nächsten Zeile unternehmen wir bewusst etwas Fragwürdiges. Wir weisen die Integervariable c umgewandelt in den Typ wchar_t der Eingabefeld-Stringvariable m_strEdit2 zu. Hierbei erfolgt eine Umwandlung der Zahl (Typ integer) in den entsprechenden ANSI-Code, ab 256 wird dies sogar zu Unicode.

Damit die Variablen auch tatsächlich an die Felder übertragen werden, folgt der wichtige Transfer-Befehl UpdateData( FALSE ). Das Programm läuft und läuft und läuft ...

Daher stoppen wir es z.B. beim Wert 255 (Sie können dies gerne variieren):

void CANSIDlg::OnTimer(UINT nIDEvent)
{
    ...
    UpdateData( FALSE );
    if ( c == 255 )
        KillTimer(1); //Timer mit der ID 1 zerstören

    CDialog::OnTimer(nIDEvent);
}

Wer Zeitgeber mit SetTimer(ID,...) startet, sollte diese ordnungsgemäß mit KillTimer(ID) beenden. Wir erledigen dies, wenn c die Zahl 255 erreicht hat. Wichtig: doppeltes Gleichheitszeichen in der Klammer der if-Kontrollstruktur. Ein einfaches Gleichheitszeichen ist nämlich eine Zuweisung! Dies ist ein häufiger logischer Fehler, den man nicht leicht findet, da das Programm technisch läuft, jedoch logisch nicht macht, was es soll.

Nun werden Sie die beiden anderen Wege zur Textausgabe einbauen. Zunächst ordnen wir den beiden ID’s der statischen Textfelder entsprechende Member-Variablen zu:

Abb. 2.12: Den beiden statischen Textfelder werden String-Variablen zugeordnet

Danach verändern/ergänzen Sie den Code bitte wie folgt:

void CANSIDlg::OnTimer(UINT_PTR nIDEvent)
{
    CClientDC dc(this);
    c++; //Zählvariable hochsetzen
    CString strZahl, strAnsi;
    strZahl.Format(L"Zahl: %d", c);
    m_strEdit1 = m_strStatic1 = strZahl;
    m_strEdit2 = m_strStatic2 = (wchar_t)c;
    UpdateData(FALSE);
    dc.TextOutW(70, 200, "   ");
    dc.TextOutW(70, 200, strZahl);
    dc.TextOutW(210, 200, "       ");
    dc.TextOutW(210, 200, m_strStatic2);
    if (c == 255)
        KillTimer(1); //Timer mit der ID 1 zerstören

    CDialogEx::OnTimer(nIDEvent);
}

Die x- und y-Koordinaten in TextOutW(...) müssen Sie evtl. auf die Abmessungen Ihres Dialogfeldes anpassen. Die Anwendung sollte wie folgt funktionieren:

Abb. 2.13: Den beiden statischen Textfelder werden String-Variablen zugeordnet

Die Eingabefelder können während des Programmablaufs vom Anwender editiert werden. Diese Eingaben werden natürlich ständig vom Programm überschrieben. Sie wissen natürlich sofort, wie man dies beheben kann: Einfach im Ressourcen-Editor unter Eigenschaften die Option "Read Only" aktivieren. Üben und testen Sie es bitte.

Angenommen, Sie wollen dies vom Programm aus erledigen. Wie geht dies? Dann benötigen wir zusätzlich eine Variable vom Typ CEdit als Kontrollvariable für die Eingabefelder. Also den Klassen-Assistent starten und die Variablen vom Typ CEdit per Eingabe erzeugen:

Abb. 2.14: Wir erzeugen die CEdit-Variablen m_ctlEdit1 und m_ctlEdit2

Sie können die beiden Variablen nun einsetzen, um die CEdit-Member-Funktion SetReadOnly(...) einzusetzen. Sie müssen keinen Parameter eingeben, da er per default als TRUE festgelegt wurde.

void CANSIDlg::OnTimer(UINT_PTR nIDEvent)
{
    m_ctlEdit1.SetReadOnly();
    m_ctlEdit2.SetReadOnly();
    ...

Zurücksetzen auf "nicht schreibgeschützt" kann man mit SetReadOnly( FALSE ).

Sie sollten zum vertieften Verständnis Ihrer Dialog-Ressource einen Blick in das Ressourcenskript (File ANSI.rc im Projektmappen-Explorer) werfen. Es dürfte etwa wie folgt aussehen:

IDD_ANSI_DIALOG DIALOGEX 0, 0, 320, 152
STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
CAPTION "ANSI"
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
    EDITTEXT        IDC_EDIT1,43,26,101,14,ES_AUTOHSCROLL
    EDITTEXT        IDC_EDIT2,174,26,101,14,ES_AUTOHSCROLL
    LTEXT           "Statisch",IDC_STATIC1,43,59,101,14
    LTEXT           "Statisch",IDC_STATIC2,174,59,101,14
    PUSHBUTTON      "ANSI-Code starten",IDC_BUTTON1,45,123,231,14
END

 


2.3 Schieberegler, Optionsfeld und Fortschrittsanzeige

Wir werden im nächsten Beispiel weitere wichtige Elemente eines Dialogfeldes kennenlernen. Zusätzlich bringen wir mit SendMessage(...) etwas Farbe ins Spiel.

Bauen Sie mittels Anwendungs-Assistent eine dialogfeldbasierende Anwendung mit Namen und Titel "Slider" auf (kein Minimize-/Maximize-Button).
"OK", "Abbrechen" und das vorgegebene Textfeld ("TODO: ...") löschen Sie bitte.

Wir benötigen in unserem Dialogfeld:


Nach dem Speichern, Kompilieren und Starten sollte das wie folgt aussehen:

Abb. 2.15: Dialogfeld mit Fortschrittsanzeige, Schieberegler, Optionsfeldern, Eingabefeld und Schaltfläche

Schauen Sie sich zunächst die Dialogressource an, damit Sie die englischen Begriffe kennen:

IDD_SLIDER_DIALOG DIALOGEX 0, 0, 320, 161
STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
CAPTION "Slider"
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
    GROUPBOX        "Farbauswahl",IDC_STATIC,64,62,125,81
    CONTROL         "Rot",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON,77,81,93,14
    CONTROL         "Grün",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON,77,103,93,14
    CONTROL         "Blau",IDC_RADIO3,"Button",BS_AUTORADIOBUTTON,77,125,93,14
    PUSHBUTTON      "Schaltfläche",IDC_BUTTON1,209,107,66,35
    EDITTEXT        IDC_EDIT1,209,66,66,12,ES_AUTOHSCROLL
    CONTROL         "",IDC_SLIDER1,"msctls_trackbar32",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,57,37,202,17
    CONTROL         "",IDC_PROGRESS1,"msctls_progress32",WS_BORDER,59,7,202,18
END

Als nächstes erzeugen Sie in der Klasse CSliderDlg folgende Member-Variablen mittels Klassen-Assistent:
 
Steuerlement-Ids Typ Element (Member-Variable)
IDC_BUTTON1 CButton m_ctlButton
IDC_EDIT1 CString m_strEdit1
IDC_EDIT1 CEdit m_ctlEdit1
IDC_PROGRESS1 CProgressCtrl m_ctlProgress1
IDC_SLIDER1 CSliderCtrl m_ctlSlider1
IDC_SLIDER1 int m_intSlider1

Gehen Sie in den Ressourcen-Editor und doppelklicken Sie auf die Schaltfläche, um die Member-Funktion OnBnClickedButton1() hinzuzufügen.
Geben Sie folgenden Programm-Code ein:

void CSliderDlg::OnButton1()
{
    UpdateData(TRUE);
    m_ctlProgress1.SetPos(m_intSlider1);     //:1
    CString str;                             //:2
    str.Format(L"%d", m_intSlider1);
    m_strEdit1 = str;
    UpdateData(FALSE);

}

Die wichtige Paarung UpdateData(TRUE) und UpdateData(FALSE) kennen Sie bereits. Diese Befehle sorgen für den notwendigen Datentransfer zwischen den Steuerelementen und den entsprechenden Member-Variablen.

In Zeile //:1 wird der Positionswert des Schiebereglers an die Fortschrittsanzeige übergeben. Beide Elemente haben einen Default-Range von 0 bis 100.

Ab Zeile //:2 wird der Integer-Wert der Schiebereglerposition als String formatiert und der Stringvariablen m_strEdit1 übergeben.

Nun gehen Sie zur Programmierung der Optionsfelder über. Der englische Begriff ist "Radiobutton". Er stammt von den Wellenbereichstasten (LW, MW, UKW) älterer Radiogeräte. Wählte man eine Taste an, wurden die anderen mechanisch ausgerastet, sodass immer nur eine Taste gedrückt sein konnte. Das funktioniert in unserem virtuellen Tastenfeld durch die gemeinsame Anordnung in einem Gruppenfeld.

Lassen Sie uns nun bei der Aktivierung eines Radiobuttons eine Funktion auslösen. Die Programmierung ist einfach. Wir können genau so vorgehen wie bei einer Schaltfläche: In der Ressource doppelklicken und den Funktionsnamen bestätigen. Wiederholen Sie dies bitte für alle drei Optionsfelder. Als Programm-Code geben Sie bitte folgendes ein:

void CSliderDlg::OnBnClickedRadio1()
{
    m_ctlProgress1.SetBarColor(RGB(255, 0, 0));
}


void CSliderDlg::OnBnClickedRadio2()
{
    m_ctlProgress1.SetBarColor(RGB(0, 255, 0));
}


void CSliderDlg::OnBnClickedRadio3()
{
    m_ctlProgress1.SetBarColor(RGB(0, 0, 255));
}

Schauen wir zunächst einmal, was nach dem Speichern, Kompilieren und Starten passiert. Wie Sie sehen, haben Sie nun drei Funktionen OnRadio1, OnRadio2 und OnRadio3, die auf unsere Optionsfelder direkt reagieren.

Sobald Sie auf ein Optionsfeld klicken, ändert sich die Farbe der Fortschrittsanzeige entsprechend dem jeweiligen RGB-Wert in unserem Programm (Sie müssen natürlich einen Schieberegler-Wert größer null auswählen). RGB bedeutet übrigens Rot-Grün-Blau und funktioniert wie die Farbmischung im Farbfernsehgerät. Sie können für diese drei Grundfarben Werte zwischen 0 und 255 angeben. Wenn Sie z.B. Gelb erzeugen wollen, geben Sie RGB( 255, 255, 0 ) ein, d.h. Sie setzen die Komplementärfarbe Blau auf null. RGB( 255, 255, 255 ) ergibt weiß und RGB( 0, 0, 0 ) schwarz.

Wichtig:
Sie sehen nur grün, keine Farbveränderung? Dann hilft nur Folgendes: Systemsteuerung - Darstellung und Anpassung - Anpassung: Windows - klassisch

Diese Funktion SetBarColor(...) wirkt nur bei diesem Theme. Zumindest haben Sie nun - vielleicht seit langer Zeit - das klassische Windows erlebt.

Das Thema wird hier behandelt, falls Sie auch auch unter Windows 7/8 die Farbe verändern möchten, was mit SetBarColor() dort so nicht vorgesehen ist.

Lassen Sie uns nun die Ressourceneigenschaften bei Fortschrittsanzeige und Schieberegler verändern. Bitte setzen Sie im Ressourcen-Editor unter Eigenschaften:

Bei der Fortschrittsanzeige die Option "Smooth", "Smooth Reverse" und "Static Edge" auf True.
Beim Schieberegler die Option "Client Edge", "Auto Ticks", "Tick Marks" und "Point: Unten/Rechts" (erhöhen Sie die Breite etwas, damit Sie die "Ticks" sehen und richten Sie die Elemente links aus)

Nach dem Kompilieren/Ausführen sieht das dann wie folgt aus:

Abb. 2.16: Die Fortschrittsanzeige reagiert nun auf kleine Veränderungen. Der Schieberegler besitzt Teilstriche. Die Farbe des Fortschrittsbalkens funktioniert nur in der Ansicht Windows - klassisch!

Die Option Teilstriche setzt automatisch einen Teilstrich beim Min- und Max-Wert des Schiebereglers. Mit der Control-Variable m_ctlSlider1 kann man mit der Member-Funktion SetTic(...) beliebig weitere Teilstriche setzen. Wenn wir z.B. bei 50 und 75 einen weiteren Teilstrich wollen, geben wir ein:

void CSliderDlg::OnBnClickedButton1()
{
    
UpdateData(TRUE);
    m_ctlProgress1.SetPos(m_intSlider1);     //:1
    CString str;                             //:2
    str.Format(L"%d", m_intSlider1);
    m_strEdit1 = str;
    UpdateData(FALSE);


    
m_ctlSlider1.SetTic(50);
    m_ctlSlider1.SetTic(75);

}

 


2.4 Kontrollkästchen, Listenfeld, Kombinationsfeld und Farbe

In diesem Beispiel werden wir unser Wissen über Dialogfeld-Elemente erweitern. Zusätzlich bringen wir mehr Farbe ins Dialogfeld. Bauen Sie mittels Anwendungs-Assistent eine dialogfeldbasierende Anwendung mit Namen Kap2_4 auf. Wir benötigen in unserem Dialogfeld zwei statische Textfelder, zwei Kontrollkästchen, ein Listenfeld, ein Kombinationsfeld und eine Schaltfläche. Die Buttons "OK" und "Abbrechen" sowie das vorgegebene Textfeld löschen Sie wie gewohnt. Nach dem Speichern, Kompilieren und Starten sollte das ungefähr wie folgt aussehen:

Abb. 2.17: Kontrollkästchen, Listenfeld und Kombinationsfeld stellen sich vor

Zunächst ein Blick ins Ressourcenskript: neue Elemente sind LISTBOX, COMBOBOX und BS_AUTOCHECKBOX

IDD_KAP2_4_DIALOG DIALOGEX 0, 0, 242, 200
STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
CAPTION "Kap2_4"
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
CONTROL "nur Hidden Files", IDC_CHECK1, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, 30, 111, 72, 11
CONTROL "nur Exe-Files", IDC_CHECK2, "Button", BS_AUTOCHECKBOX | WS_TABSTOP, 30, 131, 72, 11
LISTBOX IDC_LIST1, 149, 20, 74, 173, LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP
COMBOBOX IDC_COMBO1, 7, 20, 128, 87, CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP
PUSHBUTTON "Files anzeigen", IDC_BUTTON1, 29, 159, 69, 16
CTEXT "Verzeichnis", IDC_STATIC, 7, 7, 128, 11
CTEXT "Dateien", IDC_STATIC, 153, 7, 64, 9
END


Das Kombinationsfeld (Combobox) ist eine Kombination aus Eingabefeld und Listenfeld (Listbox). Das Kontrollkästchen gehört zur MFC-Klasse CButton. Insgesamt gibt es vier Ausprägungen dieses Elementes, die durch Stilattribute unterschieden werden:

Im nachfolgenden Beispiel werden wir über das Kombinationsfeld eine Verzeichnis-Auswahl treffen. Das Listenfeld wird dann auf Knopfdruck die in dem Verzeichnis enthaltenen Dateien ausgeben.

Im nächsten Schritt erzeugen Sie bitte in der Klasse CKap2_4Dlg folgende Member-Variablen mittels Klassen-Assistent (Strg+Shift+X):

 
Steuerlement-IDs Typ Element (Member-Variable)
IDC_CHECK1 BOOL m_bCheck1
IDC_CHECK2 BOOL m_bCheck2
IDC_COMBO1 CString m_strCombo1
IDC_COMBO1 CComboBox m_ctlCombo1
IDC_ LIST1 CString m_strList1
IDC_LIST1 CListBox m_ctlList1

Wir werden nun die Auswahlliste der Kombinationsliste mit einigen Verzeichnissen füllen. Hierzu klicken Sie mit der rechten Maustaste auf die Ressource und wählen Eigenschaften.
Dort können Sie unter Verhalten - Data durch Semikolon getrennt Listeneinträge durchführen.

Geben Sie dort ein: C:;C\:Windows;C\:Windows\System32
oder was Sie sonst sehen möchten.

Abb. 2.18: Die Auswahlliste des Kombinationsfeldes wird (mittels Rechtsklick) im Dialog Eigenschaften - Verhalten - Data gefüllt

Gehen Sie in den Ressourcen-Editor und doppelklicken Sie auf die Schaltfläche "Files anzeigen", um die Member-Funktion OnBnClickedButton1() hinzuzufügen. Geben Sie folgenden Programm-Code ein:

void CKap2_4Dlg::OnBnClickedButton1()
{  
    m_ctlList1.ResetContent();
    UpdateData(TRUE);  //Felder ---> Variablen
    if ((m_bCheck1 == TRUE)  && (m_bCheck2 == TRUE))  m_ctlList1.Dir(DDL_HIDDEN | DDL_EXCLUSIVE, m_strCombo1 + L"\\*.exe");
    if ((m_bCheck1 == TRUE)  && (m_bCheck2 == FALSE)) m_ctlList1.Dir(DDL_HIDDEN | DDL_EXCLUSIVE, m_strCombo1 + L"\\*.*");
    if ((m_bCheck1 == FALSE) && (m_bCheck2 == TRUE))  m_ctlList1.Dir(DDL_DIRECTORY, m_strCombo1 + L"\\*.exe");
    if ((m_bCheck1 == FALSE) && (m_bCheck2 == FALSE)) m_ctlList1.Dir(DDL_DIRECTORY, m_strCombo1 + L"\\*.*");
}

DDL_ARCHIVE inclusive "archived" Dateien.
DDL_DIRECTORY inclusive Unterverzeichnisse (Namen in eckigen Klammern).
DDL_DRIVES inclusive Laufwerke. Auflistung als [-x-] mit x als Laufwerkbuchstaben.
DDL_HIDDEN inclusive versteckter Dateien.
DDL_READONLY inclusive "read-only" (schreibgeschützter) Dateien.
DDL_READWRITE inclusive "read-write" Dateien.
DDL_SYSTEM inclusive "system files".
DDL_EXCLUSIVE nur Dateien mit den angegeben Attributen.
Achten Sie bei der Eingabe vor allem auf die doppelten Gleichheitszeichen in den if-Strukturen.
Sie sehen, dass Sie mit dem Kombinations- und Listenfeld in Verbindung mit Kontrollkästchen mächtige Werkzeuge zur Dialoggestaltung in Händen halten.
Hier ein Beispiel mit exe-Dateien im Verzeichnis von Windows7:

Abb. 2.19: Die Anwendung zeigt Dateien mittels der Funktion CListBox::Dir(...) im Listenfeld

Falls die ListBox zu schmal ist, können Sie diese und das Dialogfeld der Anwendung im grafischen Ressourcen-Editor entsprechend anpassen.

Lassen Sie uns die Aufgabe anpacken, eine Aktion zu starten, wenn der Anwender auf einen Dateinamen doppelklickt, denn das könnte die erste intuitive Handlung sein, wenn sich eine Liste füllt.

Wie gehen wir das Thema an? Wir benötigen eine Funktion, die auf die "Doppelklick"-Nachricht reagiert. Das ist ein Fall für unseren Klassen-Assistenten. Also Strg+Shift+X gedrückt.

Unter der Registerkarte "Befehle" wählen Sie im Feld Objekt-IDs den Eintrag IDC_LIST1 und im Feld Meldungen LBN_DBLCLK aus. Mit Doppelklick oder "Handler hinzufügen" definieren Sie jetzt die Member-Funktion CKap2_4Dlg::OnDblclkList1(). Mit "Code bearbeiten" können Sie an dieser Stelle neuen Sourcecode eingeben. Angenommen wir wollen den Pfad und das File mit einer MessageBox ausgeben:

void CKap2_4Dlg::OnDblclkList1()
{
    UpdateData(TRUE);
    MessageBox(m_strCombo1 + L"\\" + m_strList1);
}

 

Nach dem Kompilieren und Ausführen können Sie nun Pfad und File sehen. 

Konzentrieren wir uns noch einmal auf das Kombinations- und Listenfeld. Rufen Sie mit Strg+Shift+X den Klassen-Assistenten auf, um die "Befehle" zu analysieren.
Für unser Listenfeld IDC_LIST1 finden wir bei LBN_...:

 
Nachricht Bedeutung
LBN_SELCHANGE Markierung wird geändert 
LBN_DBLCLICK Doppelklick auf Zeichenfolge
LBN_ERRSPACE Listenfeld hat nicht genügend Speicherplatz (out of memory)
LBN_KILLFOCUS Listenfeld verliert Eingabefocus
LBN_SELCANCEL Markierung wurde abgebrochen
LBN_SETFOCUS Listenfeld erhält Eingabefocus

Die Doppelklick-Nachricht LBN_DBLCLICK verwenden wir bereits.

Die restlichen Nachrichten werden wir folgendermaßen austesten: Erzeugen Sie durch Doppelklick auf die jeweilige Nachricht die entsprechenden Funktionen und geben Sie innerhalb der Funktion eine entsprechende MessageBox aus:

void CKap2_4Dlg::OnSelchangeList1()
{
    MessageBox(L"Markierung wird geändert", L"Nachrichten-Info");
}


void CKap2_4Dlg::OnErrspaceList1()
{
    MessageBox(L"Listenfeld hat nicht genügend Speicherplatz (out of memory)", L"Nachrichten-Info");
}


void CKap2_4Dlg::OnKillfocusList1()
{
    MessageBox(L"Listenfeld verliert Eingabefocus", L"Nachrichten-Info");
}


void CKap2_4Dlg::OnSelcancelList1()
{
    MessageBox(L"Markierung wurde abgebrochen", L"Nachrichten-Info");
}


void CKap2_4Dlg::OnSetfocusList1()
{
    MessageBox(L"Listenfeld erhält Eingabefocus", L"Nachrichten-Info");
}

Wenn wir jetzt eine Zeichenfolge im Listenfeld auswählen wollen, bewegen wir uns in einem geschlossenen Kreis:

Versuch eine Auswahl im Listenfeld zu treffen à "Listenfeld verliert Eingabefocus" à "Listenfeld erhält Eingabefocus" à Versuch ...

Deaktivieren Sie die beiden Meldungen in OnKillFocusList1 und OnSetFocusList1 dadurch, daß Sie die MessageBox-Zeile in Kommentar umwandeln. Wenn Sie jetzt nacheinander verschiedene Zeichenfolgen auswählen, erhalten Sie jeweils die Meldung "Markierung wird geändert". Diese Nachricht LBN_SELCHANGE und die darauf reagierende Funktion OnSelchangeList1() könnte man somit für Aktionen einsetzen. Wir können z.B. den ausgewählten String versuchsweise mittels TextOut(...) im Dialogfenster ausgeben. Bitte ändern Sie unsere Funktionen wie folgt ab:

void CKap2_4Dlg::OnSelchangeList1()
{
    // MessageBox(L"Markierung wird geändert", L"Nachrichten-Info");
    UpdateData(TRUE);
    CClientDC dc(this);
    dc.TextOut(60, 150, L"                                     ");
    dc.TextOut(60, 150, m_strList1);
}


void CKap2_4Dlg::OnErrspaceList1()
{
    MessageBox(L"Listenfeld hat nicht genügend Speicherplatz (out of memory)", L"Nachrichten-Info");
}


void CKap2_4Dlg::OnKillfocusList1()
{
    // MessageBox(L"Listenfeld verliert Eingabefocus", L"Nachrichten-Info");
}


void CKap2_4Dlg::OnSelcancelList1()
{
    MessageBox(L"Markierung wurde abgebrochen", L"Nachrichten-Info");    
}


void CKap2_4Dlg::OnSetfocusList1()
{
    // MessageBox(L"Listenfeld erhält Eingabefocus", L"Nachrichten-Info");
}

Abb. 2.21: Bei Auswahl eines Strings im Listenfeld wird dieser versuchsweise mit TextOut(...) in OnSelchangeList1() ausgegeben

Nach einigem Probieren werden Sie sicher sehen, daß die beiden Nachrichten LBN_SELCHANGE und LBN_DBLCLICK und die damit verbundenen Funktionen zunächst die beiden wichtigsten Nachrichten des Listenfeldes sind. Die Nachricht LBN_ERRSPACE kann man für unsere Fehlermeldung "out of memory" verwenden.

Das Listenfeld kann jedoch nicht nur auf Nachrichten reagieren, sondern hat über die umhüllende MFC-Klasse CListBox eine große Zahl eigener Funktionen. Hierzu gehören zunächst die Member-Funktionen der Klasse CWnd wie z.B.:
 
 
Member-Funktion der Klasse CWnd Bedeutung
EnableWindow( BOOL bEnable = TRUE) erlaubt (TRUE) bzw. verbietet (FALSE) Eingaben
SetFont( HFONT hFont, BOOL bRedraw = TRUE ); verändert die Schriftart
ShowWindow( int nCmdShow ) beeinflußt die Darstellung des Fensters (SW_...)
MoveWindow( int x, int y, int nWidth, int nHeight, BOOL bRepaint = TRUE ); bewegt das Fenster und verändert die Größe
CenterWindow( HWND hWndCenter = NULL ); zentriert das Fenster in Bezug auf das Elternfenster

Diese Funktionen gelten für alle bisherigen Steuerelemente im Dialogfeld. Probieren Sie das doch einmal aus, indem Sie folgendes z.B. in die Funktion OnSetfocusList1() eingeben:

void CKap2_4Dlg::OnSetfocusList1()
{
    //MessageBox("Listenfeld erhält Eingabefocus", "Nachrichten-Info");
    m_ctlList1.CenterWindow();
}

Wenn Sie jetzt eine Auswahl im Listenfeld treffen wollen, pasiert folgendes gewolltes Mißgeschick:

Abb. 2.22: Die Funktion CenterWindow hat zugeschlagen!

Löschen Sie diese Funktion im Code, damit sie nicht wieder zuschlägt.

Probieren Sie den Rest auf eigene Faust aus. Vielleicht entdecken Sie interessante Effekte für eigene Ideen.

Nun zu den spezifischen Member-Funktionen von CListBox. Eine komplette Übersicht erhalten Sie im MSDN unter "CListBox, class members". An dieser Stelle möchte ich nur einige exemplarisch herausgreifen, die wir auch direkt in unserer Anwendung einsetzen wollen. Da wäre z.B. . Diese Member-Funktion liefert die Zahl der Elemente in der List zurück, in unserem Fall also die Zahl der Dateien bzw. Verzeichnisse. Das ist interessant.

Also zurück zu unserer Anwendung. Streichen Sie bitte CenterWindow und ähnliche Versuche. Jetzt kommt GetCount() . Ergänzen Sie bitte OnBnClickedButton1() hinter den if-Kontrollstrukturen wie folgt:

void CKap2_4Dlg::OnButton1()
{
    ...
    if ...

    int anzahl = m_ctlList1.GetCount();
    CString str;
    str.Format(L"Anzahl %i", anzahl);
    CClientDC dc(this);
    dc.TextOut(60, 180, L"               ");
    dc.TextOut(60, 180, str);

}

Nach dem Kompilieren und Ausführen haben wir jetzt eine weitere Information in unserem Dialogfeld:

Abb. 2.23: Die Funktion CListBox::GetCount() liefert die Gesamtzahl der Einträge im Listenfeld

Während Funktionen mit Get... Daten vom Objekt holen geben Funktionen mit Set... Daten an das Objekt. Ein Beispiel ist SetItemHeight ( int nIndex, UINT cyItemHeight ). Mit dieser Funktion kann man den vertikalen Abstand in der Liste verändern (Angaben in Pixel). Testen Sie in OnButton1() z.B. die Zeile:

m_ctlList1.SetItemHeight(0,25);

Wenn Sie die Elemente möglichst eng zusammen bringen wollen, sollten Sie darauf achten, daß die Unterlängen der Buchstaben nicht abgeschnitten werden. Minimalwert sind 16 Pixel. Den Indexwert können Sie ignorieren und auf null setzen (Details siehe im MSDN bei LB_SETITEMHEIGHT).

Zur Manipulation der Einträge in der Liste verwendet man vor allem folgende Member-Funktionen:

AddString, DeleteString, InsertString

Testen Sie eine dieser Funktionen, indem Sie in OnSelchangeList1()folgendes ergänzen:

void CKap2_4Dlg::OnSelchangeList1()
{
    ...
    dc.TextOut(60, 150, m_strList1);
    m_ctlList1.InsertString(0, L"Dies ist keine Datei");
}

Abb. 2.24: Die Funktion CListBox::InsertString(...) fügt neue Einträge in das Listenfeld ein

In Abb. 2.24 wurde vier Mal auf ein Element in der Liste geklickt und hierbei jeweils an der Stelle mit dem Index null - also ganz oben in der Liste - der angegebene String eingefügt.

Zum Suchen von Strings gibt es:

int FindString, int FindStringExact, int SelectString

Der Rückgabewert ist jeweils der Index des gefundenen Eintrags. Bei FindString wird der Index des ersten Eintrags, dessen Anfang mit dem Suchbegriff übereinstimmt, zurückgegeben. SelectString arbeitet wie FindString, selektiert den String aber zusätzlich und zeigt ihn damit in der Liste an. Bei FindStringExact muß der Suchbegriff mit dem Eintrag vollständig übereinstimmen.

Weitere "CListBox Member Functions" (auszugsweise):

Allgemeine Funktionen
 
GetCount liefert die Zahl der Einträge im Listenfeld
GetHorizontalExtent liefert die Breite in Pixel, die ein Listenfeld horizontal gescrollt werden kann.
SetHorizontalExtent bestimmt die Breite in Pixel, die ein Listenfeld horizontal gescrollt werden kann.
GetTopIndex liefert den Index des ersten sichtbaren Eintrags im Listenfeld.
SetTopIndex bestimmt den Index des ersten sichtbaren Eintrags im Listenfeld.
GetItemData liefert den 32-bit-Wert eines Listenfelds 
GetItemDataPtr  liefert einen Zeiger auf ein Listenfeld. 
SetItemData bestimmt den 32-bit-Wert eines Listenfelds 
SetItemDataPtr bestimmt einen Zeiger auf ein Listenfeld. 
GetItemRect liefert das begrenzende Rechteck des Listenfelds.
ItemFromPoint liefert den Index des Listenfeldes, das einem Punkt am nächsten ist.
SetItemHeight bestimmt die Höhe der Einträge im Listenfeld
GetItemHeight  liefert die Höhe der Einträge im Listenfeld
GetSel liefert den Selektionszustand eines Listenfeld-Eintrages.
GetText kopiert einen Listenfeld-Eintrag in einen Puffer oder CString.
GetTextLen liefert die Länge in Bytes eines Listenfeld-Eintrags.
SetColumnWidth bestimmt die Spaltenbreite eines Mehrspalten-Listenfelds.
SetTabStops bestimmt die Tab-Stop-Positionen in einem Listenfeld
GetLocale liefert die lokale ID eines Listenfeldes
SetLocale bestimmt die lokale ID eines Listenfeldes

Einzelauswahl-Funktionen
 
GetCurSel liefert den Index des ausgewählten Listenfeld-Eintrags.
SetCurSel  selektiert einen Listenfeld-Eintrag.

Mehrfachauswahl-Funktionen
 
SetSel selektiert oder deselektiert einen Listenfeld-Eintrag.
GetCaretIndex liefert den Index des Eintrags, der per Focus ausgewählt ist.
SetCaretIndex  bestimmt den Index des Eintrags, der per Focus ausgewählt ist.
GetSelCount liefert die Anzahl Einträge die gleichzeitig ausgewählt sind.
GetSelItems liefert die Indizes der Einträge die gleichzeitig ausgewählt sind.
SelItemRange selektiert oder deselektiert einen Listenfeld-Eintrag-Bereich.
SetAnchorIndex bestimmt den Anker-Eintrag für eine ausgedehnte Auswahl.
GetAnchorIndex  liefert den Index des Anker-Eintrags.

String-Funktionen
 
AddString fügt einen Eintrag zu.
DeleteString löscht einen Eintrag.
InsertString Inserts a string at a specific location in a list box.
ResetContent löscht alle Einträge.
Dir fügt Dateinamen aus dem ausgewählten bzw. aktuellen Verzeichnis zu. Bei Windows95 bzw. 98 nur 8 Zeichen für Dateinamen. Bei Windows NT lange Dateinamen.
FindString sucht einen Eintrag. 
FindStringExact sucht einen Eintrag, der exakt mit dem angegebenen String übereinstimmt.
SelectString wie FindString. Selektiert jedoch gleichzeitig den gefundenen Eintrag.


Analysieren Sie auch den DDX-Mechanismus in unserer Dialog-Klasse an:

Ein Beispiel aus CKap2_4Dlg sieht wie folgt aus:

void CKap2_4Dlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Check(pDX, IDC_CHECK1, m_bCheck1);
    DDX_Check(pDX, IDC_CHECK2, m_bCheck2);
    DDX_CBString(pDX, IDC_COMBO1, m_strCombo1);
    DDX_Control(pDX, IDC_COMBO1, m_ctlCombo1);
    DDX_LBString(pDX, IDC_LIST1, m_strList1);
    DDX_Control(pDX, IDC_LIST1, m_ctlList1);
}

Schauen Sie sich bitte beispielhaft die Hilfe zur List-Box-Funktion DDX_LBString an.


2.5 Objekt Dlg der Hauptanwendung

Im alten Tutorial wurde an dieser Stelle mit Hilfe der Funktion  CWinApp::SetDialogBkColor Hintergrund und Textfarbe des Dialoges direkt eingestellt. Diese Funktion ist inzwischen veraltet und funktioniert auch nicht mehr so wie früher beim Windows Classic Theme. Übergeordnete Farbschemata soll heute der Windows-User allgemein und nicht die einzelne Anwendung für sich einstellen. 

Wir schauen uns die Stelle an, an der der Dialog als Objekt dlg generiert und mittles der Funktion DoModal() dargestllt wird, nämlich CKap2_4App::InitInstance() in Kap2_4.cpp:

BOOL CKap2_4App::InitInstance()
{
    ...

    CKap2_4Dlg dlg;
    m_pMainWnd = &dlg;
   
    INT_PTR nResponse = dlg.DoModal();
   
    if (nResponse == IDOK)
    {
        // TODO: Fügen Sie hier Code ein, um das Schließen des Dialogfelds über "OK" zu steuern
    }
    else if (nResponse == IDCANCEL)
    {
        // TODO: Fügen Sie hier Code ein, um das Schließen des Dialogfelds über "Abbrechen" zu steuern
    }
    else if (nResponse == -1)
    {
        TRACE(traceAppMsg, 0, "Warnung: Fehler bei der Dialogfelderstellung, unerwartetes Beenden der Anwendung.\n");
        TRACE(traceAppMsg, 0, "Warnung: Wenn Sie MFC-Steuerelemente im Dialogfeld verwenden, ist #define _AFX_NO_MFC_CONTROLS_IN_DIALOGS nicht möglich.\n");
    }
    ...
}


CKap2_4Dlg dlg;

Das Objekt dlg der Klasse Ckap2_4Dlg wird erzeugt.

m_pMainWnd = &dlg;

Hier wird der Member-Variable m_pMainWnd (Zeiger vom Typ CWnd* auf das Hauptfenster) die Speicheradresse des gerade erzeugten Objekts dlg zugewiesen.


INT_PTR nResponse = dlg.DoModal();

Die Member-Funktion CDialog::DoModal erzeugt das Dialogfenster mit seinen untergeordneten Fenstern, den (Steuer-)Elementen. Es wird gleichzeitig "modal" angezeigt. Der Rückgabewert IDOK bzw. IDCANCEL dieser Funktion wird in der darauf folgenden if-Kontrollstruktur ausgewertet:


if (nResponse == IDOK)

{
    // Code, um ein Schließen des Dialogfelds über OK zu steuern
}
else if (nResponse == IDCANCEL)
{
    // Code, um ein Schließen des Dialogfelds über "Abbrechen" zu steuern
}

Da wir das Dialogfenster als Fenster für die Hauptanwendung (dialogbasierende Anwendung) einsetzen, steht hier kein Code, da sowohl OK als auch "Abbrechen" den Dialog beendet (UpdateData wird automatisch nur bei OK ausgeführt). Eine Übersicht über Konstruktor und Methoden der Klasse CDialog finden Sie hier. 
 

2.6 Zusammenfassung

Nachdem Sie im ersten Kapitel verstanden haben, daß ein Dialogfeld als Objekt der class CDialog : public CWnd ein Fenster ist, haben Sie nun auch die untergeordneten "Kind"-Fenster des Dialogfensters, die sogenannten Steuerelemente kennengelernt. Auch diese sind Fenster, die von der MFC-Klasse CWnd abgeleitet sind und damit deren Member-Funktionen besitzen wie z.B. ShowWindow(...) oder EnableWindow(...). Folgende wichtige Elemente haben Sie zum Einstieg kennengelernt:

Wesentlich für den Dialogfeld-Daten-Austausch (DDX) zwischen Steuerelementen und (mittels Klassen-Assistenten erzeugten) Member-Variablen ist der Befehl UpdateData(...). Er funktioniert gesteuert durch einen Parameter vom Typ Bool in beiden Richtungen:


Abb. 2.25: UpdateData(...) bewirkt in beiden Richtungen den Datenaustausch zwischen Steuerelementen und Member-Variablen

Bei unseren Übungen setzten wir erstmals die Timerfunktionen SetTimer(...) und KillTimer(...) für Start und Ende periodischer Timer ein. 

Das Thema Farbe einstellen hat man heutzutage von den Windows-Anwendungen weg zu den Windows Themes verlagert, um ein allgemeines Erscheinungsbild, z.B. für Dialog-Texte, -Hintergründe oder die Farbe von Fortschrittsbalken zu gewährleisten.

Hier geht's weiter zum nächsten Kapitel

Zurück zum Inhaltsverzeichnis