Dialoge

Inhaltsverzeichnis

Dialoge
Einführung
Beispiel
Dialoge ohne Hauptfenster
Windows-Standarddialoge

Dialoge stellen neben Popup-Fenstern eine weitere Möglichkeit dar, Informationen vom Anwender abzufragen. Sie werden in den meisten Fällen in einer Ressourcendatei definiert. Zwar ist auch die komplett dynamische Erzeugung von Dialogen möglich, von dieser Variante wird aber hier kein Gebrauch gemacht.

Einführung

Es gibt zwei große Typen von Dialogen: modale und nichtmodale bzw. moduslose. Ein modaler Dialog schränkt während seiner Anzeige den Zugriff auf andere sichtbare Fenster der gleichen Anwendung ein. Der Dialog muss erst beendet werden, damit die anderen Fenster wieder erreichbar sind. Eine Erweiterung dieses Konzepts stellen die systemmodalen Dialoge dar, die auch den Zugriff auf Fenster einschränken, die nicht zur eigenen Applikation gehören. Ein Beispiel hierfür ist der Dialog "Windows herunterfahren". Moduslose Dialoge erlauben während ihrer Anzeige den Wechsel zu anderen Fenstern der gleichen Anwendung. Dialoge zum Suchen von Text sind in vielen Anwendungen als moduslose Dialoge definiert.

Modale und Systemmodale Dialoge werden mit der Funktion DialogBoxParam() gestartet, moduslose Dialoge mit der Funktion CreateDialog().

Beiden Typen gleich ist, dass die Steuerung über eine sogenannte Dialogprozedur erfolgt, deren Aufbau mit der Windowprozedur vergleichbar ist. Dabei wird uns ein großer Teil Arbeit durch vordefinierte Mechanismen von Windows abgenommen. Insbesondere die Steuerung des Tastaturfokus mittels der Tasten Tab, Shift+Tab und den Pfeiltasten sowie Reaktionen auf Alt+Buchstabe um bestimmte Dialogelemente direkt anzuspringen muss durch uns nicht programmiert werden.

Im Gegensatz zu Menüs kommt man bei Dialogen nicht so ohne weiteres um die Verwendung von Ressourcedateien herum. Es gibt zwar auch einen Weg, Dialoge erst zur Laufzeit im Speicher aufzubauen, diese werden aber nicht in diesem Tutorial beschrieben. Falls sie sich dafür interessieren, dann schauen sie sich die Funktionen CreateDialogIndirect() und DialogBoxIndirect() sowie die Strukturen DLGTEMPLATEEX und DLGITEMTEMPLATEEX an.

Obwohl es sich bei Ressourcedateien um Textdateien handelt, werden sie bei der Arbeit mit Hochsprachen-IDEs wie etwa Visual C++ nur selten von Hand angepasst. Stattdessen gibt es dafür Ressource-Editoren, mit denen Menüs, Dialoge und sonstige Ressourcen zusammengeklickt werden können.

In der MASM32-Auslieferung ist kein Ressourcen-Editor enthalten, und wenn sie keinen haben, dann bleibt ihnen nichts anderes, als die Ressourcedatei selbst zu schreiben. Da MASM und rc.exe ihre Konstanten auf unterschiedliche Art und Weise definieren, wurde dem MASM32-Paket die Datei resource.h beigelegt, die die für rc.exe benötigten Konstantendefinitionen enthält. Diese muss dann im jeweiligen Ressource-Skript eingebunden werden.

Die definierbaren Steuerelemente entsprechend weitgehend dem, was bereits im Kapitel Fensterklassen besprochen wurde.

Es gibt zwei gleichwertige Methoden, um ein Steuerelement als Ressource zu definieren: als explizite Anweisung oder mit der allgemeineren CONTROL- Anweisung, die das Steuerelement als Parameter enthält.

Methode 1
Steuerelement [Beschriftung,] [ID,] x ,y, dx, dy, [Stil], [erw. Stil], [Hilfe-ID]
              [BEGIN
                 Datenelement1
                 Datenelement2
                 ...
              END]
              
CHECKBOX "Alles entfernen", IDC_REMOVEALL, 10, 5, 130, 10, WS_TABSTOP
Methode 2
CONTROL [Beschriftung,] ID, Fensterklasse, Stil, x ,y, dx, dy
              
CONTROL "Alles entfernen", IDC_REMOVEALL, "button", WS_TABSTOP|BS_CHECKBOX, 10, 5, 130, 10

Im obigen Beispiel wird ein Kontrollkästchen mit der Beschriftung "Alles entfernen" definiert. Die ID dieses Steuerelements lautet IDC_REMOVEALL, eine selbstdefinierte Konstante. Bei einigen Steuerelementen (z. B. LISTBOX) kann noch eine Initialisierung vorgenommen werden.

X, y, dx und dy stehen für die horizontale und vertikale Position der linken oberen Ecke relativ zum Elternobjekt sowie die horizontale und vertikale Ausdehnung. Positions- und Größenangaben in Ressourcedateien erfolgen in sog. Dialogfeldeinheiten, nicht in Pixeln. Wie groß eine Dialogfeldeinheit ist hängt von der zugrundeliegenden Schriftart ab. Bei X- Koordinaten kommt die Formel

Pixel = Wert * Durchschnittsbreite eines Buchstaben / 4

zum Einsatz und bei Y-Koordinaten die Formel

Pixel = Wert * Schrifthöhe / 8

Dieser Weg wurde gewählt, um von der Bildschirmauflösung unabhängiger zu sein. Die unterschiedlichen Teiler ergeben sich daraus, dass Schriften meist halb so breit wie hoch sind.
Als groben Richtwert kann man davon ausgehen, dass eine Dialogfeldeinheit etwa 2 Pixeln entspricht.

Eine Basisdefinition für eine Dialogbox ohne Steuerelemente sieht so aus:
MYDIALOG DIALOGEX 40, 40, 160, 150
STYLE WS_POPUP|DS_MODALFRAME|WS_VISIBLE|WS_CAPTION|WS_SYSMENU
CAPTION "Attribute ändern"
FONT 8, "MS Sans Serif"
BEGIN
  Steuerelementdefinitionen
END

MYDIALOG ist der Name des Dialogs, über den er innerhalb des Programms geladen wird.
Folgende Angaben können in Dialogfeldressourcedateien auftauchen.

