erstellt von: Dr. Erhard Henkes (e.henkes@gmx.net) - C++ und MFC - Stand: 29.05.2002
Zurueck zum Inhaltsverzeichnis
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:
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
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:
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::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:
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:
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:
wParam = 0;Sets the color of the progress indicator bar in the progress bar control.
lParam = (LPARAM)(COLORREF)clrBar;
Returns the previous progress indicator bar color, or CLR_DEFAULT if the progress indicator bar color is the default color.
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:
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+"\\*.*");
}
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:
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: