erstellt von: Dr. Erhard Henkes (e.henkes@gmx.net) - C++ und MFC - Stand: 29.05.2002

Zurueck zum Inhaltsverzeichnis

Zurück zum vorherigen Kapitel

Kapitel 2 - Elemente im Dialogfeld

2.1 AboutBox, Schaltfläche und Eingabefeld

Kommen wir zu unserem einfachen Beispiel (siehe Einführung) der Eingabe von zwei Zahlen, die miteinander multipliziert werden sollen, zurück. In der klassischen ablauforientierten C-Programmierung hätten Sie dies mit den Ein- und Ausgabebefehlen "scanf" und "printf" erledigt.

In C++ gibt es hier die Weiterentwicklung zu den Streams "cout" und "cin". 

Wie geht dies nun in Windows?

In Windows werden Sie scanf, printf oder cout, cin zunächst nicht benötigen. Dafür 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 per Mausklick auf einen Button erledigen. Als Rahmen für das Ganze benützen wir unser bereits vertrautes Dialogfeld.

Also starten wir:

Wählen Sie im Menü Datei / Neu / Projekte die Option "MFC-Anwendungs-Assistent (exe)" und geben Sie als Projektname "Multiplizieren" ein. OK.

In Schritt 1 wählen Sie "Dialogfeldbasierend". Weiter.

In Schritt 2, 3 und 4 ändern Sie bitte nichts. Weiter bzw. Fertigstellen.

Sie erhalten nun folgende Informationen zu unserem Projekt "Multiplizieren":

Abb. 2.1: Informationen zu unserem Projekt "Multiplizieren"

Neu ist jetzt die Info im Systemmenü und die Unterstützung für ActiveX-Steuerelemente. Dazu kommen wir später. Zunächst wollen wir multiplizieren. Nach einem OK landen Sie direkt bei der Dialogfeld-Ressource, die wir nun "bestücken" werden:

Jetzt sollte die Ressource in etwa wie folgt aussehen:

Abb. 2.2: 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 DIALOG DISCARDABLE 0, 0, 235, 55
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Info über Multiplizieren"
FONT 8, "MS Sans Serif"
BEGIN
ICON IDR_MAINFRAME,IDC_STATIC,11,17,20,20
LTEXT "Multiplizieren Version 1.0",IDC_STATIC,40,10,119,8,SS_NOPREFIX
LTEXT "Copyright (C) 1999",IDC_STATIC,40,25,119,8
DEFPUSHBUTTON "OK",IDOK,178,7,50,14,WS_GROUP
END

IDD_MULTIPLIZIEREN_DIALOG DIALOGEX 0, 0, 222, 96
STYLE DS_MODALFRAME | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "Multiplizieren"
FONT 8, "MS Sans Serif"
BEGIN
DEFPUSHBUTTON "OK",IDOK,165,7,50,14
PUSHBUTTON "Abbrechen",IDCANCEL,165,23,50,14
EDITTEXT IDC_EDIT1,61,10,63,12,ES_AUTOHSCROLL
EDITTEXT IDC_EDIT2,61,36,63,12,ES_AUTOHSCROLL
EDITTEXT IDC_EDIT3,61,69,63,12,ES_AUTOHSCROLL
LTEXT "Eingabe 1",IDC_STATIC,22,11,32,16
LTEXT "Eingabe 2",IDC_STATIC,22,37,32,16
LTEXT "Ausgabe ",IDC_STATIC,22,70,32,16
PUSHBUTTON "Ausgabe",IDC_BUTTON1,152,70,63,11
END

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

Abb. 2.3: Die Dialogfeld-Ressource unseres Projektes

Bitte beachten Sie, daß in der Ressource (Abb. 2.2) das Icon nicht angezeigt wird. Wenn Sie hier klicken, erscheint das Systemmenü, das nun auch zu unserer "AboutBox" (Infofeld) führt:

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

Wählen Sie den Menüeintrag "Info über ..." aus, dann erscheint das zweite Dialogfeld, dessen Definition Sie bereits im Ressourcenskript gefunden haben:

