List-/Comboboxen
und Editfelder
In dieser Lektion betrachten wir die etwas komplexeren Controls wie Editfelder,
Listboxen und Comboboxen. Außerdem lernen Sie einen neuen Typ von Anwendung kennen,
die dialogbasierende Anwendung. Eine dialogbasierende Anwendung besitzt kein Hauptfenster
im üblichen Sinne sondern besteht nur aus einem Dialog. Im Beispiel zu dieser Lektion
werden wir eine dialogbasierende Anwendung entwickeln die folgenden Dialog darstellt:

Der Dialog besteht unter anderem aus drei Listboxen, einem Editfeld sowie einer
Combobox. Zwischen den beiden oberen Listboxen können Einträge durch anklicken der
dazwischen liegenden Buttons verschoben werden. Die Listbox in der Mitte des Dialogs
kann zur Laufzeit um Einträge erweitert werden. Dazu wird im darunter stehenden
Editfeld der hinzuzufügende Eintrag eingegeben und danach der Button Übernehmen
angeklickt. Die Combobox unten im Dialog erlaubt die Auswahl eines Dateityps aus
einer vorgegebenen Liste.
Beginnen wir das Beispiel nun mit der Erstellung des Anwendungsrahmens. Als Anwendungstyp
wird wie gesagt eine dialogbasierende Anwendung gewählt.
 |
Erstellen Sie zunächst wie gewohnt eine MFC Exe-Anwendung.
Geben Sie dem Projekt den Namen LCEdit. Im daraufhin von Anwendungs-Assistenten
eingeblendeten Dialog Schritt 1 markieren Sie nun unter Anwendungsart
nicht mehr Einzelnes Dokument (SDI) sondern Dialogfeldbasierend.

Ebenfalls geändert hat sich nun der Schritt 2. Wählen Sie aus den
verfügbaren Merkmalen für das Beispiel lediglich das Merkmal 3D-Steuerelemente
aus. Im Eingabefeld ganz unten können Sie dann noch den Titel des Dialogs eingeben.

Klicken Sie dann den Button Fertigstellen an und der Anwendungs-Assistent
erstellt eine Anwendung die folgende Klassen enthält:

|
Die Anwendung besteht jetzt nur noch aus einer Applikations-Klasse CLCEditApp
und einer Dialogklasse CLCEditDlg. Sehen Sie sich nun einmal die Methode
InitInstance(...) der Klasse CLCEDitApp an. Sie werden dort u.a.
die Definition des Dialogobjektes sowie den Aufruf der Methode DoModal(...)
zur Anzeige des Dialogs finden, die Sie schon aus der letzten Lektion her kennen.
Beginnen wir jetzt mit der Erstellung des Dialogs.
 |
Wechseln Sie in die Ressourcen-Ansicht und öffnen dort den
Dialog IDD_DIALOG1. Fügen Sie zunächst die oben dargestellten Controls zum Dialog
hinzu. Lassen Sie dabei alle Eigenschaften der Controls bis auf weiteres noch
auf ihren Default-Einstellungen stehen. Um ein Editfeld, eine Listbox oder eine
Combobox zum Dialog hinzuzufügen, klicken Sie bitte die unten gekennzeichneten
Symbole im Toolbar an:

Ändern Sie die Beschriftungen der statischen Textfelder (über den oberen
Listboxen) und der Gruppenfelder wie oben dargestellt ab. Anschließend sind
die Buttons noch anzupassen. Geben Sie den Buttons die nachfolgenden IDs und
passen Sie ebenfalls deren Beschriftung an.

Zum Schluss fügen Sie zur Dialogklasse die Nachrichtenbearbeiter für die
BN_CLICKED Nachricht (wie in der letzten Lektion besprochen) für jeden der Buttons
hinzu. Übernehmen Sie die jeweils vorgeschlagenen Methodennamen. Die Methoden
werden nachher noch vervollständigt.
|
Sehen wir uns nun die Listboxen an. Listboxen werden in der MFC durch die Klasse
CListBox repräsentiert. Um in einem Dialog sinnvoll mit einer Listbox arbeiten
zu können, muss zunächst ein entsprechendes Objekt innerhalb der Dialogklasse erstellt
werden. Anschließend können Sie in der Methode OnInitDialog(...) über die
CListBox Methode AddString(...) der Listbox Einträge hinzufügen.
Als Parameter erhält die Methode einen char-Zeiger auf den hinzuzufügenden
Eintrag und liefert als Returnwert (bei erfolgreichem Aufruf) die Position innerhalb
der Listbox, an die der Eintrag eingefügt wurde. Diese Position muss nicht unbedingt
um eins höher sein als beim vorherigen Aufruf der Methode, da Listboxen über die
Eigenschaft Sortieren die Einträge alphabetisch sortieren können. Das Gegenstück
zur Methode AddString(...) ist die Methode GetText(...). Sie liest
einen Eintrag aus einer Listbox aus. Die Methode erhält als Parameter u.a. die Position
des auszulesenden Eintrags, wobei der erste Eintrag die Position '0' besitzt.
Außer Einträge hinzuzufügen und auszulesen, können Sie auch Einträge bei Bedarf
wieder aus der Listbox löschen. Um einen Eintrag zu entfernen wird die Methode
DeleteString(...) aufgerufen, der Sie die Position des zu löschenden Eintrags
als Parameter mitgeben. Als Returnwert liefert die Methode die Anzahl der danach
verbleibenden Einträge zurück.
Neben den sichtbaren (Text-)Einträgen kann eine Listbox auch pro Eintrag einen
beliebigen 32-Bit Wert aufnehmen. Dieser 32-Bit Wert wird nachher im Beispiel dazu
verwendet, um zwischen MFC und MFC-freien Kapiteln unterscheiden zu können. Über
einen Button zwischen den Listboxen können dann z.B. alle MFC oder alle MFC-freien
Kapitel auf einmal verschoben werden. Um für einen bestimmten Eintrag diesen 32-Bit
Wert zu setzen wird die CListBox Methode SetItemData(...) eingesetzt.
Auch Sie erhält wieder die Position des Listbox-Eintrags, dem der 32-Bit Wert zugewiesen
soll sowie den 32-Bit Wert selbst. Der hiermit gesetzte Wert kann später dann mittels
der Methode GetItemData(...) wieder ausgelesen werden.
 |