Dialogfeldressourcen
Ressourcebezeichner Bedeutung
Dialogboxbasisdefinition
DIALOGEX Ersetzt den früheren Ressourcetyp DIALOG. Alle innerhalb von DIALOGEX definierten Steuerelemente haben eine erweiterte Syntax
Name DIALOGEX x, y, dx, dy [,Hilfe-ID] [Optionen]
Die Angaben x und y liegen relativ zu dem Fenster, das den Dialog aufgerufen hat. Zu den Optionen zählen STYLE, EXSTYLE, CAPTION, CHARACTERISTICS, FONT, CLASS, MENU, VERSION und LANGUAGE. Einige Optionen werden in den nächsten Zeilen genauer beschrieben.
STYLE Es kommen alle Stile in Frage, die mit WS_ oder DS_ beginnen. Es sollte WS_VISIBLE verwendet werden, sonst muss der Dialog nach seinem Start noch mittels ShowWindow() sichtbar gemacht werden
CAPTION Wenn der Stil WS_CAPTION verwendet wird, dann wird durch diese Option der Dialogfeldtitel festgelegt. Der Text sollte in Anführungszeichen stehen.
CAPTION Text
FONT Durch den Font wird sowohl die Standardschriftart für den Dialog als auch indirekt die Größe der Steuerelemente.
FONT Schriftröße, Schriftart [,Gewicht] [,Kursiv] [,Zeichensatz]
FONT 10 "SYMBOL", FW_BOLD, FALSE, SYMBOL_CHARSET
Steuerelementdefinition
AUTO3STATE Ein Kontrollkästchen mit den drei Zuständen markiert, nicht markiert und unbestimmt. Das Steuerelement passt sein Aussehen selbst seinem Zustand an.
AUTO3STATE Bezeichnung, ID, x, y, dx, dy [,Stil [, erw. Stil]]
Als Stile kommen Kombinationen von WS_TABSTOP, WS_DISABLED, WS_GROUP in Frage.
AUTOCHECKBOX wie AUTO3STATE, aber ohne den Zustand unbestimmt
AUTOCHECKBOX Bezeichnung, ID, x, y, dx, dy [,Stil [, erw. Stil]]
AUTORADIOBUTTON Radiobuttons werden meist in Gruppen sich gegenseitig ausschließender Optionen eingesetzt. Das Aussehen wird automatisch dem Zustand angepasst.
AUTORADIOBUTTON Bezeichnung, ID, x, y, dx, dy [,Stil [, erw. Stil]]
Als Stile kommen Kombinationen von WS_TABSTOP, WS_DISABLED, WS_GROUP in Frage.
CHECKBOX wie AUTOCHECKBOX, allerdings wird das Aussehen nicht automatisch angepasst.
CHECKBOX Bezeichnung, ID, x, y, dx, dy [,Stil [, erw. Stil]]
COMBOBOX Definiert ein Kombinationsfeld, das typischerweise aus einem Eingabefeld sowie einem Listenfeld besteht.
COMBOBOX ID, x, y, dx, dy [,Stil [, erw. Stil]]
Als Stile kommen Kombinationen der mit CBS_ beginnenden Stile mit WS_TABSTOP, WS_DISABLED, WS_GROUP und WS_VSCROLL in Frage.
CTEXT Definiert statischen Text, der innerhalb des durch die Positionsangaben festgelegten Rechtecks zentriert dargestellt wird. Die ID ist i. d. R. -1, da statischer Text nicht auf Ereignisse reagiert. Wird nicht -1 verwendet, dann kann der Text zur Laufzeit verändert werden.
CTEXT Bezeichnung, ID, x, y, dx, dy [,Stil [, erw. Stil]]
Als Stile kommen Kombinationen der mit SS_ beginnenden Stile mit WS_TABSTOP und WS_GROUP in Frage.
DEFPUSHBUTTON Definiert die Standardschaltfläche für den Dialog. Diese wird automatisch aktiviert, wenn die ENTER-Taste gedrückt wird. Zur Kenntlichmachung wird sie mit einem etwas dickeren Rahmen versehen. Es kann nur einen DEFPUSHBUTTON je Dialog geben, alle anderen Schaltflächen müssen normale PUSHBUTTONs sein.
DEFPUSHBUTTON Bezeichnung, ID, x, y, dx, dy [,Stil [, erw. Stil]]
Als Stile kommen Kombinationen der mit BS_ beginnenden Stile mit WS_TABSTOP, WS_DISABLED und WS_GROUP in Frage.
EDITTEXT Definiert ein Eingabefeld. Dieses kommt von Haus aus mit den üblichen Bearbeitungsfunktionen (Einfügen, Löchen, Markieren) klar und bietet Befehle und ein Kontextmenü für die Arbeit mit der Zwischenablage an. Die Möglichkeit für mehrzeiligen Text und Scrolling in vertikaler und horizontaler Richtung ist eingebaut.
EDITTEXT ID, x, y, dx, dy [,Stil [, erw. Stil]]
Als Stile kommen Kombinationen der mit ES_ beginnenden Stile mit WS_TABSTOP, WS_DISABLED, WS_HSCROLL, WS_BORDER und WS_GROUP in Frage.
GROUPBOX Definiert ein statisches Element, das zum Zeichnen eines Rahmens mit einem Titel in der oberen Rahmenzeile verwendet wird. Durch diesen Rahmen werden zusammengehörende Steuerelemente auch optisch zusammengefasst. Es hat die ID -1, da das Steuerelement nicht auf Ereignisse reagiert.
GROUPBOX Bezeichnung, ID, x, y, dx, dy [,Stil [, erw. Stil]]
Als Stile kommen Kombinationen von WS_TABSTOP, WS_DISABLED und WS_GROUP in Frage.
ICON Dient der Anzeige eines Symbols in einem Dialog. Dieses Symbol muss an anderer Stelle in der Ressourcedatei mit der ICON-Anweisung definiert worden sein.
ICON Symbolname, ID, x, y, dx, dy [,Stil [, erw. Stil]]
Der Symbolname ist der Verweis auf die eigentliche ICON-Definition. Als Stil kommt nur SS_ICON in Frage.
LISTBOX Definiert eine Liste, aus der der Benutzer je nach Stil ein oder mehrere Element/e auswählen kann. LISTBOX ID, x, y, dx, dy [,Stil [, erw. Stil]]
Als Stile kommen Kombinationen der mit LBS_ beginnenden Stile mit WS_TABSTOP, WS_DISABLED, WS_VSCROLL und WS_BORDER in Frage.
LTEXT wie CTEXT, nur das der Text links ausgerichtet ist
PUSHBOX wie PUSHBUTTON allerdings ohne Rahmen. Bei mir erschien der Rahmen sobald das Steuerelement den Fokus hatte, verschwand aber nicht, wenn es den Fokus verloren hatte.
PUSHBUTTON wie DEFPUSHBUTTON, nur das die Schaltfläche explizit mit Maus oder Tastatur betätigt werden muss; der Rahmen des Steuerelements ist dünner.
RADIOBUTTON Wie AUTORADIOBUTTON, aber ohne die automatische Anpassung des Aussehens.
RADIOBUTTON Bezeichnung, ID, x, y, dx, dy [,Stil [, erw. Stil]]
RTEXT wie CTEXT, nur das der Text rechts ausgerichtet ist
SCROLLBAR Definiert Bildlaufleisten. Diese werden in Dialogen weniger zum Scrollen des gesamten Dialogbereichs verwendet, sondern um eine Art Schieberegler darzustellen, bspw. für eine Skalierung.
SCROLLBAR ID, x, y, dx, dy [,Stil [, erw. Stil]]
Als Stile kommen Kombinationen der mit SBS_ beginnenden Stile mit WS_TABSTOP, WS_DISABLED und WS_GROUP in Frage. Meist werden die Stile SBS_VERT und SBS_HORZ für vertikale und horizontale Bildlaufleisten verwendet.
STATE3 wie AUTO3STATE, aber ohne die automatische Anpassung des Aussehens. STATE3 Bezeichnung, ID, x, y, dx, dy [,Stil [, erw. Stil]]

Beispiel

Im folgenden Beispiel wird der Dialog aus dem Hauptfenster heraus gestartet. Dazu wird ein Menüpunkt verwendet. Im Hauptfenster wird in einer Liste der Inhalt des aktuellen Verzeichnisses angezeigt (nur Dateinamen). Durch den Dialog werden zum einen die aktuellen Attribute des Verzeichniseintrags angezeigt als auch zur Änderung angeboten. Es soll ebenfalls möglich sein, den Zeitstempel des Verzeichniseintrags zu verändern. Der Dialog ist in weiten Teilen von einem sehr ähnlichen Dialog des Total Commanders inspiriert.

Zur Erzeugung der Listbox wird der bereits aus dem Beispiel Fensterklassen bekannte Code verwendet. Die Listbox wird mit dem Standard-ANSI-Font fü variable Zeichenbreite dargestellt. Die Listbox verfügt nicht über die Möglichkeit der Mehrfachauswahl.
...
...
WndProc PROC hWin:dword,uMsg:dword,wParam:dword,lParam:dword
  LOCAL point:POINT, hFont:HFONT
  .IF uMsg == WM_CREATE
    ;eine Listbox erzeugen
    invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR cListClass,NULL,\
           WS_CHILD OR WS_VISIBLE OR LBS_STANDARD,\
           40,30,200,270,hWin,10,hInstance,NULL
    mov hList,eax
    ;die Listbox mit dem aktuellen Verzeichnisinhalt füllen
    invoke SendMessage,eax,LB_DIR,DDL_READWRITE OR DDL_HIDDEN \
           OR DDL_DIRECTORY OR 4000h,ADDR cFilter
    invoke GetStockObject,ANSI_VAR_FONT                  ;Font aus dem Systemvorrat holen
    invoke GetObject,eax,SIZEOF LOGFONT,ADDR logfont     ;Font-Infos nach logfont schreiben
    invoke CreateFontIndirect,ADDR logfont               ;daraus eine log. Schrift erzeugen
    invoke SendMessage,hList,WM_SETFONT,eax,0
...
...    

