Tutorial
Erste Schritte in der WinAPI mit Freebasic und FBEdit
von stephanbrunker | Seite 7 von 13 |
Comboboxen = Aufklappfelder
Fangen wir einfach mal mit dem gleichen Beispieldialog wie in der MSDN an:
Sieht einfach aus, bereitet aber einiges an Kopfzerbrechen, wenn man die Tricks nicht kennt. Die Combobox ist ein sehr vielseitiges Steuerelement und kommt schon mal in drei Varianten: Simple, Drop-Down und Drop-Down List. Bei allen dreien kann der Benutzer aus gegebenen Möglichkeiten auswählen und bei Simple und Drop-Down kann er selbst neuen Text eintragen. Die erste Hürde gibt es bereits im Resourceneditor von FBEdit zu überspringen, zeichnet man das Element einfach einzeilig wie man aus dem Bauch heraus denkt, werden nachher keine Einträge angezeigt. Vielmehr definiert man beim Einzeichnen direkt die maximale Größe des Aufklappbereichs. Im Editor sollte das also so aussehen:
Ich habe etwas geschummelt, die beiden Elemente können natürlich nicht gleichzeitig aktiv sein, aber auf die Größe kommt es an ... für den Einen mag das selbstverständlich sein, ich habe etwas gebraucht bis ich daraufgekommen bin. Man steuert das Aussehen des Elements über die Property Type. Das nächste Haken an der Sache ist, dass wenn man das Programm wie bisher compiliert, die beiden rechten Felder nämlich gleich aussehen, also mit weißem Textfeld. Um das zu ändern, müssen die Visual Styles aktiviert werden, und das geht mit einem MANIFEST, das wir bei unseren Resourcen einfügen.
Wir klicken also rechts oben (im Projektfenster) mit der rechten Maustaste und wählen "Neu hinzufügen -> Datei". Die Datei nennen wir Dialog.xml und sie bekommt folgenden Inhalt:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="*"
name="CompanyName.ProductName.YourApplication"
type="win32"
/>
<description>Your application description here.</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>
Dieses MANIFEST besagt, dass wir eben die Common Controls in der Version 6.0 haben wollen, also XP und später. Wir binden dies ein, indem wir im Menu auf "Rescourcen -> Rescourcen" klicken, dann im mittleren Fenster auf "Rescource Files" und folgendes in die mit "Add" eingefügte Zeile schreiben:
Damit wird unser MANIFEST mit compiliert und automatisch berücksichtigt. Wie bei der Transparenz allerdings nur dann, wenn der Benutzer das entsprechende Design aktiviert hat, im "Windows klassisch" sieht das wieder anders aus. Nicht erschrecken, wenn beim erneuten Öffnen des Programms die Zeile aus den "Resource Files" verschwunden ist und eine eigene Zeile bekommt!
Wer lesen kann, findet vielleicht vorher im Menü "Rescourcen -> XP Manifest hinzufügen", das macht nämlich ganz genau dasselbe - sonst kann man sich richtig schön ärgern, dass man mit mehr Arbeit das gleiche Ergebnis erzielt hat, aber jeder Programmierer dürfte das Gefühl sehr, sehr gut kennen. Andererseits haben wir jetzt im Projekt-Ordner schon vier Dateien und es lohnt sich schon fast, bei den "Projekt-Optionen" in der untersten Zeile auf "Nach Typ" umzustellen, dann werden die Dateien nach Typ in Ordnern sortiert. Wenn man seinen Code der Übersichtlichkeit in eine Reihe von *.bas und *.bi-Dateien aufteilt, dann erhöht das die Übersichtlichkeit doch ziemlich.
Über die Combobox steht natürlich alles wichtige in der MSDN: ComboBox
Nachdem die Boxen jetzt richtig angezeigt werden, geht es um den Inhalt. Und wie man sich schon fast denken kann, ist es nicht damit getan, einfach den Text an die Boxen zu senden, na ja, fast. Wie später beim ListView-Feld werden die einzelnen Elemente mit einer laufenden ID in die Box geschrieben und können auch automatisch sortiert werden. Wenn wir wollen, das ein Element vorgeschlagen wird und in der ersten Zeile erscheint, müssen wir das beim Initialisieren selektieren. Nennen wir unsere Box also IDC_CBO1 (die IDs in der Dialog.bi zu referenzieren sollte ja jedem klar sein):
SendDlgItemMessage(hWin,IDC_CBO1,CB_ADDSTRING,NULL,Cast(LPARAM,@"Red") )
SendDlgItemMessage(hWin,IDC_CBO1,CB_ADDSTRING,NULL,Cast(LPARAM,@"Blue") )
SendDlgItemMessage(hWin,IDC_CBO1,CB_ADDSTRING,NULL,Cast(LPARAM,@"Green") )
SendDlgItemMessage(hWin,IDC_CBO1,CB_SETCURSEL,NULL,0) 'select the first entry
Der Rückgabewert der CB_ADDSTRING Message ist übrigens die ID, die der CB_SETCURSEL Message als LPARAM für die Auswahl übergeben wird, hier in dem Fall ist das 0, also Red. Wie fast immer ist der richtige Platz der WM_INITDIALOG - Abschnitt für die Initialisierung.
Ändert der User jetzt die Selektion in der Combobox, dann wird eine CBN_SELCHANGE Message verschickt (als HIWORD von WM_COMMAND). Wir können die zum Bespiel dazu nutzen, um mit unserem Farbwechselcode die Static passend zu verändern. Die Variable staticcolor (as Integer) muss natürlich eine globale Variable sein, da die DlgProc zwischenzeitlich verlassen wird.
Case WM_COMMAND 'Message sent by Usercommand
Select Case HiWord(wParam) Case CBN_SELCHANGE
Select Case LoWord(wParam)
Case IDC_CBO1
Dim selected As Integer
selected = SendDlgItemMessage(hWin,IDC_CBO1,CB_GETCURSEL,0,0)
Select Case selected
Case 0
staticcolor = &h000000FF
Case 1
staticcolor = &h00FF0000
Case 2
staticcolor = &h0000FF00
End Select
RedrawWindow(hSTC1,0,0,RDW_UPDATENOW Or RDW_INVALIDATE)
End Select
...
Case WM_CTLCOLORSTATIC
Select Case lParam
Case hSTC3
'Get the DC of the Static
Dim As HDC hdcStatic = Cast(HDC,wParam)
'Set Textcolor to our global Variable
SetTextColor(hdcStatic, staticcolor)
'Set Background Color to Dialog Box Background Color:
SetBkColor(hdcStatic,GetSysColor(COLOR_3DFACE))
'Set Color for background rectangle
Return GetSysColorBrush(COLOR_3DFACE)
End Select
Ein weiteres Feature der Dropdownboxes sind die Cuebanners - auf deutsch: die Hilfetexte, die in grau angezeigt werden, solange kein Element ausgewählt ist. Auch dieses Element ist erst eine neuere Ergänzung (seit Vista) der Common Controls. Den gleichen Effekt könnte man im Prinzip erreichen, indem man einen STATIC-Text transparent und grau macht und diese sichtbar bzw. unsichtbar macht solange nichts ausgewählt ist. Aber es gibt nun einmal das Feature und deshalb erkläre ich es auch - dabei kann ich in einem Abwasch gleich die Umwandlung von ANSI zu Unicode durchgehen. Die Message heißt CB_SETCUEBANNER, und erstes Hindernis ist auch schon, dass deren Definition in den Windows.bi-Dateien fehlt. Die lässt sich aber herausfinden, und wir können damit unsere eigene *.bi ergänzen:
#Define CB_SETCUEBANNER 5891
#Define CB_GETCUEBANNER 5892
Dann verlangt die Message nach dem Text in Unicode, also der variablen Zeichencodierung von 1 bis 4 Byte pro Zeichen. Die meisten Functions gibt es hinter den Kulissen in einer FunctionnameA und FunctionnameW Variante als ANSI oder Unicode, aber soweit ich das weiß, verwendet Freebasic normalerweise die ANSI-Variante. Hier gibt es die aber nicht, also brauchen wir unsere nächste Helperfunction:
Function AnsiToUnicode(ByRef ansi As String, ByRef unicode As String) As Integer
Dim unicodelen As Integer
unicodelen = MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,StrPtr(ansi),-1,NULL,NULL)
unicode = String (unicodelen,0)
MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,StrPtr(ansi),-1,Cast(LPWSTR,StrPtr(unicode)),unicodelen)
Return 0
End Function
Es gibt natürlich viele Möglichkeiten, ich habe mich wegen der variablen Länge des Ergebnisses einfach mal für STRINGS entschieden, auch wenn die bekanntermaßen nicht ByVal, also als Returnwert zurückgegeben werden können. Das ist auch ein Fall für eine mögliche Sicherheitslücke in Windows, denn wir müssen einmal erfragen, wie lange der Zielstring genau ist und dann diesen in den in der richtigen Größe reservierten Puffer laden. Sonst kommt es zu einem Überlauf mit möglicherweise schlechten Folgen.
Damit ausgerüstet, ist der Rest ein Kinderspiel:
Dim unitext As String
AnsiToUnicode("select Colour",unitext)
SendDlgItemMessage(hWin,IDC_CBO1,CB_SETCUEBANNER,NULL,Cast(LPARAM,StrPtr(unitext)))
hier gilt wie leider zu oft bei der WindowsAPI: Es ist im Prinzip ganz einfach, aber ... herauszufinden wie das "ganz einfach" geht ist leider ziemlich zeitaufwändig.
Das letzte Feature wäre die DirectoryList. Wenn man den Inhalt eines Verzeichnisses auflisten will, geht das praktisch automatisch mit DlgDirListComboBox. Selbst das aktuelle Verzeichnis in Form einer Static wird angegeben und mit den passenden Optionen werden zum Beispiel alle Laufwerke aufgelistet. Nur die Auslesefunktion DlgDirSelectComboBoxEx will zumindest auf meinem Rechner nicht. Wir haben oben ja schon gezeigt, wie man die ID ausliest, den dazugehörigen Text bekommt man mit CB_GETLBTEXT. Will man wissen, was der User selbst eingetippt hat (bei den Simple und Drop-Down Types), dann geht das mit GetDlgItemText.
Erwähnen möchte ich noch die CB_SETITEMDATA Message, mit der einem Eintrag in der Box ein Integerwert unsichtbar mitgegeben werden kann. Lässt man die Liste nämlich sortieren oder fügt in der Mitte Einträge ein, kann man nämlich die ID's nicht mehr so recht zuordnen. Erstellt man parallel ein Array und schreibt den Index in dieses Itemdata, dann braucht man nur die Data auszulesen und kann damit über das Array den Eintrag zuordnen.
Der Code: Tutorial5.zip
Zusätzliche Informationen und Funktionen | |||||||
---|---|---|---|---|---|---|---|
|
|