Da die erwähnten Listbox-Funktionen im Fehlerfall die Returncode LB_ERR
und LB_ERRSPACE zurückliefern, die intern durch die Werte -1 und -2 repräsentiert
werden, sollten Sie -1 und -2 nicht als Zusatzinformation verwenden. |
Gehen wir nun ans Bearbeiten der beiden Listboxen im Dialog oben. Die rechte
Listbox soll mit den Kapiteln dieses Kurses belegt werden, wobei jeder Eintrag noch
eine Zusatzkennung erhält, ob es sich um ein Kapitel mit oder ohne MFC handelt.
Über die Buttons zwischen den Listboxen sollen die Kapitel von einer Listbox in
die andere verschoben, nicht kopiert, werden. Die Buttons bewirken folgende
Verschiebungen, von oben nach unten gesehen:
 |
Das in der linken Listbox markierte Kapitel wird nach
rechts verschoben.
Alle Kapitel mit MFC nach rechts verschieben
Alle Kapitel ohne MFC nach rechts verschieben
Das in der rechten Listbox markierte Kapitel wird nach links verschoben
Alle Kapitel der rechten Listbox werden nach links verschoben |
Die Auswahl von mehreren Einträgen gleichzeitig soll in beiden Listboxen nicht
erlaubt sein. Die Einstellung der Auswahl-Möglichkeit erfolgt übrigens über die
Listbox-Eigenschaft Auswahl. Durch anklicken des Fragezeichens oben links
im Eigenschaftsdialog erhalten Sie wieder eine sehr gute Erklärung der einzelnen
Möglichkeiten.

 |
Da die Listboxen vorhin schon zum Dialog hinzugefügt wurden,
müssen wir nun 'nur noch' deren Eigenschaften anpassen. Geben Sie der linken
Listbox die ID IDC_NONSELECT und der rechten die ID IDC_SELECT. Die restlichen
Eigenschaften können Sie auf ihrem Vorgabewert belassen. Standardmäßig werden
Listboxen mit einfacher Auswahl erstellt deren Einträge alphabetisch sortiert
werden. Fügen Sie anschließend zur Dialogklasse für jede Listbox eine Membervariable
über den Klassen-Assistenten hinzu. Da wir für die Listboxen entsprechende
CListBox-Objekte erstellen müssen, stellen Sie im Feld Kategorie
den Typ Control ein. Der Variablentyp wird daraufhin automatisch auf
CListBox gesetzt. Geben Sie der Membervariable für die linke Listbox
den Namen m_CNonSelect und der Membervariable für die rechte Listbox
den Namen m_CSelect.

Als nächstes werden die vorgegebenen Einträge zur linken Listbox hinzugefügt.
Dies erfolgt in der Methode OnInitDialog(...). Die rechte Listbox braucht
nicht initialisiert werden, da sie am Anfang leer ist. Erweitern Sie OnInitDialog(...)
wie folgt:
BOOL CLCEditDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Symbol für dieses Dialogfeld festlegen. Wird automatisch erledigt
// wenn das Hauptfenster der Anwendung kein Dialogfeld ist
SetIcon(m_hIcon, TRUE);
// Großes Symbol verwenden
SetIcon(m_hIcon, FALSE);
// Kleines Symbol verwenden
// ZU ERLEDIGEN: Hier zusätzliche Initialisierung einfügen
// Index des hinzugefuegten
Eintrags
int nIndex;
// Listbox mit Eintraegen
fuellen
nIndex = m_CNonSelect.AddString("Einführung
in WINDOWS");
m_CNonSelect.SetItemData(nIndex,NONMFC);
nIndex = m_CNonSelect.AddString("Einf. Progr. ohne MFC");
m_CNonSelect.SetItemData(nIndex,NONMFC);
nIndex = m_CNonSelect.AddString("Einführung MFC");
m_CNonSelect.SetItemData(nIndex,MFC);
nIndex = m_CNonSelect.AddString("Doc/View Modell");
m_CNonSelect.SetItemData(nIndex,MFC);
nIndex = m_CNonSelect.AddString("GDI");
m_CNonSelect.SetItemData(nIndex,MFC);
nIndex = m_CNonSelect.AddString("Eingaben");
m_CNonSelect.SetItemData(nIndex,MFC);
nIndex = m_CNonSelect.AddString("Ressourcen");
m_CNonSelect.SetItemData(nIndex,MFC);
return TRUE; // Geben
Sie TRUE zurück, außer ein Steuerelement soll den Fokus erhalten
} |
Legen Sie dann innerhalb der Dialogklasse noch die enum-Konstanten zur Kennzeichnung
der nicht-MFC und der MFC Kapitel fest:
class CLCEditDlg : public
CDialog
{
....
virtual void DoDataExchange(CDataExchange* pDX);
// DDX/DDV-Unterstützung
//}}AFX_VIRTUAL
private:
enum {MFC, NONMFC};
// Implementierung
protected:
HICON m_hIcon;
....
}; |
Wenn Sie jetzt das Programm übersetzen und starten, dann sollten in der linken
Listbox die vorgegebenen Einträge erscheinen. Die Buttons sind allerdings noch
ohne Funktion.
|
Jedem Eintrag in der Listbox wurde durch Aufruf der Methode SetItemData(...)
zusätzlich noch die Kennung mitgegeben, ob es sich um ein Kapitel mit oder ohne
MFC handelt. Wir brauchen diese Kennung gleich nachher beim Verschieben der Einträge.
Beachten Sie bitte auch, dass die SetItemData(...) Methode mit der Positionsnummer
aufgerufen wird, die AddString(...) zurückgeliefert hat. Die Positionsnummern
werden, da die Listbox die Einträge alphabetisch sortiert, nicht aufsteigend sein.
So, gehen wir jetzt an die Kodierung der Button-Funktionen. Über die Buttons
sollen Einträge zwischen den Listboxen verschoben werden. Dazu wird zuerst eine
allgemeine Methode implementiert, die einen Eintrag zwischen zwei Listboxen kopiert
(nicht löscht!).
 |