Die Ermittlung des ausgewählten Listenelements wird als Reaktion auf die Wahl des entsprechenden Menüpunktes eingebaut. Diese Wahl verursacht wie immer ein Ereignis des Typs WM_COMMAND, wobei im unteren Word von wParam die ID des entsprechenden Ereignisverursachers (des Menüpunkts) steht.
Indem mittels SendMessage() eine Nachricht LB_GETCURSEL an die Listbox geschickt wird, kann der null-basierte Index des markierten Elements ermittelt werden, der von SendMessage() in EAX geliefert wird. Falls in EAX LB_ERR stehen sollte, dann wurde kein Element markiert oder ein sonstiger Fehler ist aufgetreten. In diesem Fall kann die Bearbeitung des Ereignisses abgebrochen werden.

Nachdem der Index ermittelt wurde, muss die Zeichenkette des markierten Listeneintrags ermittelt werden. Dazu wird per SendMessage() die Nachricht LB_GETTEXT an die Listbox geschickt. Der Parameter wParam muss dabei den ermittelten null-basierten Index enthalten, während in lParam die Adresse eines Puffers steht, in den die Zeichenkette kopiert werden soll.

Im Anschluss werden mittels der Funktion GetFileAttributesEx() die Attribute sowie einige Zeitstempel ermittelt und in einer Variablen des Struktur-Typs WIN32_FILE_ATTRIBUTE_DATA abgelegt.

Anschließend erfolgt der Start des Dialogs. Dazu wird erst die Adresse der Dialogprozedur in EAX abgelegt, dann wird DialogBoxParam() mit dem Instanzhandle des dialogstartenden Programms, dem Fensterklassennamen, dem Handle des aufrufenden Fensters und der Adresse der Dialogprozedur als Parameter gestartet. Als 5. Parameter kann ein beliebiger 32-Bit-Wert übergeben werden. Dabei wird es sich in vielen Fällen um die Adresse einer Struktur zur Aufnahme weiterer Parameter handeln. Der Inhalt dieser Struktur wird dann durch den Dialog ausgewertet und ggf. verändert.

Interessanterweise ist in den mir vorliegenden Dokumentationen oft von der Funktion DialogBox() die Rede. Es handelt sich hierbei um ein C-Makro mit vier Parametern, das intern die Funktion DialogBoxParam() verwendet. Dabei stimmen die vier Parameter mit den ersten vier Parametern von DialogBoxParam() überein.

Die Listbox liefert auch eine Reaktion auf einen Doppelklick. Das Ereignis ist wieder WM_COMMAND. Im LoWord von wParam steckt die ID des Steuerelements, im HiWord steht die eigentliche Nachricht. Uns interessiert der Wert LBN_DBLCLK. Der Einfachheit halber prüfe ich nur auf diese Tatsache und springe bei Übereinstimmung in die Ereignisbehandlung von IDM_ATTRIBUTE.

...
...
  .ELSEIF uMsg == WM_COMMAND
    mov eax,wParam
    .IF ax==IDM_EXIT
      invoke DestroyWindow,hWnd
    .ELSEIF 10                                           ;ID der Listbox
      shr eax,16                                         ;Highword nach AX
      cmp ax,LBN_DBLCLK                                  ;Doppelklick?
      je @@setattr
    .ELSEIF ax==IDM_ATTRIBUTE
      @@setattr:
      invoke SendMessage,hList,LB_GETCURSEL,0,0          ;akt. sel. Element ermitteln
      cmp eax,LB_ERR                                     ;wenn keines markiert oder
      je @@procend                                       ;bei sonstigen Fehlern -> Ende
      ;ansonsten steht in EAX der null-basierte Index des markierten Elements
      invoke SendMessage,hList,LB_GETTEXT,eax,ADDR cBuffer ;Element in Puffer schreiben
      ;Attribute und Zugriffszeiten in Struktur win32fileattr schreiben
      invoke GetFileAttributesEx,ADDR cBuffer,0,ADDR win32fileattr
      mov eax,OFFSET AttrDlgProc                         ;Adresse der Dialogproc nach EAX
      invoke DialogBoxParam,hInstance,ADDR szClassName,hWnd,eax,NULL ;Dialogbox starten
    .ENDIF
  .ELSE
...
...    

Die Dialogprozedur hat starke Ähnlichkeit mit der Windowprozedur, zumal sie praktisch die gleichen Parameter definiert. Allerdings ist hier der erste Parameter ein Handle auf den Dialog.

Die erste Nachricht, die ein Dialog erhält, lautet WM_INITDIALOG. In Reaktion auf diese Nachricht werden i. d. R. die Steuerelemente initialisiert. Kehrt die Dialogprozedur aus dieser Nachricht mit TRUE zurüch, dann setzt Windows den Eingabefokus auf das erste Element, das den Fensterstil WS_TABSTOP hat. Anderenfalls wird der Eingabefokus unverändert gelassen bzw. kann manuell per SetFocus() eingestellt werden.

Überhaupt wird der Return-Wert der Dialogprozedur als boolscher Wert interpretiert, bei der Windowprozedur als 32-Bit-Ganzzahl. Windowprozeduren überlassen nicht bearbeitete Nachrichten der Funktion DefWindowProc(), Dialogprozeduren liefern bei bearbeiteten Ereignissen TRUE, ansonsten FALSE zurück.

Der Rest der Dialoginitialisierung befasst sich mit der Ermittlung der Dateiattribute sowie des Zeitpunkts der letzten Änderung. Beides ist in der Struktur WIN32_FILE_ATTRIBUTE_DATA zu finden.

Während die Attribute noch relativ leicht zu ermitteln sind (WIN32_FILE_ATTRIBUTE_DATA.dwFileAttributes auf bestimmte Flags testen und mittels CheckDlgButton() das zugehörige Kontrollkästchen setzen), ist die Emittlung des letzten Bearbeitungszeitpunktes schon beinahe lächerlich umständlich.
Zuerst muss die Dateizeit in die Systemzeit umgewandelt werden. Diese Zeit ist UTC-basiert. Verwendet wird dafür die Funktion FileTimeToSystemTime(), die ihr Ergebnis in einer Struktur des Typs SYSTEMTIME ablegt. Diese UTC-Zeit muss dann mittels SystemTimeToTzSpecificLocalTime() in eine "richtige" Zeit umgewandelt werden, die auch die lokale Zeitzone berücksichtigt. Das Ergebnis landet wieder in einer SYSTEMTIME-Struktur. Die einzelnen Werte für Datum (Tag, Monat, Jahr) und Zeit (Stunde, Minute, Sekunde) sind als 16-Bit-Werte gespeichert. Diese werden auf 32 Bit erweitert und mittels der Funktion wsprintf() in eine Zeichenkette konvertiert, die mit SetDlgItemText() in das jeweilige Eingabefeld geschrieben wird.

Die eben beschriebene Konvertierung der UTC-Zeit in die lokale Zeit und ihre Darstellung im Dialog wurde in eine eigene Funktion (getDateTime()) ausgelagert. Der Dialog bietet die Möglichkeit, auch die aktuelle lokale Zeit als Zeitstempel für die Datei einzustellen. Die entsprechende Ereignisbehandlung für diesen Button ruft dafür die Funktion GetSystemTime() auf, die ihr Ergebnis wieder als UTC-Zeit in einer SYSTEMTIME- Struktur ablegt. Die Darstellung in der lokalen Zeit wird erneut durch die selbstgeschriebene Funktion getDateTime() realisiert.

...
...
    .IF ax==IDC_BTNCURRENT
      invoke GetSystemTime,ADDR stUTC
      invoke getDateTime,ADDR stUTC,hDlg
      mov eax,TRUE
      ret
    .ENDIF
...
...

Wird der Dialog mit OK beendet, dann müssen die Zustände der Kontrollkästchen ermittelt sowie die Zeichenketten für Datum und Zeit in ihre numerischen Werte konvertiert werden.

Die Kontrollkästchen können die Zustände Ja, Nein und Unbestimmt annehmen, wobei Unbestimmt bewirkt, dass das entsprechende Attribut nicht verändert wird. Aus diesem Grund fällt die Auswertung der Einstellungen etwas umfangreicher aus.

