Vor
etwa 2 Jahren habe ich einmal die Frage gestellt, ob es möglich sei, eine Dialogressource mit eigenem Klassennamen zu versehen. Da ich ungern einen derart angestaubten Thread aus der Versenkung holen möchte und mein jetziges Thema zudem vom ursprünglichen abweicht, habe ich mir erlaubt, ein neues Thema zu starten.
Nun ja, man entwickelt (sich) weiter, man probiert aus und vor allem: Man fängt an, Bücher zu lesen. Zum Beispiel den "Petzold", das Standardwerk für
NonVCL-Entwickler.
Nun gut, ich wollte ja nicht Werbung für dieses Buch machen (es ist aber wirklich super!), sondern langsam zum eigentlichen Thema kommen. Charles Petzold stellt im Kapitel 11 (Dialogfelder) sein Programm HexCalc - einen Hexadezimalrechner - vor und beantwortet so ganz nebenbei meine Frage nach dem Dialog mit Klassennamen. Da mich neben dem Thema an sich auch der Code begeistert hat, habe ich das Programm nach Delphi übersetzt.

Zitat von
Charles Petzold, Windows-Programmierung:
Das im folgenden wiedergegebene Programm HEXCALC darf man wohl ohne Übertreibung als Gipfel der Faulheit bezeichnen: Es enthält überhaupt keinen Aufruf von CreateWindow, kümmert sich keinen Deut um Nachrichten des Typs WM_PAINT, interessiert sich weder für Gerätekontexte noch für Mausereignisse und umfaßt insgesamt weniger als 150 Zeilen Quelltext. Dennoch kommt dabei ein Hexadezimalrechner mit 10 Funktionen sowie einer kompletten Tastatur- und Mausschnittstelle heraus, der sich in Abbildung 10.5 [...] bewundern lässt.
Im folgenden werde ich ein paar Besonderheiten des Programms generell sowie meiner Delphi-Umsetzung erklären.
Zunächst geht es um die Registrierung der Fensterklasse und die Erstellung des Dialogfeldes. Im Ressourceneditor wird für den Dialog der Klassenname "HexCalc" eingetragen; wird der Dialog über ein Script erstellt, sieht das ganze so aus:
Code:
100 DIALOGEX 0, 0, 205, 130
STYLE DS_SETFONT | DS_3DLOOK | DS_FIXEDSYS | DS_NOFAILCREATE | DS_CENTER |
WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Hexadezimalrechner"
CLASS "HexCalc"
FONT 8, "MS Shell Dlg"
Der Fensterprozedur-Zeiger lpfnWndProc wird einer herkömmlichen Window-Prozedur (WndProc) zugewiesen, die sonst übliche Dialogprozedur fehlt. Zudem ist der Wert DLGWINDOWEXTRA der Member-Variable cbWndExtra wichtig, diese reserviert einige zusätzliche Bytes für die Nutzung einer Dialog-Ressource. Im Aufruf von CreateDialog wird schließlich der String "HexCalc" (definiert in der Konstante szAppName) der Fensterklasse übergeben. Die sonst nötige Adresse der Dialogprozedur bleibt leer, da das Programm die Nachrichten selbst verarbeitet.
Delphi-Quellcode:
var
WndClass: TWndClassEx;
msg: TMsg;
begin
WndClass.cbSize := SizeOf(TWndClassEx);
WndClass.style := CS_HREDRAW or CS_VREDRAW;
WndClass.lpfnWndProc := @WndProc;
WndClass.cbClsExtra := 0;
WndClass.cbWndExtra := DLGWINDOWEXTRA;
WndClass.hInstance := HInstance;
WndClass.hIcon := LoadIcon(hInstance, MAKEINTRESOURCE(100));
WndClass.hCursor := LoadCursor(0, IDC_ARROW);
WndClass.hbrBackground := COLOR_APPWORKSPACE;
WndClass.lpszMenuName := nil;
WndClass.lpszClassName := szAppName;
RegisterClassEx(WndClass);
InitCommonControls;
hDialog := CreateDialog(hInstance, MAKEINTRESOURCE(100), 0, nil);
while GetMessage(msg,0,0,0) do
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
ExitCode := msg.wParam;
DestroyWindow(hDialog);
end.
Die Programmlogik kommt ganz ohne die sonst notwendigen Konstanten
IDC_CLOSE und weitere aus. Zahlenwerte merken muss sich trotzdem niemand, die Lösung ist verblüffend einfach: Für die IDs der Schaltflächen wird einfach der
ASCII-Code der jeweiligen Taste benutzt, über welche die Funktionen auch ausgeführt werden können. In den meisten Fällen entspricht der Code direkt der Buttonbeschriftung. Daraus ergibt sich, dass die Nachrichten WM_CHAR und WM_COMMAND letztendlich die gleiche Funktion aufrufen können. Programmlogik ist dabei nur für einige Spezialfälle notwendig: Die Taste "=" kann auch über die Eingabetaste angesprochen werden und statt der Rücktaste ist auch Pfeil-Links möglich.
Delphi-Quellcode:
function WndProc(hwnd: HWND; uMsg: UINT; wParam: WPARAM;
lParam: LPARAM): LRESULT; stdcall;
var
hButton: THandle;
cCommand: CHAR;
begin
case uMsg of
// [...]
WM_KEYDOWN:
If wParam = VK_LEFT then HandleKey(VK_BACK);
WM_CHAR:
begin
cCommand := UpCase(Chr(wParam));
If wParam = VK_RETURN then cCommand := '=';
hButton := GetDlgItem(hWnd, Ord(cCommand));
If hButton <> 0 then
begin
HandleKey(Ord(cCommand));
If IsWindowsXPOrHigher
then EnableWindow(hButton, False)
else SendMessage(hButton, BM_SETSTATE, 1, 0);
Sleep(100);
If IsWindowsXPOrHigher
then EnableWindow(hButton, True)
else SendMessage(hButton, BM_SETSTATE, 0, 0);
end else begin
MessageBeep(0);
end;
end;
WM_COMMAND:
begin
SetFocus(hWnd);
{$IFDEF CloseOnESC}
if(wParam = IDCANCEL) then SendMessage(hDlg,WM_CLOSE,0,0)
else
{$ENDIF}
if HIWORD(wParam) = BN_CLICKED then
begin
HandleKey(LoWord(wParam));
end;
end;
end;
Result := DefWindowProc(hwnd, uMsg, wParam, lParam);
end;
Egal, ob die Maus oder die Tastatur verwendet wird: Der entsprechende Code wird an die Prozedur
HandleKey weitergeleitet, welche die Eingabe verarbeitet. Bei der Tastatureingabe wird dazu noch kurzzeitig der Button gedrückt, um eine optische Rückmeldung zu erhalten. Leider funktioniert dies nur bis einschl. Windows 2000, so dass ich mich entschlossen habe, bei Windows XP oder darüber den Button kurz zu deaktivieren. Die Funktion
IsWindowsXPOrHigher liefert True, wenn die Windows-Version mindestens Version 5.1 ist.
Der Befehl
SetFocus im WM_COMMAND-Zweig ist nötig, damit das Hauptfenster auch weiterhin die WM_CHAR-Nachrichten erhält, wenn der Rechner mit der Maus bedient wurde.
Die Prozedur
HandleKey habe ich erstellt, um den Programmablauf etwas logischer zu gestalten, eine "durchgehende Case-Anweisung", wie sie bei C (switch ohne break) vorhanden ist, gibt es bei Delphi nicht. Die Prozedur dient zum Einlesen der Tasten, Erstellen der Zahlen zum Rechnen und Setzen der globalen Variablen
iOperation und
bNewNumber, die bestimmen, welche Rechenoperation durchgeführt werden soll und ob eine neue Zahleneingabe begonnen werden soll. Die beiden Funktionen IsHex und HexCharToInt habe ich hinzugefügt, da Delphi die C-Funktionen isdigit und isxdigit nicht kennt.
Delphi-Quellcode:
procedure HandleKey(Key: Integer);
function IsHex(C: Char): Boolean; // [...]
function HexCharToInt(Key: Byte): Cardinal; // [...]
begin
If Key = VK_ESCAPE then
begin
iNumber := 0;
ShowNumber(hDialog, iNumber);
end else if Key = VK_BACK then
begin
iNumber := iNumber div 16;
ShowNumber(hDialog, iNumber);
end else if IsHex(Chr(Key)) then
begin
if bNewNumber then
begin
iFirstNum := iNumber;
iNumber := 0;
end;
bNewNumber := False;
if (iNumber <= MAXDWORD shr 4) then
begin
iNumber := 16 * iNumber + HexCharToInt(Key);
ShowNumber(hDialog, iNumber);
end else MessageBeep(0);
end else begin
If not bNewNumber then
begin
iNumber := CalcIt(iFirstNum, iOperation, iNumber);
ShowNumber(hDialog, iNumber);
end;
bNewNumber := True;
iOperation := Key;
end;
end;
Die Funktion
CalcIt schließlich führt die eigentlichen Berechnungen durch und gibt das Ergebnis zurück. Auch hier musste der Code angepasst werden, da es die von C bekannten bedingten Anweisungen in Delphi nicht gibt - diese geringere Effizienz wird allerdings mit drastisch verbesserter Lesbarkeit belohnt.
Delphi-Quellcode:
function CalcIt(iFirstNum: Cardinal; iOperation: Integer;
iNum: Cardinal): Cardinal;
begin
Case Chr(iOperation) of
'=': Result := iNum;
'+': Result := iFirstNum + iNum;
'-': Result := iFirstNum - iNum;
'*': Result := iFirstNum * iNum;
'&': Result := iFirstNum and iNum;
'|': Result := iFirstNum or iNum;
'<': Result := iFirstNum shl iNum;
'>': Result := iFirstNum shr iNum;
'X': Result := iFirstNum xor iNum;
'%': If iNum <> 0
then Result := iFirstNum mod iNum
else Result := MAXDWORD;
'/': If iNum <> 0
then Result := iFirstNum div iNum
else Result := MAXDWORD;
else Result := 0;
end;
end;
Bestandteil des Programms ist auch die
Unit HexCalcUtils. Diese existiert lediglich, um nicht die Units CommCtrl und SysUtils verwenden zu müssen, damit das Kompilat schön klein bleibt.
Noch ein paar Worte zum eigentlichen Programm: Es verwendet die Infix-Notation, funktioniert also mit Berechnungen in der Form 3 + 4 = wie der normale Windows-Rechner. Neben den vier Grundrechenarten beherrscht das Programm den Divisionsrest, bitweises AND, OR und XOR sowie bitweises Links- und Rechtsschieben. Der Rechner arbeitet mit 32-Bit-Darstellung (DWORD). Auf Division und Modulo durch 0 reagiert das Programm mit der Anzeige FFFF FFFF.
Die Tastenkombinationen:
Code:
Funktion Taste
==============================
0..9 0..9
A..F A..F
+ +
- -
* *
/ ÷ oder /
Mod %
And &
Or |
Xor X
Shl <
Shr >
= = oder Eingabetaste
C (Clear) ESC
Korrektur Backspace oder Pfeil-Links
So, dann wünsche ich viel Spaß beim Hex-Rechnen
In eigener Sache: Die Wahl des richtigen Forums war nicht leicht. Es ist ein NonVCL-Thema, aber eigentlich auch Open Source und genaugenommen auch ein Tutorial. Wenn's hier nicht passt, möge es bitte ein Moderator verschieben, Cross-posten oder was auch immer.
Edit: Nachdem mein "Vorkoster" ein paar Bissen dieses Beitrags probiert hat, konnte ich einige Schreibfehler beseitigen. Danke, Wastl!