Fügen Sie die Kopierfunktion CopyLBEntries(...) zur
Dialogklasse hinzu. Definieren Sie die Methode wie folgt:
void CLCEditDlg::CopyLBEntries(CListBox
&CFrom, CListBox &CTo, int nIndex)
{
char *pszText; // Umzukopierender
Text
DWORD dwData;
// und Datum
int nNewIndex; // Neuer
Index in der Ziel-Listbox
int nSelTextLen; // Laenge des zu
kopierenden Textes
// Textlaenge ermitteln
(abschl. '0' dabei nicht vergessen!)
nSelTextLen = CFrom.GetTextLen(nIndex)+1;
// Speicher anfordern
pszText = new char[nSelTextLen];
// Text und 32-Bit Wert
auslesen
CFrom.GetText(nIndex,pszText);
dwData = CFrom.GetItemData(nIndex);
// und wieder einfuegen
nNewIndex = CTo.AddString(pszText);
CTo.SetItemData(nNewIndex,dwData);
// Speicher freigeben!
delete [] pszText;
} |
Die Methode kopiert den Eintrag an der Position nIndex aus der Listbox
CFrom in die Listbox CTo. Beachten Sie hier, dass nicht der
Eintrag alleine sondern auch der dazugehörige 32-Bit Wert mit kopiert werden
muss.
|
Als nächstes kann es ans kodieren der Button-Funktionen selbst gehen. Die Button-Methoden
wurden bereits am Anfang der Lektion zur Dialogklasse hinzugefügt, so dass sie jetzt
nur noch vervollständigt werden müssen. Zwei der Methoden sollen lediglich den in
einer der Listboxen markierten Eintrag verschieben. Um den aktuell markierten Eintrag
festzustellen, wird die CListBox Methode GetCurSel(...) aufgerufen.
Sie liefert als Returnwert den Index des markierten Eintrags. Ist kein Eintrag markiert,
so wird der Fehlercode LB_ERR zurückgegeben.
 |
So, und jetzt dürfen Sie wieder rann! Versuchen Sie einmal
die Button-Methoden OnSselect(...) und OnSdeselect(...) zu
vervollständigen. Beim Anklicken des Buttons ID_SSELECT soll der in der linken
Listbox markierte Eintrag in die rechte Listbox verschoben werden und
beim Anklicken des Buttons ID_SDESELECT der in der rechten Listbox markierte
Eintrag in die linke Listbox. Alle dazu notwendigen Methoden haben Sie bereits
kennen gelernt. HINWEIS: Kopieren Sie zuerst den markierten Eintrag mit Hilfe
der vorhin implementierten Methode CopyLBEntries(...) und löschen Sie
ihn erst danach.
 |
Aber wenn Sie wollen kann ich Ihnen auch meine Lösung anzeigen. |
Lösung zum Verschieben von Einträgen zwischen Listboxen
void CLCEditDlg::OnSdeselect()
{
// TODO: Code für die Behandlungsroutine der Steuerelement-....
// Ausgewaehlten
Eintrag auslesen
int nSelect = m_CSelect.GetCurSel();
// Abpruefen, ob etwas
ausgewaehlt ist
if (nSelect != LB_ERR)
{
// Markierten Eintrag kopieren
CopyLBEntries(m_CSelect, m_CNonSelect, nSelect);
// und Eintrag loeschen
m_CSelect.DeleteString(nSelect);
}
else
MessageBox("Keine Auswahl getroffen","FEHLER",
MB_OK|MB_ICONINFORMATION);
}
void CLCEditDlg::OnSselect()
{
// TODO: Code für die Behandlungsroutine der Steuerelement-....
// Ausgewaehlten
Eintrag auslesen
int nSelect = m_CNonSelect.GetCurSel();
// Abpruefen, ob etwas
ausgewaehlt ist
if (nSelect != LB_ERR)
{
// Markierten Eintrag kopieren
CopyLBEntries(m_CNonSelect, m_CSelect, nSelect);
// und Eintrag loeschen
m_CNonSelect.DeleteString(nSelect);
}
else
MessageBox("Keine Auswahl getroffen","FEHLER",
MB_OK|MB_ICONINFORMATION);
} |
Haben Sie auch an die 'bösen' Anwender gedacht, die den Verschiebe-Button
anklicken ohne dass ein Eintrag markiert ist?
Ende der Lösung
|
Übersetzen und starten Sie das Programm nun erneut. Sie können schon einzelne
Einträge zwischen den Listboxen verschieben.
|
Beim Verschieben von mehreren Einträgen müssen Sie unbedingt beachten, dass sich
nach dem Löschen eines Eintrags die Positionen aller nachfolgenden Einträge verschieben.
Löschen Sie z.B. den Eintrag an der Position 0, so wird danach der Eintrag, der
bisher auf der Position 1 stand, die Position 0 erhalten. Die beste und einfachste
Vorgehensweise beim Löschen von mehreren Einträgen ist, die Listbox-Einträge von
hinten nach vorne zu bearbeiten. Um die Anzahl der Einträge, und damit auch indirekt
die letzte Position, auszulesen, wird die CListBox Methode GetCount(...)
aufgerufen.
 |