...
...
  .IF uMsg==WM_COMMAND
    mov eax,wParam
    .IF ax==IDC_BTNOK
      ;Test, ob Attribute angeklickt ist
      invoke IsDlgButtonChecked,hDlg,IDC_ARCHIVE
      cmp eax,BST_UNCHECKED
      jne @@test_ARCH_CHECKED
      and win32fileattr.dwFileAttributes,NOT FILE_ATTRIBUTE_ARCHIVE
      @@test_ARCH_CHECKED:
      cmp eax,BST_CHECKED
      jne @@test_RO_UNCHECKED
      or win32fileattr.dwFileAttributes,FILE_ATTRIBUTE_ARCHIVE
    
    
      @@test_RO_UNCHECKED:
...
...

Die Änderung der Attribute wird dann durch die Funktion SetFileAttributes() erledigt.
Schwieriger ist da schon die Umsetzung der Datums/Zeit-Zeichenketten. Diese müssen in eine FILETIME-Struktur konvertiert werden. Dazu muss allerdings erst eine SYSTEMTIME-Struktur gefüllt werden, die mit SystemTimeToFileTime() in eine lokale FILETIME und anschließend mit LocalFileTimeToFileTime() in eine UTC-FILETIME konvertiert wird. Erst mit dieser Zeit kann mit SetFileTime() der Zeitstempel der Datei manipuliert werden. SetFileTime() erwartet den Handle einer Datei, die zumindest mit dem Zugriffs-Flag FILE_WRITE_ATTRIBUTES geöffnet wurde. Dieses Flag ist im Wert GENERIC_WRITE enthalten.

Die Schwierigkeit hierbei ist der menschliche Faktor. Das Abfangen von Fehleingaben ist schon in einer Hochsprache ein umfangreiches Unterfangen, in Assembler wird es in der Regel noch schwieriger, wenn die Funktionen zur Textauswertung und -manipulation selbst geschrieben werden müssen.
Die von mir geschriebene Routine convertDateTime() geht einfach davon aus, dass die Eingaben korrekt in den Formaten TT.MM.JJJJ und HH:MM:SS vorliegen. Das Trennzeichen ist dabei jeweils egal, wichtig ist die Stelligkeit.

convertDateTime() stützt sich zu einem großen Teil auf den Befehl aad, der eigentlich vorgesehen ist, gepackte BCDs für die Division vorzubereiten. Wird bspw. die Zeichenkette '29' ins Register AX geladen, dann enthält AL den Wert 32h und AH den Wert 39h, wegen der Intel-Byteanordnung steht in AX 3932h. Um aus diesen ASCII-Zahlen eine 'richtige' Zahl machen, ist von AL und AH jeweils 30h abzuziehen: sub ax,3030h erledigt das - in AX steht jetzt 0902h.
aad wirkt nun so, das AH mit 10 multipliziert, dann zu AL addiert und danach auf Null gesetzt wird. Zum jetzigen Zeitpunkt ergibt sich dabei der Wert 92, deswegen muss vor aad die Bytereihenfolge in AX getauscht werden.
Für das Jahr erfolgt die Konvertierung zweistufig: erst die ersten beiden Ziffern, die nach der Multiplikation mit 100 das Jahrhundert ergeben, dann die letzten beiden Ziffern, deren Wert zum Jahrhundert dazu addiert wird.

...
...
convertDateTime PROC pDateString:dword, pTimeString:dword 
  ;bedient sich des ASCII-Adjust-Befehls aad, um eine gepackte
  ;BCD in eine Binaerzahl zu konvertieren. Dazu muss aber der
  ;Inhalt von AX umgedreht werden (xchg)
  push ebx
  push ecx
  mov ebx,pDateString
  mov ax,[ebx]
  xchg al,ah
  sub ax,3030h
  aad
  mov stLocal.wDay,ax
...
...

Hier das gesamte Programm

;dlgfattr.asm - Dateiattribute ändern
.686
.MODEL flat,stdcall
OPTION casemap:none

include c:\masm32\include\windows.inc
include c:\masm32\include\user32.inc
include c:\masm32\include\kernel32.inc
include c:\masm32\include\gdi32.inc
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\gdi32.lib

WndProc PROTO :dword, :dword, :dword, :dword
getDateTime PROTO :dword, :dword
convertDateTime PROTO :dword, :dword

.CONST
IDM_NEW    EQU 40001
IDM_OPEN   EQU 40002
IDM_SAVE   EQU 40003
IDM_SAVEAS EQU 40004
IDM_EXIT   EQU 40005

IDM_CUT    EQU 40011
IDM_COPY   EQU 40012
IDM_INSERT EQU 40013
IDM_UNDO   EQU 40014
IDM_ATTRIBUTE EQU 40015

IDC_ARCHIVE EQU 10
IDC_READONLY EQU 11
IDC_HIDDEN EQU 12
IDC_SYSTEM EQU 13
IDC_DATETIME EQU 15
IDC_BTNCURRENT EQU 16
IDC_DATE EQU 18
IDC_TIME EQU 20
IDC_BTNOK  EQU 25

.DATA
szCaption db "Dialoge",0
szClassName db "AttrDialog",0
cMenuName db "WinMenu",0
cMenuFile db "&Datei",0                             ;Hauptmenüpunkt
cMenuNew db "&Neu" ,0
cMenuOpen db "Ö&ffnen...",0
cMenuSave db "&Speichern",0
cMenuSaveAs db "Speichern &unter...",0
cMenuAttribute db "&Attribute ändern",0
cMenuExit db "&Beenden",0 
cFilter db "*.*",0
cListClass db "listbox",0
cBuffer db 80 dup (0)
cDateString db 13 dup (0)
cTimeString db 9 dup (0)
cDateFormat db "%02d.%02d.%04d",0
cTimeFormat db "%02d:%02d:%02d",0

.DATA?			;uninitialisierte Daten
hInstance HINSTANCE ?
hIcon HANDLE ?
hCursor HANDLE ?
hWnd HWND ?
cCmdline LPSTR ?
hMenuBar HANDLE ?
hMenu HANDLE ?
hList HANDLE ?
logfont LOGFONT <?>
win32fileattr WIN32_FILE_ATTRIBUTE_DATA <?>
ftUTC FILETIME <?>
ftLocal FILETIME <?>
stUTC SYSTEMTIME <?>
stLocal SYSTEMTIME <?>

.CODE
start:
  call mymain
  invoke ExitProcess,0


mymain PROC
  LOCAL wwd:dword,wht:dword,wtx:dword,wty:dword,wc:WNDCLASSEX
  
  invoke GetModuleHandle,NULL
  mov hInstance,eax
  invoke GetCommandLine
  mov cCmdline,eax

  mov wtx,CW_USEDEFAULT ;oder konkrete Angaben: 0
  mov wty,CW_USEDEFAULT ;0
  mov wwd,CW_USEDEFAULT ;400
  mov wht,CW_USEDEFAULT ;400
  
  mov wc.cbSize,SIZEOF WNDCLASSEX
  mov wc.style,CS_BYTEALIGNCLIENT or CS_BYTEALIGNWINDOW
  mov wc.lpfnWndProc,OFFSET WndProc
  mov wc.cbClsExtra,NULL
  mov wc.cbWndExtra,NULL
  push hInstance
  pop wc.hInstance
  mov wc.hbrBackground,COLOR_BTNFACE+1
  mov wc.lpszMenuName,NULL
  mov wc.lpszClassName,OFFSET szClassName
  invoke LoadIcon,NULL,IDI_APPLICATION
  mov hIcon,eax
  mov wc.hIcon,eax
  mov wc.hIconSm,eax
  invoke LoadCursor,NULL,IDC_ARROW
  mov hCursor,eax
  mov wc.hCursor,eax
  
  invoke RegisterClassEx,ADDR wc
  
  invoke CreateWindowEx,WS_EX_LEFT,ADDR szClassName,ADDR szCaption,
                        WS_OVERLAPPEDWINDOW,wtx,wty,wwd,wht,NULL,NULL,hInstance,NULL
  mov hWnd,eax
  call erzeugeMenu
  mov hMenuBar,eax
  invoke SetMenu,hWnd,eax
  
  invoke ShowWindow,hWnd,SW_SHOWNORMAL
  invoke UpdateWindow,hWnd
  
  call MsgLoop
  ret
mymain ENDP
  

