Tutorial
Erste Schritte in der WinAPI mit Freebasic und FBEdit
von stephanbrunker | Seite 3 von 13 |
Button
Für das Button-Element stehen die Funktionen und Messages z.B. hier: MSDN - Button Control.
Ein Button macht aber auch nicht viel, zum einen interessiert uns wenn er gedrückt wird (er sendet die WM_COMMAND Message) und falls wir zwischendurch den Text ändern wollen, geht das mit SetDlgItemText(hWin,IDC_BTN1,@"Neuer Buttontext"). Hier kommt die Sache mit den Pointern schon ins Spiel: Den Text übergeben wir der Funktion als PTR zu einem ZSTRING. Man könnte also auch einen ZString dimensionieren, einen Pointer und diesen dann übergeben. Was aber nun, wenn der Button zuerst inaktiv ist und durch eine Aktion aktiviert werden soll? Hier kommen die Window Styles bzw. Extended Window Styles ins Spiel, die im Resourcen-Editor (Nr. 5) eingestellt werden kann. Mit einem Doppelklick auf den xStyle oder xExStyle- Eintrag in der Liste können diese auch explizit angesehen werden. Hier gibt es dafür den Style enabled.
Erzeugen wir also mit dem Resourceneditor einen zweiten Button IDC_BTN2 - wenn im Rescourceneditor nichts angezeigt wird, auf den Dialog IDD_DLG1 in der Rescourcenübersicht (Nr. 3 im Bild) doppelklicken - : In der Werkzeugleiste den Button auswählen und auf dem Dialog zeichnen. Dann ändern wir die Beschriftung (Caption) z. B. auf "enable/disable". Der gezeichnete Button sollte dann den Namen IDC_BTN2 tragen und die ID 1003 haben. Diese Informationen tragen wir in unsere Dialog.bi ein:
#Define IDC_BTN2 1003
und unsere DlgProc sieht für WM_COMMAND dann so aus:
Case WM_COMMAND 'Message sent by Usercommand
Select Case HiWord(wParam)
Case BN_CLICKED 'Left Mousebutton
Select Case LoWord(wParam)
Case IDC_BTN1 'Click on the Button
SetDlgItemText(hWin,IDC_EDT1,@"Hello World!") 'Set Text in Editwindow
Case IDC_BTN2
Dim hBTN1 As HWND 'get Handle of the Button2
hBTN1 = GetDlgItem(hWin,IDC_BTN1)
If IsWindowEnabled( hBTN1 ) Then
EnableWindow( hBTN1, FALSE ) 'disable Button1
Else
EnableWindow( hBTN1, TRUE ) 'enable Button2
EndIf
End Select
End Select
hier erfragen wir zuerst den Handle von IDC_BTN2 mit GetDlgItem und fragen dann mit IsWindowEnabled den Status ab ("True" oder "False"). Abhängig vom Ergebnis führen wir dann EnableWindow aus. In der Praxis muss man sich in der MSDN die passenden Funktionen heraussuchen, das kann ich mit meinem Tutorial unmöglich schaffen. Ich versuche einfach, soviel Startwissen mitzugeben, damit der Anfang schon gemacht ist. Die Suchfunktion der MSDN spuckt für "enable" dann vielleicht das Button_Enable-Makro aus. Hier hat Freebasic leider eine kleine Unzulänglichkeit, denn ein guter Teil der Makros fehlt in den *.bi-Dateien. In dem Fall ist die Funktion aber genau die gleiche wie EnableWindow.
Jetzt den geänderten Code ausführen und voilà - mit einem Klick auf den zweiten Button können wir den ersten Button aktivieren oder deaktivieren. Klappt nicht? Hier ist das fertige Projekt zum Verleich: Tutorial1.zip
Nach dem gleichen Schema würde auch die Funktion ShowWindow den Button verschwinden lassen, wenn man ihr den Handle des Buttons und den Wert SW_HIDE übergibt.
Und wenn der Button dann noch ein schöne Grafik haben soll? Tja, soweit bin ich noch nicht ...
Edit Control = Textfeld
In unserem kleinen Beispielprogramm befindet sich auch ein EditControl, ein Textfeld. Wenn der Style enabled gesetzt ist, kann der Benutzer hier Text eingeben. Das man mit SetDlgItemText einen Text eingeben kann, hatten wir schon, mit GetDlgItemText kann man diesen auslesen. Sehen wir uns GetDlgItemText in der MSDN an:
UINT WINAPI GetDlgItemText(
_In_ HWND hDlg,
_In_ int nIDDlgItem,
_Out_ LPTSTR lpString,
_In_ int nMaxCount
);
Das ist zwar C, aber für Basic-User durchaus verständlich. Windows arbeitet mit ZSTRINGS, und die werden mit einer festen Länge alloziiert. Für diese Funktion müssen wir also einen Zstring dimensionieren und der Funktion den Pointer zum String übergeben sowie die Länge des dafür reservierten Speichers. Wenn das Textfeld mehr Zeichen enthält, wird der rest abgeschnitten. Dieses Vorgehen ist für Freebasic-Programmierer vielleicht etwas umständlich, denn wir könnten ja auch die Variable direkt ByRef übergeben oder eine SUB anstelle einer Function benutzen, aber Windows arbeitet nun einmal nicht so. Es hindert den fleißigen Programmierer natürlich nicht daran, intern in seinem Programm STRINGS ByRef zu übergeben, aber bei der Kommunikation mit Windows immer nur ZSTRINGS fester Länge verwenden - und Pointer, Pointer, Pointer. Mit den Pointern kann man nämlich bei einer Function mehrere Werte ausgeben, indem die Werte an den Pointern verändert werden ...
Mit diesem Wissen sollten wir unser Programm jetzt so verändern können, dass der User in ein erstes Textfeld IDC_EDT1 einen Text eingibt, der dann durch Drücken des IDC_BTN1 in ein zweites Textfeld kopiert wird.
Im RC-Editor bauen wir dann etwa so etwas:
Dabei ändern wir den Status des zweiten Textfeldes von enabled= TRUE zu FALSE, um dem Benutzer klarzumachen, dass er dort nichts eingeben kann.
Die Dialog.bi wird dann entsprechend abgeändert:
'Define the Identifiers for the Window Elements
#Define IDD_DLG1 1000
#Define IDC_BTN1 1001
#Define IDC_EDT1 1002
#Define IDC_EDT2 1003
und unser Case WM_COMMAND in der Dialog.bas sollte dann so aussehen:
Case WM_COMMAND 'Message sent by Usercommand
Select Case HiWord(wParam)
Case BN_CLICKED 'Left Mousebutton
Select Case LoWord(wParam)
Case IDC_BTN1 'Click on the Button
Dim text As ZString * 200
GetDlgItemText(hWin,IDC_EDT1,@text,SizeOf(text)) 'Get text from Editwindow1
SetDlgItemText(hWin,IDC_EDT2,@text) 'Set Text in Editwindow2
End Select
End Select
Als fertiges Projekt zum Download: Tutorial2.zip
Will man eine mehrzeilige Editbox oder einen Button, dann muss man einerseits die Property Multiline auf TRUE setzen und dann den Zeilenumbruch im Text mit CHR(13,10) erzeugen, wenn man den Text mit SetDlgItemText zuweist:
Dim text As String
text = "Hello World!" & Chr(13,10) & "zweite Zeile"
SetDlgItemText(hWin,IDC_EDT1,StrPtr(text) )
Im Rescourceneditor ist das leider etwas anders, dort schreibt man in die CAPTION \r\n anstelle des Zeilenumbruchs. Wenn Multiline auf TRUE gesetzt ist und der Text zu lang für eine Zeile ist, wird der Umbruch automatisch vorgenommen, man braucht das also nur wenn man den Umbruch selbst definieren will.
Tastaturbedienung
An dieser Stelle ein kleiner Exkurs (kann man zuerst mal überspringen, gehört aber systematisch hier hin) über die Bedienung des Programms mit der Tastatur und dem, was damit zusammenhängt. Mausbedienung ist zwar wahrscheinlich verbreiteter, aber man sollte die Tastatur auch sauber implementieren, selbst wenn der User nur einzelne Funktionen mit der Tastatur bedient. Eine Regel ist die, dass wenn im Dialog mindestens ein Button erscheint, dieser der Default-Button ist (Eigenschaft Default = TRUE oder BS_DEFPUSHBUTTON). Dieser Button wird aktiviert, wenn der User die RETURN-Taste betätigt. Es sollte natürlich nur genau einen Default-Button geben (kann man mit einer DM_SETDEFID Message ändern). Dann gibt es bei den Steuerelementen die Werte Tabstop und TabIndex. Ersterer legt fest, ob das Steuerelement mit dem Tabulator angesprochen werden soll, und der Index legt hier die Reihenfolge fest. Bei obigem Programm habe ich die Editbox aus dem Tabulator herausgenommen und Button1 als Defaultbutton festgelegt. Wenn durch Button2 der erste disabled wird, dann wird Button2 automatisch zum Default. Als letztes noch ein Hinweis: man sollte bei einem Dialog den Eingabefokus nicht mit SetFocus festlegen, sondern mit einer WM_NEXTDLGCTL - Message. Der Dialog hat nämlich in Windows eine eigene Routine, mit der die Tabulator- Pfeiltasten und RETURN-Kommandos abgefangen werden (IsDialogMessage in der Messageloop ist ein Teil davon) - und diese Routine bringt man mit SetFocus kräftig durcheinander - am Ende steht man mit mehreren Defaultbuttons oder gar keinem da. Sollte man an der integrierten Funktion herumbasteln wollen und einzelne Kommandos abfangen wollen, dann gibt es WM_GETDLGCODE dafür, denn im Normalfall lösen diese Tasten nämlich keine WM_KEYDOWN-Message aus.
Die Zeile:
'set Focus to the first edit box
SendMessage(hMain, WM_NEXTDLGCTL,GetDlgItem(hMain,IDC_EDT1),TRUE)
setzt damit den Fokus auf die erste Editbox, wir tippen den Text ein und RETURN betätigt Button1 und kopiert damit den Text in die zweite Editbox. Und ein Fall für das besagte Abfangen der Standardkommandos wäre, wenn wir besagtes RETURN für den Zeilenumbruch bräuchten und nicht wollten, das damit der Button ausgelöst würde. Der Code dafür benötigt Subclassing und ein paar andere Feinheiten, die erst später kommen, deshalb hier erstmal nur der Democode:
Tutorial2a.zip
Weiter mit Radiobuttons und statischen Textelementen ...
Zusätzliche Informationen und Funktionen | |||||||
---|---|---|---|---|---|---|---|
|
|