Erweitern Sie die Methoden zum Verschieben von mehreren Einträgen
wie folgt. Beachten Sie dabei bitte, dass die Schleife zum Verschieben der Einträge
von 'Anzahl der Einträge - 1' bis '0' bis läuft, da der erste Eintrag die Positionsnummer
'0' besitzt!
void CLCEditDlg::OnMfcselect()
{
// TODO: Code für die Behandlungsroutine der Steuerelement-....
// Anzahl der Eintraege
holen
int nNoOfEntries = m_CNonSelect.GetCount();
// Eintraege verschieben
for (int iLoop=nNoOfEntries-1;
iLoop>=0; iLoop--)
{
// Falls Kapitel zur MFC gehoert
if(m_CNonSelect.GetItemData(iLoop) == MFC)
{
// zuerst kopieren, dann loeschen
CopyLBEntries(m_CNonSelect,m_CSelect,iLoop);
m_CNonSelect.DeleteString(iLoop);
}
}
}
void CLCEditDlg::OnNomfcselect()
{
// TODO: Code für die Behandlungsroutine der Steuerelement-....
// Anzahl der Eintraege
holen
int nNoOfEntries = m_CNonSelect.GetCount();
// Eintraege verschieben
for (int iLoop=nNoOfEntries-1;
iLoop>=0; iLoop--)
{
// Falls Kapitel nicht zur MFC gehoert
if(m_CNonSelect.GetItemData(iLoop) == NONMFC)
{
// zuerst kopieren, dann loeschen
CopyLBEntries(m_CNonSelect,m_CSelect,iLoop);
m_CNonSelect.DeleteString(iLoop);
}
}
}
void CLCEditDlg::OnAlldeselect()
{
// TODO: Code für die Behandlungsroutine der Steuerelement-....
// Anzahl der Eintraege
holen
int nNoOfEntries = m_CSelect.GetCount();
// Abpruefen, ob ueberhaupt
etwas zu kopieren ist
if (nNoOfEntries != 0)
{
// Eintraege von hinten nach vorne verschieben
for (int iLoop=nNoOfEntries-1; iLoop>=0; iLoop--)
{
CopyLBEntries(m_CSelect,m_CNonSelect,iLoop);
m_CSelect.DeleteString(iLoop);
}
}
else
MessageBox("Keine Einträge vorhanden!","HINWEIS",
MB_OK|MB_ICONINFORMATION);
} |
Wenn Sie das Programm übersetzen und starten, so werden je nach Button die
entsprechenden Einträge zwischen den Listboxen verschoben.
|
 |
Und noch einmal, weil man's gerne vergisst: denken Sie immer daran, dass
die Klasse CListBox, wie auch die anderen Controls, von der Klasse
CWnd abgeleitet ist und damit auch über deren Funktionalität verfügt.
So kann eine Listbox z.B. mittels EnableWindow(...) vom Programm aus
gesperrt werden. |
Wenden wir uns nun der Listbox in der Mitte des Dialogs zu. Diese Listbox weist
zwei Besonderheiten auf: zum einen wird sie nicht mehr vom Dialog selbst initialisiert,
wie die obige linke Listbox, sondern durch die Anwendung, und zum anderen können
ihr Einträge über das darunter stehende Editfeld hinzugefügt werden. Außerdem werden
nach dem Schließen des Dialogs alle in der Listbox vorhandenen Einträge, auch die
neu hinzugefügten, im Ausgabefensters des Debuggers ausgegeben, d.h. diese Listbox
verfügt nun über den DDX-Mechanismus.
 |
Aber passen wir zuerst die Eigenschaften der Listbox und des
Editfeldes an. Bis auf die IDs können Sie alle Eigenschaften der Controls auf
ihren Defaultwerten belassen. Geben Sie den Controls die folgenden IDs:

|
Als nächstes werden wir das Hinzufügen von Einträgen zur Listbox einbauen. Der
hinzuzufügende Eintrag wird im Editfeld IDC_NEWENTRY eingegeben. Wird anschließend
der Button Übernehmen angeklickt, so wird der eingegebenen Text in die
Listbox übernommen und das Editfeld gelöscht. Die gesamte Funktionalität hierzu
ist nachher im Nachrichtenbearbeiter des Buttons untergebracht.
Fangen wir mit dem Editfeld zur Eingabe an. Editfelder werden in der MFC durch
die Klasse CEdit repräsentiert. Diese Klasse ist ebenfalls von der Klasse
CWnd abgeleitet, so dass auch hier die gesamten Fensterfunktionen zur Verfügung
stehen. Außerdem enthält die Klasse CEdit, genauso wie die vorhin schon
behandelte Klasse CListBox, eine ganze Reihe von zusätzlichen Methoden.
WINDOWS kennt zwei Arten von Editfelder: Editfelder mit nur einer Zeile und Editfelder
mit mehreren Zeilen. Im Beispiel wird ein Editfeld mit nur einer Zeile verwendet.
Für Editfelder mit mehreren Zeilen stehen zusätzliche Methoden zur Verfügung wie
z.B. GetLineCount(...) um die Anzahl der Zeilen im Editfeld zu bestimmen.
Bevor der Text aus dem Editfeld zur Listbox hinzugefügt wird muss erst einmal
abgeprüft werden, ob überhaupt ein Text im Editfeld eingegeben wurde. Dies wird
durch den Aufruf der CEdit-Methode LineLength(...) erreicht. Als
Parameter erhält die Methode die Zeilennummer, für die die Zeilenlänge bestimmt
werden soll. Da im Beispiel nur ein einzeiliges Editfeld eingesetzt wird, kann dieser
Parameter auf seinem Defaultwert -1 belassen werden. Als Returnwert liefert die
Methode die Anzahl der Zeichen in der entsprechenden Zeile. Um den Text des Editfeldes
auszulesen wird (aber nur bei einzeiligen Editfelder!) die CWnd-Methode
GetWindowText(...) verwendet.
Damit wäre der erste Teil, das Auslesen des eingegebenen Textes, vollständig
beschrieben. Um diesen ausgelesenen Text anschließend zur Listbox hinzuzufügen,
wird die vorhin eingeführte CListBox-Methode AddString(...) verwendet.
Hierzu muss aber wiederum ein zur Listbox gehöriges CListBox-Objekt definiert
sein.
 |