MsgLoop PROC
  LOCAL msg:MSG
  
  Startloop:
    invoke GetMessage,ADDR msg,NULL,0,0
    or eax,eax
    je Exitloop
    invoke TranslateMessage, ADDR msg
    invoke DispatchMessage,ADDR msg
    jmp Startloop
  Exitloop:
  mov eax,msg.wParam
  ret
MsgLoop ENDP

WndProc PROC hWin:dword,uMsg:dword,wParam:dword,lParam:dword
  LOCAL point:POINT, hFont:HFONT
  .IF uMsg == WM_CREATE
    ;eine Listbox erzeugen
    invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR cListClass,NULL,\
           WS_CHILD OR WS_VISIBLE OR LBS_STANDARD,\
           40,30,200,270,hWin,10,hInstance,NULL
    mov hList,eax
    ;die Listbox mit dem aktuellen Verzeichnisinhalt füllen
    invoke SendMessage,eax,LB_DIR,DDL_READWRITE OR DDL_HIDDEN \
           OR DDL_DIRECTORY OR 4000h,ADDR cFilter
    invoke GetStockObject,ANSI_VAR_FONT                  ;Font aus dem Systemvorrat holen
    invoke GetObject,eax,SIZEOF LOGFONT,ADDR logfont     ;Font-Infos nach logfont schreiben
    invoke CreateFontIndirect,ADDR logfont               ;daraus eine log. Schrift erzeugen
    invoke SendMessage,hList,WM_SETFONT,eax,0
  .ELSEIF uMsg == WM_DESTROY
    invoke PostQuitMessage,NULL
  .ELSEIF uMsg == WM_COMMAND
    mov eax,wParam
    .IF ax==IDM_EXIT
      invoke DestroyWindow,hWnd
    .ELSEIF ax==10                                           ;ID der Listbox
      shr eax,16                                         ;Highword nach AX
      cmp ax,LBN_DBLCLK                                  ;Doppelklick?
      jne @@nodblclk
      invoke SendMessage,hWin,WM_COMMAND,IDM_ATTRIBUTE,NULL
      @@nodblclk:
    .ELSEIF ax==IDM_ATTRIBUTE
      @@setattr:
      invoke SendMessage,hList,LB_GETCURSEL,0,0          ;akt. sel. Element ermitteln
      cmp eax,LB_ERR                                     ;wenn keines markiert oder
      je @@procend                                       ;bei sonstigen Fehlern -> Ende
      ;ansonsten steht in EAX der null-basierte Index des markierten Elements
      invoke SendMessage,hList,LB_GETTEXT,eax,ADDR cBuffer ;Element in Puffer schreiben
      ;Attribute und Zugriffszeiten in Struktur win32fileattr schreiben
      invoke GetFileAttributesEx,ADDR cBuffer,0,ADDR win32fileattr
      mov eax,OFFSET AttrDlgProc                         ;Adresse der Dialogproc nach EAX
      invoke DialogBoxParam,hInstance,ADDR szClassName,hWnd,eax,NULL ;Dialogbox starten
    .ENDIF
  .ELSE
    invoke DefWindowProc,hWin,uMsg,wParam,lParam
    ret
  .ENDIF
  @@procend:
  xor eax,eax
  ret
WndProc ENDP

AttrDlgProc PROC hDlg:dword,uMsg:dword,wParam:dword,lParam:dword
  .IF uMsg==WM_INITDIALOG
    ;ab hier Test der Attribute
    test win32fileattr.dwFileAttributes,FILE_ATTRIBUTE_ARCHIVE
    jz @@test_RO
    invoke CheckDlgButton,hDlg,IDC_ARCHIVE,BST_CHECKED
    @@test_RO:
    test win32fileattr.dwFileAttributes,FILE_ATTRIBUTE_READONLY
    jz @@test_HID
    invoke CheckDlgButton,hDlg,IDC_READONLY,BST_CHECKED
    @@test_HID:
    test win32fileattr.dwFileAttributes,FILE_ATTRIBUTE_HIDDEN
    jz @@test_SYS
    invoke CheckDlgButton,hDlg,IDC_HIDDEN,BST_CHECKED
    @@test_SYS:
    test win32fileattr.dwFileAttributes,FILE_ATTRIBUTE_SYSTEM
    jz @@test_END
    invoke CheckDlgButton,hDlg,IDC_SYSTEM,BST_CHECKED
    @@test_END:
    ;Dateizeit in Systemzeit (UTC) umwandeln
    invoke FileTimeToSystemTime,ADDR win32fileattr.ftLastWriteTime,ADDR stUTC
    invoke getDateTime,ADDR stUTC,hDlg
    invoke GetDlgItem,hDlg,IDC_DATE
    invoke EnableWindow,eax,FALSE
    invoke GetDlgItem,hDlg,IDC_TIME
    invoke EnableWindow,eax,FALSE
    mov eax,TRUE
    ret
  .ENDIF
  .IF uMsg==WM_COMMAND
    mov eax,wParam
    .IF ax==IDC_DATETIME
      invoke IsDlgButtonChecked,hDlg,IDC_DATETIME
      cmp eax,BST_UNCHECKED
      jne @@dtchecked
        invoke GetDlgItem,hDlg,IDC_DATE
        invoke EnableWindow,eax,FALSE
        invoke GetDlgItem,hDlg,IDC_TIME
        invoke EnableWindow,eax,FALSE
        jmp short @@dtende
      @@dtchecked:
        invoke GetDlgItem,hDlg,IDC_DATE
        invoke EnableWindow,eax,TRUE
        invoke GetDlgItem,hDlg,IDC_TIME
        invoke EnableWindow,eax,TRUE
      @@dtende:
    .ELSEIF ax==IDC_BTNOK
      ;Test, ob Attribute angeklickt ist
      invoke IsDlgButtonChecked,hDlg,IDC_ARCHIVE
      cmp eax,BST_UNCHECKED
      jne @@test_ARCH_CHECKED
      and win32fileattr.dwFileAttributes,NOT FILE_ATTRIBUTE_ARCHIVE
      @@test_ARCH_CHECKED:
      cmp eax,BST_CHECKED
      jne @@test_RO_UNCHECKED
      or win32fileattr.dwFileAttributes,FILE_ATTRIBUTE_ARCHIVE
    
    
      @@test_RO_UNCHECKED:
      invoke IsDlgButtonChecked,hDlg,IDC_READONLY
      cmp eax,BST_UNCHECKED
      jne @@test_RO_CHECKED
      and win32fileattr.dwFileAttributes,NOT FILE_ATTRIBUTE_READONLY
      @@test_RO_CHECKED:
      cmp eax,BST_CHECKED
      jne @@test_HID_UNCHECKED
      or win32fileattr.dwFileAttributes,FILE_ATTRIBUTE_READONLY

      @@test_HID_UNCHECKED:
      invoke IsDlgButtonChecked,hDlg,IDC_HIDDEN
      cmp eax,BST_UNCHECKED
      jne @@test_HID_CHECKED
      and win32fileattr.dwFileAttributes,NOT FILE_ATTRIBUTE_HIDDEN
      @@test_HID_CHECKED:
      cmp eax,BST_CHECKED
      jne @@test_SYS_UNCHECKED
      or win32fileattr.dwFileAttributes,FILE_ATTRIBUTE_HIDDEN

      @@test_SYS_UNCHECKED:
      invoke IsDlgButtonChecked,hDlg,IDC_SYSTEM
      cmp eax,BST_UNCHECKED
      jne @@test_SYS_CHECKED
      and win32fileattr.dwFileAttributes,NOT FILE_ATTRIBUTE_SYSTEM
      @@test_SYS_CHECKED:
      cmp eax,BST_CHECKED
      jne @@setAttr
      or win32fileattr.dwFileAttributes,FILE_ATTRIBUTE_SYSTEM
      @@setAttr:
      invoke SetFileAttributes,ADDR cBuffer,win32fileattr.dwFileAttributes
      ;Test, ob Zeitstempel geaendert werden soll
      invoke IsDlgButtonChecked,hDlg,IDC_DATETIME
      cmp eax,BST_UNCHECKED
      je @@DialogFertig
      ;Zeichenketten Datum und Uhrzeit holen
      invoke GetDlgItemText,hDlg,IDC_DATE,ADDR cDateString,13
      invoke GetDlgItemText,hDlg,IDC_TIME,ADDR cTimeString,9
      ;Zeichenketten in FILETIME-Struktur eintragen
      invoke convertDateTime,ADDR cDateString,ADDR cTimeString
      ;Datum und Uhrzeit der Datei setzen
      call setDateTime
      @@DialogFertig:
      invoke EndDialog,hDlg,TRUE                    ;Dialog mit TRUE beenden
    .ENDIF
    .IF ax==IDC_BTNCURRENT
      invoke GetSystemTime,ADDR stUTC
      invoke getDateTime,ADDR stUTC,hDlg
      invoke GetDlgItem,hDlg,IDC_DATE
      invoke EnableWindow,eax,TRUE
      invoke GetDlgItem,hDlg,IDC_TIME
      invoke EnableWindow,eax,TRUE
      mov eax,TRUE
      ret
    .ENDIF
    .IF ax==IDCANCEL
      invoke EndDialog,hDlg,FALSE                   ;Dialog mit FALSE beenden
    .ENDIF
  .ENDIF
  mov eax,FALSE
  ret
