Hallo,
Wie versprochen gibt’s ein zweites Tutorial von mir, in dem ich weitere Elemente erkläre und wir sie in die Klassen einbauen werden.
Definierungen:
Als erstes gibt es neue Error – Codes:
|
C-/C++-Quelltext
|
1
2
|
const unsigned long ERROR_INVALID_PARAM = 0x00000002;
const unsigned long ERROR_NULL_POINTER = 0x00000003;
|
und neue Defines zur erkennung der Elemente:
|
C-/C++-Quelltext
|
1
2
3
4
5
|
const unsigned long WIN_CHECK = 0x00000004L;
const unsigned long WIN_COMBO = 0x00000005L;
const unsigned long WIN_FRAME = 0x00000006L;
const unsigned long WIN_RADIO = 0x00000007L;
const unsigned long WIN_LIST = 0x00000008L;
|
Das wars vorerst mal mit den Defines.
Weitere Veränderungen:
Die Klasse Element hat in der Zwischenzeit noch mehr Freunde gefunden mit der sie ihr privates teilen will:
|
C-/C++-Quelltext
|
1
2
3
4
5
|
friend class Combo;
friend class List;
friend class Frame;
friend class Check;
friend class Radio;
|
Machen wir also mit einem der neuen Freunde von Element weiter.
Eine Combobox:
Eines muss ich vorweg sagen. Die Elemente in diesem Tutorial werden nicht so einfach wie die im letzten. Aber trotzdem machbar.
Also fangen wir wieder mit der Klasse an und nehmen sie später auseinander.
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
class Combo : public Element
{
public:
// erstellt eine combobox
unsigned long create (unsigned int x, unsigned int y,
unsigned int Width, unsigned int Height,
HWND ParentWindow,
unsigned long ID,
HINSTANCE Instance);
// fügt einen Listeneintrag hinzu
unsigned int add_entry (wchar_t *pText);
// Liefert den Index des gerade angewählten eintrags
unsigned int get_selected_entry_index ();
// Liefert die anzahl der einträge
unsigned int get_num_entry ();
// Wählt einen Eintrag aus
unsigned long select_entry (unsigned int Index);
// Leert die Liste
unsigned long empty ();
// Verknüpft einen Eintrag mit Daten
template <typename T>
unsigned long set_data (unsigned int Index, T Data);
// Liefert die Daten eines Eintrags
template <typename T>
unsigned long get_data(unsigned int Index, T *pData);
// Zerstört das Element
unsigned long destroy ();
};
|
Werfen wir mal einen Blick auf die
create Methode:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
unsigned long Combo::create(unsigned int x, unsigned int y,
unsigned int Width, unsigned int Height,
HWND ParentWindow, unsigned long ID,
HINSTANCE Instance)
{
// Membervariablen initialisieren
m_ID = ID;
m_Parent = ParentWindow;
m_Instance = Instance;
m_Type = WIN_COMBO;
// Button erstellen
if ((m_Element = CreateWindowW (L"COMBOBOX", L"",
WS_VISIBLE | WS_CHILD | CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP,
x, y,
Width, Height,
m_Parent,
reinterpret_cast<HMENU>(m_ID),
m_Instance,
NULL)) == NULL)
{
return ERROR_INVALID_API_CALL;
}
return OK;
}
|
Nichts großes eigentlich das gleiche wie sonst auch. Der erste Parameter der CreateWindow ist klar, das ist die vorgefertigte Struktur für eine Combobox. Text geben wir keinen an, da dies ja sowieso keinen sinn hätte. Als Styles geben wir an, dass das Fenster sichtbar sein soll, dass es ein child fenster sein soll und dass es eine Dropdownliste haben soll. Darauf folgen die Koordinaten der linken oberen Ecke, die Fenstergröße und das Parentwindow. Dann brauchen wir noch die ID und die Instanz.
Natürlich sollten wir unserer Combobox auch Einträge hinzufügen können. Dies funktioniert mit der
add_entry Methode.
|
C-/C++-Quelltext
|
1
2
3
4
|
unsigned int Combo::add_entry(wchar_t *pText)
{
return static_cast<unsigned int>(SendMessageW (m_Element, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(pText)));
}
|
Wir senden in dieser Methode nur die Nachricht CB_ADDSTRING und übergeben ihr im lParam den Text der angezeigt werden soll. Im gegenzug liefert uns die SendMessage Funktion den Index des Eintrags, den wir dann auch gleich zurückliefern.
Später könnte es doch auch Hilfreich sein Daten mit so einem Eintrag zu verknüpfen. Damit man hier auch jeden Datentyp übergeben kann benutzen wir hier ein Template.
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
template <typename T>
unsigned long Combo::set_data(unsigned int Index, T Data)
{
T *pData = NULL;
// parameter prüfen
if (Index > get_num_entry ()-1)
return ERROR_INVALID_PARAM;
// Speicher reservieren
pData = new T;
*pData = Data;
if (!pData)
return ERROR_NULL_POINTER;
// Daten mit listeneintrag verknüpfen
if ((SendMessageW (m_Element, CB_SETITEMDATA, Index, reinterpret_cast<LPARAM>(pData))) == CB_ERR)
return ERROR_INVALID_API_CALL;
return OK;
}
|
Diese Funktion erwartet zuerst den Index des Eintrags mit dem die Daten verknüpft werden sollen. Und als nächstes kommen die Daten an sich.
Als erstes wird hier überprüft ob der Eintrag, mit dem die Daten verknüpft werden sollen auch wircklich existiert. Nach diesem Vorgang wird Speicher für den angegebenen Wert reserviert, da man der SendMessage Funktion eben einen Zeiger übergeben muss. Danach wird die Message CB_SETITEMDATA gesendet.
Im wParam geben wir den Index an und in lParam den Zeiger.
Natürlich darf auch eine Funktion zum abfragen der Daten nicht Fehlen:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
template <typename T>
unsigned long Combo::get_data(unsigned int Index, T *pData)
{
T *pTemp = NULL;
// Parameter prüfen
if (Index > get_num_entry ()-1)
return ERROR_INVALID_PARAM;
// Daten abfragen
if ((pTemp = reinterpret_cast<T*>(SendMessageW (m_Element, CB_GETITEMDATA, Index, 0))) == reinterpret_cast<T*>(CB_ERR))
return ERROR_INVALID_API_CALL;
*pData = *pTemp;
return OK;
}
|
Hier wird wiederum der Index überprüft. Danach wird die Message CB_GETITEMDATA an das Element gesendet. Sie erwartet als wParam den Index und Liefert die Daten zurück.
Und jeder weiß es, wo new im spiel ist darf auch delete nicht weit sein. Darum gibt es die
empty Methode:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
unsigned long Combo::empty()
{
void *pData = NULL;
for (unsigned int i=0; i < get_num_entry (); ++i)
{
pData = reinterpret_cast<void*>(SendMessageW (m_Element, CB_GETITEMDATA, i, 0));
if (pData != reinterpret_cast<void*>(CB_ERR))
{
SAFE_DELETE (pData);
}
}
SendMessageW (m_Element, CB_RESETCONTENT, 0, 0);
return OK;
}
|
Diese Funktion macht nichts weiter als jeden Eintrag durchzugehen und den (Falls vorhandenen)Speicher zu löschen. Danach werden noch mit der CB_RESETCONTENT Message alle Einträge gelöscht.
Die Methode
destroy macht dann auch nichts weiter als diese
empty Methode aufzurufen und die Combobox durch DestroyWindow zu zerstören.
Wichtig ist auch eine Methode zum auswählen eines Eintrags:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
|
unsigned long Combo::select_entry (unsigned int Index)
{
if (Index > get_num_entry () - 1)
return ERROR_INVALID_PARAM;
SendMessageW (m_Element, CB_SETCURSEL, Index, 0);
return OK;
}
|
Hier wird der Parameter überprüft und die Message CB_SETCURSEL an das Element gesendet. Der wParam Paremeter ist hier mal wieder der Index.
Wer bis jetzt genau aufgepasst hat hat festgestellt, dass wir immer diese
get_num_entry Methode benutzen. Hier ist sie:
|
C-/C++-Quelltext
|
1
2
3
4
|
unsigned int Combo::get_num_entry()
{
return static_cast<unsigned int>(SendMessageW (m_Element, CB_GETCOUNT, 0, 0));
}
|
Um an die anzahl der Einträge zu kommen reicht ein SendMessage aufruf mit der CB_GETCOUNT Message und die Funktion liefert die Anzahl.
Als letztes kommt jetzt noch die sehr wichtige Methode
get_selected_entry_index, mit der wir den Index des gerade angewählten Eintrags in Erfahrung bringen können.
|
C-/C++-Quelltext
|
1
2
3
4
|
unsigned int Combo::get_selected_entry_index()
{
return static_cast<unsigned int>(SendMessageW (m_Element, CB_GETCURSEL, 0, 0));
}
|
Für diesen Zweck verwendet man die CB_GETCURSEL Message, und man hat schon sein Ergebnis.
Listen:
Listen sind im prinzip genau das gleiche wie Comboboxen. Darum werden wir auch nur die
create Methode besprechen, da die anderen Methoden genau gleich sind.
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
class List : public Element
{
public:
// erstellt eine Liste
unsigned long create (unsigned int x, unsigned int y,
unsigned int Width, unsigned int Height,
HWND ParentWindow, unsigned long ID,
HINSTANCE Instance, bool ThirdDimension = true,
bool Sort = false);
// fügt einen Listeneintrag hinzu
unsigned int add_entry (wchar_t *pText);
// Liefert den Index des gerade angewählten eintrags
unsigned int get_selected_entry_index ();
// Liefert die anzahl der einträge
unsigned int get_num_entry ();
// Leert die Liste
unsigned long empty ();
// Verknüpft einen Eintrag mit Daten
template <typename T>
unsigned long set_data (unsigned int Index, T Data);
// Liefert die Daten eines Eintrags
template <typename T>
unsigned long get_data(unsigned int Index, T *pData);
// Zerstört das Element
unsigned long destroy ();
};
|
Wie versprochen die
create:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
unsigned long List::create(unsigned int x, unsigned int y,
unsigned int Width, unsigned int Height,
HWND ParentWindow, unsigned long ID,
HINSTANCE Instance,
bool ThirdDimension, bool Sort)
{
unsigned long ExStyle = NULL;
unsigned long Style = WS_VISIBLE | WS_CHILD | LBS_NOTIFY | WS_VSCROLL | WS_BORDER;
// Membervariablen initialisieren
m_ID = ID;
m_Parent = ParentWindow;
m_Instance = Instance;
m_Type = WIN_LIST;
if (ThirdDimension)
{
ExStyle = WS_EX_CLIENTEDGE;
Style = WS_VISIBLE | WS_CHILD | LBS_NOTIFY | WS_VSCROLL;
}
if (Sort)
Style = Style | LBS_SORT;
if (!(m_Element = CreateWindowExW (ExStyle, L"LISTBOX",
L"", Style, x, y,
Width, Height, m_Parent,
reinterpret_cast<HMENU>(m_ID),
m_Instance, NULL)))
{
return ERROR_INVALID_API_CALL;
}
return OK;
}
|
Diese Prozedur mit den Styles kommt uns doch noch von der Edit Klasse bekann vor, oder?
Also der Standard Wert für die Style Variable ist hier eine 2D Liste mit Rahmen. Falls ThirdDimension jedoch true ist nehmen wir den Rahmen wieder raus, denn der sieht bei 3D Schlecht aus. Weiterhin setzten wir in diesem Fall auch noch den Exteded Style auf WS_EX_CLIENTEDGE. Falls Sort true ist wird die Liste Alphabetisch geordnet und der LBS_SORT Style wird angehängt.
Danach wird dann nur noch das Element mit CreateWindowEx erstellt und gut ist.
Das Problem mit den Radiobuttons:
Stellt euch vor, wir würden jetzt eine Gruppe Radiobuttons erstellen, die angibt welche Farbe ein Rand eines Quadrats hat und noch eine Gruppe Radiobuttons die angibt welche Farbe das innere des Quadrats hat und jeden Button dem Hauptfenster unterordnen.
Was soll passieren? Natürlich! Man sollte von jeder Gruppe einen Radiobutton auswählen können. Aber nix da! Man wird feststellen, dass man nur einen Button von beiden gruppen zusammen auswählen kann. Nun woran liegt das?
Nach längerem überlegen kommt man doch zu dem Schluss, dass das nur daran liegen kann, dass wir alle Buttons dem gleichen Fenster untergeordnet haben. Jetzt stellt sich die Frage wie wir das Lösen können.
Ganz einfach. Jeder hat doch sicherlich schonmal die Rahmen gesehen, die um solche Buttons immer Rum sind. Da hab ich mir gedacht, dass man die Buttons doch dem jeweiligen Rahmen unterordnen kann und siehe da es funktioniert.
Also machen wir einen kleinen Abstecher zu den Rahmen und kommen später auf die Radiobutons zurück.
Rahmen:
Zuerst die Klasse, dann das Vergnügen. :-)
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
class Frame : public Element
{
public:
// Erstellt den Rahmen
unsigned long create (wchar_t *pText,
unsigned int x, unsigned int y,
unsigned int Width, unsigned int Height,
HWND ParentWindow, unsigned long ID,
HINSTANCE Instance);
// Fügt einen Radiobutton hinzu
unsigned long add_radio (wchar_t *pText,
unsigned int x, unsigned int y,
unsigned int Width, unsigned int Height,
unsigned long ID);
unsigned long add_check (wchar_t *pText,
unsigned int x, unsigned int y,
unsigned int Width, unsigned int Height,
unsigned long ID);
// Liefert einen Radiobutton
Element *get_element (unsigned long ID);
// Zersört das Element
unsigned long destroy ();
private:
std::list <Element*> m_Elements;
std::list <Element*>::iterator m_i;
};
|
Wie man sieht kann man später Radiobuttons und Checkboxen einem Rahmen zuweißen. Diese Elemente werden wieder in einer Liste gespeichert, damit man sie leicht verwalten kann.
Ein alter bekannter Freund:
create
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
unsigned long Frame::create(wchar_t *pText,
unsigned int x, unsigned int y,
unsigned int Width, unsigned int Height,
HWND ParentWindow, unsigned long ID,
HINSTANCE Instance)
{
// Membervariablen initialisieren
m_ID = ID;
m_Parent = ParentWindow;
m_Instance = Instance;
m_Type = WIN_FRAME;
// Rahmen erstellen
if ((m_Element = CreateWindowW (L"BUTTON", pText,
WS_CHILD | WS_VISIBLE | BS_GROUPBOX,
x, y, Width, Height, m_Parent,
reinterpret_cast<HMENU>(ID),
m_Instance, NULL)) == NULL)
{
return ERROR_INVALID_API_CALL;
}
return OK;
}
|
Nichts neues. Sollte jeder selber hinbekommen.
Also weiter.
add_radio und
add_check. Da beides das gleiche ist werden wir uns nur eine anschauen.
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
unsigned long Frame::add_radio(wchar_t *pText,
unsigned int x, unsigned int y,
unsigned int Width, unsigned int Height,
unsigned long ID)
{
Radio *pRadio = new Radio;
Element *pElement = NULL;
unsigned long Result = OK;
if (FAIL(Result = pRadio->create (pText,
x, y, Width, Height,
m_Element, ID, m_Instance)))
{
return Result;
}
pElement = reinterpret_cast<Element*>(pRadio);
m_Elements.push_back (pElement);
return OK;
}
|
Hier ist es dasselbe wie bei der Window Klasse. Eine Instanz der Radio Klasse wird ezeugt. Danach wird der Radiobutton erstellt, gecastet und in die Liste geschoben.
Bei
get_element wird jeder Eintrag der Liste mit dem der angegeben ID verglichen und falls man erfolgreich war wird der gefundene Eintrag zurückgeliefert. Falls dies nicht der Fall ist NULL.
Die Methode
destroy kennen wir ebenfalls schon. Jeder Eintrag wird durchgegangen, je nach Typ gecastet und die jweilige
destroy wird aufgerufen.
Jetzt können wir auch schon Rahmen erstellen und diesen Radio und Checkbuttons zuordnen. Jetzt brauchen wir nur noch was? – Natürlich die Klassen für diese beiden Elemente.
Radiobuttons und Checkboxen:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
class Radio : public Element
{
public:
// erstellt einen Radiobutton
unsigned long create (wchar_t *pText,
unsigned int x, unsigned int y,
unsigned int Width, unsigned int Height,
HWND ParentWindow, unsigned long ID,
HINSTANCE Instance);
// Ist der Button gecheckt?
bool is_checked ();
// Checkt den Button
unsigned long check (bool check);
// Zerstört den Button
unsigned long destroy ();
};
|
und noch die zweite:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
class Check : public Element
{
public:
// erstellt einen Checkbutton
unsigned long create (wchar_t *pText,
unsigned int x, unsigned int y,
unsigned int Width, unsigned int Height,
HWND ParentWindow, unsigned long ID,
HINSTANCE Instance);
// Ist der Button gecheckt?
bool is_checked ();
// Checkt den Button
unsigned long check (bool check);
// Zerstört den Button
unsigned long destroy ();
};
|
Was fällt auf? Ist doch ganz klar: Bei der Radio klasse ist keine Leerzeile
nach public. Nein Scherz beiseite. Wie man sieht gibt es zwischen den Klassen keine änderungen. Und genau so sieht es auch bei den implementierungen aus. Nur dass bei der create ein Style – Flag anders ist als bei der anderen.
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
unsigned long Radio::create(wchar_t *pText,
unsigned int x, unsigned int y,
unsigned int Width, unsigned int Height,
HWND ParentWindow, unsigned long ID,
HINSTANCE Instance)
{
// Membervariabeln initialisieren
m_ID = ID;
m_Instance = Instance;
m_Parent = ParentWindow;
m_Type = WIN_RADIO;
if (!(m_Element = CreateWindowW (L"BUTTON", pText, WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
x, y, Width, Height, ParentWindow, reinterpret_cast<HMENU>(ID), Instance, NULL)))
{
return ERROR_INVALID_API_CALL;
}
return OK;
}
|
Hier werden zuerst die Membervariablen gesetzt und dann das Fenster erstellt. Übrigends heißt das Flag für eine Checkbox BS_AUTOCHECKBOX.
Die Klasse Window:
Natürlich gibt es auch Änderungen in der Window Klasse. Es gibt drei neue Methoden zum hinzufügen von Elementen:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// Fügt eine Combobox hinzu
unsigned long add_combo (unsigned long x, unsigned long y,
unsigned long Width, unsigned long Height,
unsigned long ID);
// Fügt eine Liste hinzu
unsigned long add_list (unsigned long x, unsigned long y,
unsigned long Width, unsigned long Height,
unsigned long ID, bool ThirdDimension = true,
bool Sort = false);
// Fügt einen Rahmen hinzu
unsigned long add_frame (wchar_t *pText,
unsigned long x, unsigned long y,
unsigned long Width, unsigned long Height,
unsigned long ID);
|
Ich denke wir müssen diese Methoden nicht besprechen, denn es sind genau dieselben wie im Tutorial zuvor.
Bei der
destroy – Methode gab es aber ein paar kleine Änderungen:
|
C-/C++-Quelltext
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
unsigned long Window::destroy()
{
// Jedes Element durchgehen
for (m_i=m_Elements.begin(); m_i != m_Elements.end(); ++m_i)
{
switch ((*m_i)->get_type())
{
case WIN_COMBO: reinterpret_cast<Combo*>((*m_i))->destroy(); break;
case WIN_FRAME: reinterpret_cast<Frame*>((*m_i))->destroy(); break;
case WIN_LIST: reinterpret_cast<List*>((*m_i))->destroy(); break;
case WIN_BUTTON: reinterpret_cast<Button*>((*m_i))->destroy(); break;
case WIN_STATIC: reinterpret_cast<Static*>((*m_i))->destroy (); break;
case WIN_EDIT: reinterpret_cast<Edit*>((*m_i))->destroy(); break;
}
SAFE_DELETE (*m_i);
}
m_Elements.clear ();
return OK;
}
|
Hier kamen einfach nur neue Verzweigungen hinzu, nämlich WIN_COMBO, WIN_FRAME und WIN_LIST.
Fertig:
So, jetzt geht dieses Tutorial auch so langsam zum Ende.
Ich habe mir überlegt, wenn euch diese Standard – Elemente nicht genug sind könnte man sich ja auch noch mit etwas Exotischeren Elementen beschäftigen und man könnte zum Beispiel auch noch eine Klasse schreiben für ein Menü, dass man dann später an die create eines Fensters übergibt oder so.
Aber für heute ist erstmal Schluss.
Anfänger
Anhang:
Beipielprogramm mit Projektmappe im zip – Format(26,73 KB)
Kapselung der Windowsoberfläche – Teil1