Fügen Sie zur Dialogklasse zunächst die Membervariable
m_CApiListbox vom Typ CListBox hinzu. Beachten Sie bitte bei der
Definition der Membervariable über den Klassen-Assistent, dass im Feld Kategorie
auch der Eintrag Control steht. Erweitern Sie anschließend die Methode
On2listbox(...) des Buttons Übernehmen wie nachfolgend angegeben.
Beachten Sie bitte, dass das Editfeld nach dem Kopieren des Textes noch explizit
gelöscht werden muss.
void CLCEditDlg::On2listbox()
{
// TODO: Code für die Behandlungsroutine der Steuerelement-....
// Zeiger auf das mit
dem Edit-Control assoziierte
// CEdit-Objekt holen
CEdit *pEntry = static_cast<CEdit*>(GetDlgItem(IDC_NEWENTRY));
// Abpruefen ob Eingabe
im Editfeld vorhanden ist
if (pEntry->LineLength()
== 0)
MessageBox("Kein Eintrag im Eingabefeld
vorhanden!");
else
{
// Text aus Editfeld auslesen
CString CEntryText;
pEntry->GetWindowText(CEntryText);
// und zur Listbox hinzufuegen
m_CApiListbox.AddString(CEntryText);
// Eingabefeld loeschen
pEntry->SetWindowText("");
}
} |
Übersetzen und starten Sie das Programm. Sie können nun der Listbox über
das Editfeld und dem Button Übernehmen Einträge hinzufügen
|
Sehen wir uns jetzt den Datenaustausch der Listbox mit der Anwendung an. Hier
wird's leider etwas komplizierter. Da eine Listbox mehrere Einträge aufnehmen kann,
ist eine einfache Initialisierung, z.B. über ein CString-Objekt, nicht
mehr möglich. Wir werden im Beispiel die Listbox-Einträge über ein CStringList-Objekt
verwalten. Die Klasse CStringList ist eine MFC-Klasse, die beliebig viele
Texte in einer verketteten Liste verwaltet. Auf eine detaillierte Beschreibung dieser
Klasse soll hier verzichtet werden. Wir sehen uns nur die Methoden an, die für das
Beispiel relevant sind. Um zu einer CStringList einen Text hinzuzufügen,
kann u.a. deren Methode AddTail(...) eingesetzt werden. Sie erhält als
Parameter einen char-Zeiger auf den zur Liste hinzuzufügenden Text.
 |
Wenn Sie in der Online-Hilfe zu dieser Methode nachsehen werden
Sie nur die Beschreibung der Methode zur Klasse CObjList finden. Die
Klasse CStringList ist in manchen Versionen der Online-Hilfe leider
nicht richtig beschrieben. Sie ist aber von CObject abgeleitet. |
 |
Bauen wir das Ausfüllen der Liste nun ins Beispiel ein. Zuerst
wird zur Dialogklasse das für den Datenaustausch zuständige Objekt vom
Typ CStringList hinzugefügt. Beachten Sie bitte, dass das Objekt
public sein muss, da die Liste später von der Anwendung mit den Listbox-Einträgen
gefüllt wird.

Anschließend kann die Liste in der Methode InitInstance(...) des
Anwendungsobjektes mit vorzugebenden Listbox-Einträgen gefüllt werden.
BOOL CLCEditApp::InitInstance()
{
....
m_pMainWnd = &dlg;
// Listbox-Eintraege in
CStringList aufnehmen
dlg.m_CListboxString.AddTail("Message
API");
dlg.m_CListboxString.AddTail("Telephone API");
dlg.m_CListboxString.AddTail("Speech API");
dlg.m_CListboxString.AddTail("Windows API");
int nResponse = dlg.DoModal();
....
} |
|
Nachdem die Liste mit den entsprechenden Einträgen gefüllt wurde, müssen die
Einträge in einer Methode der Dialogklasse irgendwie in die Listbox kopiert werden.
Ein erster Ansatz hierzu wäre, die Listbox wieder in der OnInitDialog(...)
Methode zu initialisieren. Leider hat die Sache aber einen Haken. Die Listbox könnte
zwar vor dem Darstellen des Dialogs mit den Einträgen initialisiert werden. Da die
Listbox aber während des Programmlaufes um zusätzliche Einträge erweitert werden
kann, müssen alle Listbox-Einträge beim Schließen des Dialogs auch wieder in die
Liste zurück kopiert werden. Vielleicht fällt Ihnen jetzt spontan die Methode
OnClose(...) ein um die Einträge zurück zu kopieren. Diese Methode wird
ja bei einem Fenster immer dann aufgerufen, wenn das Fenster geschlossen werden
soll. Doch leider funktioniert auch dies nicht, da Dialoge diese Methode nicht verwenden.
Auch die Methode OnDestroy(...) kann hierfür nicht eingesetzt werden, da
sie erst nach dem Zerstören des Dialogs, und damit auch nach dem Zerstören der Listbox,
aufgerufen wird. Aber keine Panik, die Lösung naht in Form der vom Klassen-Assistent
zum Dialog hinzugefügten Methode DoDataExchange(...). Innerhalb dieser
Methode können Sie immer Daten von der Anwendung zum Dialog und umgekehrt transferieren.
Das einzige Problem das hierbei zu lösen gilt ist: woher weiß die Methode, in welche
Richtung der Datentransfer zu erfolgen hat. Nun, die Methode erhält als Parameter
einen Zeiger auf ein Objekt vom Typ CDataExchange. Und innerhalb dieser
Klasse gibt es eine public Membervariable m_bSaveAndValidate die
den Wert TRUE annimmt, wenn Daten vom Dialog zur Anwendung zu transferieren
sind und FALSE wenn der Datenaustausch in die andere Richtung erfolgt.
Nachdem wir wieder einmal auch dieses Problem 'gelöst' haben sehen wir uns jetzt
an, wie die Dialogklasse mit dem CStringList-Objekt arbeitet. Als erstes
muss innerhalb des Dialogs abgeprüft werden, ob überhaupt Einträge in der Liste
vorhanden sind. Dazu wird die Methode IsEmpty(...) der CStringList
Klasse aufgerufen. Sie liefert als Returnwert einen Wert ungleich 0 wenn die Liste
Einträge enthält. Nachdem festgestellt wurde, dass die Liste Einträge enthält, muss
das erste Listenelement adressiert werden. Hierfür wird die Methode GetHeadPosition(...)
verwendet. Sie liefert als Returnwert die Position des ersten Listenelementes. Beachten
Sie bitte, dass die Methode nur die Position und noch nicht den Eintrag selbst liefert.
Um durch die Liste der Einträge durchzulaufen, rufen Sie die CStringList-Methode
GetNext(...) auf. Sie erhält als Parameter die Position, dessen Eintrag
Sie auslesen möchten. Als Returnwert liefert die Methode einen Zeiger auf eine Referenz
auf den aktuellen Eintrag in der Liste (welche wunderbare Konstruktion!). Hört sich
komplizierter an als es ist, Sie werden das nachher gleich sehen. Gleichzeitig setzt
die Methode noch die Position in der Liste um einen Eintrag weiter, so dass Sie
danach nur noch GetNext(...) aufrufen müssen um den jeweils nächsten Eintrag
zu erhalten. Wurde das letzte Listenelement ausgelesen, so wird durch die Methode
GetNext(...) die aktuelle Position auf NULL gesetzt. Damit können wir jetzt
die Liste komplett auslesen.
 |
Fügen wir jetzt den Code zum Auslesen der CStringList
zu unserem Beispiel hinzu. Erweitern Sie dazu die Methode DoDataExchange(...)
wie folgt:
void CLCEditDlg::DoDataExchange(CDataExchange*
pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CLCEditDlg)
....
//}}AFX_DATA_MAP
if (pDX->m_bSaveAndValidate)
// Datentransfer zur Anwendung
{
// das kommt noch
}
else // Datentransfer von der Anwendung
{
// Abpruefen ob CStringList leer ist
if (!m_CListboxString.IsEmpty())
{
// Position des ersten Listenelements holen
POSITION ListPos = m_CListboxString.GetHeadPosition();
// Liste solange durchlaufen, bis alle Eintraege
// ausgelesen
wurden
while (ListPos != NULL)
{
// Listenelement auslesen
CString CText = m_CListboxString.GetNext(ListPos);
// und zur Listbox hinzufuegen
m_CApiListbox.AddString(CText);
}
}
}
} |
Wenn Sie nun das Beispiel übersetzen, so werden in der Listbox die von der
Anwendung vorgegebenen Einträge dargestellt. Wenn Sie über den Button Übernehmen
weitere Einträge zu Listbox hinzufügen, so erhält die Listbox bei Bedarf automatisch
einen Scrollbar um durch die Einträge durchgehen zu können. Dies wird durch
die standardmäßig gesetzte Eigenschaft Vertik. Bildlauflauf der Listbox
erreicht.
|
Sehen wir uns jetzt den anderen Fall an, den Datentransfer vom Dialog zur Anwendung.
Zuerst muss die Liste CStringList geleert werden; schließlich soll die Liste
ja nur die Einträge enthalten, die aktuell in der Listbox vorhanden sind. Um eine
CStringList Liste zu leeren wird deren Methode RemoveAll(...) aufgerufen.
Danach sollte vor dem Transfer auch wieder abgeprüft werden, ob die Listbox überhaupt
Einträge enthält. Dies erfolgt durch die bereits weiter oben beschriebene CListBox-Methode
GetCount(...). Enthält die Listbox Einträge, kann mit der bereits bekannten
CListBox-Methoden GetText(...) ein Eintrag nach dem anderen aus
der Listbox auslesen und dann mittels der CStringList-Methode AddTail(...)
zur Liste hinzufügen. Zum Schluss können Sie in der InitInstance(...) Methode
der Anwendung alle aktuellen Einträge der Listbox noch im Debugger-Fenster ausgeben.
 |
Versuchen nun wieder einmal selbst beim Schließen des Dialogs
den Inhalt der Listbox in das CStringList Objekt zu kopieren und dann
die darin enthaltenen Strings in der Methode InitInstance(...) des
Anwendungsobjekts im Debuggerfenster mittels TRACE(...) auszugeben.
 |
Wenn Sie genug Schweiß vergossen haben, kann ich Ihnen auch meine Lösung
anzeigen. |
Lösung zum Listbox-DDX
Zuerst sehen wir uns an, wie der Dialog die aktuellen Listbox-Einträge
in die CStringList umkopiert.
void CLCEditDlg::DoDataExchange(CDataExchange*
pDX)
{
....
if (pDX->m_bSaveAndValidate)
// Datentransfer zur Anwendung
{
// CStringList leeren!
m_CListboxString.RemoveAll();
// Anzahl der Eintraege in der Listbox auslesen
int nNoOfEntries = m_CApiListbox.GetCount();
// Falls Eintraege in der Listbox vorhanden sind
if (nNoOfEntries != 0)
{
CString CText;
// Eintraege in CStringList umkopieren
for (int iLoop=0; iLoop<nNoOfEntries; iLoop++)
{
// Eintrag aus Listbox auslesen
m_CApiListbox.GetText(iLoop,CText);
// und zu CStringList hinzufuegen
m_CListboxString.AddTail(CText);
}
}
}
else // Datentransfer von der Anwendung
{
....
}
} |
Die Ausgabe der CStringList-Einträge in der Methode InitInstance(...)
kann dann wie folgt erfolgen:
BOOL CLCEditApp::InitInstance()
{
....
if (nResponse == IDOK)
{
// ZU ERLEDIGEN: Fügen Sie hier Code
ein, um ein Schließen des
// Dialogfelds über
OK zu steuern
// Falls Listbox Eintraege enthalten hat
if (!dlg.m_CListboxString.IsEmpty())
{
// Alle Eintraege ausgeben
TRACE("Listbox Eintraege:\n");
POSITION ListPos
= dlg.m_CListboxString.GetHeadPosition();
while (ListPos
!= NULL)
{
CString CText = dlg.m_CListboxString.GetNext(ListPos);
TRACE("%s\n",CText);
}
}
else
TRACE("Listbox
ist leer!\n");
}
....
} |
Ende der Lösung
|
Sie können das Beispiel nun übersetzen und starten. Die Listbox wird dann
die gewünschte Funktionalität zeigen. Fügen Sie auch Einträge zur Listbox hinzu
und sehen Sie sich dann die Ausgabe im Debugger-Fenster an.
|
Kommen wir nun zum letzten Element im Dialog, der Combobox. Comboboxen bestehen
aus einem Edit-/Textfeld und einem Listenfeld, in dem die Einträge dargestellt werden.
WINDOWS kennt drei Arten von Comboboxen:
|
Stil
|
Darstellung
|
Anmerkung
|
| Einfach |
 |
Das Listenfeld ist ständig sichtbar und das Editfeld ist mit
diesem verbunden. Werden im Editfeld Eingaben vorgenommen, so wird das Listenfeld
soweit durchgescrollt, das in diesem einer der Eingabe am nächsten kommende
Eintrag sichtbar ist. Wird ein Eintrag ausgewählt, so wird er im Eingabefeld
angezeigt. Neue Eingaben werden nicht automatisch ins Listenfeld übernommen. |
| Dropdown |
 |
Wie einfache Combobox, nur dass das Listenfeld erst dann dargestellt
wird, wenn der Dropdown-Pfeil (rechts im Eingabefeld) angeklickt wird. Wird
ein Eintrag ausgewählt, so wird er im Editfeld angezeigt. Neue Eingaben werden
nicht automatisch ins Listenfeld übernommen. |
| Dropdown-Liste |
 |
Das Listenfeld wird auch hier erst dann dargestellt, wenn der
Dropdown-Pfeil angeklickt wird. Die Dropdown-Liste lässt keine Eingaben zu.
Im Textfeld wird der aktuell ausgewählte Eintrag dargestellt. |
Passen wir zunächst die Eigenschaften der am Anfang der Lektion erstellten Combobox
unseren Anforderungen an. Im Beispiel soll innerhalb der Combobox aus einer vorgegebenen
Anzahl von Dateitypen einer ausgewählt werden können. Hierzu wird eine Combobox
vom Typ DropDown-Liste verwendet.
 |
Geben Sie zuerst der Combobox die ID IDC_FILETYP.

Als nächstes legen Sie die Größe des Listenfeldes fest. Öffnen Sie dazu den
Eigenschaftsdialog der Combobox und wählen dann den Tabulator Formate
aus. Stellen Sie als Typ zunächst Einfach ein. Nun können Sie durch
ziehen am Rahmen die Größe der aufgeklappten Combobox einstellen.
 |
Sie können die Größe des Listenfeldes nur dann einstellen, wenn Sie
den Typ Einfach ausgewählt haben. Bei den beiden anderen Combobox-Typen
ist dies nicht möglich! |
Haben Sie Größe der Combobox entsprechend eingestellt, so stellen Sie dann
den gewünschten Typ der Combobox ein, in unserem Beispiel als Dropdown-Liste.
Wollen Sie nachher nochmals die Größe des Listenfeldes ändern, so müssen Sie
zuerst wieder den Typ Einfach einstellen.
Um das Aussehen der Combobox testen zu können wechseln Sie in den Dialog
Daten. Im Eingabefeld des Dialogs können Sie nun ein paar Einträge
eingeben.

 |
Achtung! Um einen neue Eintrag einzugeben drücken Sie auf keinen Fall
die <RETURN>-Taste, dies würde den Eigenschaftsdialog schließen. Drücken
Sie am Ende des Eintrags <STRG>-<RETURN> um zum nächsten Eintrag zu gelangen.
Außerdem werden die hier angegebenen Einträge automatisch in die Combobox
übernommen. Wollen Sie die Einträge per Programm erstellen, so wie im Beispiel,
so müssen Sie die Einträge zum Schluss löschen! |
Testen Sie nun das Aussehen der Combobox in dem Sie im Menü Layout
den Menüpunkt Testen auswählen. Wenn alles zu Ihrer Zufriedenheit eingestellt
ist, löschen Sie die Einträge im Dialog Daten und schließen dann den
Eigenschaftsdialog.
|
Soweit zur Ressource-Eigenschaft unserer Combobox. Kommen wir nun zur programmtechnischen
Handhabung. Comboboxen werden unter der MFC durch die Klasse CComboBox
repräsentiert, die ebenfalls wieder von der Klasse CWnd abgeleitet ist.
Wenn Sie in einem Dialog eine Combobox einsetzen, müssen Sie zur Dialogklasse ein
entsprechendes CComboBox Objekt hinzufügen. Über dieses Objekt können Sie
dann der Combobox Einträge hinzufügen. Rufen Sie dazu dessen Methode AddString(...)
auf. Die Methode erhält als Parameter einen char-Zeiger auf den hinzuzufügenden
Eintrag und liefert als Returnwert wieder die Position, an der der Eintrag eingefügt
wurde. Soll ein bestimmter Eintrag der Combobox im Edit-/Textfeld dargestellt werden,
so können Sie dies durch den Aufruf von SetCurSel(...) erreichen. Geben
Sie der Methode die Position des Eintrags mit (0-basierend), die als Vorgabe verwendet
werden soll.
 |
Sie könnten jedem Combobox-Eintrag, genauso wie bei der Listbox, einen 32-Bit
Wert zuordnen. Wenn Sie sich die Klasse CComboBox einmal ansehen werden
Sie viele Methoden finden, die auch in der Klasse CListBox vorhanden
sind. So liefert z.B. die Methode GetCount(...) auch die Anzahl der
Einträge in der Combobox. |
Aber initialisieren wir jetzt unsere Combobox.
 |
Fügen Sie zunächst zur Dialogklasse das der Combobox zugeordnete
CComboBox Objekt hinzu. Geben Sie dem Objekt den Namen m_CFileTyp.
Beachten Sie dabei bitte, dass als Kategorie Control eingestellt ist.
Über dieses neu hinzugefügte Objekt kann die Combobox nun in der OnInitDialog(...)
Methode initialisiert werden. Wir initialisieren hier die Combobox innerhalb
des Dialogs und nicht vom Anwendungsobjekt aus, da der Dialog die anzuzeigenden
Dateitypen vorgeben soll.
BOOL CLCEditDlg::OnInitDialog()
{
....
m_CNonSelect.SetItemData(nIndex,MFC);
// Combobox initialisieren
m_CFileTyp.AddString("*.cpp");
m_CFileTyp.AddString("*.h");
m_CFileTyp.AddString("*.rc");
m_CFileTyp.AddString("*.dsw");
// Ersten Eintrag als Defaultwert
vorgeben
m_CFileTyp.SetCurSel(0);
return TRUE; // Geben
Sie TRUE zurück, außer ein Steuerelement soll....
} |
Wenn Sie das Programm jetzt starten, so wird in der Combobox die Auswahlliste
der möglichen Dateitypen angezeigt. Sollten Sie noch weitere Einträge sehen
so liegt dies wahrscheinlich daran, dass Sie bei den Eigenschaften der Combobox
die Daten noch nicht gelöscht haben.
|
Da wir im Beispiel keine Nachrichten von der Combobox verarbeiten müssen, werden
wir auch gleich an den Datentausch mit der Anwendung gehen.
 |
Bei Comboboxen der Typen Einfach und Dropdown
könnten Sie z.B. die Nachricht CBN_EDITCHANGE verarbeiten die anzeigt, dass
der Text im Editfeld der Combobox verändert wurde. Um für diese Nachrichten
entsprechende Nachrichtenbearbeiter zu installieren, klicken Sie die Combobox
mit der rechten Maustaste an und wählen aus dem Kontextmenü den Eintrag
Ereignisse... aus.

|
Zum Schluss fehlt uns nur noch das Auslesen des aktuell eingestellten Datentyps
innerhalb der Anwendung. Und hierbei lernen Sie auch gleich einen kleinen Kniff
kennen, der Ihnen den Datentausch mit einer Combobox etwas erleichtert. Standardmäßig
verwenden Comboboxen vom Typ Dropdown und Dropdown-Liste für den
Datenaustausch eine int-Variable. Da uns aber nur der eingestellte Datentyp
als String interessiert, werden wir uns um den Datenaustausch selbst kümmern. Um
den aktuell eingestellten Combobox-Eintrag in einem CString-Objekt abzulegen,
wird das Makro DDX_CBString(...) verwendet. Außer diesem DDX-Makro gibt
es noch eine Vielzahl weiterer, die Sie der Online-Hilfe unter dem Stichwort DDX_xxx
entnehmen können.
Bauen wir jetzt unseren DDX-Mechanismus für die Combobox jetzt ein.
 |
Fügen Sie zunächst dem Dialog die public Membervariable
m_CSelFiletyp mit dem Datentyp CString hinzu. Anschließend
passen Sie die Methode DoDataExchange(...) noch wie folgt an:
void CLCEditDlg::DoDataExchange(CDataExchange*
pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CLCEditDlg)
....
//}}AFX_DATA_MAP
// Unser eigener Datenaustausch!
DDX_CBString(pDX, IDC_FILETYP,
m_CFileString);
if (pDX->m_bSaveAndValidate)
// Datentransfer zur Anwendung
....
} |
Zum Schluss kann die Anwendung dann nach dem Schließen des Dialogs diese
Membervariable auslesen um den ausgewählten Datentyp auszuwerten. Erweitern
Sie Methode InitInstance(...) der Anwendung dazu wie folgt.
BOOL CLCEditApp::InitInstance()
{
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
....
TRACE("Ausgewählter Dateityp: %s\n",(LPCSTR)dlg.m_CFileString);
}
....
} |
|
Damit ist das Beispiel, und diese Lektion, (endlich) komplett und sie können
es übersetzen. Das fertige Beispiel finden Sie unter 08Dialoge\LCEdit.
In der nächsten Lektion lernen Sie dann weitere Controls wie z.B. den Fortschrittsbalken
oder auch den Schieberegler kennen.
|