AttrDlgProc ENDP

erzeugeMenu PROC
  invoke CreateMenu
  mov hMenuBar,eax
  invoke CreateMenu
  mov hMenu,eax
  invoke AppendMenu,hMenu,MF_STRING,IDM_NEW,ADDR cMenuNew
  invoke AppendMenu,hMenu,MF_STRING,IDM_OPEN,ADDR cMenuOpen
  invoke AppendMenu,hMenu,MF_STRING,IDM_SAVE,ADDR cMenuSave
  invoke AppendMenu,hMenu,MF_STRING,IDM_SAVE,ADDR cMenuSaveAs
  invoke AppendMenu,hMenu,MF_SEPARATOR,0,NULL
  invoke AppendMenu,hMenu,MF_STRING,IDM_ATTRIBUTE,ADDR cMenuAttribute
  invoke AppendMenu,hMenu,MF_SEPARATOR,0,NULL
  invoke AppendMenu,hMenu,MF_STRING,IDM_EXIT,ADDR cMenuExit
  invoke AppendMenu,hMenuBar,MF_POPUP,hMenu,ADDR cMenuFile
  mov eax,hMenuBar
  ret
erzeugeMenu ENDP

getDateTime PROC pSystemTime:dword, hDlg:dword
;ermittelt aus der UTC-Systemzeit eine TZ-basierte Zeit und füllt den Dialog
  push ebx                                        ;Register sichern
  push ecx
  push edx
  ;Systemzeit in lokale Zeit (mit Zeitzone) umwandeln
  invoke SystemTimeToTzSpecificLocalTime,NULL,pSystemTime,ADDR stLocal
  ;Tag, Monat, Jahr auf 32 Bit umsetzen
  movzx ebx,stLocal.wDay
  movzx ecx,stLocal.wMonth
  movzx edx,stLocal.wYear
  ;anhand des Datumformates in  einen Puffer schreiben
  invoke wsprintf,ADDR cDateString,ADDR cDateFormat,ebx,ecx,edx
  ;und ins Dialogelement schreiben
  invoke SetDlgItemText,hDlg,IDC_DATE,ADDR cDateString
  ;Stunde, Minute, Sekunde auf 32 Bit umsetzen
  movzx ebx,stLocal.wHour
  movzx ecx,stLocal.wMinute
  movzx edx,stLocal.wSecond
  invoke wsprintf,ADDR cTimeString,ADDR cTimeFormat,ebx,ecx,edx
  invoke SetDlgItemText,hDlg,IDC_TIME,ADDR cTimeString
  ;gesicherte Register wieder vom Stack holen
  pop edx
  pop ecx
  pop ebx
  ret
getDateTime ENDP

convertDateTime PROC pDateString:dword, pTimeString:dword 
  ;bedient sich des ASCII-Adjust-Befehls aad, um eine gepackte
  ;BCD in eine Binaerzahl zu konvertieren. Dazu muss aber der
  ;Inhalt von AX umgedreht werden (xchg)
  push ebx
  push ecx
  mov ebx,pDateString
  mov ax,[ebx]
  xchg al,ah
  sub ax,3030h
  aad
  mov stLocal.wDay,ax
  mov ax,[ebx+3]
  xchg al,ah
  sub ax,3030h
  aad
  mov stLocal.wMonth,ax
  mov ax,[ebx+6]
  xchg al,ah
  sub ax,3030h
  aad
  mov cl,100
  mul cl
  mov stLocal.wYear,ax
  mov ax,[ebx+8]
  xchg al,ah
  sub ax,3030h
  aad
  add stLocal.wYear,ax
  
  mov ebx,pTimeString
  mov ax,[ebx]
  xchg al,ah
  sub ax,3030h
  aad
  mov stLocal.wHour,ax
  mov ax,[ebx+3]
  xchg al,ah
  sub ax,3030h
  aad
  mov stLocal.wMinute,ax
  mov ax,[ebx+6]
  xchg al,ah
  sub ax,3030h
  aad
  mov stLocal.wSecond,ax
  invoke SystemTimeToFileTime,ADDR stLocal,ADDR ftLocal
  invoke LocalFileTimeToFileTime,ADDR ftLocal,ADDR ftUTC
  invoke GetLastError
  pop ecx
  pop ebx
  ret
convertDateTime ENDP

setDateTime PROC
  LOCAL hFile:HANDLE
  invoke CreateFile,ADDR cBuffer,GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL
  cmp eax,INVALID_HANDLE_VALUE
  mov hFile,eax
  je @@procend
  invoke SetFileTime,eax,NULL,NULL,ADDR ftUTC
  invoke GetLastError
  invoke CloseHandle,hFile
  @@procend:
  ret
setDateTime ENDP

END start
Passend dazu die Ressource-Datei testdlg.rc
#include "\masm32\include\resource.h"

#define IDC_SUBDIR 9
#define IDC_ARCHIVE 10
#define IDC_READONLY 11
#define IDC_HIDDEN 12
#define IDC_SYSTEM 13
#define IDC_DATETIME 15
#define IDC_BTNCURRENT 16
#define IDC_DATE 18
#define IDC_TIME 20
#define IDC_BTNOK 25
#define IDC_BTNHELP 26


ATTRDIALOG DIALOGEX 40, 40, 160, 150
STYLE WS_POPUP|DS_MODALFRAME|WS_VISIBLE|WS_CAPTION|WS_SYSMENU
CAPTION "Attribute ändern"
FONT 8, "MS Sans Serif"
BEGIN
  GROUPBOX "Attribute ändern",-1,5,17,150,70,WS_GROUP
  AUTO3STATE "&a Archiv-Bit", IDC_ARCHIVE,10,27,70,10,WS_GROUP|WS_TABSTOP
  AUTO3STATE "&r Nur Lesen-Bit", IDC_READONLY,10,39,70,10
  AUTO3STATE "&h Versteckt-Bit", IDC_HIDDEN,10,51,70,10
  AUTO3STATE "&s System-Bit", IDC_SYSTEM,10,63,70,10
  
  GROUPBOX "",-1,5,93,150,37,WS_GROUP
  AUTOCHECKBOX "&Datum/Zeit ändern:",IDC_DATETIME,10,100,80,10,WS_GROUP|WS_TABSTOP
  PUSHBUTTON "A&ktuelles",IDC_BTNCURRENT,110,100,40,12
  LTEXT "Datu&m:",-1,10,117,22,10
  EDITTEXT IDC_DATE,35,115,42,12
  LTEXT "&Zeit:",-1,90,117,14,10
  EDITTEXT IDC_TIME,107,115,42,12
  
  DEFPUSHBUTTON "OK",IDC_BTNOK,30,135,40,12
  PUSHBUTTON "Abbrechen",IDCANCEL,72,135,40,12,WS_GROUP
  PUSHBUTTON "Hilfe",IDC_BTNHELP,114,135,40,12
END

Die Eingabefelder für Datum und Uhrzeit werden nur freigeschaltet, wenn entweder das Kontrollkästchen auf Ja gestellt wurde, oder der Button Aktuelles geklickt wurde. Verantwortlich dafür sind die Funktionen GetDlgItem(), die aus der ID eines Steuerelements das Handle ermittelt sowie die Funktion EnableWindow(), die die Steuerelemente aktiviert und deaktiviert.