Abb. 2.5: "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 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
    {   CDialog::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 deklariert. Die Member-Funktion DoModal() zeigt dieses Dialogfenster dann auf dem Bildschirm, bis wir mit OK, "X" oder Alt + F4 abbrechen.

An diesem Beispiel erkennen Sie, 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.

Kehren wir nun zu unserer Anwendung zurück. Sie können in die drei Eingabefelder schreiben. Wenn Sie in einem Eingabefeld rechts klicken, dann öffnet sich ein Editier-Menü mit "Ausschneiden, Kopieren, Einfügen, Löschen", wie Sie es in Windows gewöhnt sind. Probieren Sie es aus.

Was natürlich nicht funktioniert, ist unsere Multiplikation. Wenn Sie auf den Knopf "Ausgabe" drücken, passiert nichts.

Wir gehen jetzt schrittweise vor:

Abb. 2.6: Das Ausgabefeld ist nun schreibgeschützt

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

EDITTEXT IDC_EDIT3,61,69,63,12,ES_AUTOHSCROLL | ES_READONLY

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

Abb. 2.8: Der "Member-Variable hinzufügen" - 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 Namen m_Eingabe1 ein. Schauen Sie sich nun die unteren beiden Listenfelder genau an:

Unter Kategorie finden Sie "Wert" und "Control ". Belassen Sie es bei Wert, da wir eine Variable brauchen, die eine Zahl speichern soll.

In dem Listenfeld Variablentyp finden Sie eine reichhaltige Auswahl: CString, int, UINT, long, 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.9: Wir haben drei Member-Variablen hinzugefügt

void CMultiplizierenDlg::OnButton1()
{
      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 testen:

Abb. 2.10: 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:

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

//{{AFX_DATA(CMultiplizierenDlg)
enum { IDD = IDD_MULTIPLIZIEREN_DIALOG };
double m_Eingabe2;
double m_Ausgabe;
double m_Eingabe1;
//}}AFX_DATA

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

//{{AFX_DATA_INIT(CMultiplizierenDlg)
m_Eingabe2 = 0.0;
m_Ausgabe = 0.0;
m_Eingabe1 = 0.0;
//}}AFX_DATA_INIT

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)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CMultiplizierenDlg)
DDX_Text(pDX, IDC_EDIT2, m_Eingabe2);
DDV_MinMaxDouble(pDX, m_Eingabe2, 0., 1000000000.);
DDX_Text(pDX, IDC_EDIT3, m_Ausgabe);
DDX_Text(pDX, IDC_EDIT1, m_Eingabe1);
DDV_MinMaxDouble(pDX, m_Eingabe1, 0., 1000000000.);
//}}AFX_DATA_MAP
}

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 z.B. in die "Technical Note 26" (TN026: DDX and DDV Routines, siehe dort insbesondere Abschnitt "How Does It Work?") im MSDN.

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

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

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

Zur Unterscheidung von der Wert-Variable m_Ausgabe fügen Sie das Präfix "ctl" (control) hinzu und erhalten m_ctlAusgabe. Sie sehen, daß jetzt 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:

//{{AFX_DATA(CMultiplizierenDlg)
enum { IDD = IDD_MULTIPLIZIEREN_DIALOG };
CEdit m_ctlAusgabe;
double m_Eingabe2;
double m_Ausgabe;
double m_Eingabe1;
//}}AFX_DATA