Wenn sie im Programm die Zeile
invoke DialogBoxParam,hInstance,ADDR szClassName,hWnd,eax,NULL ;Dialogbox starten
gegen die Zeile
invoke CreateDialogParam,hInstance,ADDR szClassName,hWnd,eax,NULL ;Dialogbox starten
austauschen, dann haben sie eine nichtmodale Dialogbox. Sie können damit zwar zeitgleich mehrere Dialogboxen mit den Dateiattributen öffnen, aber das ergibt bei dieser Anwendung zum einen nicht viel Sinn, zum anderen funktioniert es zumindest bei mir auch nicht richtig mit der Navigation im Dialog (Tab-Taste, Enter) sowie der Zeitstempelmanipulation. Für die Navigation ist in MsgLoop() noch ein Aufruf von IsDialogMessage notwendig.

Dialoge ohne Hauptfenster

Was bisher gezeigt wurde, war die klassische Variante des Dialogs, der innerhalb eines Hauptfensters aufgerufen wurde. Windows bietet mindestens zwei Varianten, um einen Dialog auch ohne ein Hauptfenster aufzurufen, allerdings werde ich nur eine davon vorstellen.

Es kommt hier wieder der Dialog zum Ändern der Dateiattribute zum Einsatz. Da das Fenster mit der Liste der Dateien hier nicht vorhanden ist, bedienen sich diese Programme der Kommandozeile, die bisher recht stiefmütterlich behandelt wurde. Das verwendete Verfahren entspricht dem im Kapitel Kommandozeilenparameter. Da hier bestimmte Unicode-Funktionen verwendet werden, sind die Programme dieses Unterkapitels nicht unter Windows 95/98/Me lauffähig.

Da hierbei die Kommandozeilenargumente im Unicode-Format geliefert werden, müssen die Funktionen für die Dateiattribute, die mit einem Dateinamen arbeiten, ebenfalls durch ihre Unicode-Pendants ersetzt werden.

Gegenüber dem vorherigen Programm ändert sich einiges. Die Prozeduren MsgLoop() und WndProc() werden nicht mehr benötigt, da es nur noch die Dialogprozedur gibt, der Rest der Ereignisbehandlung geht über den Windows-Dialogmanager. Mymain() wird kürzer und beschränkt sich auf die Ermittlung der Kommandozeile. Sollte kein Argument angegeben werden, bricht das Programm mit einer Hinweismeldung ab. Ansonsten wird die Adresse des ersten Arguments in einer Variablen gespeichert.

Diese Variable wird den Dateinamen-orientierten Funktionen übergeben. Der Rest des Programms stimmt weitestgehend mit dem vorherigen Programm überein, abgesehen von den notwendigen Anpassungen der Funktionsnamen auf die Unicode-Variante.

Als letzte Aufgabe verbleibt in mymain dann nur noch der Aufruf von DialogBoxParam(). Der dritte Parameter muss hier NULL sein, da es kein Hauptfenster gibt.

;dlgfatr2 - Dialog von Kommandozeile als Hauptfenster
.686
.MODEL flat,stdcall
OPTION casemap:none

include c:\masm32\include\windows.inc
include c:\masm32\include\user32.inc
include c:\masm32\include\kernel32.inc
include c:\masm32\include\gdi32.inc
include c:\masm32\include\shell32.inc
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\gdi32.lib
includelib c:\masm32\lib\shell32.lib

getDateTime PROTO :dword, :dword
convertDateTime PROTO :dword, :dword

.CONST
IDC_ARCHIVE EQU 10
IDC_READONLY EQU 11
IDC_HIDDEN EQU 12
IDC_SYSTEM EQU 13
IDC_DATETIME EQU 15
IDC_BTNCURRENT EQU 16
IDC_DATE EQU 18
IDC_TIME EQU 20
IDC_BTNOK  EQU 25

.DATA
cParamTitle db "Parameterfehler",0
cParamFehler db "Unzureichende Anzahl Parameter: mindestens ein Dateiname erforderlich",0
szClassName db "AttrDialog",0
cDateString db 13 dup (0)
cTimeString db 9 dup (0)
cDateFormat db "%02d.%02d.%04d",0
cTimeFormat db "%02d:%02d:%02d",0

.DATA?			;uninitialisierte Daten
hInstance HINSTANCE ?
pCmdline LPSTR ?
pArgs dword ?           ;Zeiger auf ein Array
iArgs dword ?           ;Anzahl Argumente
pFilename dword ?       ;Adresse des Dateinamens in der Kommandozeile
win32fileattr WIN32_FILE_ATTRIBUTE_DATA <?>
ftUTC FILETIME <?>
ftLocal FILETIME <?>
stUTC SYSTEMTIME <?>
stLocal SYSTEMTIME <?>

.CODE
start:
  call mymain
  invoke LocalFree,pArgs
  invoke ExitProcess,0


mymain PROC
  
  invoke GetModuleHandle,NULL
  mov hInstance,eax
  invoke GetCommandLineW    ;Verwendung der Unicode-Version
  mov pCmdline,eax
  invoke CommandLineToArgvW,eax, ADDR iArgs
  mov pArgs,eax
  cmp iArgs,2           ;gibt es mindestens ein Programmargument
  jge @@weiter
  invoke MessageBox,0,ADDR cParamFehler,ADDR cParamTitle,MB_OK
  ret                   ;wenn nicht, dann Abbruch mit Meldung
  @@weiter:
  mov eax,pArgs         ;Startadresse des Arrays nach EAX
  mov ebx,[eax + 4]     ;Startadresse des ersten Programmarguments ermitteln
  mov pFilename,ebx     ;und speichern 
  ;ab jetzt wie gewohnt weiter
  invoke GetFileAttributesExW,pFilename,0,ADDR win32fileattr
  mov eax,OFFSET AttrDlgProc                         ;Adresse der Dialogproc nach EAX
  invoke DialogBoxParam,hInstance,ADDR szClassName,NULL,eax,NULL ;Dialogbox starten
  ret
mymain ENDP

AttrDlgProc PROC hDlg:dword,uMsg:dword,wParam:dword,lParam:dword
  .IF uMsg==WM_INITDIALOG
    ;ab hier Test der Attribute
    test win32fileattr.dwFileAttributes,FILE_ATTRIBUTE_ARCHIVE
    jz @@test_RO
    invoke CheckDlgButton,hDlg,IDC_ARCHIVE,BST_CHECKED
    @@test_RO:
    test win32fileattr.dwFileAttributes,FILE_ATTRIBUTE_READONLY
    jz @@test_HID
    invoke CheckDlgButton,hDlg,IDC_READONLY,BST_CHECKED
    @@test_HID:
    test win32fileattr.dwFileAttributes,FILE_ATTRIBUTE_HIDDEN
    jz @@test_SYS
    invoke CheckDlgButton,hDlg,IDC_HIDDEN,BST_CHECKED
    @@test_SYS:
    test win32fileattr.dwFileAttributes,FILE_ATTRIBUTE_SYSTEM
    jz @@test_END
    invoke CheckDlgButton,hDlg,IDC_SYSTEM,BST_CHECKED
    @@test_END:
    ;Dateizeit in Systemzeit (UTC) umwandeln
    invoke FileTimeToSystemTime,ADDR win32fileattr.ftLastWriteTime,ADDR stUTC
    invoke getDateTime,ADDR stUTC,hDlg
    invoke GetDlgItem,hDlg,IDC_DATE
    invoke EnableWindow,eax,FALSE
    invoke GetDlgItem,hDlg,IDC_TIME
    invoke EnableWindow,eax,FALSE
    mov eax,TRUE
    ret
  .ENDIF
  .IF uMsg==WM_COMMAND
    mov eax,wParam
    .IF ax==IDC_DATETIME
      invoke IsDlgButtonChecked,hDlg,IDC_DATETIME
      cmp eax,BST_UNCHECKED
      jne @@dtchecked
        invoke GetDlgItem,hDlg,IDC_DATE
        invoke EnableWindow,eax,FALSE
        invoke GetDlgItem,hDlg,IDC_TIME
        invoke EnableWindow,eax,FALSE
        jmp short @@dtende
      @@dtchecked:
        invoke GetDlgItem,hDlg,IDC_DATE
        invoke EnableWindow,eax,TRUE
        invoke GetDlgItem,hDlg,IDC_TIME
        invoke EnableWindow,eax,TRUE
      @@dtende:
    .ELSEIF ax==IDC_BTNOK
      ;Test, ob Attribute angeklickt ist
      invoke IsDlgButtonChecked,hDlg,IDC_ARCHIVE
      cmp eax,BST_UNCHECKED
      jne @@test_ARCH_CHECKED
      and win32fileattr.dwFileAttributes,NOT FILE_ATTRIBUTE_ARCHIVE
      @@test_ARCH_CHECKED:
      cmp eax,BST_CHECKED
      jne @@test_RO_UNCHECKED
      or win32fileattr.dwFileAttributes,FILE_ATTRIBUTE_ARCHIVE
    
    
      @@test_RO_UNCHECKED:
      invoke IsDlgButtonChecked,hDlg,IDC_READONLY
      cmp eax,BST_UNCHECKED
      jne @@test_RO_CHECKED
      and win32fileattr.dwFileAttributes,NOT FILE_ATTRIBUTE_READONLY
      @@test_RO_CHECKED:
      cmp eax,BST_CHECKED
      jne @@test_HID_UNCHECKED
      or win32fileattr.dwFileAttributes,FILE_ATTRIBUTE_READONLY

      @@test_HID_UNCHECKED:
      invoke IsDlgButtonChecked,hDlg,IDC_HIDDEN
      cmp eax,BST_UNCHECKED
      jne @@test_HID_CHECKED
      and win32fileattr.dwFileAttributes,NOT FILE_ATTRIBUTE_HIDDEN
      @@test_HID_CHECKED:
      cmp eax,BST_CHECKED
      jne @@test_SYS_UNCHECKED
      or win32fileattr.dwFileAttributes,FILE_ATTRIBUTE_HIDDEN

      @@test_SYS_UNCHECKED:
      invoke IsDlgButtonChecked,hDlg,IDC_SYSTEM
      cmp eax,BST_UNCHECKED
      jne @@test_SYS_CHECKED
      and win32fileattr.dwFileAttributes,NOT FILE_ATTRIBUTE_SYSTEM
      @@test_SYS_CHECKED:
      cmp eax,BST_CHECKED
      jne @@setAttr
      or win32fileattr.dwFileAttributes,FILE_ATTRIBUTE_SYSTEM
      @@setAttr:
      invoke SetFileAttributesW,pFilename,win32fileattr.dwFileAttributes
      ;Test, ob Zeitstempel geaendert werden soll
      invoke IsDlgButtonChecked,hDlg,IDC_DATETIME
      cmp eax,BST_UNCHECKED
      ;Zeichenketten Datum und Uhrzeit holen
      invoke GetDlgItemText,hDlg,IDC_DATE,ADDR cDateString,13
      invoke GetDlgItemText,hDlg,IDC_TIME,ADDR cTimeString,9
      ;Zeichenketten in FILETIME-Struktur eintragen
      invoke convertDateTime,ADDR cDateString,ADDR cTimeString
      ;Datum und Uhrzeit der Datei setzen
      call setDateTime
      @@DialogFertig:
      invoke EndDialog,hDlg,TRUE                    ;Dialog mit TRUE beenden
    .ENDIF
    .IF ax==IDC_BTNCURRENT
      invoke GetSystemTime,ADDR stUTC
      invoke getDateTime,ADDR stUTC,hDlg
      invoke GetDlgItem,hDlg,IDC_DATE
      invoke EnableWindow,eax,TRUE
      invoke GetDlgItem,hDlg,IDC_TIME
      invoke EnableWindow,eax,TRUE
      mov eax,TRUE
      ret
    .ENDIF
    .IF ax==IDCANCEL
      invoke EndDialog,hDlg,FALSE                   ;Dialog mit FALSE beenden
    .ENDIF
  .ENDIF
  mov eax,FALSE
  ret
AttrDlgProc ENDP

getDateTime PROC pSystemTime:dword, hDlg:dword
;ermittelt aus der UTC-Systemzeit eine TZ-basierte Zeit und füllt den Dialog
  ...
  ...
  ret
getDateTime ENDP

convertDateTime PROC pDateString:dword, pTimeString:dword 
  ;bedient sich des ASCII-Adjust-Befehls aad, um eine gepackte
  ;BCD in eine Binaerzahl zu konvertieren. Dazu muss aber der
  ;Inhalt von AX umgedreht werden (xchg)
  ...
  ...
  ret
convertDateTime ENDP

setDateTime PROC
  LOCAL hFile:HANDLE
  invoke CreateFileW,pFilename,GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL
  cmp eax,INVALID_HANDLE_VALUE
  mov hFile,eax
  je @@procend
  invoke SetFileTime,eax,NULL,NULL,ADDR ftUTC
  invoke GetLastError
  invoke CloseHandle,hFile
  @@procend:
  ret
setDateTime ENDP

END start

Der Inhalt von getDateTime() und convertDateTime() habe ich rausgenommen, er stimmt mit dem vorherigen Programm überein.

Windows-Standarddialoge

Windows stellt eine Reihe von vorgefertigten Dialogen bereit, von denen man vielen in der täglichen Arbeit begegnet.

Standarddialoge
Dialog Bedeutung
Color Der Nutzer kann eine Farbe auswählen bzw. sich selbst zusammenstellen. Die Funktion ChooseColor() sowie eine Struktur des Typs CHOOSECOLOR werden benötigt
Find Standarddialog zur Textsuche, verwendet z. B. in Notepad. Die Funktion FindText() sowie eine Struktur des Typs FINDREPLACE werden benötigt
Font Der Standardschriftauswahldialog mit Schriftarten, Größen und weiteren Attributen. Die Funktion ChooseFont() sowie eine Struktur des Typs CHOOSEFONT werden benötigt
Open Zeigt einen Dateiauswahldialog an. Der Nutzer kann eine Datei oder Shell-Erweiterung wählen. Filtern nach Dateiendung und Laufwerkswechsel sind ebenfalls möglich. Die Funktion GetOpenFileName() sowie eine Struktur des Typs OPENFILENAME werden benötigt
Page Setup Dialog Seite einrichten. Bietet die aktuelle Seitenkonfiguration wie Abmessungen, Hoch/Querformat und Ränder zur Änderung an. Die Funktion PageSetupDlg() sowie eine Struktur des Typs PAGESETUPDLG werden benötigt
Print Der Drucken-Dialog. Auswahl des Druckers sowie Einstellung von Druckoptionen. Die Funktion PrintDlg() sowie eine Struktur des Typs PRINTDLG werden benötigt
Print Property Sheet Erst ab Windows 2000/XP. Der Dialog besteht aus mehreren Karteikarten (Sheets), wobei die erste Karteikarte dem Print-Dialog ähnelt. Durch den Nutzer können anwendungsspezifische Karteikarten hinzugefügt werden. Die Funktion PrintDlgEx() sowie eine Struktur des Typs PRINTDLGEX werden benötigt
Replace Standarddialog zum Suchen und Ersetzen, verwendet z. B. in Notepad. Die Funktion ReplaceText() sowie eine Struktur des Typs FINDREPLACE werden benötigt
Save As Zeigt einen Dateiauswahldialog an. Der Nutzer kann eine Datei oder Shell-Erweiterung wählen. Filtern nach Dateiendung und Laufwerkswechsel sind ebenfalls möglich. Die Funktion GetSaveFileName() sowie eine Struktur des Typs OPENFILENAME werden benötigt

Es existiert zwar auch ein Print Setup-Dialog, aber in neuen Anwendungen sollte der Page Setup-Dialog verwendet werden.

Abgesehen vom Find- und Replace-Dialog sind alle Dialoge modal. Im Falle der beiden Ausnahmen müssen sie in der Nachrichtenschleife die Funktion IsDialogMessage() verwenden, damit diese Dialoge Tastatureingaben korrekt verarbeiten.

Gerade im Falle der Suchen- und Ersetzen-Dialoge werden von vielen Anwendungen eigene Dialoge eingesetzt, die z. B. über eine Eingabehistorie und erweiterte Suchoptionen (reguläre Ausdrücke, nur ganze Wörter suchen) verfügen.