Wie wenden Sie dieses Objekt vom Typ CEdit nun 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)
{
    ...

    DDV_MinMaxDouble(pDX, m_Eingabe1, 0., 1000000000.);
    //}}AFX_DATA_MAP
    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 OnButton1 durch zweifachen Aufruf von UpDateData programmiert. Verabschieden Sie den gesamten Dialog mit der OK-Schaltfläche, dann sehen Sie, daß 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.

Wollen Sie sofort spezifische Member-Funktionen zur Steuerung von Eingabefeldern sehen, dann schauen Sie im MSDN mit dem Stichwort "CEdit Member Functions". Wie so oft, hat man eine reichhaltige Auswahl. Zu diesem Zeitpunkt sollten Sie sich jedoch nicht an einem Element zu sehr festhalten, sondern zunächst weitere 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 wichtiger und hilfreicher Baustein in der Dialogfeld-Programmierung. "Statisch" klingt, als ob man einen einmal (z.B. im Ressourcenskript) vorgegebenen Text nicht mehr ändern könnte. Die Veränderbarkeit ist jedoch auch während des Programmablaufs möglich. Damit wird dieses Feld ein ideales Hilfsmittel zur Textausgabe.

Wir werden im nachfolgenden Beispiel Text zum Vergleich 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 statische Textfelder, zwei Eingabefelder und eine Schaltfläche (Button). "OK", "Abbrechen" und das vorgegebene Textfeld löschen Sie bitte. Nach dem Speichern, Kompilieren und Starten sollte das wie folgt aussehen:

Abb. 2.12: Ein Dialogfeld mit zwei Eingabefelder, zwei statischen Textfeldern und einer Schaltfläche

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

Abb. 2.13: Die beiden Eingabefelder erhalten Member-Variablen

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

void CANSIDlg::OnButton1()
{
    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 0,5 Sekunden automatisch 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 nIDEvent)
{
    c++; //Zählvariable hochsetzen
    CString strZahl, strAnsi;
    strZahl.Format("Zahl: %d", c);
    m_strEdit1 = strZahl;
    m_strEdit2 = c;
    UpdateData(FALSE);
    CDialog::OnTimer(nIDEvent);
}

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

class CANSIDlg : public CDialog
{
    // Konstruktion
    public:
    int c;
    CANSIDlg(CWnd* pParent = NULL); // Standard-Konstruktor
    ...

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

Abb. 2.14: 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) übrigens 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("Zahl: %i", c);

Zwei Variablen der MFC-Klasse CString werden deklariert. 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 CString-Member-Funktion Format(...).

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

Jetzt können wir den formatierten String der Eingabefeld-Stringvariable m_strEdit1 zuweisen. In der nächsten Zeile unternehmen wir bewußt etwas fragwürdiges. Wir weisen die Integervariable c direkt der Eingabefeld-Stringvariable m_strEdit2 zu. Hierbei erfolgt eine Umwandlung der untersten 8 bit (dezimal: 0 bis 255) der Zahl (Typ integer) in den entsprechenden ANSI-Code (Typ character). Dies ist in unserem Fall absolut erwünscht. Damit die Variablen auch tatsächlich an die Felder übertragen werden, folgt der wichtige Transfer-Befehl UpdateData( FALSE ).

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.15: Den beiden statischen Textfelder werden String-Variablen zugeordnet

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

void CANSIDlg::OnTimer(UINT nIDEvent)
{
    CClientDC dc( this );
    c++;
    CString strZahl, strAnsi;
    strZahl.Format("Zahl: %i", c);
    m_strEdit1 = m_strStatic1 = strZahl;
    m_strEdit2 = m_strStatic2 = c;
    UpdateData( FALSE );
    dc.TextOut( 70, 200, " ");
    dc.TextOut( 70, 200, strZahl);
    dc.TextOut( 210, 200, " ");
    dc.TextOut( 210, 200, m_strStatic2);
    if ( c == 255 ) KillTimer(1);

    CDialog::OnTimer(nIDEvent);
}

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

Abb. 2.16: 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 | Formate die Option "Schreibgeschützt" aktivieren. Das funktioniert bestens. Testen Sie es.

Angenommen, Sie wollen dies vom Programm aus erledigen. Wie geht dies? Wir benötigen eine Variable vom Typ CEdit als Kontrollvariable für die Eingabefelder. Also den Klassen-Assistent mit Strg + W starten und die Variablen vom Typ CEdit per Eingabe erzeugen:

Abb. 2.17: 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 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 werfen. Es dürfte wie folgt aussehen:

IDD_ANSI_DIALOG DIALOGEX 0, 0, 206, 172
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "ANSI-Code"
FONT 8, "MS Sans Serif"
BEGIN
EDITTEXT IDC_EDIT1,36,28,62,17,ES_AUTOHSCROLL
EDITTEXT IDC_EDIT2,108,28,62,17,ES_AUTOHSCROLL
LTEXT "Static",IDC_STATIC2,108,68,62,17
LTEXT "Static",IDC_STATIC1,36,68,62,17
PUSHBUTTON "ANSI-Code starten",IDC_BUTTON1,33,147,141,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 Slider auf.

Wir benötigen in unserem Dialogfeld:
eine Fortschrittsanzeige (Progress Bar),
einen Schieberegler (Slider),
ein Gruppenfeld mit drei Optionsfeldern (Radio Button),
ein Eingabefeld (Edit) und
eine Schaltfläche (Button).

"OK", "Abbrechen" und das vorgegebene Textfeld löschen Sie bitte.
Nach dem Speichern, Kompilieren und Starten sollte das wie folgt aussehen:

Abb. 2.18: 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, 298, 159
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "Slider"
FONT 8, "MS Sans Serif"
BEGIN
CONTROL "Progress1",IDC_PROGRESS1," msctls_progress32",WS_BORDER,59,7,202,18
CONTROL "Slider1",IDC_SLIDER1," msctls_trackbar32",TBS_BOTH |
TBS_NOTICKS | WS_TABSTOP,57,37,202,17
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
END

Als nächstes erzeugen Sie 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 OnButton1() hinzuzufügen. Geben Sie folgenden Programm-Code ein:

void CSliderDlg::OnButton1()
{
    UpdateData(TRUE);
    m_ctlProgress1.SetPos(m_intSlider1);     //:1
    CString str;                             //:2
    str.Format("%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 (Steuer-)Elementen im Dialogfenster 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, sodaß immer nur eine Taste gedrückt sein konnte. Das funktioniert in unserem virtuellen Tastenfeld bereits bestens 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 vorgehen, wie bei einer Schaltfläche. Einfach 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::OnRadio1()
{
    m_ctlProgress1.SendMessage(PBM_SETBARCOLOR,0,(LPARAM) RGB(255,0,0));
}

void CSliderDlg::OnRadio2()
{
    m_ctlProgress1.SendMessage(PBM_SETBARCOLOR,0,(LPARAM) RGB(0,255,0));
}

void CSliderDlg::OnRadio3()
{
    m_ctlProgress1.SendMessage(PBM_SETBARCOLOR,0,(LPARAM) 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.

Der einzeilige Code ist für Sie im Moment sicher schwer verständlich. Solche für Sie neuartigen Code-Zeilen werden Ihnen immer wieder begegnen. Daher ist es wichtig, daß Sie Hilfsmittel finden, solche Zeilen eigenständig zu analysieren. Wie gehen Sie vor? Der erste Schritt sollte die Hilfe (MSDN) sein. Wählen Sie zu Beginn am besten "Gesamte Sammlung" und "nur Titel suchen" aus.

Suchbegriff eingeben: CWnd::SendMessage. Sie finden:

CWnd::SendMessage

Call this member function to send the specified message to this window.

The SendMessage member function calls the window procedure directly and does not return until that window procedure has processed the message.

Syntax: LRESULT SendMessage( UINT message, WPARAM wParam = 0, LPARAM lParam = 0 );

Sie sehen aus der Funktionsbeschreibung, daß es sich hier um eine Nachrichten- Funktion handelt. Die Aufgabe besteht darin, eine dringende Nachricht (Message) unter Umgehung der Nachrichtenwarteschlange direkt an ein Fenster zu schicken. Hierzu werden drei Parameter eingesetzt:

UINT message beschreibt den Typ der Nachricht. In unserem Fall PBM_SETBARCOLOR. Zusätzlich können zwei 32-Bitwerte der Typen WPARAM und LPARAM übermittelt werden. In unserem Fall wird nur LPARAM genutzt. Der RGB-Wert wird durch das vorgestellte (LPARAM) in diesen Typ umgewandelt. Jetzt sind wir jedoch nur ein wenig schlauer.

Also suchen wir weiter unter PBM_SETBARCOLOR und finden:

PBM_SETBARCOLOR

wParam = 0;
lParam = (LPARAM)(COLORREF)clrBar;
Sets the color of the progress indicator bar in the progress bar control.

Returns the previous progress indicator bar color, or CLR_DEFAULT if the progress indicator bar color is the default color.

clrBar
COLORREF value that specifies the new progress indicator bar color. Specify the CLR_DEFAULT value to cause the progress bar to use its default progress indicator bar color.
Das ist ein MSDN-Volltreffer. Der Satz "Sets the color of the progress indicator bar in the progress bar control." beschreibt exakt die beobachtete Funktion, und die Beschreibung "lParam = (LPARAM) (COLORREF) clrBar;" führt uns zu unserem "(LPARAM) RGB(...);".

Nun können Sie Ihre Fortschrittsanzeigen in der Farbe Ihrer Wahl (256*256*256 = 16777216 = 224) darstellen. Man fragt sich, warum dies in MFC noch derart kompliziert gelöst wird. Eine "SetColor"-Funktion wäre hier wahrhaft wünschenswert.

CWnd::SendMessage ist eine vielseitige Funktion. Wenn Sie im MSDN mittels Index unter PBM... nachschauen, finden Sie eine ganze Reihe von Messages, die Sie in dieser Funktion verwenden können. So sind z.B. die nachfolgenden Programmzeilen gleichwertig:

m_ctlProgress1.SetPos( m_intSlider1 );

m_ctlProgress1.SendMessage( PBM_SETPOS, (WPARAM) m_intSlider1, 0 );

Probieren Sie es bitte aus. Sie können eine Zeile einfach durch den Doppel-Slash in Kommentar umwandeln, wenn Sie experimentieren wollen. Die genaue Syntax von PBM_SETPOS finden Sie im MSDN.

Lassen Sie uns nun die Ressourceneigenschaften bei Fortschrittsanzeige und Schieberegler verändern. Bitte setzen Sie im Ressourcen-Editor jeweils unter Eigenschaften beim Schieberegler die Option "Teilstriche" und "Punkt: Unten/Rechts" sowie bei der Fortschrittsanzeige die Option "Glatt". Nach dem Kompilieren/Ausführen sieht das dann wie folgt aus:

Abb. 2.19:

Die Fortschrittsanzeige reagiert nun auf kleine Veränderungen. Der Schieberegler besitzt Teilstriche.

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::OnButton1()
{ ...
    m_ctlSlider1.SetTic(50);
    m_ctlSlider1.SetTic(75);
    //m_ctlSlider1.SendMessage( TBM_SETTIC, 0, (LPARAM) 50 );
    //m_ctlSlider1.SendMessage( TBM_SETTIC, 0, (LPARAM) 75 );
}

Um die Wirkung der Funktion SendMessage noch einmal zu verdeutlichen, wurde hier der gleichwertige Code als Kommentar beigefügt. Probieren Sie es aus. Ein Klick auf die Schaltfläche erzeugt in beiden Fällen die gewünschten Teilstriche am Schieberegler.
 
 

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.20: Kontrollkästchen, Listenfeld und Kombinationsfeld stellen sich vor

Zunächst ein Blick ins Ressourcenskript (neu ist LISTBOX, COMBOBOX, BS_AUTOCHECKBOX):

IDD_KAP2_4_DIALOG DIALOGEX 0, 0, 242, 200
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "Kap2_4"
FONT 8, "MS Sans Serif"
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 raffinierte 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 ein 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 folgende Member-Variablen mittels Klassen-Assistent (Strg + W):
 
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 erhalten unter der Registerkarte "Daten" ein Eingabefeld. Der Übergang zur nächsten Zeile erfolgt hier mit Strg + Enter:

Abb. 2.21: Die Auswahlliste des Kombinationsfeldes wird im Dialog Eigenschaften | Daten gefüllt

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

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

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, daß Sie mit dem Kombinations- und Listenfeld in Verbindung mit Kontrollkästchen mächtige Werkzeuge zur Dialoggestaltung in Händen halten. Hier ein Beispiel mit versteckten Dateien im Systemverzeichnis von Windows 95:

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

Stören Sie sich im Moment nicht an der Begrenzung der Namen auf acht Zeichen unter Windows 95 bzw. Windows 98 (bei Windows NT tritt dieses Problem nicht auf). Die historische Entwicklung von Windows wird Sie als Programmierer ständig quälen. Lassen Sie uns lieber 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 + W gedrückt. Unter der Registerkarte "Nachrichtenzuordnungstabellen" wählen Sie im Feld Objekt-IDs den Eintrag IDC_LIST1 und im Feld Nachrichten LBN_DBLCLK aus. Mit Doppelklick oder "Funktion hinzufügen" definieren Sie jetzt die Member-Funktion CKap2_4Dlg::OnDblclkList1(). Daraufhin können Sie an dieser Stelle Code eingeben. Angenommen wir wollten Exe-Files direkt starten, dann können Sie das bereits bekannte WinExec(...) einsetzen:

void CKap2_4Dlg::OnDblclkList1()
{
    UpdateData(TRUE);
    WinExec(m_strList1, SW_NORMAL);
}
 

Nach dem Kompilieren und Ausführen können Sie nun direkt Exe-Anwendungen starten. Seien Sie hier bitte vorsichtig, damit Sie keine Ihnen unbekannten Programme ausführen, die Schaden anrichten könnten. Zum Testen empfehlen sich z.B. folgende harmlosen Programme, die sich vielleicht im Windows-Verzeichnis befinden:

calc.exe (Taschenrechner)
cdplayer.exe (Audio-CD-Player)
pbrush.exe (Malprogramm Paint)
sysmon.exe (Systemmonitor zur Überwachung der Rechnerauslastung)
write.exe (Texteditor Wordpad)

Abb. 2.23: Die Anwendung kann ausführbare Dateien per Doppelklick starten

Wenn Sie auch Dateien zur Ausführung bringen wollen, die nicht selbst starten können, dann starten Sie diese innerhalb WinExec z.B. einfach mit dem Explorer (achten Sie auf das Blank hinter explorer) wie folgt:

void CKap2_4Dlg::OnDblclkList1()
{
    UpdateData(TRUE);
    WinExec("explorer " + m_strCombo1 + "\\" + m_strList1, SW_NORMAL);
}

Wir werden hier keinen eigenen Explorer basteln, sondern es geht darum, daß Sie erkennen, wie man bereits mit bescheidenen Mitteln Dateien auflisten, auswählen und sogar ausführen kann.

Konzentrieren wir uns noch einmal auf das Kombinations- und Listenfeld. Rufen Sie mit Strg + W den Klassen-Assistenten auf, um die Nachrichtenzuordnungstabellen zu analysieren. Für unser Listenfeld IDC_LIST1 finden wir:
 
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 zum Füllen der Liste mit Dateien aus dem im Kombinationsfeld ausgewählten Verzeichnis.

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("Markierung wird geändert", "Nachrichten-Info");
}

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

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

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

void CKap2_4Dlg::OnSetfocusList1()
{
    MessageBox("Listenfeld erhält Eingabefocus", "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 mittels TextOut(...) im Dialogfenster ausgeben. Bitte ändern Sie unsere Funktionen wie folgt ab:

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

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

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

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

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

 

Abb. 2.24: Bei Auswahl eines Strings im Listenfeld wird dieser 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 (Steuer-)Elemente 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.25: Die Funktion CenterWindow hat zugeschlagen!

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 OnButton1() hinter den if-Kontrollstrukturen wie folgt:

void CKap2_4Dlg::OnButton1()
{
    ...
    if ...
    long anzahl = m_ctlList1.GetCount();
    CString str;
    str.Format("Anzahl %i",anzahl);
    CClientDC dc(this);
    dc.TextOut(60, 180, " ");
    dc.TextOut(60, 180, str);
}

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

Abb. 2.26: 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( LPCTSTR lpszItem );
DeleteString( UINT nIndex );
InsertString( int nIndex, LPCTSTR lpszItem );

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,"Dies ist keine Datei");
}

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

In Abb. 2.28 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 nStartAfter, LPCTSTR lpszItem ) const;
int FindStringExact( int nIndexStart , LPCTSTR lpszFind );
int SelectString( int nStartAfter, LPCTSTR lpszItem );

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.

Wir testen dies in der Funktion OnButton1(). Wir suchen "mfc42.dll" in der Liste des Windows-System-Verzeichnisses, das wir über das Kombinationsfeld aussuchen (für Windows 95 z.B.: C:\Windows\System). Den Index geben wir mittels TextOut zurück. Ergänzen Sie:

void CKap2_4Dlg::OnButton1()
{
    ...
    long anzahl = m_ctlList1.GetCount();
    int nIndex = m_ctlList1.FindString(0,"mfc42");
    CString str;
    str.Format("Anzahl %i",anzahl);
    CClientDC dc(this);
    dc.TextOut(60, 180, " ");
    dc.TextOut(60, 180, str);
    str.Format("Index %i",nIndex);
    dc.TextOut(60, 200, " ");
    dc.TextOut(60, 200, str);
}

Abb. 2.28: Die Funktion CListBox::FindString(...) liefert einen Index zurück, selektiert aber nicht den Eintrag.

Als nächstes wandeln wir die entsprechende Zeile mit FindString(...) um in:

int nIndex = m_ctlList1.SelectString(0,"mfc42");
 

Abb. 2.29: Die Funktion CListBox::SelectString(...) liefert einen Index zurück und selektiert den Eintrag.

Zum Schluß testen Sie bitte noch FindStringExact mit "mfc42". Hierbei wird der Index -1 zurückgeliefert. Erst, wenn Sie "mfc42.dll" als Suchstring festlegen, findet er den entsprechenden Index.

Mit dem jetzt erworbenen praktischen Verständnis können wir bezüglich weiterer Member-Funktionen auf das MSDN (Stichwort "CListBox Member Functions" als Einstieg) verweisen. Hier ein Auszug:

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.

 

F arb e im Dialog:

Mit Hilfe der Funktion  CWinApp::SetDialogBkColor   kann man den Hintergrund und die Textfarbe eines Dialoges direkt einstellen. Wir probieren die genaue Wirkung sofort am aktuellen Beispiel aus. Bitte ergänzen Sie in Ckap2_4App::InitInstance() einfach folgende Zeile:

BOOL CKap2_4App::InitInstance()
...
CKap2_4Dlg dlg;
m_pMainWnd = &dlg;
SetDialogBkColor( RGB(0,80,150), RGB(255,0,0) ); // bestimmt Hintergrund- und Textfarbe
int nResponse = dlg.DoModal();
if ...

Der Hintergrund des Dialogfeldes ist nun nicht mehr standardgrau "RGB( 192, 192, 192)", sondern blau-grün und der Text der Kontrollkästchen und der statischen Textfelder wird in roter Farbe ausgegeben.

An dieser Stelle analysieren wir auch das Umfeld der gerade eingegebenen Zeile:

CKap2_4Dlg dlg;

Das Objekt dlg der Klasse Ckap2_4Dlg wird deklariert.

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 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 Hauptanwendung einsetzen, steht hier kein Code, da sowohl OK als auch "Abbrechen" den Dialog beendet (UpdateData wird automatisch jedoch nur bei OK ausgeführt).
 
 

2.5 Zusammenfassung

Nachdem Sie im ersten Kapitel verstanden haben, daß ein Dialogfeld 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 in beiden Richtungen:


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

Bei unseren Übungen setzten wir erstmals die Timerfunktionen SetTimer(...) und KillTimer(...) ein. Auf dieses Thema werden wir später noch genauer eingehen.

VisualC++ macht uns die Anwendung von Farbe im Dialogfeld nicht gerade leicht. Es gibt hier keine einheitliche Vorgehensweise. Sie haben bisher folgende Methoden kennengelernt:

Wir werden diesen Punkt später noch zusammenfassend vertiefen.
 

Hier geht's weiter Zum Nächsten Kapitel

Zurueck zum Inhaltsverzeichnis