WIN32 API professionell
Programmer’s Choice
Klaus Prinz
WIN32 API professionell Praktische Lösungen für Visual Basic und VBA
An imprint of Pearson Education München • Boston • San Francisco • Harlow, England Don Mills, Ontario • Sydney • Mexico City Madrid • Amsterdam
Die Deutsche Bibliothek – CIP-Einheitsaufnahme Ein Titeldatensatz für diese Publikation ist bei Der Deutschen Bibliothek erhältlich. Die Informationen in diesem Produkt werden ohne Rücksicht auf einen eventuellen Patentschutz veröffentlicht. Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt. Bei der Zusammenstellung von Abbildungen und Texten wurde mit größter Sorgfalt vorgegangen. Trotzdem können Fehler nicht vollständig ausgeschlossen werden. Verlag, Herausgeber und Autoren können für fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag und Herausgeber dankbar. Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und der Speicherung in elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigten Modelle und Arbeiten ist nicht zulässig. Fast alle Hardware- und Softwarebezeichnungen, die in diesem Buch erwähnt werden, sind gleichzeitig eingetragene Warenzeichen oder sollten als solche betrachtet werden. Umwelthinweis: Dieses Produkt wurde auf chlorfrei gebleichtem Papier gedruckt. Die Einschrumpffolie – zum Schutz vor Verschmutzung – ist aus umweltverträglichem und recyclingfähigem PE-Material.
5 4 05
3 04
2 1 03 02 01
ISBN 3-8273-1720-7
© 2001 by Addison-Wesley Verlag, ein Imprint der Pearson Education Deutschland GmbH, Martin-Kollar-Straße 10–12, D-81829 München/Germany Alle Rechte vorbehalten Einbandgestaltung: Christine Rechl, München Titelbild: Echeveria magnifica, Echeverie © Karl Blossfeldt Archiv – Ann und Jürgen Wilde, Zülpich/VG Bild-Kunst Bonn, 2001 Lektorat: Christina Gibbs,
[email protected] Korrektorat: Simone Burst, Großberghofen Herstellung: Monika Weiher,
[email protected] Satz: reemers publishing services gmbh, Krefeld, www.reemers.de Druck und Verarbeitung: Bercker, Kevelaer Printed in Germany
Inhalt
Vorwort
15
1
Referenz
19
1.1 1.1.1 1.1.2 1.1.3 1.2 1.3 1.4 1.4.1 1.4.2 1.4.3 1.5 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.6 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.6.10 1.7 1.7.1 1.7.2 1.7.3 1.7.4
Funktionen übergeordneten Charakters Sleep well Wenn Sekunden zu ungenau sind Statt Refresh oder Repaint Allgemeine Hinweise zu den Komponenten Ländereinstellungen clsSysNLS Dialoge Datei Öffnen- und Schließen-Dialog Farbdialog Verzeichnis-Dialog Systeminformationen Anmeldenamen NetBIOS- und DNS-Name Betriebssystem IP-Adresse GUIDs Laufwerke DrivesCount-Eigenschaft DriveName-Eigenschaft DriveIndex-Eigenschaft DriveReady-Eigenschaft DriveType-Eigenschaft DriveTypeName-Eigenschaft DriveFreeSpace-Eigenschaft DriveTotalSpace-Eigenschaft CDDrivesCount-Eigenschaft CDDriveName-Eigenschaft Dateisystem FileInUse-Eigenschaft DisplayName-Eigenschaft TypeName-Eigenschaft IconHandle-Eigenschaft
19 19 20 20 21 22 24 24 26 27 28 28 28 29 30 30 31 31 31 32 32 32 32 32 32 33 33 33 33 34 34 34
6
Inhalt
1.8 1.8.1 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.9 1.9.1 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 1.10 1.10.1 1.10.2 1.10.3 1.10.4 1.11 1.11.1 1.11.2 1.11.3 1.12 1.12.1 1.12.2 1.12.3 1.12.4 1.13 1.13.1 1.13.2 1.13.3 1.13.4 1.13.5 1.13.6 1.13.7 1.13.8
Registry HasKey-Methode GetRegistryValue-Methode SetRegistryValue-Methode DeleteRegistryValue-Methode EnumKeys-Methode EnumValues-Methode Die Settings-Schnittstelle Drucker DefaultPrinter-Eigenschaft PrinterCount- und PrinterName-Eigenschaft ServerName-Eigenschaft Printer-Eigenschaft PaperBinCount-Eigenschaft GetPaperBinName-Methode PaperBin-Eigenschaft Orientation-Eigenschaft hDC-Eigenschaft Fonts FontCount- und FontName-Eigenschaft DefaultGUIFontName-Eigenschaft DefaultGUIFontSize-Eigenschaft WriteVerticalText-Methode Windows SystemHWnd-Eigenschaft SystemHDC-Eigenschaft GetDC-Methode Shell LaunchApplication-Methode SetWindowDimensions-Methode ResizeApplicationWindow-Methode CloseApplication-Methode Maus und Tastatur MouseButtonsSwappedEigenschaft MouseLeftButtonPressed-Eigenschaft MouseMiddleButtonPressed-Eigenschaft MouseRightButtonPressed-Eigenschaft ControlKeyPressed-Eigenschaft ShiftKeyPressed-Eigenschaft AltKeyPressed-Eigenschaft KeyPressed-Eigenschaft
36 36 37 37 38 38 39 40 41 41 42 42 42 42 42 43 43 44 44 44 45 45 45 45 46 46 46 46 46 47 48 48 48 48 49 49 49 49 49 49 50
2
Grundlagen
51
2.1 2.2 2.2.1 2.2.2 2.3 2.3.1
Zugriff über Verweise Zugriff über API Welche Bibliotheken gibt es? Struktur einer API-Deklaration Datentypen String
52 53 53 54 55 55
Inhalt
7
2.3.2 2.3.3 2.3.4 2.3.5 2.4 2.5 2.6
Byte, Integer, Long etc. Strukturen Arrays Die wichtigsten C-Datentypen Ein Beispiel Der WinAPI-Viewer Header-Dateien
56 58 59 59 60 61 63
3
Locale Settings
65
3.1 3.2 3.3 3.4
Anwendungsfälle Language-ID und Locale-ID Auslesen von Werten Klassenaufbau
65 66 67 69
4
Datei- und Verzeichnisdialoge
71
4.1 4.1.1 4.1.2 4.1.3 4.1.4 4.2 4.2.1 4.2.2 4.2.3 4.3 4.3.1 4.3.2
Dateidialoge OPENFILENAME-Struktur ComposeFileFilter-Funktion GetFileName-Funktion Die öffentlichen Methoden Farbendialog CHOOSECOLOR-Struktur CustomizedColor-Eigenschaft GetColor-Methode Verzeichnisdialog BROWSEINFO-Struktur GetPath-Methode
71 73 75 76 77 78 78 80 80 81 81 82
5
Systeminformationen
85
5.1 5.2 5.2.1 5.2.2 5.3 5.3.1 5.3.2 5.3.3 5.4 5.4.1 5.4.2 5.4.3 5.4.4 5.4.5 5.4.6 5.4.7 5.5
Anmeldename NetBIOS- und DNS-Name NetBIOS-Name bzw. Computer-Name DNS-Name Das Betriebssystem GetVersionEx-Funktion OSVERSIONINFO-Struktur Der Code IP-Adresse InitializeSockets CleanupSockets GetHostByName-Funktion HOSTENT-Struktur CopyMemory-Funktion GetHostName-Methode IPAddress-Eigenschaft GUIDs
85 86 86 87 87 88 88 89 90 91 92 93 93 94 95 95 96
8
Inhalt
5.5.1 5.5.2 5.5.3 5.5.4
CoCreateQuid-Funktion GUID-Struktur StringFromGUID2-Funktion GetNewGUID-Methode
96 96 96 97
6
Laufwerke
99
6.1 6.1.1 6.1.2 6.2 6.3 6.4 6.4.1 6.5 6.6 6.7 6.8 6.9 6.10 6.10.1 6.10.2 6.10.3 6.10.4 6.10.5 6.10.6 6.11
Initialisierung der Arrays GetLogicalDriveStrings-Funktion Die Prozedur InitializeArrays Die Eigenschaft DrivesCount Die Eigenschaft DriveName Die Eigenschaft DriveType GetDriveType-Funktion Die Eigenschaft DriveTypeName Die Eigenschaft DriveIndex Die Eigenschaft DriveReady Die Eigenschaft CDDrivesCount Die Eigenschaft CDDriveName Die Eigenschaft DriveFreeSpace GetDiskFreeSpace oder GetDiskFreeSpaceEx? GetModuleHandle-Funktion GetProcAddress-Funktion GetDiskFreeSpace-Funktion GetDiskFreeSpaceEx-Funktion Behandlung des 64 Bit-Integers Die Eigenschaft DriveTotalSpace
100 101 101 102 102 102 103 103 103 104 104 105 105 106 106 106 107 108 109 111
7
23Dateisystem
7.1 7.1.1 7.1.2 7.1.3 7.2 7.2.1 7.2.2 7.3 7.4 7.5
FileInUse-Eigenschaft CreateFile-Funktion CloseHandle-Funktion FileInUse-Eigenschaft SHGetFileInfo und SHFILEINFO SHGetFileInfo-Funktion SHFILEINFO-Struktur DisplayName-Eigenschaft TypeName-Eigenschaft IconHandle
8
Registry
8.1 8.1.1 8.1.2 8.2 8.2.1 8.2.2 8.3
Struktur und Datentypen Struktur Datentypen Öffnen und Schließen eines Schlüssels Öffnen eines Schlüssels (RegOpenKeyEx) Schließen eines Schlüssels (RegCloseKey) Werte auslesen
113 113 114 116 116 117 117 118 119 119 119
121 122 122 122 124 125 127 128
Inhalt 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.5 8.5.1 8.5.2 8.6 8.6.1 8.6.2 8.7 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.10 8.11 8.11.1 8.12 8.12.1 8.13 8.13.1 8.13.2 8.13.3 8.13.4 8.13.5 8.13.6 8.13.7 8.13.8
9 RegQueryValueEx Die Methode GetRegistryValue Expandieren eines Strings Werte schreiben RegCreateKeyEx RegSetValueEx Die Methode SetRegistryValue Werte löschen RegDeleteValue Die Methode DeleteRegistryValue Fehlerbehandlung FormatMessage Die Methode GetErrorMessage Eine Settings-Schnittstelle Enumeration von Schlüsseln und Werten RegQueryInfoKey RegEnumKeyEx Die Methode EnumKeys RegEnumValue Die Methode EnumValues Registry-Viewer Marke Eigenbau Und das Ganze in VBA Von .doc zum Word-Dokument Die Methode ExtensionToFileType Von .doc zu ContentType Die Methode ExtensionToContentType Vom Dateinamen zum DefaultIcon Die Konstellationen in der Registry ExtractIcon Die Methode FileToIconHandle Die weitere Verarbeitung des Icon-Handles DrawIcon DestroyIcon Die Prozedur AddFile Grenzen dieser Vorgehensweise
9
Drucker
9.1 9.1.1 9.1.2 9.1.3 9.1.4 9.2 9.2.1 9.2.2 9.2.3 9.2.4
Druckerauswahl EnumPrinters-Funktion PtrToStr-Funktion (lstrcpyA) strlen-Funktion Die Methode EnumeratePrinters Default-Drucker GetDefaultPrinter-Funktion Property Get DefaultPrinter SetDefaultPrinter-Funktion OpenPrinter-Funktion
128 129 131 133 134 136 137 139 139 139 140 141 142 143 146 146 148 149 151 152 154 157 161 161 162 162 164 165 166 167 169 170 170 170 172
173 173 174 177 177 177 180 180 180 181 181
10
Inhalt
9.2.5 9.2.6 9.2.7 9.3 9.3.1 9.3.2 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.6 9.6.1 9.6.2 9.6.3 9.7 9.7.1 9.8 9.8.1 9.8.2 9.8.3
PRINTER_DEFAULTS-Struktur ClosePrinter-Funktion Property Let DefaultPrinter Drucker in MS-Office Die Methode GetPrinterConnection Die Unterschiede innerhalb von MS-Office DEVMODE-Elemente lesen und schreiben DEVMODE-Struktur Von PRINTER_INFO_2 zu DEVMODE Eine Lesemethode für DEVMODE Eine Schreibmethode für DEVMODE DeviceCapabilities DeviceCapabilities-Funktion Die Methode EnumerateBins Klassenschnittstelle für Papierzufuhr PaperBin-Eigenschaft PaperBinCount-Eigenschaft GetPaperBinName-Methode Papierausrichtung Orientation-Eigenschaft Gerätekontext hDC CreateDC-Funktion DeleteDC-Funktion hDC-Eigenschaft
10
Fonts
10.1 10.1.1 10.1.2 10.1.3 10.1.4 10.1.5 10.1.6 10.2 10.2.1 10.2.2 10.2.3 10.2.4 10.3 10.3.1 10.3.2 10.3.3 10.3.4 10.3.5 10.4 10.4.1 10.4.2
Fonts enumerieren EnumFontFamiliesEx-Funktion LOGFONT-Struktur EnumFonts-Prozedur ENUMLOGFONTEX-Struktur EnumFontCallBack-Funktion AddFont-Methode DefaultGUIFontName-Eigenschaft Die Eigenschaftsprozedur GetDeviceFontName-Prozedur GetTextFace-Funktion GetMappedFont-Prozedur DefaultGUIFontSize-Eigenschaft Die Eigenschaftsprozedur GetDeviceFontSize-Prozedur SetMapMode-Funktion GetTextMetrics TEXTMETRIC-Struktur Schriften außerhalb der Horizontalen CreateFontIndirect-Funktion SelectObject-Funktion
182 182 183 184 185 186 186 187 192 194 195 195 195 197 197 198 199 199 199 199 200 200 201 201
203 204 205 206 208 209 210 210 210 211 211 211 212 212 212 213 213 214 214 217 217 218
Inhalt
11
10.4.3 DeleteObject-Funktion 10.4.4 Die WriteVerticalText-Methode 10.5 Zum Schluss
11
Windows
11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.1.5 11.1.6 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.4 11.4.1 11.4.2 11.4.3 11.4.4 11.4.5 11.4.6 11.5 11.5.1 11.5.2 11.5.3 11.5.4
hWnd und hDC GetDesktopWindow GetWindowDC ReleaseDC SystemHWnd-Eigenschaft SystemHDC-Eigenschaft GetDC-Methode Das TOPMOST-Problem SetWindowPos-Funktion Einsatz der Funktion Undo – oder lieber doch nicht SendMessage-Funktion Ereignisroutine für TextBoxes Das Rückgängigmachen Ein RTF-ToolTip-Fenster mit AutoSize ScrollBar-Funktionen SCROLLINFO-Struktur GetScrollInfo-Funktion GetScrollPos- und GetScrollRange-Funktion AutoSize des RTF-Steuerelements Kann man der nPos trauen? SubClassing am Beispiel des RTF-Controls Hook und Unhook Auswertung der VSCROLL-Nachricht Das Scroll-Ereignis Zum Schluss
12
Shell
12.1 12.1.1 12.1.2 12.1.3 12.2 12.2.1 12.2.2 12.2.3 12.2.4 12.2.5 12.3 12.3.1 12.3.2 12.3.3
Programm starten ShellExecute-Funktion FindWindow-Funktion Die Methode LaunchApplication Größe und Position des Fensters bestimmen SetWindowPos-Funktion SendMessage-Funktion RedrawWindow-Funktion RECT-Struktur Die Methode ResizeApplicationWindow Programm beenden PostMessage-Funktion WaitForSingleObject-Funktion Die Methode CloseApplication
218 218 219
221 221 221 221 222 222 222 223 223 223 225 226 227 227 228 229 230 231 232 233 234 235 237 239 241 242 243
245 246 246 249 249 250 251 252 252 253 254 254 255 255 255
12
Inhalt
13
Maus und Tastatur
13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.1.5 13.2
Maus GetSystemMetrics-Funktion Class_Initialize-Routine MouseButtonsSwapped-Eigenschaft GetAsyncKeyState-Funktion MouseLeftButtonPressed-Eigenschaft Tastatur
14
Funktionsreferenz
14.1 14.2 14.3 14.4 14.5 14.6 14.7 14.8 14.9 14.10 14.11 14.12 14.13 14.14 14.15 14.16 14.17 14.18 14.19 14.20 14.21 14.22 14.23 14.24 14.25 14.26 14.27 14.28 14.29 14.30 14.31 14.32 14.33 14.34 14.35
CallWindowProc ChooseColor (APIChooseColor) CloseHandle ClosePrinter CoCreateGUID CoFileTimeNow CopyMemory CreateDC CreateFile CreateFontIndirect DeleteDC DeleteObject DestroyIcon DeviceCapabilities DrawIcon EnumFontFamiliesEx EnumPrinters ExpandEnvironmentStrings ExtractIcon FindWindow FormatMessage GetAsyncKeyState GetComputerName GetDesktopWindow GetDefaultPrinter GetDiskFreeSpace GetDiskFreeSpaceEx GetDriveType GetHostByName GetHostName GetLocaleInfo GetLogicalDrives GetLogicalDriveStrings GetModuleHandle GetOpenFileName (APIGetOpenFileName)
257 257 258 258 258 259 259 260
261 261 262 262 263 263 264 264 265 266 266 267 267 268 268 269 270 271 272 273 273 274 275 275 276 276 277 278 279 280 281 281 283 283 284 284
Inhalt 14.36 14.37 14.38 14.39 14.40 14.41 14.42 14.43 14.44 14.45 14.46 14.47 14.48 14.49 14.50 14.51 14.52 14.53 14.54 14.55 14.56 14.57 14.58 14.59 14.60 14.61 14.62 14.63 14.64 14.65 14.66 14.67 14.68 14.69 14.70 14.71 14.72 14.73 14.74 14.75 14.76 14.77 14.78 14.79 14.80
13 GetPrinter GetProcAddress GetSaveFileName (APIGetSaveFileName) GetScrollBarInfo GetScrollInfo GetScrollPos GetScrollRange GetStockObject GetSystemDefaultLCID GetSystemMetrics GetTextFace GetTextMetrics GetTickCount GetUserDefaultLCID GetUserName GetVersionEx GetWindowDC GetWindowLong lstrcat (GetANSIStringPointer) lstrcpy OpenPrinter PostMessage RedrawWindow RegCloseKey RegCreateKeyEx RegDeleteKey RegDeleteValue RegEnumKeyEx RegEnumValue RegOpenKeyEx RegQueryInfoKey RegQueryValueEx RegSetValueEx ReleaseDC SelectObject SendMessage SetDefaultPrinter SetLocaleInfo SetMapMode SetPrinter SetWindowLong SetWindowPos SHBrowseForFolder SHGetFileInfo SHGetPathFromIDList
285 286 286 287 288 289 289 290 291 291 292 293 293 294 294 294 295 296 296 297 298 299 300 301 301 302 303 304 305 306 306 307 308 309 310 310 312 312 313 315 315 316 317 318 319
14
Inhalt
14.81 14.82 14.83 14.84 14.85 14.86 14.87 14.88
ShellExecute Sleep StringFromGUID2 StrLen WaitForSingleObject WSAGetLastError WSAStartup WSACleanup
15
Konstanten
327
16
Strukturen
343
16.1 16.2 16.3 16.4 16.5 16.6 16.7 16.8 16.9 16.10 16.11 16.12 16.13 16.14 16.15 16.16 16.17 16.18 16.19 16.20 16.21 16.22
ACL BROWSEINFO CHOOSECOLOR DEVMODE ENUMLOGFONTEX FILETIME GUID HOSTENT LOGFONT OPENFILENAME OSVERSIONINFO PRINTER_DEFAULTS PRINTER_INFO_2 PRINTER_INFO_4 PRINTER_INFO_5 RECT SCROLLBARINFO SCROLLINFO SECURITY_DESCRIPTOR SHFILEINFO TEXTMETRIC WSADATA
Index
319 323 323 324 324 325 325 326
343 343 344 344 345 346 346 346 347 347 348 349 349 350 350 351 351 351 352 352 353 354
355
Vorwort Sie kennen das Problem sicherlich. Die Uhr tickt und Sie benötigen eine Funktionalität, die über das hinausgeht, was Visual Basic oder Visual Basic for Applications anzubieten haben. Vor Ihnen liegt alles an Literatur, was über VB und VBA greifbar ist, und nichts passt auf Anhieb. Und die MSDN bietet entweder meterlangen Beispielcode für Visual Basic oder kurze, prägnante Beispiele in C. Es ist zum Aus-der-Haut-Fahren. Diesem Buch liegt ein anderer Ansatz zugrunde. Gleich im ersten Kapitel befinden sich thematisch geordnete Lösungen, die sich alle durch den Aufruf einer Eigenschaft oder Methode einer bestimmten Klasse verwenden lassen. Diese Klassen sind natürlich alle auf der Buch-CD zu finden. Aber noch eine weitere Besonderheit weist dieses Buch auf. Alle Klassen sind autark! Zwar existiert eine Klasse, die Registry-Zugriffe kapselt, doch wenn eine andere Klasse zur Durchführung Ihrer Methoden Informationen aus der Registry benötigt, so ist der dazu erforderliche Code in der Klasse selbst in privaten Methoden enthalten, die nach außen hin somit nicht sichtbar sind. Das bringt zwar bei der Integration aller Klassen in ein Projekt Redundanzen, ermöglicht andererseits aber erst die Absicht dieses Buches, schnelle und einfach zu handhabende Lösungen in autarken Klassen zu präsentieren. Auch enthält keine dieser Klassen irgendeinen Verweis zu Fremdbibliotheken, denn die Funktionalität ist jeweils vollständig über das API gelöst. Mit diesem Konzept machte ich mich im Mai 2000 an die Arbeit. Im September 2000 kam dann die BASTA in München, wo ich im Rahmen eines Vortrags die RegistryKomponente aus Kapitel 8 vorstellte, die gerade fertig geworden war. Und Abends plauschte ich mit einem Vertreter von Microsoft über dies und das und fragte ihn natürlich, wie denn die Zukunft des Win32 API unter der Ägide von .NET aussähe. »Well, das Win32 API wird nicht mehr unterstützt. Die Funktionalität ist in der neuen Common Language Runtime gekapselt. Alles neu geschrieben. Großartige Arbeit, das.« Den Rest des Abends über weinte ich leise ins Bier. Und dabei hätte es so ein gutes Buch werden können.
16
Vorwort
Auf der Rückreise von München beschloss ich, die Flinte nicht ins Korn zu werfen, noch nicht. »Was ist das überhaupt, das Win32 API? Nun, das ist eine Reihe von Bibliotheken, die Funktionen exportieren. Und das neue Windows 2000? Da sind sie doch noch alle drin: Kernel32, Winspool und wie sie alle heißen. Wie soll das nun funktionieren?« Ich hatte wieder Hoffnung. Am nächsten Tag rief ich Dietmar Hasse an, den Reich-Ranicky der Visual-Basic-Literatur, schilderte ihm mein Dilemma und verdeutlichte auch, dass ich das Buchprojekt von seiner Aussage abhängig machen würde. Und was riet er mir? »Zu Ende schreiben, auf jeden Fall zu Ende schreiben.« So wuchs das Buch allmählich und nahm Gestalt an, ein Kapitel nach dem anderen wurde fertig. Und Ende Februar kam dann die ViBAT 2001 in Frankfurt, wo ich unter anderem über Win32 API-Datentypen und ihre Behandlung in VB(A) referierte. Auch hier war Microsoft vertreten, auch hier direkt vom Olymp. Im Rahmen eines Plauschs (Sie wissen schon: über dies und das) kam dann auch das Gespräch auf die Lebenserwartung des Win32 API. »Well, Ende 2004 wird ein Betriebssystem erscheinen, das völlig neu geschrieben sein wird. Und dort werden keine Win32-Bibliotheken mehr enthalten sein.« Und dann erzählte ich von einem meiner Kunden, der rund zweitausend PCs unter Windows NT betreibt. Dieses Jahr wird unter anderem die Einführung eines neuen Zeiterfassungssystems abgeschlossen, das schon die eine oder andere Anleihe bei den API-Bibliotheken macht. »Sollen die am Jahresende 2004 sechs Wochen lang den Betrieb schließen«, fragte ich dann provokativ, »um das neue Windows zu installieren und alle die angepassten oder neu geschriebenen Anwendungen zu integrieren?« Und ich bot ihm eine Wette an: »Wenn wir bei diesem neuen Windows 2004 den Teppich mal anheben und drunter schauen, dann werden wir eine Kernel32.dll dort finden und eine Winspool.drv wird auch nicht weit sein. Möglicherweise ist kein funktionaler Code mehr darin enthalten, aber sie werden schnittstellenkompatibel sein und möglicherweise die Funktionsaufrufe nur an die Common Language Runtime weiterleiten.« Er nahm die Wette nicht an. Wir plauderten noch über das und dies, ich verließ die Arena aufrecht und weinte diesmal nicht ins Bier. Es kommt offensichtlich nicht nur darauf an, welche Fragen man stellt, sondern auch, wann man sie stellt. Und das Frühjahr scheint hierfür besser geeignet zu sein als der Herbst. Was heißt das nun für uns hinsichtlich des Win32 API? Dort, wo nötig, werden wir API-Aufrufe verwenden, und zwar bis zur letzten Minute. Und diese letzte Minute wird 2004 beginnen und ein, zwei Jährchen dauern. Und ich könnte mir gut vorstellen, im Jahr 2005 alte Quellen noch durch den VB-6-Compiler zu schicken, weil die Applikation eben noch läuft. Unter NT natürlich, 4 oder 5.
Vorwort
17
Und da diese API-Aufrufe schön sauber in Komponenten gekapselt sind, werden diese Komponenten irgendwann gegen andere Komponenten ausgetauscht, die statt API auf die Common Language Runtime zugreifen. Also komponentenorientiert, schlicht und ergreifend komponentenorientiert. Im Lektorat saß Christina Gibbs und fragte regelmäßig und bestimmt, ob denn schon erkennbar sei, ob das Buch irgendwann fertig werde. Zusammen mit Simone Burst zeichnet sie auch bei diesem Werk darüber hinaus verantwortlich für die Kompatibilität zur deutschen Sprache. Herzlichen Dank an beide. Die hausinterne Vor- und Nachbearbeitung lag auch diesmal wieder in den bewährten Händen von Carmen Zenner. Und unser Azubi Gert Waidner testete alle Komponenten noch einmal unter Windows 95. Auch euch beiden vielen Dank. Und meine Kunden brachten auch Verständnis dafür mit, dass mich der Buchtermin arg zwickte – sie kannten das immerhin schon vom letzten Jahr. Aber größten Dank bin ich meiner Gattin Edith schuldig, die mich abends so zu Hause empfing, als käme ich von der Arbeit. Dabei hatte ich mich den ganzen Tag mal wieder nur mit dem Buch beschäftigt ;-))
1 Referenz Dieses Kapitel stellt den Leitfaden zur Verwendung der in den weiteren Kapiteln besprochenen Komponenten dar. Hierbei werden alle Eigenschaften und Methoden einer jeweiligen Klasse vorgestellt, erklärt, was sie tun, und anhand einfacher Beispiele aufgezeigt, wie sie verwendet werden können. Es handelt sich wohlgemerkt hierbei nicht um eine lyrische Dichtung rund um das Win32-API. Die dargestellten Komponenten sind vielmehr das Ergebnis professioneller Beschäftigung mit dem Gegenstand der Komponenten, nämlich den Ländereinstellungen, der Registry oder den Laufwerken. Der Komponentengedanke wiederum ist einer der elementaren Ansätze der Softwareentwicklung. Was liegt also näher, als die entsprechende in Klassen als Methoden oder Eigenschaften zu kapseln? Nun, höchstens noch, alle vorgestellten Komponenten in einer VB-DLL zu integrieren. Aber dieser Schritt bleibt Ihnen vorbehalten. Und außerdem können so auch VBA-Entwickler von den Klassen profitieren, denn in VBA sollte man ohne Not keine DLLs verwenden, die möglicherweise auf den Zielrechnern installiert werden müssen.
1.1 Funktionen übergeordneten Charakters Funktionen rund um das Win32-API gehören in Klassen und haben in Client-Modulen nichts verloren. Diese Regel halte ich im Hinblick auf ein strukturiertes Softwaredesign für überaus wichtig. Aber dennoch gibt es Ausnahmen, und die eine oder andere Funktion wird unter Umständen sogar mehrfach mitten im Code eingesetzt. Und diese wenigen Fälle werden nun vorab vorgestellt. Es ist schon fast selbstverständlich, dass es sich hierbei nur um einfache Funktionen handelt, die als Einzeiler abgearbeitet werden.
1.1.1
Sleep well
Gelegentlich benötigen Programme eine Pause im Ablauf, und sei es nur, um eine Splash-Form anzuzeigen. Verpönt sind natürlich Techniken wie For i = 1 To 10.000.000
20
1
Referenz
oder Ähnliches, denn allein schon die unterschiedliche Rechnerhardware lässt diese Vorgehensweise kaum als geeignet erscheinen. Aber Windows stellt in Form der Funktion Sleep ein geeignetes Mittel zur Verfügung, eine in Millisekunden definierbare Unterbrechung anzufordern. Neben der Determinierbarkeit der Unterbrechung hat sie den Vorteil, dass sie von Windows dazu genutzt werden kann, in der Zwischenzeit andere Applikationen zu bedienen. Public Declare Sub Sleep Lib "kernel32" (ByVal lngMilliseconds As Long)
Sleep ist also eine der seltenen Funktionen ohne Rückgabe. Im folgenden Beispiel erscheint die Splash-Form frmSplash für die Dauer von 3 Sekunden, bevor die frmMain aufgerufen wird: frmSplash.Show vbModeless frmSplash.Refresh Sleep 3000 Unload frmSplash frmMain.Show vbModeless
1.1.2
Wenn Sekunden zu ungenau sind
Die Funktion GetTickCount gibt die Zeit in Millisekunden zurück, die seit dem Systemstart vergangen ist. Sie kann dazu verwendet werden, die Dauer irgendwelcher Operationen zu bestimmen, bei denen die Sekundenauflösung der Time-Funktion (oder Now) zu ungenau ist. Public Declare Function GetTickCount Lib "kernel32" () As Long
Hier sehen Sie ein kleines Beispiel für den Einsatz dieser Funktion: Dim lngStart As Long lngStart = GetTickCount ... 'zu messende Operationen ... MsgBox GetTickCount – lngStart & " Millisekunden"
1.1.3
Statt Refresh oder Repaint
Ein Refresh auf einer Form führt zu einem recht umfangreichen Nachrichtenverkehr zwischen Windows und der betreffenden WindowProc der Fensterklasse. Da werden mitunter Berechnungen von Regions durchgeführt, obwohl dazu überhaupt keine Notwendigkeit besteht. Wir wollen nur, dass die Caption eines Labels aktualisiert wird oder eine ProgressBar sich neu zeichnet. Zu diesem Zweck existiert die Funktion SendMessage, mit der gezielt eine Nachricht an ein Fenster gesendet werden kann.
Allgemeine Hinweise zu den Komponenten
21
Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Nun benötigen wir noch eine Konstante, die dem Fenster verdeutlicht, was wir von ihm wollen: Public Const WM_PAINT = &HF
Jetzt können Fenster gezielt aufgefordert werden, sich zu aktualisieren. Im folgenden Beispiel werden die Prognoseumsätze einer Reihe von Märkten durchgerechnet. Die Nummer des jeweiligen Marktes wird in einem Label angezeigt: For iStore = 0 To UBound(lngStores) lblStatus.Caption = "Der Prognoseumsatz für Markt " & _ lngStores(iStore) & " wird ermittelt." SendMessage Me.hwnd, WM_PAINT, 0&, 0& ... eigentliche Berechnung Next
Im nächsten Beispiel sehen Sie eine Prozedur, mit der eine ProgressBar inkrementiert wird: Private Sub ProgBar_Increment() ProgBar.Value = CLng(ProgBar.Value + 1) SendMessage Me.hwnd, WM_PAINT, 0&, 0& End Sub
Vielleicht wundern Sie sich ein wenig über den Ausdruck CLng(ProgBar.Value + 1), aber die Value-Eigenschaft ist ein Single, und dieser neigt aufgrund der BCD-Konvertierung gelegentlich dazu, statt einer 9 eine 9,000000002 anzunehmen. Steht der Max nun auf 10, und Sie addieren eine 1 zu diesem 9,000000002, so erhalten Sie gnadenlos einen Laufzeitfehler.
1.2 Allgemeine Hinweise zu den Komponenten Da alle Komponenten dieses Buches in Form von Klassen vorliegen, haben Sie generell die Möglichkeit der Deklaration ohne und mit dem Schlüsselwort New. Die Referenzierung ohne New könnte so aussehen: Public SysNLS As clsSysNLS
Danach muss die Instanz noch manuell mit Leben gefüllt werden: Set SysNLS = New clsSysNLS
Dieser zweite Schritt kann natürlich entfallen, sofern die Deklaration As New erfolgte: Public SysNLS As New clsSysNLS
22
1
Referenz
Sind Initialisierungen in den Komponenten erforderlich, so werden diese ausdrücklich nicht im Class_Initialize-Ereignis angestoßen, da Fehlernummer und -text im Fehlerfalle von COM unkenntlich gemacht werden. Es existiert aber in diesem Fall eine separate Initialize-Methode, die im Idealfall während der Programminitialisierung gestartet wird. Da die Komponente jedoch weiß, ob sie initialisiert wurde, prüft sie dies beim Aufruf einer jeden Eigenschaft oder Methode und startet gegebenenfalls die Initialisierung. Durch diesen im Übrigen völlig überflüssigen Abschnitt 1.2 ist es gelungen, die Nummerierung der weiteren Abschnitte dieses Kapitels mit den folgenden Kapiteln in Beziehung zu setzen: Das Know-how zu Abschnitt 1.x steht in Kapitel x. Genial, nicht wahr?
1.3 Ländereinstellungen clsSysNLS Diese Komponente gewährt lesenden Zugriff auf die Ländereinstellungen. Details können Sie in Kapitel 3 nachlesen. Für die Rückgabe der korrekten Währungssymbole ab 2002 muss möglicherweise noch ein Patch installiert werden.
CountryCode-Eigenschaft Rückgabe des Ländercodes, z.B. 049 für Deutschland. MsgBox SysNLS.CountryCode
CountryEnglish-Eigenschaft Rückgabe des Landesnamens in englischer Sprache, z.B. Germany für Deutschland. MsgBox SysNLS.CountryEnglish
CountryLocal-Eigenschaft Rückgabe des lokalen Landesnamens, z.B. Deutschland. MsgBox SysNLS.CountryLocal
CurrencySymbolInternational-Eigenschaft Rückgabe des internationalen Währungssymbols nach ISO 4217, z.B. DEM für Deutschland bis Ende 2001, danach EUR. MsgBox SysNLS.CurrencySymbolInternational
Ländereinstellungen clsSysNLS
23
CurrencySymbolLocal-Eigenschaft Rückgabe des lokalen Währungssymbols, z.B. DM für Deutschland bis Ende 2001, danach _. MsgBox SysNLS.CurrencySymbolLocal
DecimalSeparator-Eigenschaft Rückgabe des Dezimaltrennzeichens, im deutschsprachigen Raum in der Regel ein Komma. MsgBox SysNLS.DecimalSeparator
LanguageEnglish-Eigenschaft Rückgabe des englischen Sprachnamens, also German für Deutschland. MsgBox SysNLS.LanguageEnglish
LanguageNative-Eigenschaft Rückgabe des lokalen Sprachnamens, also Deutsch für Deutschland. MsgBox SysNLS.LanguageNative
ListDelimiter-Eigenschaft Rückgabe des Listentrennzeichens, im deutschsprachigen Raum in der Regel ein Semikolon. MsgBox SysNLS.ListDelimiter
MonthName-Eigenschaft Rückgabe des lokalen Namens des i-ten Monats, z.B. Januar für den 1. Monat. MsgBox SysNLS.MonthName(iMonth)
MonthNameArray-Eigenschaft Rückgabe eines String-Arrays mit dem lokalen Monatsnamen, also Januar bis Dezember für Deutschland. Dim strMonths() As String strMonths = SysNLS.MonthNameArray
24
1
Referenz
Das Füllen des Arrays strMonths ist hierdurch etwas einfacher als durch die Verwendung der Format-Funktion wie im folgenden Beispiel: Dim strMonths(12) As String Dim iMonth As Long For iMonth = 1 To 12 strMonths(iMonth) = Format(DateSerial(2000, iMonth, 1), "MMMM") Next
1.4 Dialoge Die hier vorgestellten Methoden sind in der Klasse clsSysDialogs enthalten. Das Know-how können Sie in Kapitel 4 nachlesen. Alle Dialogmethoden verfügen über das optionale Argument hWndOwner, welches den Handle des aufrufenden Fensters darstellt. Wenn Sie einen gültigen Handle übergeben, so erscheint der Dialog nicht in der Taskleiste. Fehlt das Argument oder ist es Null, so taucht der Dialog im Register Anwendungen des Task Managers auf und ist auch in der Taskleiste sichtbar. In Visual Basic können Sie die hWnd-Eigenschaft der Form als Handle übergeben. In VBA steht diese Eigenschaft nicht direkt zur Verfügung, kann aber über die Methode hWnd der clsSysWin ermittelt werden.
1.4.1
Datei Öffnen- und Schließen-Dialog
Abbildung 1.1 zeigt den Dialog, den Sie mit der hier vorgestellten Methode hervorrufen können.
Abbildung 1.1: Datei-Öffnen-Dialog
Dialoge
25
Die beiden Methoden GetOpenFileName und GetSaveFileName haben die folgende Signatur: Argumente
Inhalt
InitialPath
Startverzeichnis, z.B. »G:\Controlling«
FileName
Defaultname der Datei
FileTypes
Dateitypen, semikolon-getrennt, z.B. »xml«, »doc;txt« oder »Import*.csv«
Title
optional, Dialogtitel, z.B. »Dateiauswahl«
hWndOwner
optional, Handle des aufrufenden Dialogs
Tabelle 1.1: Methodensignatur der GetOpenFileName und GetSaveFileName
Die Rückgabe enthält den vollständigen Dateinamen der ausgewählten Datei oder »«, wenn der Dialog vom Anwender abgebrochen wurde. Ein Beispiel: strFile = cSysDLG.GetOpenFileName("E:\Test\xml", "Default.xml", _ "xml;xsl", "XML-Import", Me.hWnd) If strFile <> "" Then ' End If
Der folgende Aufruf erzeugt den in Abbildung 1.2 zu sehenden Dialog: strFile = cSysDLG.GetSaveFileName("E:\Test\xml", "Default.xml", _ "xml", "XML-Import", Me.hWnd) ...
Abbildung 1.2: Datei-Speichern-Dialog
26
1
Referenz
Besonderheiten Im Unterschied zum CommonDialog-Steuerelement ist die Übergabe des Filters stark vereinfacht worden. Suchen Sie nur einen Dateityp, so übergeben Sie die entsprechende Dateierweiterung. Möchten Sie Dateien mehrerer Typen zur Auswahl anbieten, so geben Sie alle Dateierweiterungen jeweils mit Semikolon getrennt an, z.B. »xml;xsl«. Die jeweiligen Namen der Dateitypen werden aus der Registry ermittelt. Ist eine Dateierweiterung in der Registry unbekannt, so wird »Unbekannter Dateityp« im Dialog eingetragen.
1.4.2
Farbdialog
Der in Abbildung 1.3 gezeigte Farbdialog kann mit dieser Methode hervorgerufen und ausgewertet werden.
Abbildung 1.3: Farbdialog
Die Komponenten verfügen über ein Array mit 16 benutzerdefinierten Farben, das per Eigenschaft verändert werden kann: Dim i As Long For i = 0 To 15 cSysDLG.CustomizedColor(i + 1) = RGB(16 * i, 16 * i, 16 * i) Next
In dieser For-Next-Schleife werden die 16 Grautöne erzeugt, die in Abbildung 1.3 zu sehen sind. In folgender Zeile erhält lbl5 die 5. benutzerdefinierte Farbe als Hintergrundfarbe: lbl5.BackColor = cSysDLG.CustomizedColor(5)
Dialoge
27
Tabelle 1.2 zeigt die Signatur des Methodenaufrufs: Argumente
Inhalt
InitColor
optional, Initialisierungsfarbe oder -1, wenn keine Initialisierungsfarbe angezeigt werden soll
hWndOwner
optional, Handle des aufrufenden Dialogs
Tabelle 1.2: Methodensignatur der GetColor
Wird der Dialog abgebrochen, so gibt die Methode -1 zurück; andernfalls die ausgewählte Farbe. Der Dialogaufruf könnte so erfolgen: Dim lngColor As Long lngColor = cSysDLG.GetColor(lblColor.BackColor, Me.hWnd) If lngReturn > -1 Then lblColor.BackColor = lngColor End If
1.4.3
Verzeichnis-Dialog
Abbildung 1.4 zeigt den Verzeichnisdialog aus der Shell, um den es sich hier dreht.
Abbildung 1.4: Verzeichnis-Dialog
28
1
Referenz
Die entsprechende Methode heißt GetPath und hat folgende Signatur: Argumente
Inhalt
hWndOwner
optional, Handle des aufrufenden Dialogs
Tabelle 1.3: Methodensignatur der GetPath
Die Rückgabe enthält den Verzeichnisnamen oder »«, wenn der Dialog vom Anwender abgebrochen wurde. Ein Beispiel: strPath = cSysDialogs.GetPath(Me.Hwnd) If strPath <> "" Then ' End If
1.5 Systeminformationen Die Klasse clsSysInfo hält einige interessante Informationen für Sie bereit. Dazu gehören die Betriebssystemversion, Anmeldename und Computername.
1.5.1
Anmeldenamen
Verfügt Ihre Anwendung über ein Login, so macht es Sinn, den Anmeldenamen von Windows in diesen Dialog bereits einzutragen: Private Sub Form_Load() ... txtName.Text = cSysInfo.LoginName End Sub
1.5.2
NetBIOS- und DNS-Name
Führt Ihre Anwendung Buch über verschiedene Aktionen, so ist es möglicherweise nicht nur wichtig, wer etwas gemacht hat, sondern auch von welchem Rechner aus eine Operation durchgeführt wurde. Hierzu stehen gleich zwei Eigenschaften zur Verfügung: cLogBook.Write cSysInfo.NetBIOSName, ...
oder cLogBook.Write cSysInfo.DNSName, ...
In der Regel sind die beiden Werte gleich.
Systeminformationen
1.5.3
29
Betriebssystem
Spätestens bei der Verwendung einiger Funktionen aus dem Win32-API ist es gut zu wissen, welches Betriebssystem auf dem aktuellen Rechner installiert ist. In der folgenden Tabelle sind die einzelnen Betriebssysteme und die Eigenschaften der clsSysInfoKlasse gegenübergestellt: Win95
Win98
WinNT
NTVersion
Windows 95
True
False
False
n/a
Windows 98
False
True
False
n/a
Windows NT 4
False
False
True
4
Windows 2000
False
False
True
5
Tabelle 1.4: Betriebssystemversionen und SysInfo-Eigenschaften
Bemerkenswert in dieser Tabelle ist lediglich, dass Windows 2000 sich selbst als NT 5 zu erkennen gibt. Im folgenden Beispiel werden die 4 Betriebssysteme auseinander gehalten: If cSysInfo.WinNT Then Select Case cSysInfo.NTVersion Case 4 'Win NT 4 Case 5 'Win 2000 End Select Else If cSysInfo.Win95 Then 'Win 95 ElseIf cSysInfo.Win98 Then 'Win 98 End If End If
Darüber hinaus steht die Eigenschaft ServicePack zur Verfügung, die gerade unter NT sinnvoll ist: If cSysInfo.WinNT Then Select Case cSysInfo.NTVersion Case 4 If cSysInfo.ServicePack < 6 Then '...
30
1
1.5.4
Referenz
IP-Adresse
Die Eigenschaft IPAddress gibt die IP-Adresse des aktuellen Rechners in der Form »200.100.80.11« zurück: strIPAddress = cSysInfo.IPAddress
Wenn Sie über einen DNS-Namen eines derzeit im Netz aktiven Rechners verfügen, so können Sie die IP-Adresse dieses Rechners über die Methode HostByName ermitteln lassen: strIPAddress = cSysInfo.HostByName(strDNSName)
Der übergebene Name muss zu einem gültigen Rechner gehören und dieser im Netz angemeldet sein.
1.5.5
GUIDs
Hinter GUIDs verbergen sich weltweit eindeutige Bezeichner, die von Windows beispielsweise zur Identifizierung von Komponenten, also Bibliotheken und Klassen, verwendet werden. Die GUID {00000205-0000-0010-8000-00AA006D2EA4} beispielsweise gehört zur ADO 2.5. Wenn Sie eindeutige Kennzeichen benötigen, um Objekte in Collections zu identifizieren, so können Sie solche GUIDs erzeugen lassen: obj.ID = cSysInfo.GetNewGUID
Die Rückgabe enthält keine geschweiften Klammern, aber die vier Bindestriche, also z.B.: DB64D08D-F90A-11D4-AA87-00609728D886 Im folgenden Beispiel wird die GetNewGUID-Methode verwendet, um eine Boundary in einem Multipart MIME-Header zu erzeugen: Dim strGUID As String strGUID = cSysInfo.GetNewGUID strGUID = "0__=" & Replace(strGUID, "-", "X")
Das Ergebnis unter Verwendung der obigen GUID könnte so aussehen: 0__= DB64D08DXF90AX11D4XAA87X00609728D886.
Laufwerke
31
1.6 Laufwerke Die Komponente clsSysDrives versorgt uns mit Informationen rund um Laufwerke. Für viele Funktionen ist ein Index erforderlich, der zwischen 1 und DrivesCount liegt. Die Reihe der Laufwerke ist lückenlos, das heißt, fehlt zum Beispiel Laufwerk B:\, so trägt A:\ den Index 1 und C:\ den Index 2. Der Vorteil dieser lückenlosen Indexreihe liegt darin, dass eine Schleife folgenden Typs aufgebaut werden kann: For iDrive = 1 to cSysDrives.DrivesCount ... = cSysDrives.xyz(iDrive) Next
Generell kann die zugrunde liegende API-Funktion folgende Dateitypen unterscheiden: Konstante
Wert TypeName
Laufwerksart
DRIVE_REMOVABLE
2
FloppyDrive
Wechseldatenträger (Diskette, Zip, Wechselplatte)
DRIVE_FIXED
3
LocalDrive
Fest installierte lokale Laufwerke (Festplatten)
DRIVE_REMOTE
4
NetDrive
Netzlaufwerke
DRIVE_CDROM
5
CDDrive
CD-Laufwerke
DRIVE_RAMDISK
6
LocalDrive
RAM-Disks
Tabelle 1.5: Laufwerkstypen
Die Komponente verfügt neben den allgemeinen und für alle Laufwerkstypen geltenden Eigenschaften lediglich über gesonderte Eigenschaften für CD-Laufwerke. Sollten Sie sich auch gezielt für einen anderen Typ interessieren, so wird Ihnen Kapitel 6 alles an die Hand geben, um neue Eigenschaften für andere Typen zu schreiben.
1.6.1
DrivesCount-Eigenschaft
DrivesCount gibt die Anzahl der verfügbaren Laufwerke zurück. Hierin sind alle die enthalten, die der Windows-Explorer auch anbietet – egal, ob sie bereit sind oder nicht: nDrives = cSysDrives.DrivesCount
1.6.2
DriveName-Eigenschaft
DriveName benötigt einen Index und ermittelt damit den Namen des betreffenden Laufwerks. Der folgende Beispielcode gibt den Namen des Laufwerks mit dem größten Index zurück, bei mir beispielsweise »Z:\«: strLastDrive = cSysDrives.DriveName(cSysDrives.DrivesCount)
Der BackSlash gehört übrigens zum Namen.
32
1
1.6.3
Referenz
DriveIndex-Eigenschaft
Da der Regelzugang zu den Laufwerksdaten über einen numerischen Index erfolgt, vermag diese Eigenschaft den Index eines übergebenen Laufwerksnamens zurückzugeben. Die nachfolgende Beispielzeile ermittelt den verfügbaren Speicherplatz auf Laufwerk E: ... = cSysDrives.DriveFreeSpace(cSysDrives.DriveIndex("E:\"))
1.6.4
DriveReady-Eigenschaft
DriveReady gibt Auskunft über die Bereitschaft eines Laufwerks. Im Beispiel wird geprüft, ob das Diskettenlaufwerk bereit ist: If Not cSysDrives.DriveReady(1) Then
1.6.5
DriveType-Eigenschaft
Die DriveType-Eigenschaft gibt die dem Laufwerkstyp zugeordnete Nummer zurück. Die Nummern können Sie Tabelle 1.5 entnehmen. Das folgende Beispiel prüft, ob das Laufwerk ein Wechseldatenträger ist: If Not cSysDrives.DriveType(1) = DRIVE_REMOVABLE Then
1.6.6
DriveTypeName-Eigenschaft
Den einzelnen Laufwerkstypen wurden noch Klartextnamen zugeordnet, die auch in Tabelle 1.5 zu sehen sind. Diese Namen können Sie natürlich ändern. Die Beispielzeile gibt den Klartextnamen des ersten Laufwerks zurück: strDriveType = cSysDrives.DriveTypeName(1)
1.6.7
DriveFreeSpace-Eigenschaft
Diese Eigenschaft gibt die freie Laufwerkskapazität des angegebenen Laufwerks an. Der Datentyp ist ein Decimal: varCapacity = cSysDrives.DriveFreeSpace(4)
1.6.8
DriveTotalSpace-Eigenschaft
Diese Eigenschaft gibt die gesamte Laufwerkskapazität des angegebenen Laufwerks an. Der Datentyp ist auch ein Decimal: varCapacity = cSysDrives.DriveTotalSpace(4)
Dateisystem
1.6.9
33
CDDrivesCount-Eigenschaft
CDDrivesCount gibt die Anzahl der verfügbaren CD-Laufwerke zurück. Im folgenden Beispiel wird sichergestellt, dass mindestens 1 CD-Laufwerk vorhanden ist: If cSysDrives.CDDrivesCount > 0 Then
1.6.10 CDDriveName-Eigenschaft Mit einem Index zwischen 1 und CDDrivesCount kann der Name des gewünschten CDLaufwerks ermittelt werden. Im Beispielcode wird der Name des ersten CD-Laufwerks erfragt: strCDDrive = cSysDrives.CDDriveName(1)
1.7 Dateisystem Da Visual Basic fast alles zu bieten hat, was im Umgang mit Dateien erforderlich ist, fällt clsSysFiles mit drei Eigenschaften recht klein aus.
1.7.1
FileInUse-Eigenschaft
Diese Eigenschaft prüft, ob die übergebene Datei bereits geöffnet ist. Das folgende Beispiel ist ein Auszug aus einer Routine, die einen Export zum Abschluss bringt. Bei AskUser und InfoMessage handelt es sich um zwei Hilfsroutinen, deren Bedeutung aus den Namen hervorgeht. Wenn die Datei bereits existiert, wird geprüft, ob sie gerade verwendet wird. Wenn ja, wird der Anwender aufgefordert die Datei zu schließen, wird sie hingegen nicht verwendet, wird die Datei gelöscht und neu durch die Excel-Komponente cExcel erzeugt. If LenB(Dir(strFileName)) > 0 Then strMessage = "Die Datei " & strFileName & " existiert bereits... " If AskUser(strMessage) = vbYes Then If cSysFiles.FileInUse(strFileName) Then InfoMessage "Die Datei " & strFileName & _ " ist bereits geöffnet.", "Bitte schließen ..." Else Kill strFileName cExcel.SaveFile strFileName End If End If Else cExcel.SaveFile strFileName End If
34
1
1.7.2
Referenz
DisplayName-Eigenschaft
Aus dem Dateinamen mit Laufwerk und Pfad »D:\Projekte\.....\Test.xls« extrahiert diese Eigenschaft den reinen Dateinamen »Test.xls«. Damit ließe sich das vorstehende Beispiel noch ein wenig optimieren, indem die MessageBox nur den Anzeigenamen darstellt: If LenB(Dir(strFile)) > 0 Then strMessage = "Die Datei " & cSysFiles.DisplayName(strFileName) & _ " existiert bereits... " If AskUser(strMessage) = vbYes Then
Mittels der InStrRev-Funktion oder InStr in einer Schleife kann man den Anzeigenamen auch ohne API-Unterstützung extrahieren.
1.7.3
TypeName-Eigenschaft
Diese Eigenschaft gibt den einer Datei zugeordneten Typnamen zurück. Aus »...\Test.xls« wird »Microsoft Excel-Arbeitsmappe«. Ein zusammenhängendes Beispiel ist unter 1.7.4 zu sehen.
1.7.4
IconHandle-Eigenschaft
IconHandle gibt ein Handle auf das Icon zurück, welches der übergebenen Datei zugeordnet ist. Die Prozedur im folgenden Beispiel ist einem SMTP Mail Client entnommen, den ich schreiben musste, als ich in einem Projekt mit Lotus Notes 4.6 und dessen absoluter Kooperationsunfähigkeit zu tun hatte. In Abbildung 1.5 ist dieser Client zu sehen. Am unteren Rand ist ein ListView für die Anhänge integriert, der von der folgenden Methode jeweils mit einer Datei gefüllt wird. Der DisplayName wird zur Text-Eigenschaft des ListItems. Das IconHandle wird über ein Picture-Steuerelement in eine ImageList fabriziert, von wo aus dieses ListImage dem ListItem zugeordnet wird: Public Dim Dim Dim Set
Sub AddNewAttachment(ByVal strFile As String) actListItem As MSComctlLib.ListItem actListImage As ListImage hIcon As Long 'Handle des der Datei zugeordneten Icons actListItem = lvwAttachment.ListItems.Add(, , _ cSysFiles.DisplayName(strFile), "Attachment") actListItem.Tag = strFile Set picAttachment.Picture = LoadPicture("") picAttachment.AutoRedraw = True 'sicherheitshalber hIcon = cSysFiles.IconHandle(strFile) If hIcon <> 0 Then DrawIcon picAttachment.hdc, 0, 0, hIcon
Dateisystem
35
DestroyIcon hIcon picAttachment.Refresh Set actListImage = imlAttachment.ListImages.Add(, "X" & _ imlAttachment.ListImages.Count, _ picAttachment.Image) actListItem.Icon = actListImage.Key End If End Sub
Abbildung 1.5: Dateien mit Icons in ListView
Die DrawIcon-Funktion ermöglicht, ein Icon in einem Objekt mit einem Device Context zu erzeugen. Das wiederum kann ein Picture-Steuerelement sein. Die folgende Deklaration muss hierzu verfügbar sein: Declare Function DrawIcon Lib "user32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByVal hIcon As Long) As Long
Wenn Sie ein IconHandle aus der Komponente auslesen, so müssen Sie dieses Handle explizit wieder löschen. Dies geschieht über die Funktion DestroyIcon, die folgendermaßen deklariert wird: Declare Function DestroyIcon Lib "user32" (ByVal hIcon As Long) As Long
36
1
Referenz
1.8 Registry Die Registry-Komponente wartet mit einem elementaren Methodensatz auf, mit dem Registry-Schlüssel und Registry-Werte bearbeitet werden können: Erzeugen, Enumerieren, Auslesen, Schreiben und Löschen. Eigentlich war’s das schon. In Kapitel 8 selbst finden Sie noch eine Reihe von Anwendungsfällen, doch diese greifen alle auf die Basismethoden der Komponente zurück. In 1.8.7 werden noch abschließend ein paar Eigenschaften und Methoden beleuchtet, die geeignet sind, die Funktionen GetSetting, SaveSetting, und DeleteSetting zu ersetzen und hierbei ein wenig aufzubohren. Diese Settings-Schnittstelle setzt natürlich letztlich auch auf den Basismethoden auf. Beginnen wir also mit diesen Basismethoden.
1.8.1
HasKey-Methode
Die HasKey-Methode prüft, ob der übergebene Schlüssel oder Wert existiert: Argumente
Inhalt
lngHKEY
HKEY-Enumeration mit einem der folgenden Werte: enumHKEY_CLASSES_ROOT
HKEY_CLASSES_ROOT
enumHKEY_CURRENT_USER
HKEY_CURRENT_USER
enumHKEY_LOCAL_MACHINE
HKEY_LOCAL_MACHINE
enumHKEY_USERS
HKEY_USERS
enumHKEY_CURRENT_CONFIG
HKEY_CURRENT_CONFIG
enumHKEY_DYN_DATA
HKEY_DYN_DATA
strKey
Name des Schlüssels
strValue
optional, Name des Wertes
Tabelle 1.6: Methodensignatur der HasKey-Methode
Fehlt strValue, so wird geprüft, ob strKey vorhanden ist. Wird strKey hingegen übergeben, so untersucht die Methode, ob der Wert strValue existiert. Existiert strKey nicht, so wird in jedem Falle ein False zurückgegeben. Ähnlich dem Dateisystem sind die einzelnen Schlüssel einer Kette durch »\« voneinander getrennt. Der klassische Fall liegt bei der spät gebundenen Automatisierungskomponente: Const strClassName As String = "Excel.Application" If Not cSysReg.HasKey(enumHKEY_CLASSES_ROOT, strClassName) Then
Registry
37
In diesem Beispiel wird geprüft, ob der Klassenname von Excel vorhanden ist. Kommt hierbei ein False heraus, ist Excel nicht installiert und wir brauchen erst gar nicht damit zu beginnen, eine Excel-Instanz zu erhaschen.
1.8.2
GetRegistryValue-Methode
Mit dieser Funktion kann der Wert eines Schlüssels zurückgegeben werden. Argumente
Inhalt
lngHKEY
Enumeration der HKEY-Konstanten
strKey
Name des Schlüssels
strValue
Name des Wertes
Tabelle 1.7: Methodensignatur der GetRegistryValue-Methode
Der einzige Unterschied zur HasKey-Methode ist, dass strValue zwingend übergeben werden muss. Die Rückgabe ist ein Long beim Datentyp REG_DWORD und ein String in allen anderen Fällen. Sind hierbei Schlüssel oder Wert nicht vorhanden, so ist die Rückgabe Empty. Im folgenden Beispiel wird das Importverzeichnis des Programmes »ProgName« ermittelt. strImportPath = cSysReg.GetRegistryValue(enumHKEY_CURRENT_USER, _ "Software\Kunde\ProgName\Settings", _ "ImportPath")
1.8.3
SetRegistryValue-Methode
Die SetRegistry-Methode dient dazu, einen Wert zu schreiben. Gegenüber der GetRegistryValue-Methode, die sich auf die Namen von Schlüssel und Wert beschränken, benötigen wir hier natürlich den Wert selbst. Da SetRegistry allerdings auch alle erforderlichen Schlüssel und den Wert anlegt für den Fall, dass etwas fehlen sollte, wird auch der Typ des anzulegenden Wertes benötigt. Argumente
Inhalt
lngHKEY
Enumeration der HKEY-Konstanten
strKey
Name des Schlüssels
strValueName
Name des Wertes
lngType
Datentyp-Enumeration mit einem der folgenden Werte: enumREG_SZ/String-Datentyp
Tabelle 1.8: Methodensignatur der GetRegistryValue-Methode
38
1
Argumente
varValue
Referenz
Inhalt enumREG_EXPAND_SZ
potenziell expandierfähiger String-Datentyp
enumREG_BINARY
binärer Datentyp
enumREG_DWORD
Long-Datentyp
enumREG_MULTI_SZ
NULL-separiertes String-Array
zu schreibender Wert
Tabelle 1.8: Methodensignatur der GetRegistryValue-Methode (Fortsetzung)
Im folgenden Beispiel wird ein Long-Wert eingetragen: cSysReg.SetRegistryValue enumHKEY_CURRENT_USER, _ "Software\Kunde\ProgName\Settings", _ "ImportLimit", enumREG_DWORD, 10
1.8.4
DeleteRegistryValue-Methode
Die DeleteRegistryValue-Methode zum Löschen eines Wertes hat die folgende Signatur: Argumente
Inhalt
lngHKEY
Enumeration der HKEY-Konstanten
strKey
Name des Schlüssels
strValueName
Name des Wertes
Tabelle 1.9: Methodensignatur der DeleteRegistryValue-Methode
Das nächste Beispiel löscht eine RecentFiles-List: For iFile = 1 To 9 cSysReg.DeleteRegistryValue enumHKEY_CURRENT_USER, _ "Software\Kunde\ProgName\Settings", "File" & iFile Next
Der Löschversuch eines nicht vorhandenen Wertes führt zu keinem Fehler.
1.8.5
EnumKeys-Methode
Diese Methode gibt ein eindimensionales String-Array mit den Unterschlüsseln des übergebenen Schlüssels zurück; existieren keine Schlüssel unter dem aktuellen Schlüssel, so erhalten wir Empty.
Registry
39
Argumente
Inhalt
lngHKEY
Enumeration der HKEY-Konstanten
strKey
Name des Schlüssels
Tabelle 1.10: Methodensignatur der EnumKeys-Methode
Die folgende Prozedur zeigt eine rekursive Prozedur, die zum Abbilden einer RegistryStruktur in einem Treeview verwendet wird: Private Sub Tvw_LoadChildren(lngKey As Long, _ actNode As MSComctlLib.Node, strKey As String) Dim NewNode As MSComctlLib.Node Dim varSubKeys As Variant Dim iSubKey As Long varSubKeys = cSysReg.EnumKeys(lngKey, strKey) If LenB(strKey) > 0 Then strKey = strKey & "\" If Not IsEmpty(varSubKeys) Then For iSubKey = 0 To UBound(varSubKeys) Set NewNode = Tvw.Nodes.Add(actNode.Key, tvwChild, _ "K" & Tvw.Nodes.Count, varSubKeys(iSubKey), _ "Closed", "Open") Tvw_LoadChildren lngKey, NewNode, strKey & NewNode.Text Next End If End Sub
1.8.6
EnumValues-Methode
Diese Methode gibt ein eindimensionales String-Array mit den Werten des übergebenen Schlüssels zurück; existieren keine Werte unter dem aktuellen Schlüssel, so erhalten wir Empty. Der Standard-Wert (Standard) ist nicht in diesem Array enthalten, da jeder Schlüssel darüber verfügt. Argumente
Inhalt
lngHKEY
Enumeration der HKEY-Konstanten
strKey
Name des Schlüssels
Tabelle 1.11: Methodensignatur der EnumValues-Methode
Das folgende Codefragment zeigt die Verwendung dieser Methode: varValues = cSysReg.EnumValues(lngKey, strKey) If Not IsEmpty(varValues) Then For iValue = 0 To UBound(varValues, 2) ... Next End If
40
1
1.8.7
Referenz
Die Settings-Schnittstelle
Mittels eines Teils der hier vorgestellten Methoden lässt sich eine Settings-Schnittstelle bedienen, die mit einigen wichtigen Einschränkungen der Setting-Funktionen aufräumt. Störend ist, dass die Settings nur unterhalb von »VB and VBA Program Settings« anzusiedeln sind. Auch nicht gerade vorteilhaft wirkt sich aus, dass wir generell auf die Verwendung des String-Datentyps beschränkt sind; vor allem der Long-Datentyp wäre schon wünschenswert. Weniger ins Gewicht fällt hingegen, dass zwischen Application und Value nur eine Ebene für Sections angeordnet werden kann. Ich vermutete auch, dass ich nun reihenweise in weitere Tiefen abtauchen würde. Inzwischen läuft die Settings-Schnittstelle in drei größeren VB-Applikationen, ohne dass auch nur einmal eine mehr als eingliedrige Section-Ebene zur Anwendung kam. Aber gut zu wissen, dass wir es könnten ... Die Aufteilung in Application, Section und Value ist zum einen nicht schlecht und zum anderen Allgemeingut. Application wiederum ist ein Wert, der für alle Einstellungen gilt und somit als Eigenschaft bestens aufgehoben. Diese Eigenschaft wird so früh wie möglich eingestellt. In meinem Verständnis einer Anwendung sollte allerdings der Zugriff auf Einstellungen in (mindestens) einem Objekt gekapselt werden. Der Zugriff auf eine Einstellung sieht in meinen Programmen generell so aus: strFile = Dir(cSettings.ImportPath & "\*.xml")
Bei der Initialisierung dieser Settings-Klasse wird der Registry-Wrapper wiederum versorgt: cSysReg.AppName = "Kundenname\Programmname"
Die Konstante »Software« wird nicht übergeben, sondern in der Komponente bei jedem Zugriff vorangestellt. Die Section wiederum wurde auch als Eigenschaft ausgebildet und gilt jeweils so lange, bis sie verändert wird. Beispiele werden wir gleich sehen. In Schreibrichtung wiederum benötigen wir den Datentyp, der auch über eine Eigenschaft eingestellt wird. Im folgenden Beispiel werden die Form-Werte gespeichert: Private Sub Form_Unload(Cancel As Integer) ... If Me.WindowState <> vbMinimized Then cSysReg.Section = "frmMain" cSysReg.DataType = enumREG_DWORD cSysReg.SaveSetting "Windowstate", Me.WindowState cSysReg.SaveSetting "Top", Me.Top cSysReg.SaveSetting "Left", Me.Left cSysReg.SaveSetting "Width", Me.Width
Drucker
41
cSysReg.SaveSetting "Height", Me.Height End If ... End Sub
Gegenüber der herkömmlichen Variante erfordert dieser Wrapper mitunter die eine oder andere Programmzeile für Section und DataType, aber die Übersichtlichkeit steigt. Zum Abschluss noch ein Beispiel zum Schreiben eines Wertes: cSysReg.Section = "frmMain" Me.WindowState = cSysReg.GetSetting("WindowState", vbMaximized)
1.9 Drucker Die Klasse clsSysPrinter bietet Zugang zu Druckern und deren Einstellungen wie Papierbehältern oder Orientation. Zur Verwendung in der Font-Komponente wurde noch eine hDC-Eigenschaft spendiert, die ein Handle des Gerätekontexts des aktuellen Druckers zurückgibt.
Abbildung 1.6: Der Drucker-Beispiel-Dialog
1.9.1
DefaultPrinter-Eigenschaft
Die Auswahl eines Druckers erfolgt über die Eigenschaft DefaultPrinter, die in Schreib- und Leserichtung implementiert ist: strDefaultPrinter = cSysPrinter.DefaultPrinter cSysPrinter.DefaultPrinter = strDefaultPrinter
42
1
1.9.2
Referenz
PrinterCount- und PrinterName-Eigenschaft
Über diese beiden Eigenschaften besteht ein Zugang zu den verfügbaren Druckern. Durch das Einbinden der DefaultPrinter-Eigenschaft wird im folgenden Codeauszug nach dem Laden der Drucker der Default-Drucker ausgewählt: Dim iPrinter As Long Dim iDefault As Long Dim strDefault As String strDefaultPrinter = cSysPrinter.DefaultPrinter For iPrinter = 1 To cSysPrinter.PrinterCount lstPrinters.AddItem cSysPrinter.PrinterName(iPrinter) If cSysPrinter.PrinterName(iPrinter) = strDefaultPrinter Then iDefault = iPrinter – 1 End If Next lstPrinters.ListIndex = iDefault
1.9.3
ServerName-Eigenschaft
Diese Eigenschaft gibt den Namen des Servers zurück, über den der betreffende Drucker bereitgestellt wird: ... = cSysPrinter.ServerName(1)
1.9.4
Printer-Eigenschaft
Diese nur in Schreibrichtung implementierte Eigenschaft startet die Enumeration der Papierzufuhr: cSysPrinter.Printer = lstPrinters.Text
1.9.5
PaperBinCount-Eigenschaft
Diese Eigenschaft teilt uns mit, über wie viele Papierzufuhren der aktuelle Drucker verfügt: ... = cSysPrinter.PaperBinCount
1.9.6
GetPaperBinName-Methode
Mit GetPaperBinName kann der Name einer Papierzufuhr ermittelt werden: ... = cSysPrinter.GetPaperBinName(iBin)
Drucker
1.9.7
43
PaperBin-Eigenschaft
Diese Eigenschaft liest die Papierzufuhr des Default-Druckers aus beziehungsweise wählt eine neue Zufuhr aus: strBin = cSysPrinter.PaperBin cSysPrinter.PaperBin = strBin
Im folgenden Beispiel wird das Click-Ereignis der ListBox lstPrinter dazu genutzt, die verfügbaren Papierzufuhren des gewählten Druckers in die ListBox lstBins einzutragen: Private Sub lstPrinters_Click() Dim iBin As Long 'Zeiger in PaperBinCount Dim strBin As String 'aktuelle Papierzufuhr des Default-Druckers Dim iListIndex As Long 'ListIndex der aktuellen Papierzufuhr 'Enumeration mit gewähltem Drucker einleiten cSysPrinter.Printer = lstPrinters.Text 'aktuelle Papierzufuhr des Default-Druckers ermitteln strBin = cSysPrinter.PaperBin lstBins.Clear For iBin = 1 To cSysPrinter.PaperBinCount 'Papierzufuhr ergänzen lstBins.AddItem cSysPrinter.GetPaperBinName(iBin) 'ListIndex merken, falls es aktuelle Papierzufuhr ist If cSysPrinter.GetPaperBinName(iBin) = strBin Then iListIndex = iBin – 1 End If Next lstBins.ListIndex = iListIndex End Sub
1.9.8
Orientation-Eigenschaft
Mit dieser Eigenschaft kann die Ausrichtung des Papiers eingestellt werden. Aus Gründen der Bequemlichkeit sind die Werte für Hochformat und Querformat in einer Enumeration hinterlegt. In Abbildung 1.6 sind zwei Icons für diese Formate zu sehen, deren Eigenschaftsroutinen so aussehen: Private Sub imgLandscape_Click() cSysPrinter.Orientation = enumOrientationLandscape End Sub Private Sub imgPortrait_Click() cSysPrinter.Orientation = enumOrientationPortrait End Sub
44
1
1.9.9
Referenz
hDC-Eigenschaft
Diese Eigenschaft gibt den Gerätekontext des aktuellen Druckers zurück. Im folgenden Codeauszug der Font-Komponente wird dieser Gerätekontext verwendet, um die Druckerschrift zu enumerieren: hDC = cSysPrinter.hDC EnumFontFamiliesEx hDC, LF, AddressOf EnumFontCallBack, 0&, 0&
1.10 Fonts Die Klasse clsSysFonts weicht von unserem bisherigen Standard ab, da sie das Modulblatt modCallFonts benötigt und somit nicht so schön gekapselt ist wie die anderen Komponenten. Dieses Modulblatt ist nötig, da ein Callback-Kanal zwischen Windows und der Fontklasse eingerichtet werden muss, und das geht nun mal nicht ohne Modul.
Abbildung 1.7: Der Fonts-Beispiel-Dialog
1.10.1 FontCount- und FontName-Eigenschaft Die Klasse enumeriert nach der Initialisierung die Schriftarten des Systems und des aktuellen Druckers. Der Zugriff darauf erfolgt über die beiden Eigenschaften FontCount und FontName:
Windows
45
Dim iFont As Long For iFont = 1 To cSysFont.FontCount lstFonts.AddItem cSysFont.FontName(iFont) Next
1.10.2 DefaultGUIFontName-Eigenschaft Diese Eigenschaft ermittelt den von Windows in Systemdialogen verwendeten Font: ... = cSysFont.DefaultGUIFontName
1.10.3 DefaultGUIFontSize-Eigenschaft DefaultGUIFontSize ermittelt die Größe des von Windows in Systemdialogen verwendeten Fonts: ... = cSysFont.DefaultGUIFontSize
1.10.4 WriteVerticalText-Methode Diese Methode stellt einen Text senkrecht und von rechts lesbar in einem Picture-Steuerelement dar. Argumente
Inhalt
actPic
Verweis auf das PictureBox-Steuerelement
strFontName
Fontname
sngFontSize
Fontgröße
strText
Text
Tabelle 1.12: Methodensignatur der WriteVerticalText-Methode
Die folgende Zeile erzeugt den Text in 24 Punkt Arial im Steuerelement picTest: cSysFont.WriteVerticalText picTest, "Arial", 24, "vertikaler Text"
1.11 Windows Im Gegensatz zu den anderen Kapiteln besteht dieses Kapitel nicht ausschließlich aus sofort einsetzbaren und universellen Komponenten. Es wäre sicherlich möglich, aber auch sehr aufwändig und im Ergebnis gemessen am Aufwand zumindest fraglich. Aber die in Kapitel 11 beschriebenen Beispiele dürften geeignet sein, nach einer entsprechenden Anpassung auch in Ihren Projekten ihre Dienste zu verrichten.
46
1
Referenz
Dennoch bietet auch dieser Abschnitt in Form der clsSysWindow eine Komponente, die ein paar Eigenschaften aufweist.
1.11.1 SystemHWnd-Eigenschaft Dahinter verbirgt sich ein Handle auf den Desktop, das für Zugriffe auf das System zur Anwendung kommt: ... = cSysWnd.SystemHWnd
1.11.2 SystemHDC-Eigenschaft Diese Eigenschaft liefert ein Handle auf den Device Context des Desktops, womit Zugriff auf grafische Systemelemente besteht: ... = cSysWnd.SystemHDC
1.11.3 GetDC-Methode Die GetDC-Methode gibt ein Handle auf den Device Context des übergebenen Handles auf ein beliebiges Fenster zurück. Der Device Context wird für verschiedene, vor allem grafische Operationen am Fenster benötigt. Die folgende Zeile, die in einem Form-Modul stehen könnte, gibt den Gerätekontext der Form selbst zurück: ... = cSysWnd.GetDC(Me.hWnd)
1.12 Shell In diesem Abschnitt geht es darum, eine fremde Applikation zu starten, Größe und Position des Top-Level-Fensters der Applikation zu verändern und diese wieder zu beenden. Die Klasse clsSysShell kann As New deklariert werden.
1.12.1 LaunchApplication-Methode Diese Methode zum Starten einer Anwendung hat die folgende Signatur: Argumente
Inhalt
strApp
Name und Pfad der zu startenden Anwendung
lngWindowState
WindowState
bSetTopMost
True, wenn Anwendung TopMost sein soll
Tabelle 1.13: Methodensignatur der LaunchApplication-Methode
Shell
Argumente
47
Inhalt
strParam
optional, an strApp zu übergebender String
strWindowTitle
optional, Fenstertitel
Tabelle 1.13: Methodensignatur der LaunchApplication-Methode (Fortsetzung)
Die folgende Beispielzeile startet das im aktuellen Verzeichnis liegende Notepad maximiert und mit Fokus, das Fenster ist zwar im Vordergrund, aber nicht TopMost: cSysShell.LaunchApplication App.Path & "\Notepad.exe", _ vbMaximizedFocus, False, , _ "Unbenannt – Editor"
Die nächsten Zeilen starten den Standard-Browser und übergeben ihm eine Hilfedatei im html-Format: Dim cSysReg As New clsSysRegistry Dim cSysShell As New clsSysShell Dim strBrowser As String Dim strFile As String strBrowser = cSysReg.WebBrowserExe strFile = App.Path & "\Help.htm" If Dir(strFile) = "" Then MsgBox strFile & " is missing." Else strFile = Chr(34) & "file://" & strFile & Chr(34) cSysShell.LaunchApplication strBrowser, vbMaximizedFocus, False, _ strFile End If
Die Registry-Komponente clsSysRegistry (siehe Kapitel 8) kommt hier auch zum Einsatz.
1.12.2 SetWindowDimensions-Methode Diese Methode dient dazu, die Größe und Position der gestarteten Applikation zu ändern. Argumente
Inhalt
lTop
Y-Komponente der oberen linken Koordinate
lLeft
X-Komponente der oberen linken Koordinate
lWidth
Y-Komponente der unteren rechten Koordinate
lHeight
X-Komponente der unteren rechten Koordinate
Tabelle 1.14: Methodensignatur der SetWindowDimensions-Methode
Die Argumente werden Client-seitig in Twips angegeben.
48
1
Referenz
Die folgende Zeile beschreibt ein Fenster mit 5000 Twips Breite und 4000 Twips Höhe, dessen Koordinatenursprung in der oberen linken Bildschirmecke liegt (0, 0): cSysShell.SetWindowDimensions 0, 0, 5000, 4000
1.12.3 ResizeApplicationWindow-Methode Werden nach dem Start der Applikation Größe oder Position geändert, so wirkt diese Änderung erst, nachdem diese Methode abgearbeitet wird. Das Beispiel bewirkt die Änderung der neuen Koordinaten: cSysShell.SetWindowDimensions 0, 0, 5000, 4000 cSysShell.ResizeApplicationWindow
1.12.4 CloseApplication-Methode Diese Methode beendet eine per LaunchApplication-Methode gestartete Anwendung: cSysShell.CloseApplication
1.13 Maus und Tastatur Die Klasse clsSysKeyboard enthält ein paar Eigenschaften, die Auskunft über den Zustand der Tasten von Tastatur und Maus geben. Eine besondere Initialisierung ist nicht erforderlich. Die Eigenschaften für die linke und rechte Maustaste beziehen sich auf die logischen Maustasten und nicht auf die physikalischen.
1.13.1 MouseButtonsSwappedEigenschaft Diese Eigenschaft signalisiert, ob die Maustasten in der Systemsteuerung für Linkshänder konfiguriert wurden. strMessage = "Bitte drücken Sie die " If cSysKeyboard.MouseButtonsSwapped Then strMessage = strMessage & "rechte Maustaste." Else strMessage = strMessage & "linke Maustaste." End If
In diesem Beispiel wird ein Text abhängig von dieser Eigenschaft zusammengestellt.
Maus und Tastatur
49
1.13.2 MouseLeftButtonPressed-Eigenschaft Diese Eigenschaft teilt uns mit, ob die logische linke Maustaste gedrückt ist. Private Sub Tree_MouseDown(Button As Integer, Shift As Integer, _ X As Single, Y As Single) ... Sleep 150 If cSysKeyboard.MouseLeftButtonPressed Then Tree.Drag vbBeginDrag End If ... End Sub
In diesem Beispiel wird ein Drag gestartet, wenn die linke Maustaste nach 150 ms immer noch gedrückt ist.
1.13.3 MouseMiddleButtonPressed-Eigenschaft Mit dieser Eigenschaft können Sie feststellen, ob die mittlere Maustaste gedrückt ist: If cSysKeyboard.MouseMiddleButtonPressed Then
1.13.4 MouseRightButtonPressed-Eigenschaft MouseRightButtonPressed ist True, wenn die logisch rechte Maustaste gedrückt ist: If cSysKeyboard.MouseRightButtonPressed Then
1.13.5 ControlKeyPressed-Eigenschaft Ist eine der beiden Steuerungstasten betätigt, so gibt diese Eigenschaft ein True zurück: If cSysKeyboard.ControlKeyPressed Then
1.13.6 ShiftKeyPressed-Eigenschaft ShiftKeyPressed zeigt an, ob eine der beiden Umschalttasten heruntergedrückt ist: If cSysKeyboard.ShiftKeyPressed Then
1.13.7 AltKeyPressed-Eigenschaft Ob die Alt-Taste oder die Alt-Gr-Taste gedrückt ist, können wir diese Eigenschaft fragen: If cSysKeyboard.AltKeyPressed Then
Der Zustand der Alt-Gr-Taste beeinflusst auch die rechte Steuerungstaste.
50
1
Referenz
1.13.8 KeyPressed-Eigenschaft Ob irgendeine beliebige Taste gedrückt ist, bringt diese Eigenschaft in Erfahrung. Sie steht stellvertretend für alle nicht funktionalen Tasten. In diesem Beispiel wird geprüft, ob die Alt-Taste und die A-Taste gedrückt sind: If cSysKeyboard.AltKeyPressed And cSysKeyboard.KeyPressed(vbKeyA) Then
2 Grundlagen Das Akronym API steht für Application Programming Interface, wird gelegentlich aber auch mit Application Programmer’s Interface bezeichnet. Ganz allgemein versteht man unter einem API die Funktionen eines Programmes X, die von Programm Y verwendet werden können. X wird hierbei zu einem Server, weil X eben einen Service zur Verfügung stellt. Y ist der Kunde (Client), der diesen Service nutzt. Gelegentlich findet man auch die Bezeichnung Controller für den Client, denn er steuert (control) den Server. Generell unterscheidet man bei Servern In-Process-Server und Out-Of-Process-Server. Beide Ausdrücke beziehen sich auf den Prozess, in dem der Client läuft. Ein In-Process-Server läuft im Prozess des Clients und ein Out-Of-Process-Server außerhalb des Client-Prozesses. InProc-Server, wie Erstere auch kurz genannt werden, sind als Dynamic Link Library (DLL) implementiert, die dynamisch, also zur Laufzeit, zum Server verbunden werden. DLLs sind nicht eigenständig lauffähige Programmdateien, die also immer nur mit einem Client zusammen ihre Fähigkeiten entfalten können. Im Gegensatz dazu sind Out-Of-Process-Server Executables, also Exe-Dateien, die immer einen eigenen Prozess beanspruchen. DAO und ADO sind beispielsweise DLLs, die oft in VB und VBA eingesetzt werden. Word oder Excel wiederum sind Beispiele für OutOf-Process-Server. Wenn allerdings vom API die Rede ist, so sind zumeist nicht solche Server wie DAO, ADO, Excel oder Word gemeint, sondern ganz allgemein in C geschriebene Bibliotheken ohne die lieb gewonnenen ActiveX-Overheads. Da wohl alle Windows-Bibliotheken in C geschrieben sein dürften, bietet uns das API somit auch Zugang zu elementaren Windows-Funktionen. Hierbei werden viele Mechanismen von VBA und der Entwicklungsumgebung umgangen. Das ist gleichermaßen das Wohl und Wehe des API, denn die Entwicklungsumgebung kapselt unseren Code und tut eine ganze Menge im Hintergrund, erstellt Referenzen, löst sie auf und fängt Fehler für uns ab. Die Entwicklungsumgebung ist unser Panzer, der uns vor Windows und seinen »Gemeinheiten« schützt. Über DLLs kann man dicke Bücher schreiben, und es sind auch schon dicke Bücher darüber geschrieben worden, die allerdings ohne ein gerütteltes Maß C-Kenntnisse nur schwer verdaulich sind. Uns interessiert in diesem Zusammenhang jedoch nur, ob
52
2
Grundlagen
wir einen Verweis darauf einrichten können oder nicht. Können wir einen Verweis darauf einrichten, ist die Welt in Ordnung und wir können auf die Funktionalität der Bibliothek über Objektvariablen und deren Methoden und Eigenschaften zugreifen, wie wir es von Steuerelementen bereits kennen. Wenn wir keinen Verweis darauf einrichten können (und auch kein Late Binding funktioniert), dann sind wir an eine Bibliothek geraten, bei der wir uns des API bedienen müssen.
2.1 Zugriff über Verweise Die Möglichkeit zum Einrichten eines Verweises finden Sie in der Entwicklungsumgebung von VB im Menü Projekt und in der von VBA im Menü Extras. Abb. 2.1 zeigt den Dialog, der sich dahinter verbirgt.
Abbildung 2.1: Verweise-Dialog
Wenn ein Verweis auf eine DLL eingerichtet werden kann, muss für diese DLL eine so genannte Type Library vorhanden sein, die sowohl als eigene Datei mit der Erweiterung tlb (für DLL-Server) oder olb (für Exe-Server) vorliegt, aber auch in den eigentlichen Bibliotheken enthalten sein kann. Diese Type Library wartet mit allen Schnittstelleninformationen des Servers auf. Die für uns eigentlich unsichtbare Type Library manifestiert sich jedoch im Objektkatalog einer Bibliothek, denn wenn Sie im Objektkatalog das ADODB-Objektmodell 2.1 oder 2.5 bewundern können, schauen Sie im Prinzip in die Type Library msado21.tlb oder msado25.tlb. Dieses Verfahren eines Verweises auf eine Bibliothek, was unter dem Namen Early Binding bekannt geworden ist, bringt für
Zugriff über API
53
uns eine Reihe von Vorteilen. Wir können die Struktur und die Methodensignaturen eines solchen Objektmodells im Objektkatalog ansehen. Aber auch die Mechanismen Elemente automatisch auflisten, automatische QuickInfo oder automatische DatenTipps werden durch dieses Early Binding ermöglicht. Wenn Sie einen Verweis einrichten, legt der Client (also das Programm, das diese Bibliothek verwenden will) die ClassID der Type Library im Projekt ab. Unter dieser ClassID (z.B. {00000205-0000-0010-8000-00AA006D2EA4}) findet der Client auf dem Zielrechner wiederum Pfad und Name der dazugehörigen DLL (HKEY_CLASSES_ ROOT\TypeLib). Voraussetzung für dieses Early Binding ist, dass die DLL über eine Virtual Function Table (vTable oder VTBL) verfügt. Der Vollständigkeit halber sei erwähnt, dass es auch noch Bibliotheken gibt, die zwar kein Early Binding mit all seinen Vorzügen unterstützen, stattdessen aber ein so genanntes Late Binding ermöglichen. Late Binding meint, dass die Bibliotheken zur Laufzeit über eine der beiden Funktionen CreateObject oder GetObject referenziert werden können. Auf diese Bibliotheken können wir auch mit Objektbezügen zugreifen, die allerdings erst zur Laufzeit überprüft werden können. Verantwortlich dafür ist ein Interface namens IDispatch, welches auf Anforderung die Schnittstellenbeschreibung herausrückt, aber eben erst zur Laufzeit, also reichlich late. Die meisten zeitgemäßen Bibliotheken implementieren heutzutage beide Interfaces, also vTable und IDispatch, was durch den Begriff Dual Interface signalisiert wird.
2.2 Zugriff über API Stößt man auf den Begriff API, so steht dieser meist im Kontext von Windows und seinen Bibliotheken, obwohl auf Ihrem Rechner vermutlich Hunderte von C-Bibliotheken zu finden sein werden, die per API angesprochen werden können. Der Gedanke hinter diesen Windows-Bibliotheken ist auch hier, den Code für immer wiederkehrende Operationen einmal auf dem Rechner abzulegen und von allen anderen Applikationen, seien sie nun von Microsoft, von anderen Softwareherstellern oder aber von Ihnen. Man kann somit das Windows-API als den Ursprung des heutigen Komponentengedankens ansehen. Zeitgemäße Komponenten wie etwa ADO verhalten sich allerdings zum Windows-API wie Word 2000 zu dem legendären EDLIN.
2.2.1
Welche Bibliotheken gibt es?
Wenn vom Windows-API die Rede ist, dreht es sich zumeist um die zentralen Windows-Bibliotheken, die in Tabelle 2.1 dargestellt sind.
54
2
Bibliothek
Grundlagen
Inhalt
advapi32.dll
im Wesentlichen Security, Event Log, Registry
kernel32.dll
Prozess-Handling, Speicheroperationen, Systeminfos
user32.dll
Message Handling, Timers, Bedienelemente
gdi32.dll
Graphics Device Interface (GDI) mit geräteabhängigen Funktionen
shell32.dll
Dateien, Dateierweiterungen, Verzeichnisse, sowie Starten von Programmen
winspool.dll
Druckausgaben
Tabelle 2.1: Zentrale Windows-Bibliotheken
Wenn wir den Kreis ein wenig erweitern, so sind MAPI, TAPI, ODBC, OLE DB, NETAPI und WINMMAPI noch hinzuzuzählen. Doch gerade bei der zuerst genannten MAPI scheinen die besten Zeiten vorbei zu sein, denn unter Windows 2000 wird die Verwendung der ADSI beziehungsweise LDAP C API empfohlen, wobei die MAPI aus Gründen der Kompatibilität weiterhin (wie lange?) unterstützt wird. Kramt man ein wenig in der MSDN herum, so findet man eine Reihe von Beiträgen über APIs zu Datenbankzugriffen. Schaut man sich diese jedoch näher an, so stellt man mit Freude fest, dass diese Funktionalität in unseren Tagen in DAO und ADO ihren Platz gefunden haben. Lediglich zur Ermittlung der Data Sources muss man noch auf das API zurückgreifen.
2.2.2
Struktur einer API-Deklaration
Per API aufrufbare Methoden verfügen in der Regel über eine Rückgabe und sind somit zum überwiegenden Teil Funktionen. Hier die Struktur einer Funktion: [Public | Private] Declare Function Name Lib "Bibliothek" [Alias "AliasName"] [([Argumentliste])] [As Typ]
In Ausnahmefällen sind auch Subs anzutreffen, denen natürlich der Rückgabetyp fehlt: [Public | Private] Declare Sub Name Lib "Bibliothek" [Alias "AliasName"] [([Argumentliste])]
Folgende Elemente können in einer Deklaration auftauchen: 왘 Private | Public gibt den Gültigkeitsbereich der Deklaration an. 왘 Declare kennzeichnet die Deklaration eines Aufrufs in einer DLL. 왘 Sub | Function sagt aus, ob es sich um eine Prozedur oder eine Funktion mit Rückgabe handelt. 왘 Name ist der Name, den wir im Code zum Aufruf der Funktion verwenden. Fehlt Alias, so ist es auch der Name der Funktion in der Bibliothek.
Datentypen
55
왘 Lib leitet den Namen der Bibliothek ein. 왘 Als »Bibliothek« wird der Name der DLL angegeben, die wir nutzen möchten. 왘 Alias leitet den Namen ein, unter dem die Prozedur in der DLL steht. Dieses Argument ist nicht immer verfügbar und interessiert uns im Grunde auch nicht. 왘 Die Argumentliste enthält alle an die Prozedur zu übergebenden Variablen in einer Syntax, wie wir sie aus unseren Argumentlisten auch kennen. 왘 Bei Funktionen folgt noch die Angabe, welchen Datentyps die Rückgabe der Funktion ist. In der Regel handelt es sich hierbei um einen Long-Wert. Beispiele werden wir weiter unten noch sehen.
2.3 Datentypen API-Funktionen sind in C geschrieben und erwarten somit auch C-Datentypen, die uns in VB und VBA so nicht immer zur Verfügung stehen. Wenn ein API-Aufruf zu einem Crash führt, so ist oft ein falscher oder falsch übergebener Datentyp dafür verantwortlich. Datentypen und deren Konvertierung stellen das eigentliche Problem für VB-Entwickler dar, die nicht mit den C-Strukturen vertraut sind. In diesem Abschnitt werden Sie mit ein paar Grundlagen versorgt, aber es erwartet Sie keine umfassende theoretische Abhandlung des Themas. Im Übrigen werden Sie ein sicheres Gefühl für Datentypen bekommen, wenn Sie die nachfolgenden Kapitel durcharbeiten.
2.3.1
String
Mit Strings hat es so seine besondere Bewandtnis, was man auf den ersten Blick nicht vermuten würde. Beim näheren Hinsehen leuchtet es aber ein. Die meisten Datentypen, die wir in Visual Basic verwenden, haben eine definierte Größe. So umfassen der Datentyp Byte ein Byte, Boolean und Integer zwei Bytes, Long und Single vier Bytes, Double, Currency und Date je acht Bytes. Auch eine Objektvariable hat eine feste Größe, denn genau genommen enthält sie eine vier Byte große Speicheradresse, die auf das IUnknown-Interface des Objekts zeigt. Die einzigen Datentypen variabler Größe sind String und Variant, wobei Letztere eine recht komplexe Angelegenheit darstellen. Alle Variablen sind aber benannte Speicheradressen, beinhalten letztendlich also eine Typinformation und eine Speicheradresse. Woher soll das Programm bei einer StringVariablen nun wissen, wie viele Bytes ihr in dem Speicher denn tatsächlich zugeordnet sind? Steht nun »x« im Speicher oder ein ganzer Roman?
56
2
Grundlagen
Die Antwort ist ganz einfach, denn seine Größe ist in den ersten vier Bytes der Variablen selbst enthalten. Unser String ist eigentlich ein so genannter Basic String (BSTR) und wie in Abbildung 2.2 gezeigt aufgebaut.
Abbildung 2.2: Aufbau eines Basic Strings
Nun erwarten API-Funktionen des Argumenttyps LPSTR keinen Verweis auf die Struktur inklusive Header, sondern nur auf die eigentliche Null-terminierte Variable. Und genau diese Aufgabe erledigt das Schlüsselwort ByVal, dem also in diesem Zusammenhang eine völlig andere Bedeutung zukommt. Bislang entkoppelten wir quasi eine Variable, indem sie als Kopie mit ByVal übergeben wurde. Eine API-Funktion ist aber wohl in der Lage, in unsere ByVal übergebene Variable (sprich Speicheradresse) zurückzuschreiben und den Wert so zu verändern. Doch sie darf die ursprüngliche Länge der Variablen hierbei auf keinen Fall überschreiten, da dies unter Umständen eine Verletzung der Integrität des Variablenspeichers nach sich ziehen würde. Somit muss die an die Funktion übergebene Variable bereits groß genug sein, um die Rückgabe vollständig aufzunehmen. Halten wir also fest: String-Variablen müssen ByVal übergeben werden und groß genug sein, um den Inhalt aufzunehmen, der ihnen von der API-Funktion zugeordnet wird. Vergessen Sie hierbei ByVal, so ernten Sie eine Schutzverletzung! Hier können wir also auch mit dem Märchen aufräumen, dass der String-Datentyp einen Speicherbedarf von einem Byte je Zeichen hat, es sind vielmehr zwei Byte je Zeichen plus Header und Null mit zusammen sechs Bytes. Wer sich dafür interessiert, wird in der MSDN genügend Informationen zu C-Datentypen und ihren Entsprechungen in Visual Basic finden, um ein langes Wochenende damit bequem auszufüllen.
2.3.2
Byte, Integer, Long etc.
In der Regel werden bei ganzzahligen Argumenten Long-Datentypen ausgetauscht. Reine [IN]-Parameter können ByVal oder ByRef übergeben werden. Sobald es sich aber um einen [OUT] oder [IN/OUT]-Parameter handelt, ist größte Vorsicht geboten, denn das API fasst diesen Wert immer als Zeiger auf eine andere, nämlich die zu beschreibende Speicheradresse auf. In diesem Falle muss die Variable ByRef übergeben werden.
Datentypen
57
Wird ein Wert von der Funktion zurückgegeben, so muss die Übergabe ByRef erfolgen. Ein Problem taucht in Form der unterschiedlichen ganzzahligen Datentypen in C und Visual Basic auf.
Abbildung 2.3: 4-Bit-Integer mit und ohne Vorzeichen
Abbildung 2.3 zeigt einen theoretischen 4-Bit-Integer, der zwar nicht existiert, aber die Problematik erkennen lässt. Nur knapp die Hälfte der Wertebereiche eines vorzeichenlosen und eines vorzeichenbehafteten Datentyps stimmen überein. Es kommt nicht oft vor, dass man auf diese Konvertierungsprobleme stößt, aber im Kapitel 6 werden wir ihnen begegnen. Schauen wir uns einmal die acht Datentypen an und füllen die identischen Typen mit den VB-Entsprechungen (Tabelle 2.2). 8 Bit vorzeichenlos vorzeichenbehaftet
16 Bit
32 Bit
Integer
Long
Byte
Tabelle 2.2: Ganzzahlige Datentypen und ihre Entsprechungen in VB I
64 Bit
58
2
Grundlagen
Tabelle 2.2 lässt also einige Felder unbesetzt, wie man sehen kann. Was stellen wir mit diesen Datentypen an, wenn Sie uns begegnen? Tabelle 2.3 hält die Lösung bereit: 8 Bit
16 Bit
32 Bit
64 Bit
vorzeichenlos
Byte
Byte-Array
Byte-Array
Byte-Array
vorzeichenbehaftet
gibt’s nicht
Integer
Long
Long + 4 Bytes
Tabelle 2.3: Ganzzahlige Datentypen und ihre Entsprechungen in VB II
Ein vorzeichenbehafteter 8-Bit-Wert ist mir bislang noch nicht begegnet, so wage ich denn die kühne Behauptung, dass es ihn im API nicht gibt. Alle vorzeichenlosen Datentypen können wir durch ein Byte-Array ersetzen, wie Abbildung 2.4 zeigt.
Abbildung 2.4: Byteweise Betrachtung von 32 Bit
Jeweils 8 Bit werden in einem Byte zusammengefasst und mit Exponenten 0, 8, 16 und 24 und zur Basis 2 multipliziert. Und bei einem 64-Bit-Integer wird das vorderste Byte eben mit 256 multipliziert.
2.3.3
Strukturen
Strukturen sind benutzerdefinierte Datentypen und als solche aus den elementaren VB-Datentypen zusammengesetzt. Strukturen oder benutzerdefinierte Datentypen werden immer ByRef übergeben.
Datentypen
59
Viel mehr ist im Moment hierzu nicht zu sagen. Im Druckerkapitel werden wir noch auf Probleme stoßen, wenn ein Wert aus einer in einer anderen Struktur enthaltenen Unterstruktur gelesen oder geschrieben werden soll. Doch auch dort hilft uns die byteweise Betrachtung und ein wenig Kopieren.
2.3.4
Arrays
Wenn Arrays an der Grenze zum API auftauchen, ist man recht schnell der Verzweiflung nahe. Im Druckerkapitel werden wir sogar einem gemischten, zweidimensionalen Array begegnen. Spätestens da werden wir Strings mit anderen Augen sehen, denn wir müssen sie als Zeiger auf andere Speicherbereiche verstehen und behandeln. Lassen Sie sich überraschen. Aber auch mit Long-Arrays werden wir gelegentlich konfrontiert, so zum Beispiel in Kapitel 4 beim Farbendialog. Dort kommt eine Struktur namens CHOOSECOLOR zum Einsatz, deren Argument lpCustColors wie folgt beschrieben ist: Pointer to an array of 16 COLORREF values that contain red, green, blue (RGB) values for the custom color boxes in the dialog box. Und selten war in der MSDN-Library so ein Blödsinn zu lesen wie in den VB-Beispielen zu diesem Argument. In einem Beispiel war lpCustColors als String deklariert und mit StrConv(x, vbFromUnicode) gefüllt worden. Im anderen war lpCustColors ein Long und es wurden Speicherbereiche alloziert und Bytes hin und her kopiert. Das erste tat nichts, stürzte aber auch nicht ab, und das zweite, zuletzt im März 1999 durchgesehene Beispiel arbeitete noch mit 16-Bit-Funktionen. Ab und zu verlässt einen der Mut angesichts der VB-Beispiele in der MSDN. Doch was steht noch mal im ersten Satz? »Zeiger auf ein Array von 16 Long-Werten ...« Ein Zeiger ist ein Long. Ein Array mit 16-Long-Werten wiederum an einen einfachen Long zu übergeben funktioniert natürlich nicht (weshalb in der MSDN kurzerhand hieraus ein String wurde). Also erzeugen wir ein Long-Array und übergeben einen Zeiger darauf mit der VarPtr-Funktion und dem Element 0 des Arrays: tChooseColor.lpCustColors = VarPtr(lngCustColors(0))
lngCustColors ist übrigens folgendermaßen deklariert: Private lngCustColors(15) As Long
2.3.5
Die wichtigsten C-Datentypen
Die folgende Tabelle, die dem Abschnitt »Converting C Declarations to Visual Basic« der MSDN (fast unverändert) entnommen ist, zeigt eine Gegenüberstellung von C-und Visual-Basic-Deklarationen.
60
2
C-Datentyp
Deklaration in Visual Basic
ATOM
ByVal variable As Integer
BOOL
ByVal variable As Long
BYTE
ByVal variable As Byte
CHAR
ByVal variable As Byte
COLORREF
ByVal variable As Long
DWORD
ByVal variable As Long
HWND, HDC etc.
ByVal variable As Long
INT, UINT
ByVal variable As Long
LONG
ByVal variable As Long
LPARAM
ByVal variable As Long
LPDWORD
variable As Long
Grundlagen
LPINT, LPUINT
variable As Long
LPSTR (LPCSTR, LPCTSTR, oder LBTSTR)
ByVal variable As String oder vbNullString oder aber ByVal bzw. ByRef As Long
LPWORD
variable As Integer
LRESULT
ByVal variable As Long
SHORT
ByVal variable As Integer
WORD
ByVal variable As Integer
WPARAM
ByVal variable As Long
Tabelle 2.4: Gegenüberstellung von C-und Visual-Basic-Deklarationen
2.4 Ein Beispiel Schauen wir uns nun ein recht einfaches Beispiel aus der Nähe an. Die hier verwendete Funktion GetUserName ermittelt den Anmeldenamen und schreibt diesen in eine String-Variable. Private Declare Function GetUserName Lib "advapi32.dll" Alias "GetUser-NameA" (ByVal lpBuffer As String, nSize As Long) As Long
Mit lpBuffer und nSize verfügt sie über zwei Argumente, die oft unter diesen oder ähnlichen Namen paarweise auftreten. Die erste der beiden muss eine Zeichenkette in ausreichender Länge sein, damit die aufgerufene Funktion das Ergebnis hineinschreiben kann. Das zweite Argument muss eine Zahl sein, die mindestens so groß ist wie die Anzahl der Zeichen in der ersten Variablen, also die Anzahl der erwarteten Zeichen.
Der WinAPI-Viewer
61
Private Sub LoginName() Dim strName As String Dim nSize As Long Dim lngResult As Long nSize = 100 strName = Space(100) lngResult = GetUserName(strName, nSize) MsgBox Left(strName, nSize – 1) End Sub
Die beiden Argumente werden von der Funktion, mit Werten gefüllt, wieder zurückgegeben. Für den Anmeldenamen Administrator werden die ersten 14 Blanks durch den Namen Administrator und ein nachfolgendes Chr(0) ersetzt und nSize beinhaltet mit 14 die Bruttobreite der Rückgabe. Deswegen kann mit MsgBox Left(strName, nSize – 1)
der Anmeldename aus der Rückgabe herausgeschnitten werden. Abbildung 2.5 zeigt die beiden Variablen vor und nach der Veränderung durch die GetUserName-Funktion, wobei als Länge der Einfachheit halber nur die Größe 10 gewählt wurde.
Abbildung 2.5: Der String in GetUserName
Es genügt übrigens nicht, einen ausreichend großen String zu übergeben. Es muss der Funktion auch signalisiert werden, wie viele Zeichen ihr zur Verfügung stehen. Würden wir nSize mit 2 vorbelegen, so würden wir nur die ersten beiden Buchstaben des Anmeldenamens gefolgt von dem obligatorischen Null zurückerhalten. Mit welchen Zeichen Sie eine Zeichenkette vorbelegen, ist im Prinzip egal. Sie können Blanks mit Space(n) erzeugen, aber ebenso gut Nulls mit String(n, Chr(0)) hineinpacken.
2.5 Der WinAPI-Viewer Es gibt Tausende von API-Funktionen, die teilweise auch recht lang sind. Und obwohl in der MSDN alle auffindbar sind, ist die dortige Notation nicht notwendigerweise die, mit der wir eine Funktion in VB oder VBA deklarieren können. Zum Glück gibt es den
62
2
Grundlagen
API-Viewer, der die Funktionen aus adva-pi32.dll, kernel32.dll, user32.dll, gdi32.dll sowie einiger Gerätetreiber beinhaltet, lediglich die shell32.dll findet keine Berücksichtigung. Wenn Sie im Menü Add-Ins den WinAPI-Viewer nicht finden, müssen Sie ihn über den Add-In-Manager der Entwicklungsumgebung zuerst aktivieren. Danach präsentiert er sich, wie in Abbildung 2.7 zu sehen ist. Zuerst müssen Sie jedoch eine Textdatei laden. Wählen Sie hierzu im Menü Datei den Eintrag Textdatei laden ... und wählen Sie dort bitte die Win32API.txt.
Abbildung 2.6: WinAPI-Viewer mit geladener Win32API.txt
Sie können in der TextBox die Anfangsbuchstaben des gewünschten Eintrags eingeben, und die ListBox wird zu dem ersten passenden Element springen. Durch die Hinzufügen-Schaltfläche oder einen Doppelklick befördern Sie den Eintrag in die untere TextBox. Dort können Sie den Cursor in eine der Zeilen positionieren und den Eintrag über die Schaltfläche Kopieren in die Zwischenablage transportieren, von wo aus er über Einfügen (Strg)-(V) in den Code gelangt. Üben Sie ein wenig, und Sie werden das Handling schnell beherrschen.
Header-Dateien
63
2.6 Header-Dateien Nicht selten kommt es vor, dass die MSDN von einer Konstante erzählt, die der WinAPI-Viewer nicht im Angebot hat. Abbildung 2.8 zeigt einen Ausschnitt der Beschreibung zur PRINTER_INFO_2-Struktur, wo die Konstante PRINTER_STATUS_ POWER_SAVE referenziert wird, die wiederum nicht in Win32API.txt enthalten ist.
Abbildung 2.7: Deklaration in MSDN
Im Abschnitt Requirements steht allerdings: Header: Declared in Winspool.h; include Windows.h.
Einige dieser Header-Dateien sind auf der Buch-CD enthalten, bei der Installation von Visual Studio oder dem Platform SDK erhalten Sie (nach meinem Stand) 1067 dieser Dateien, die mit dem Notepad geöffnet werden können. Laden Sie die Header-Datei WinSpool.h in einen Texteditor, suchen Sie nach der Konstanten PRINTER_STATUS_ POWER_SAVE, und Sie werden ein ähnliches Ergebnis haben, wie in Abbildung 2.8 dargestellt.
64
2
Grundlagen
Und aus der Zeile in der Header-Datei #define PRINTER_STATUS_POWER_SAVE
0x01000000
wird die VB-Deklaration Private Const PRINTER_STATUS_POWER_SAVE = &H1000000
Zahlen in Header-Dateien erscheinen in hexadezimaler Schreibweise. Diese Header-Dateien enthalten nicht nur die Konstanten, sondern auch Strukturen und ebenso Funktionen. Mit etwas Übung werden Sie als ultima ratio auch Strukturen und Funktionen aus einer Header-Datei erzeugen können.
Abbildung 2.8: WinSpool.h im Notepad
3 Locale Settings Fremde Länder, fremde Listentrennzeichen, könnte man sagen. Aber nicht nur die Listentrennzeichen unterscheiden sich je nach Land, sondern auch Datumsformate, Währungssymbole und vor allem die Sprache. Und hier erfahren Sie, wie Sie diese Ländereinstellungen ermitteln können. Im Zusammenhang mit Ländereinstellungen tauchen in Windows zumeist die Begriffe NLS für National Language Support oder Locale Settings auf. NLS bezeichnet generell die Schnittstelle zu sprachabhängigen und regionalen Einstellungen und Locale Settings die Ländereinstellungen selbst. Das NLS-API stellt uns also u.a. die Werkzeuge zur Verfügung, um auf die Locale Settings zuzugreifen. Diese Locale Settings existieren zum einen für das ursprünglich installierte Gebietsschema (SystemDefault), aber auch für das aktuell gewählte Gebietsschema des angemeldeten Anwenders (UserDefault). Sofern niemand seit der Installation das Gebietsschema verändert hat, werden beide Wertegruppen übereinstimmen. Sind sie jedoch verschieden, so interessieren uns die Werte, die der angemeldete Anwender eingestellt hat. Die SystemDefault-Einstellungen können wir also getrost ignorieren und uns auf die UserDefault-Werte beschränken. Erwartungsgemäß legt Windows die UserDefault-Werte in der Registry ab, wo sie unter HKEY_CURRENT_USER\Control Panel\International bewundert werden können. Dort stehen allerdings nur die Werte, die veränderbar sind und vom SystemDefault abweichen. Wir können insgesamt auf rund 100 verschiedene Einstellungen zugreifen. Hierunter fallen der Name der aktiven Sprache und des Landes in der Landessprache und in Englisch, das kurze und das lange Datumsformat, die Namen der Wochentage und Monate in der Landessprache, Listen- und Dezimaltrennzeichen oder Währungssymbole, um nur einige zu nennen. Von diesen 100 Werten (Win95 < NT4 < NT2000) dürfen wir genau 30 auch verändern.
3.1 Anwendungsfälle Es gibt eine Reihe von Situationen, in denen es sinnvoll ist, die eine oder andere Einstellung zu kennen. Das Paradebeispiel ist sicherlich die mehrsprachige Applikation. Dort gilt es zuförderst, beim Start die aktuelle Sprache zu ermitteln, um alle Captions
66
3
Locale Settings
und Messages auf diese Sprache einzustellen. Sofern Sie Datumsausgaben in Form von Zeichenketten ausgeben müssen, lassen sich diese über die Format-Funktion und eines der beiden Datumsformate ausgeben. Ein anderer Fall begegnete mir jüngst in einem Pharmaunternehmen. Bei einer im Übrigen unveränderten deutschen Sprachumgebung waren bei einigen Rechnern das Dezimal- und das Listentrennzeichen auf die angloamerikanische Variante eingestellt. Das Schreiben von Gleitkommawerten in Excel-Zellen machte natürlich keine Schwierigkeiten, da VBA diese automatisch korrekt anpasst. Gelegentlich mussten allerdings durch Komma getrennte Zahlen in Zellen geschrieben werden, die glatt als Dezimalzahl interpretiert wurden, sofern es genau zwei waren. Diesem Problem konnten wir nur dadurch begegnen, indem wir diese Zahlen mit dem Listentrennzeichen aus den Locale Settings kombinierten.
3.2 Language-ID und Locale-ID Die Language-ID oder auch kurz LANGID ist die dem Gebietsschema zugeordnete Sprache als Integer-Wert. Die Bits 0 bis 9 bilden hierbei die so genannten Primary Language ID. Die Sprache Deutsch hat beispielsweise den Wert 7. Die oberen 6 Bits hingegen codieren die regionale Komponente Secondary Language ID mit 1 für Deutschland, 2 für Schweiz, 3 für Österreich, 4 für Luxemburg und 5 für Liechtenstein. Somit ergibt sich für Deutsch Deutschland 1 x 2 hoch 10 + 7 = 1031 und für Österreich 3 x 2 hoch 10 + 7 = 3079. Halten wir fest: Die Language-ID codiert die Sprachregion über einen Integer-Wert. Die Locale-ID, oft auch LCID genannt, geht einen Schritt weiter und enthält einen 4 Bit großen Bereich, mit dem pro gewählte Region noch in unterschiedlichen Sortierungen unterschieden wird. Für unsere deutschen Language-IDs existieren hier allerdings nur die Standardsortierung und die heutzutage wohl kaum gebräuchliche Telefonbuchsortierung. Logischerweise wurde der Standardsortierung der Wert 0 zugewiesen, wodurch Language-ID und Locale-ID betragsmäßig gleich, aber unterschiedlichen Datentyps sind: Language-ID ist ein Integer und Locale-ID ein Long. In der Welt des API ist dies ein kleiner, aber feiner Unterschied.
Abbildung 3.1: Die Strukturen der Locale-ID und der Language-ID
Auslesen von Werten
67
Den nachfolgend vorgestellten Code finden Sie auf der Buch-CD in der Datei Locale Settings.xls.
3.3 Auslesen von Werten Die Locale Settings auszulesen, ist ein vergleichsweise einfaches Unterfangen, denn wir benötigen im Prinzip nur eine Funktion. Diese heißt GetLocaleInfo und ist folgendermaßen aufgebaut: Declare Function GetLocaleInfo Lib "kernel32" Alias "GetLocaleInfoA" (ByVal Locale As Long, ByVal LCType As Long, ByVal lpLCData As String, ByVal cchData As Long) As Long
Locale Locale legt fest, ob auf SystemDefault- oder UserDefault-Werte zugegriffen werden soll.
LCType LCType steuert die Art der angeforderten Einstellung.
LpLCData Von der Funktion gefüllte Zeichenkette.
CchData Bruttolänge von lpLCData (also inklusive abschließendem Chr(0))
Rückgabe 0 im Fehlerfalle, wofür aber nur eine ungültige Locale oder LCType verantwortlich sein kann. Als Locale-Argument können wir entweder die Konstante LOCALE_USER_DEFAULT = &H400 übergeben oder uns von der Funktion GetUserDefaultLCID die tatsächliche Locale-ID (LCID) zurückgeben lassen. Im Argument LCType informieren wir die Funktion über eine Konstante, welche Information wir von ihr benötigen. Tabelle 2 zeigt eine Auswahl von Konstanten, die in der noch vorzustellenden Klasse clsSysNLS verwendet wird.
68
3
Konstante
Wert
Locale Settings
Bedeutung
LOCALE_ICOUNTRY
&H5
Country Code (z.B. 49 für Deutschland)
LOCALE_SENGCOUNTRY
&H1002
Englischer Landesname (z.B. Germany für Deutschland)
LOCALE_SCOUNTRY
&H6
Lokaler Landesname (z.B. Deutschland für Deutschland)
LOCALE_SINTLSYMBOL
&H15
Internationales Währungssymbol (DEM für Deutschland)
LOCALE_SCURRENCY
&H14
Lokales Währungssymbol (DM für Deutschland)
LOCALE_SDECIMAL
&HE
Dezimaltrennzeichen (üblicherweise »,« im deutschsprachigen Raum)
LOCALE_SLIST
&HC
Listentrennzeichen (üblicherweise »;« im deutschsprachigen Raum)
LOCALE_SENGLANGUAGE
&H1001
Englischer Name der Landessprache (German im deutschsprachigen Raum)
LOCALE_SNATIVELANGNAME
&H4
Lokaler Name der Landessprache (Deutsch im deutschsprachigen Raum)
LOCALE_SLONGDATE
&H20
Langes Datumsformat, beispielsweise d. MMMM yyyy für 8. Juni 2000
LOCALE_SSHORTDATE
&H1F
Kurzes Datumsformat, beispielsweise dd.MM.yyyy für 08.06.2000
LOCALE_SMONTHNAME1
&H38
Lokaler Name des ersten Monats (Januar bzw. Jänner im deutschsprachigen Raum)
Tabelle 3.1: Auswahl an LCType-Konstanten
Im Argument lpLCData legt die Funktion den gewünschten Wert gefolgt von dem üblichen Chr(0) ab. In cchData signalisieren wir der Funktion, wie groß lpLCData ist. Im Gegenzug schreibt sie die von ihr genutzte Bruttogröße, also inklusive Chr(0), in dieses Argument. Wie in solchen Fällen üblich, müssen wir lpLCData in ausreichender Größe übergeben, damit es die angeforderte Information aufnehmen kann. Reichen 100 Zeichen, oder nehmen wir besser 200? Die GetLocaleInfo nimmt uns diese Entscheidung auf Verlangen an, denn wir können sie mit einer 0 (Zero, nicht Null) im Argument cchData aufrufen, woraufhin die Rückgabe der Funktion die erforderliche Bruttolänge von lpLCData beinhaltet: nSize = GetLocaleInfo(lngLocale, lngLCType, strValue, 0&) strValue = Space(nSize) hResult = GetLocaleInfo(lngLocale, lngLCType, strValue, nSize) GetLocaleValue = Left(strValue, nSize – 1)
lngLCType ist in diesem Beispiel eine der LCType-Konstanten aus Tabelle 3.1.
Klassenaufbau
69
3.4 Klassenaufbau Bevor wir an die Codierung schreiten, sollten wir ein paar Gedanken über die sinnvolle Struktur einer Klasse anstellen, in der die Zugriffe auf die Ländereinstellungen gekapselt werden. Als Schnittstellen kommen für diese Aufgabenstellung lediglich Eigenschaftsroutinen in Frage. Der eigentliche Aufruf der GetLocaleInfo nebst Fehlerbehandlung ist natürlich in einer privaten Funktion am Besten aufgehoben. Somit übergeben die Eigenschaftsroutinen die Art ihres Begehrs in Form der Konstanten an die Funktion und erhalten im Gegenzug die gewünschte Information in Form eines Strings, wie in Abb. 3.2 zu sehen ist.
Abbildung 3.2: Struktur der Locale Settings-Klasse
Beginnen wir mit dem schwierigsten Teil und codieren die Funktion, die in Listing 1 dargestellt ist. Neben der Konstante erhält die Funktion noch einen String, der jedoch nur für die Erzeugung eines Fehlertextes Verwendung findet. Zuerst wird über die GetUserDefaultLCID-Funktion die LCID für die User-Default-Einstellungen ermittelt. Im nächsten Schritt erfragen wir die benötigte Bruttolänge für lpLCData und erzeugen den String strValue in der erforderlichen Länge. Beide werden nun in einem erneuten Aufruf an GetLocaleInfo übergeben, die lpLCData ihrerseits mit dem gewünschten Wert füllt. Bleibt noch, den Nettowert von lpLCData zur Rückgabe an den Funktionsnamen zu übergeben. Da in dieser Klasse schlechterdings kein Datenfehler auftreten kann, wird im Fehlerfalle ein Zugriffsfehler angenommen und unter Verwendung des übergebenen Arguments strSetting eine entsprechende Fehlermeldung erzeugt. Private Function GetLocaleValue(ByVal lngLCType As Long, ByVal _ strSetting As String) As String 'Argumente:
70
3
Locale Settings
' lngLCType: entsprechende LC-Konstante ' strSetting: Name der Einstellung im Klartext für Fehlertext Dim strValue As String 'angeforderte Einstellung Dim nSize As Long 'Bruttolänge der strValue Dim lngResult As Long 'Rückgabe der Funktion Dim lngLocale As Long 'Konstante für User-Default-Einstellung 'LC-ID ermitteln lngLocale = GetUserDefaultLCID 'erforderliche Größe für strValue ermitteln nSize = GetLocaleInfo(lngLocale, lngLCType, strValue, 0&) strValue = Space(nSize) 'Funktion aufrufen lngResult = GetLocaleInfo(lngLocale, lngLCType, strValue, nSize) 'auf Fehler prüfen If lngResult = 0 Then Err.Raise 5, "Der lesende Zugriff auf die Ländereinstellung " & _ strSetting & " wurde verweigert." End If 'Nettorückgabe zuschneiden GetLocaleValue = Left(strValue, nSize – 1) End Function
Was bleibt, ist die nun recht einfache Gestaltung der einzelnen Eigenschaftsroutinen, die nun am Beispiel der CountryCode Routine dargestellt ist: Public Property Get CountryCode() As String Const LOCALE_ICOUNTRY As Long = &H5 CountryCode = GetLocaleValue(LOCALE_ICOUNTRY, "CountryCode") End Property
Man könnte hier auch auf die Deklaration der Konstanten verzichten und den Wert direkt an die Methode übergeben. Ich habe sie integriert, um über den Originalnamen der Konstante im Zweifel in der MSDN weitere Informationen auffinden zu können.
4 Datei- und Verzeichnisdialoge In 4.1 wird ein Windows-Dateidialog vorgestellt, der auch als Methode des Common Dialog Steuerelements (Comdlg32.ocx) verfügbar ist. VB-Entwickler sind also fein raus, ebenso wie die VBA-Entwickler, die keine Hemmungen haben, Steuerelemente mit ihren Anwendungen zu verteilen. Aber für alle VBA-Entwickler, die keine Steuerelemente mit Ihren Anwendungen verteilen wollen, sind die beiden vorgestellten Dialoge nicht ersetzbar. Zwar bieten einzelne Office-Applikationen solche oder ähnliche Dialoge als Methoden unterschiedlicher Objekte an, doch innerhalb einer ExcelAnwendung damit eine XML-Datei zu speichern, ist schlechterdings nicht möglich. Kapitel 4.2 bietet einen Windows-Dialog, der auch nicht über Microsoft-Steuerelemente hervorgelockt werden kann. Dies ist in jedem Falle ein echter Gewinn für Ihre Anwendungen.
4.1 Dateidialoge Um den in Abb. 4.1 dargestellten Dateidialog hervorzurufen, genügt im Prinzip der Aufruf einer einzigen Funktion, die wie folgt deklariert wird: Declare Function GetOpenFileName Lib "comdlg32.dll" Alias "GetOpenFileNameA" (pOpenfilename As OPENFILENAME) As Long
Aber auch der Dialog aus Abbildung 4.2 geht im Prinzip auf eine Funktion zurück, deren Deklaration Sie hier sehen können: Declare Function GetSaveFileName Lib "comdlg32.dll" Alias "GetSaveFileNameA" (pOpenfilename As OPENFILENAME) As Long
Die Funktionen produzieren zwei sehr ähnliche Dialoge mit zwei sehr ähnlichen Funktionen, die auch beide den OPENFILENAME-Datentyp verwenden. Was liegt also näher, als die beiden Aufrufe in einer private Methode enden zu lassen, die wiederum von zwei öffentlichen Methoden adressiert wird. Abbildung 4.3 gibt diesen Aufbau wieder. Wenden wir uns nun der Struktur OPENFILENAME zu.
72
Abbildung 4.1: Datei Öffnen-Dialog
Abbildung 4.2: Datei Speichern-Dialog
Abbildung 4.3: Struktur des Dateidialogaufrufs
4
Datei- und Verzeichnisdialoge
Dateidialoge
4.1.1
73
OPENFILENAME-Struktur
Die OPENFILENAME-Struktur besteht aus 20 einzelnen Elementen, die beschrieben sind.
lStructSize Long, Länge der kompletten Struktur in Bytes: Len(OPENFILENAME)
hWndOwner Long, Handle des Besitzer dieses Dialoges oder 0
hInstance Long, Handle der aufrufenden Instanzen, das in VB(A) ohne Bedeutung ist.
lpstrFilter String, NULL-terminierter String mit Dateitypinformationen. Diese etwas kryptische Zeichenkette wird durch die Funktion ComposeFileFilter zusammengesetzt.
lpstrCustomFilter String, Filter ähnlich lpstrFilter, für uns jedoch ohne Bedeutung.
nMaxCustFilter Long, Länge von lpstrCustomFilter
nFilterIndex Long, lfd. Nummer des vorselektierten Filters in lpstrFilter. Dieser Wert wird auf 1 eingestellt.
lpstrFile String, NULL-terminierter, vollständiger Name der ausgewählten Datei. Dieser Wert muss in ausreichender Größe (MAX_PATH = 260) vorbelegt sein.
nMaxFile Long, Nettogröße von lpstrFile
74
4
Datei- und Verzeichnisdialoge
lpstrFileTitle String, NULL-terminierter Name (ohne Pfad) der ausgewählten Datei. Dieser Wert muss in ausreichender Größe vorbelegt sein, wenn seine Rückgabe erwünscht ist.
nMaxFileTitle Long, Nettolänge von lpstrFileTitle
lpstrInitialDir String, anzuzeigendes Startverzeichnis
lpstrTitle String, anzuzeigender Titel des Dialogs
flags Long, 26 teils miteinander kombinierbare Flags zur Steuerung des Dialogs
nFileOffset Integer, Zeiger auf erste Stelle des Dateinamens in lpstrFile
nFileExtension Integer, Zeiger auf erste Stelle der Dateierweiterung in lpstrFile
lpstrDefExt String, Default-Dateierweiterung für nicht existierende Dateien (siehe flags)
lCustData Long, Parameter, der zur Hook-Prozedur gesendet wird. Zum Glück läuft der Dialog ausgezeichnet ohne Hooks.
lpfnHook Long, Zeiger auf Hook-Prozedur, über die zukünftig der Nachrichtenverkehr des Dialogs abgewickelt wird.
lpTemplateName String, Name der Vorlagenressource, was wir auch getrost ignorieren dürfen.
Dateidialoge
75
Die Argumente bereiten keine Schwierigkeiten, einzig lpstrFilter fällt ein wenig aus dem Rahmen, weshalb dieses Argument auch durch eine eigene Funktion versorgt wird.
4.1.2
ComposeFileFilter-Funktion
Wer sich bereits mit den Dialogen aus dem Comdlg32.ocx herumgeplagt hat, kennt den Aufbau des Dateifilters (siehe Abbildung 4.4). Im ersten Teilstring steht der anzuzeigende Text des Filters. In den Abbildungen 4.1 und 4.2 lautet dieser »Extensible Markup Language (*.xml)«. Neben dem anzuzeigenden Text benötigt der Dialog noch die Dateimaske »*.xml«. Beide Teilzeichenketten sind am Ende jeweils durch ein NULL (Chr(0)) begrenzt. Sollen mehrere Dateitypen angezeigt werden, so werden Anzeige und Dateimaske dieses zweiten Dateityps einfach hinter den ersten Filterausdruck angefügt. In diesem Falle müssen Sie den Filter am Ende mit einem weiteren NULL begrenzen.
Abbildung 4.4: Aufbau des Dateifilters
Die Entwickler des Comdlg32.ocx haben sich ihre Arbeit allzu leicht gemacht und fordern von uns den Dateifilter exakt so, wie er von der OPENFILENAME-Struktur erwartet wird. Zwischen »xml« und »Extensible Markup Language« besteht jedoch eine völlig eindeutige Beziehung, die in der Registry nachzulesen ist. Und genau das werden wir auch tun. Deshalb können wir uns darauf beschränken, die gewünschten Dateitypen in der Form »xml« oder »xml;xsl« zu übergeben, und die Klartextnamen werden von der Komponenten selbst ermittelt. Hier nun die Methode ComposeFileFilter: Private Function ComposeFileFilter(ByVal strExtensions As String) _ As String 'Argumente: ' strExtension: Dateierweiterungen, Semikolon-getrennt Dim strExt() As String 'Array zur Aufnahme der Dateierweiterungen Dim iExt As Long 'Zeiger in strExt() If strExtensions = "" Then ComposeFileFilter = "" Else strExt = Split(strExtensions, ";") For iExt = 0 To UBound(strExt) If InStr(1, strExt(iExt), ".") = 0 Then
76
4
Datei- und Verzeichnisdialoge
'nur Dateierweiterung, z.B. "xml" ComposeFileFilter = ComposeFileFilter & _ cSysReg.ExtensionToFileType(strExt(iExt)) & " (*." & _ strExt(iExt) & ")" & Chr(0) & "*." & strExt(iExt) & Chr(0) Else 'Dateimaske, z.B. "Import*.xml" ComposeFileFilter = ComposeFileFilter & strExt(iExt) & _ Chr(0) & strExt(iExt) & Chr(0) End If Next End If If UBound(strExt) > 0 Then 'mit einem weiteren NULL anschließen, wenn mehrere Einzelfilter ComposeFileFilter = ComposeFileFilter & Chr(0) End If End Function
Die hervorgehobene Stelle ermittelt nun über die in Kapitel 8 vorgestellte RegistryKomponente den dem Dateityp zugeordneten Namen (siehe 8.11). Durch If InStr(1, strExt(iExt), ".") = 0 Then
wird in Dateierweiterungen (»xml«) und Dateimasken (»Import*.xml«) unterschieden. Solchermaßen ausgestattet können wir uns nun an die private Methode GetFileName heranwagen.
4.1.3
GetFileName-Funktion
Die Funktion GetFileName nimmt alle Argumente entgegen, die sie benötigt und verabreicht sie an OpenFile. In Abhängigkeit von lngMode (enumOpenFileName oder enumSaveFileName) wird GetOpenFileName oder GetSaveFileName aufgerufen. Private Function GetFileName(ByVal lngMode As enumSaveOpenFileName, _ ByVal strInitialPath As String, _ ByVal strFileName As String, _ ByVal strFileTypes As String, _ Optional ByVal strTitle As String, _ Optional ByVal hWndOwner As Long) _ As String 'Argumente: ' lngMode: Modus (Datei Öffnen oder Speichern) ' strInitialPath: Startverzeichnis ' strFileName: vorgeschlagener Dateiname ' strFileTypes: Dateitypen, ;-getrennt ("xls" oder "doc;txt") ' strTitle: Dialogtitel, wenn "", wird "Öffnen" angezeigt ' hWndOwner: Handle des Owner-Fensters 'Rückgabe: vollständiger Name der ausgewählten Datei _ ' oder "" bei Abbruch Dim OpenFile As OPENFILENAME 'Daten- und Parameterstruktur OpenFile.hWndOwner = hWndOwner
Dateidialoge
77
OpenFile.lStructSize = Len(OpenFile) OpenFile.lpstrFilter = ComposeFileFilter(strFileTypes) OpenFile.nFilterIndex = 1 OpenFile.lpstrFile = strFileName & String(260 – Len(strFileName), 0) OpenFile.nMaxFile = Len(OpenFile.lpstrFile) – 1 OpenFile.lpstrFileTitle = OpenFile.lpstrFile OpenFile.nMaxFileTitle = OpenFile.nMaxFile OpenFile.lpstrInitialDir = strInitialPath If strTitle <> "" Then OpenFile.lpstrTitle = strTitle End If If lngMode = enumOpenFileName Then APIGetOpenFileName OpenFile Else APIGetSaveFileName OpenFile End If GetFileName = ClearAPIString(OpenFile.lpstrFile) End Function
Da die Rückgabe – sofern der Dialog nicht abgebrochen wurde – NULL-terminiert ist, sorgt ClearAPIString für eine Rückgabe ohne diese störenden NULLs.
4.1.4
Die öffentlichen Methoden
GetFileName ist eine private Funktion. Als Schnittstelle benötigen wir also etwas anderes: Public Function GetOpenFileName(ByVal InitialPath As String, _ ByVal FileName As String, _ ByVal FileTypes As String, _ Optional ByVal Title As String, _ Optional ByVal hWndOwner As Long) _ As String GetOpenFileName = GetFileName(enumOpenFileName, InitialPath, _ FileName, FileTypes, Title, hWndOwner) End Function
sowie Public Function GetSaveFileName(ByVal InitialPath As String, _ ByVal FileName As String, _ ByVal FileTypes As String, _ Optional ByVal Title As String, _ Optional ByVal hWndOwner As Long) _ As String GetSaveFileName = GetFileName(enumSaveFileName, InitialPath, _ FileName, FileTypes, Title, hWndOwner) End Function
Die Unterschiede liegen also nur im ersten Argument, das den Modus der von den Methoden jeweils aufgerufenen Funktion GetFileName steuert.
78
4
Datei- und Verzeichnisdialoge
4.2 Farbendialog Der in Abbildung 4.5 zu sehende Farbendialog folgt dem Muster der beiden anderen Dialoge: eine Struktur wird parametriert und an eine API-Funktion übergeben, die wiederum die Struktur mit der Rückgabe des Dialogs füllt. Die Funktion wird wie folgt deklariert: Declare Function APIChooseColor Lib "comdlg32.dll" Alias "ChooseColorA" (pChoosecolor As CHOOSECOLOR) As Long
Und auch dieser Dialog bietet etwas, das selbiger aus der Comdlg32.ocx nicht bietet: die Verwendung der 16 benutzerdefinierten Farben.
4.2.1
CHOOSECOLOR-Struktur
Neun Argumente weist CHOOSECOLOR auf:
lStructSize Long, Länge der kompletten Struktur in Bytes: Len(CHOOSECOLOR).
hWndOwner Long, Handle des Besitzer dieses Dialoges oder 0.
hInstance Long, Handle der aufrufenden Instanzen, das in VB(A) ohne Bedeutung ist.
rgbResult Long, der RGB-Wert, wenn eine Farbe gewählt wurde. Dieser Wert kann auch mit einer Farbe vorbelegt werden, sofern CC_RGBINIT in flags enthalten ist.
lpCustColors Long, Zeiger auf ein Array von 16 Long-Werten, in denen die benutzerdefinierten Farben übergeben und wieder entgegengenommen werden können.
flags Long, eine Kombination aus folgenden Konstanten:
Farbendialog
79
Abbildung 4.5: Farbendialog Konstante
Wert
Bedeutung
CC_ANYCOLOR
&H100
Erlaubt auch die Auswahl von Nicht-VGA-Farben.
CC_FULLOPEN
2
Startet den Dialog mit eingeblendeter Farbdefinition.
CC_PREVENTFULLOPEN
4
Startet den Dialog ohne Farbdefinition und disabled die Schaltfläche zum Einblenden der Farbdefinition.
CC_RGBINIT
1
Ermöglicht die Übergabe einer Initialisierungsfarbe.
CC_SOLIDCOLOR
&H80
Bewirkt, dass statt einer Nicht-VGA-Farben eine passende VGAFarbe verwendet wird.
Tabelle 4.1: Flags-Konstanten der CHOOSECOLOR-Struktur
lCustData Long, Parameter, der zur Hook-Prozedur gesendet wird. Zum Glück läuft der Dialog ausgezeichnet ohne Hooks.
lpfnHook Long, Zeiger auf Hook-Prozedur, über die zukünftig der Nachrichtenverkehr des Dialogs abgewickelt wird.
lpTemplateName String, Name der Vorlagenressource, was wir auch getrost ignorieren dürfen.
80
4
4.2.2
Datei- und Verzeichnisdialoge
CustomizedColor-Eigenschaft
Die Komponente pflegt ein privates Long-Array für die benutzerdefinierten Farben: Private lngCustColors(15) As Long
Ein Paar von Eigenschaftsroutinen bildet die Schnittstelle: Public Property Let CustomizedColor(ByVal Index As Long, _ ByVal NewColor As Long) If Index < 1 Or Index > 16 Then Err.Raise 9, "Der Index muss zwischen 1 und 16 liegen." End If lngCustColors(Index – 1) = NewColor End Property Public Property Get CustomizedColor(ByVal Index As Long) As Long If Index < 1 Or Index > 16 Then Err.Raise 9, "Der Index muss zwischen 1 und 16 liegen." End If CustomizedColor = lngCustColors(Index – 1) End Property
Obwohl diese Schnittstelle von einem Entwickler für einen Entwickler (der weiß, dass Arrays üblicherweise (bis VB 6) bzw. generell (ab VB 7) nullbasiert sind) geschrieben ist, definiere ich Indizes grundsätzlich zwischen 1 und Dings.Count.
4.2.3
GetColor-Methode
Die Methode erzeugt eine CHOOSECOLOR-Variable und parametriert die Elemente lStructSize, hWndOwner, lpCustColor und Flags. Die Flags können Sie natürlich nach Belieben ändern oder – die Komfortlösung – über Eigenschaften einstellbar machen. Public Function GetColor(Optional InitColor As Long = -1, _ Optional ByVal hWndOwner As Long) As Long 'Argumente: ' InitColor: Initalisierungsfarbe oder -1 für keine ' hWndOwner: Handle des aufrufenden Fensters 'Rückgabe: RGB-Farbe oder -1, wenn Dialog abgebrochen wurde Dim tChooseColor As CHOOSECOLOR 'Datenstruktur für APIChooseColor Dim lngReturn As Long 'Rückgabe aus Funktion tChooseColor.lStructSize = Len(tChooseColor) tChooseColor.hwndOwner = hWndOwner tChooseColor.lpCustColors = VarPtr(lngCustColors(0)) If InitColor = -1 Then tChooseColor.Flags = CC_SOLIDCOLOR + CC_FULLOPEN Else tChooseColor.Flags = CC_SOLIDCOLOR + CC_FULLOPEN + CC_RGBINIT tChooseColor.RgbResult = InitColor End If lngReturn = APIChooseColor(tChooseColor)
Verzeichnisdialog
81
If lngReturn = 0 Then GetColor = -1 Else GetColor = tChooseColor.RgbResult End If End Function
4.3 Verzeichnisdialog Die Rückgabe eines Dateinamens zum Öffnen oder Schließen funktioniert – mit Abstrichen – auch ohne die hier vorgestellte Komponente. Ein Verzeichnisdialog, wie in Abb. 4.6 gezeigt, ist jedoch eine krasse Lücke, die wir nun schließen werden. Auch hinter diesem Dialog steckt eine Funktion, die über eine Struktur mit uns kommuniziert. Die Funktion hört auf den Namen SHBrowseForFolder und die Funktion heißt BROWSEINFO. SHBrowseForFolder wird folgendermaßen deklariert: Declare Function SHBrowseForFolder Lib "shell32" (lpbi As BrowseInfo) As Long
4.3.1
BROWSEINFO-Struktur
BROWSEINFO ist folgendermaßen aufgebaut.
hWndOwner Long, Handle des Besitzers dieses Dialoges oder 0
pIDLRoot Long, eine Struktur eines Namespace-Objekts für das Rootverzeichnis oder 0 als Long für Desktop-Ordner
pszDisplayName Long, der reine Name des ausgewählten Verzeichnisses
lpszTitle String, Titel des Dialogs
ulFlags Long, Kombination von bis zu 13 Konstanten, die das Verhalten des Dialogs steuern. Wir verwenden BIF_RETURNONLYFSDIRS und BIF_DONTGOBELOWDOMAIN, die bedeuten, dass nur Einträge des Dateisystems und keine Laufwerke unterhalb der Domänenebene ausgewählt werden können.
82
4
Datei- und Verzeichnisdialoge
Abbildung 4.6: Verzeichnis-Dialog
lpfnCallback Long, Adresse der Callback-Funktion.
lParam Long, Parameter, der zur Callback-Prozedur gesendet werden kann.
iImage Long, Index des Icons des ausgewählten Eintrags aus der System Image List.
4.3.2
GetPath-Methode
Der Aufruf der Methode gestaltet sich recht einfach. Das einzige, was übergeben wird, ist das Handle des aufrufenden Fensters. Wird also ein gültiges Handle übergeben, so erscheint der Dialog als Child dieses Fensters und ohne Knopf in der Taskleiste. Eine 0 als Handle führt dazu, dass der Dialog mit einem eigenen Knopf in der Taskleiste erscheint. Der Hintergrund ist recht einfach. Wird ein Parent-Window eingeblendet, so erscheinen automatisch alle ihm zugeordneten Child-Windows. Die separate Repräsentation eines solchen Child-Windows durch einen Button in der Taskleiste ist nicht erforderlich. Erzeugt eine Applikation (oder besser ein Fenster) hingegen Fenster, ohne ihnen einen Parent zuzuordnen, so würde dieses nach Deaktivierung nicht mit seinem Parent wieder aktiviert werden und erst durch Minimieren aller Fenster sichtbar werden. Ein Beispiel gefällig? Beobachten Sie einmal die Dialoge der Systemsteuerung.
Verzeichnisdialog
83
Das macht Microsoft so menschlich: Neben all der Genialität und Professionalität streuen sie dennoch hin und wieder ein paar Schlampereien in ihre Software. Public Function GetPath(Optional ByVal hWndOwner As Long) As String 'Argumente: ' hWndOwner: Handle des aufrufenden Fensters 'Rückgabe: vollständiger Name des ausgewählten Verzeichnisse _ ' oder "" bei Abbruch Dim lngIDList As Long 'Zeiger auf das ausgesuchte Verzeichnis Dim strTitle As String 'Titel des Dialogs Dim tBrowseInfo As BrowseInfo tBrowseInfo.hWndOwner = hWndOwner strTitle = "Bitte wählen Sie ein Verzeichnis aus" tBrowseInfo.lpszTitle = strTitle tBrowseInfo.ulFlags = BIF_RETURNONLYFSDIRS + BIF_DONTGOBELOWDOMAIN lngIDList = SHBrowseForFolder(tBrowseInfo) If lngIDList <> 0 Then GetPath = Space(260) SHGetPathFromIDList lngIDList, GetPath GetPath = Left(GetPath, InStr(GetPath, vbNullChar) – 1) End If End Function
Bedauerlicherweise schreibt die Funktion den Namen des ausgewählten Verzeichnisses nicht in die BROWSEINFO-Struktur, so wie es mit dem isolierten Pfadnamen passiert, vielmehr erhalten wir einen Zeiger auf eine ItemIDList als Rückgabe. Die APIFunktion SHGetPathFromIDList ist aber in der Lage, diesen Zeiger in einen für unsere Zwecke geeigneten String zu konvertieren. Declare Function SHGetPathFromIDList Lib "shell32" (ByVal pidList As Long, ByVal lpBuffer As String) As Long
Neben dem Zeiger auf die ItemIDList erhält die Funktion einen mit 260 Zeichen vorbelegten String, in den diese wiederum den gewünschten Namen schreibt. Diese NULL-terminierte Rückgabe wird durch ClearAPIString bereinigt. Auch auf die Rückgabe des in pszDisplayName verborgenen Namen des Verzeichnisses kann verzichtet werden, da uns ohnehin nicht der isolierte Name interessiert, sondern der vollständige Bezug inklusive Laufwerk.
5 Systeminformationen Die Klasse clsSysInfo hält eine Reihe nützlicher Informationen bereit. Darunter sind einfache Zugriffe zu finden, aber auch welche, die es in sich haben. So hat es mich selbst einigermaßen überrascht, dass es keine Funktion gibt, die eine IP-Adresse auf einen Schlag zurückgibt: Dim byteAddress(3) As Byte Dim iByte As Long Dim strAddress As String GetIPAddress byteAddress(0) For iByte = 0 To UBound(byteAddress) strAddress = strAddress & byteAddress(iByte) & "." Next strAddress = Left(strAddress, Len(strAddress) – 1)
Doch dieser Code ist eine pure Erfindung, denn es gibt keine Funktion GetIPAddress. In Kapitel 5.4 werden wir aber das Geheimnis lüften und die IP-Adresse ermitteln, etwas umständlicher vielleicht. Abschließend werden wir noch eine GUID erzeugen. GUIDs (Globally Unique IDentifier) sind weltweit eindeutige 16-Byte-große hexadezimale IDs, die unter Verwendung von Hardware-Informationen (vermutlich MAC-ID der Netzwerkkarte) und der UTCZeit erzeugt wird. Sie enthält auch Mechanismen, die verhindern, dass durch Zeitmanipulationen identische IDs erzeugt werden können. Microsoft sagt, dass man 3240 Jahre lang 10.000.000 GUIDs je Computer erzeugen kann, ohne Gefahr zu laufen, sich eine doppelte ID einzufangen. Die GUID {00000205-0000-0010-8000-00AA006D2EA4} beispielsweise gehört zur Microsoft ActiveX Data Objects 2.5 Library, also zur ADO 2.5.
5.1 Anmeldename Der Anmeldenamen kann über die Funktion GetUserName ermittelt werden: Declare Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA" (ByVal lpBuffer As String, nSize As Long) As Long
86
5
Systeminformationen
lpBuffer ist ein String, der, hinreichend vorbelegt, an die Funktion übergeben wird und mit dem Anmeldenamen gefolgt von einem NULL zurückkommt. nSize transportiert die Größe der Vorbelegung von lpBuffer und beinhaltet auf dem Rückweg die Bruttolänge des in lpBuffer enthaltenen Namens. Der Code: Public Property Get LoginName() As String Dim strBuffer As String * 255 'Zwischenvariable für API-Funktion Dim nSize As Long 'Größe von strBuffer Dim lngResult As Long 'Rückgabe der Funktion nSize = 255 lngResult = GetUserName(strBuffer, nSize) LoginName = Left(strBuffer, nSize – 1) End Property
5.2 NetBIOS- und DNS-Name Der Unterschied zwischen NetBIOS-Name und DNS-Name blieb mir bislang verborgen, aber zwei Aussagen hätte ich zu bieten: 1. Der NetBIOS-Name wird beim Startup aus der Registry gelesen. 2. Der DNS-Name wird sicher von der GetHostByName interpretiert. Da die beiden Aussagen einander weder bedingen noch ausschließen, sind wir genauso weit wie vorher. Bei Änderung des NetBIOS-Namen wird der DNS-Name übrigens mit dem geänderten NetBIOS-Namen überschrieben, wobei dieser hierbei in Kleinbuchstaben umgewandelt wird.
5.2.1
NetBIOS-Name bzw. Computer-Name
Den aus der Registry ausgelesenen NetBIOS-Name gibt die Funktion GetComputerName auf Anfrage zurück: Declare Function GetComputerName Lib "kernel32" Alias "GetComputerNameA" (ByVal lpBuffer As String, nSize As Long) As Long
Funktionsweise und Parametrierung entsprechen der Funktion GetUserName, also lpBuffer vorbelegen sowie nSize einstellen und nach Rückgabe nSize – 1 Zeichen aus lpBuffer herausschneiden: Public Property Get NetBIOSName() As String Dim strBuffer As String * 255 'Zwischenvariable für API-Funktion Dim nSize As Long 'Größe von strBuffer Dim lngResult As Long 'Rückgabe der Funktion nSize = 255 lngResult = GetComputerName(strBuffer, nSize) NetBIOSName = Left(strBuffer, nSize) End Property
Das Betriebssystem
5.2.2
87
DNS-Name
Falls Sie DNS im Einsatz haben, könnte dieser Name für Sie von Interesse sein. Verantwortlich ist die GetHostName-Funktion: Declare Function GetHostName Lib "WSOCK32.DLL" Alias "gethostname" (ByVal HostName As String, ByVal HostLen As Integer) As Long
HostName muss hinreichend vorbelegt werden, HostLen beinhaltet die Länge der Vorbelegung, gibt aber keinen korrigierten Wert zurück. Aber das können wir bereits unschwer an dem Schlüsselwort ByVal in der Deklaration erkennen. GetHostName ist Bestandteil der WinSock-DLL, und diese Bibliothek muss vor der ersten Verwendung durch eine Anwendung initialisiert werden. In Abschnitt 5.4.1 werden die erforderlichen Schritte vorgestellt. Hier mag es genügen, dass Me.Initialize aufgerufen wird, wenn die Variable bInitialized False signalisieren sollte. Public Property Get DNSName() As String Dim strBuffer As String * 255 'Zwischenvariable für API-Funktion Dim lngSize As Long 'Größe von strBuffer Dim lngResult As Long 'Rückgabe der Funktion If Not bInitialized Then Me.Initialize End If lngResult = GetHostName(strBuffer, 255) If lngResult = SOCKET_ERROR Then Err.Raise 9999, , "Windows Sockets Error: " & WSAGetLastError() Else DNSName = Left(strBuffer, InStr(1, strBuffer, Chr(0)) – 1) End If End Property
Im Fehlerfalle gibt die Funktion SOCKET_ERROR (-1) zurück; WSAGetLastError gibt die entsprechende Fehlernummer zurück. Die MSDN-Library enthält die rund 60 Fehlercodes und ihre Erklärung unter »Error Codes in the API« in »Windows Sockets: Platform SDK«. WSAGetLastError ist unspektakulär: Declare Function WSAGetLastError Lib "WSOCK32.DLL" () As Long
5.3 Das Betriebssystem Nicht zuletzt beim Einsatz diverser API-Funktionen ist es gut zu wissen, ob das aktuelle Betriebssystem nun Windows NT ist oder Win 95 bzw. Win 98. Ein Antwort auf diese Frage kann uns die Funktion GetVersionEx liefern.
88
5
5.3.1
Systeminformationen
GetVersionEx-Funktion
Die GetVersionEx-Funktion füllt die Struktur OSVERSIONINFO, aus der wir alle benötigten Informationen zum Betriebssystem entnehmen können. Declare Function GetVersionEx Lib "kernel32" Alias "GetVersionExA" (lpVersionInformation As OSVERSIONINFO) As Long
lpVersionInformation Hinter diesem Argument verbirgt sich eine OSVERSIONINFO-Struktur, die im folgenden Abschnitt vorgestellt wird.
Rückgabe Im Fehlerfalle ist die Rückgabe 0, andernfalls ein Wert ungleich 0 (meist 1).
5.3.2
OSVERSIONINFO-Struktur
Private Type OSVERSIONINFO dwOSVersionInfoSize As Long dwMajorVersion As Long dwMinorVersion As Long dwBuildNumber As Long dwPlatformId As Long szCSDVersion As String * 128 End Type
dwOSVersionInfoSize Die Größe OSVERSININFO-Struktur in Bytes. Diesen Wert müssen wir übergeben.
dwMajorVersion Das ist die Nummer der Hauptversion bei Windows NT.
dwMinorVersion Die Nummer der Unterversion des Betriebssystems.
dwBuildNumber Mit einem Build wird ein Kompilat identifiziert.
Das Betriebssystem
89
dwPlatformId Dieser Wert bildet die Gruppen Win32S auf Windows 3.1, Win 95 oder 98 sowie Win NT. Als Platform-IDs sind folgende Konstanten vorhanden: Konstante
Wert
Bedeutung
VER_PLATFORM_WIN32s
0
VER_PLATFORM_WIN32_WINDOWS
1
Win 95 und Win 98
VER_PLATFORM_WIN32_NT
2
Win NT Win 2000
Tabelle 5.1: Die Plattformkonstanten
Diese Platform-ID ist also dazu geeignet, die Profi-Betriebssysteme NT und Windows 2000 von Windows 95 und 98 abzugrenzen.
szCSDVersion Bei Windows NT verbirgt sich dahinter das installierte Service Pack im Klartext. Die folgende Tabelle zeigt die einzelnen Werte bei den heutigen Betriebssystemen: Betriebssystem Platform-ID
Major Version
Minor Version
Win 32 S
VER_PLATFORM_WIN32s
Win 95
VER_PLATFORM_WIN32_WINDOWS
0
Win 98
VER_PLATFORM_WIN32_WINDOWS
>0
NT 3.51
VER_PLATFORM_WIN32_NT
3
51
NT 4
VER_PLATFORM_WIN32_NT
4
0
Win 2000
VER_PLATFORM_WIN32_NT
5
0
Tabelle 5.2: Platform-ID sowie Major und Minor Versions der MS-Betriebssysteme
5.3.3
Der Code
Durch die Initialize-Methode der Klasse wird die Prozedur LoadVersionStructure aufgerufen: Private Sub LoadVersionStructure() Dim lngResult As Long 'Rückgabe udtVersionInfo.dwOSVersionInfoSize = Len(udtVersionInfo) lngResult = GetVersionEx(udtVersionInfo) If lngResult = 0 Then Err.Raise 440, "clsSysInfo:LoadVersionStructure", _
90
5
Systeminformationen
"GetVersionEx failed to load OSVERSIONINFO-type." End If End Sub
Danach steht die private udtVersionInfo zur Auswertung in den betreffenden Eigenschaftsroutinen bereit. Public Property Get Win95() As Boolean If udtVersionInfo.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS Then Win95 = (udtVersionInfo.dwMinorVersion = 0) End If End Property Public Property Get Win98() As Boolean If udtVersionInfo.dwPlatformId = VER_PLATFORM_WIN32_WINDOWS Then Win98 = (udtVersionInfo.dwMinorVersion > 0) End If End Property Public Property Get WinNT() As Boolean WinNT = (udtVersionInfo.dwPlatformId = VER_PLATFORM_WIN32_NT) End Property Public Property Get NTVersion() As Long If udtVersionInfo.dwPlatformId = VER_PLATFORM_WIN32_NT Then NTVersion = udtVersionInfo.dwMajorVersion End If End Property Public Property Get ServicePack() As String ServicePack = udtVersionInfo.szCSDVersion End Property
Zumindest für die Belange der weiteren API-Zugriffe sind wir hinreichend gerüstet, denn zumeist reicht die Unterscheidung in Win 95 und 98 einerseits und NT bzw. Windows 2000 andererseits aus. In einigen Sonderfällen fällt auch Windows 98 in die Kategorie von Windows NT, was durch die Kombination der Eigenschaften Win98 und WinNT erreicht werden kann.
5.4 IP-Adresse Bevor die IP-Adresse ermittelt werden kann, muss sichergestellt sein, dass die Windows Sockets initialisiert sind. Das werden wir in 5.4.1 bewerkstelligen. In 5.4.2 wird die Sitzung aufgehoben. Die Funktion zur Ermittlung der lokalen IP-Adresse wiederum schaut nicht einfach in der Registry nach, sie übergibt stattdessen den DNSNamen, der laut MSDN sicher aufgelöst (will be successfully parsed) wird. Die betreffende Funktion kann nicht nur den DNS-Namen des eigenen Rechners ermitteln, sie kann vielmehr auch die Namen anderer aktiver Rechner der Domäne auflösen. Aus
IP-Adresse
91
diesem Grunde ist dieser Vorgang in einer eigenen Methode ausgegliedert, die in 5.4.6 beschrieben ist. 5.4.7 schließlich enthält die Eigenschaftsroutine zur Rückgabe der IPAdresse des eigenen Rechners.
5.4.1
InitializeSockets
In 5.2.1 sprachen wir bereits über die notwendige Initialisierung des WSA (Windows Sockets API). Zum Glück gibt’s in der MSDN-Library zwei Visual Basic Beispiele, allerdings scheint der Autor nach Codezeilen bezahlt worden zu sein. Mit zwei Hilfsroutinen kommt er zusammen auf 40 Zeilen, aber mit einem Drittel davon funktioniert es auch und man hat vor allem auch eine Chance zu verstehen, was da passiert. Ich erspare in die LP-Version, wir werden die Kurzfassung untersuchen. Initialisiert wird durch den Aufruf der Funktion WSAStartup: Declare Function WSAStartup Lib "WSOCK32.DLL" (ByVal wVersionRequired As Integer, lpWSAData As WSADATA) As Long
wVersionRequired Darin übergeben wir die größte Version, die wir noch nutzen können. Im LowByte steckt die Major und im HighByte die Minor Version. In Form der Konstanten WSARequVersion übergeben wir &H101, und das müsste eigentlich überall erfüllt sein.
lpWSAData Eine WSADATA-Struktur: Name
Typ
wVersion
Integer Version gemäß Anforderung (Aufbau wie wVersionRequired)
wHighVersion
Integer größte verfügbare Version
szDescription(256)
Byte
maximal 256 (+ NULL) Zeichen langer Text des Herstellers
szSystemStatus(128)
Byte
maximal 128 (+ NULL) Zeichen zusätzlicher Text
iMaxSockets
Integer ursprünglich Zahl der verfügbaren Sockets, soll aber nun ignoriert werden
iMaxUdpDg
Integer maximale Nachrichtengröße, die auch ignoriert werden soll
lpszVendorInfo
Long
Tabelle 5.3: WSADATA-Struktur
Bedeutung
Zeiger auf Informationen, die auch ignoriert werden soll
92
5
Systeminformationen
Rückgabe 0, wenn alles geklappt hat, sonst einer der folgenden Werte: Konstante
Wert
Bedeutung
WSASYSNOTREADY
10091
Netzwerk Subsystem ist nicht bereit
WSAVERNOTSUPPORTED
10092
angeforderte Version ist nicht verfügbar
WSAEINPROGRESS
10036
eine Windows Sockets 1.1 blockiert den Vorgang
WSAEPROCLIM
10067
Grenze der unterstützten Instanzen erreicht
WSAEFAULT
10014
lpWSAData ist ein ungültiger Zeiger
Tabelle 5.4: Rückgabe der WSAStartup-Funktion
In der folgenden Prozedur ist ausnahmsweise der ErrorHandler dargestellt, was damit zusammenhängt, dass dort die Routine CleanupSockets aufgerufen werden muss. Private Sub InitializeSockets() Const WSARequVersion As Integer = &H101 Dim udtWSADATA As WSADATA 'WSADATA-Struktur Dim lngReturn As Long 'Rückgabe aus WASStartup lngReturn = WSAStartup(WSARequVersion, udtWSADATA) If lngReturn <> 0 Then Err.Raise 9999, , "Winsock.dll is not responding." End If If udtWSADATA.wVersion < WSARequVersion Then Err.Raise 9999, , "The requested version is not available." End If Exit Sub errHandler: Call CleanupSockets Err.Raise Err.Number, "clsSysInfo:InitializeSockets" End Sub
Wird die Konstante mit &H101 vorbelegt, so dürfte auch auf dem ältesten Win 95 Rechner noch eine funktionsfähige WinSock-Bibliothek aufgestöbert werden können.
5.4.2
CleanupSockets
Wenn man sich schon irgendwo anmelden muss, so gilt das zumeist auch fürs Abmelden: Declare Function WSACleanup Lib "WSOCK32.DLL" () As Long
WSACleanup gibt eine 0 zurück, wenn das Abmelden erfolgreich war. Ein paar Versuche ergaben aber, dass gelegentlich ein halbes Dutzend Cleanups erforderlich waren, bis die Abmeldung einen Wert ungleich 0 zurückgab. Das wird wohl mit unsauberen Programm-
IP-Adresse
93
abbrüchen ohne durchlaufene Cleanup-Routine zusammenhängen, aber der Entschluss reifte relativ schnell, dann eben so lange Cleanups zu machen, bis dieses selbst genug davon hat. Private Sub CleanupSockets() Dim lngReturn As Long 'Rückgabe der Funktion Dim lngCounter As Long 'Durchlaufzähler On Error Resume Next Do lngCounter = lngCounter + 1 lngReturn = WSACleanup() Loop Until lngReturn <> 0 Or lngCounter > 10 End Sub
Der eine nennt es defensive Softwareentwicklung, der andere schlicht Angst, aber ich habe nun mal Angst vor Endlosschleifen. Deshalb wurde die Routine mit einem Durchlaufzähler versehen, der bei zehn Durchläufen den Sack zumacht. Stellen Sie sich mal vor: Der Kunde ruft an und erzählt, dass er Ihr Programm gerade eben mit dem TaskManager abschießen musste! Im Class_Termine-Ereignis wird CleanupSockets natürlich auch aufgerufen.
5.4.3
GetHostByName-Funktion
Die GetHostByName-Funktion gibt Informationen über einen Netzwerkrechner in Form einer HOSTENT-Struktur zurück: Declare Function GetHostByName Lib "WSOCK32.DLL" Alias "gethostbyname" (ByVal strHostname As String) As Long
strHostname DNS-Name des Rechners. Interessanterweise identifiziert die Funktion einen leeren String mit dem lokalen Rechner.
Rückgabe Ein Zeiger auf die HOSTENT-Struktur des Prozesses. Die MSDN empfiehlt dringend, nicht in diesem Adressbereich herumzufummeln und in jedem Falle eine Kopie zu erzeugen, bevor die nächste WSA-Operation durchgeführt wird.
5.4.4
HOSTENT-Struktur
Die HOSTENT-Struktur enthält Informationen über einen Netzwerkrechner.
94
5
Systeminformationen
hName Long, Zeiger auf DNS-Namen des Rechners
hAliases Long, Zeiger auf Array von Alias-Namen des Rechners
hAddrType Integer, Adresstyp (was auch immer das ist)
hLength Integer, Länge der hAddrList in Bytes
hAddrList Long, Zeiger auf NULL-terminierte Liste von IP-Adressen, wobei uns Element 0 interessiert.
5.4.5
CopyMemory-Funktion
Um aus der Rückgabe der GetHostByName-Funktion eine IP-Adresse zu machen, müssen wir dreimal Speicherbereiche kopieren. Und das erledigt diese Funktion für uns: Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (hpvDest As Any, ByVal hpvSource As Long, ByVal cbCopy As Long)
hpvDest Zeiger auf Zielbereich. Um bei der Wahl des Zieldatentyps möglichst keiner Einschränkung zu unterliegen, ist hpvDest ByRef und As Any deklariert. Also Vorsicht bei Strings.
hpvSource Zeiger auf den Quellbereich
cbCopy Anzahl der zu kopierenden Bytes
IP-Adresse
5.4.6
95
GetHostName-Methode
Ich möchte noch einmal die Aussage in Erinnerung rufen, dass wir nicht in der Original-HOSTENT-Struktur arbeiten sollen. Also wird nach Aufruf der GetHostByName eine Kopie dieses Originalspeicherbereichs in udtHOSTENT angelegt. Danach wird die numerische IP-Adresse, die in Network-Byte-Sortierung vorliegt, über einen Long-Werte in das Byte-Array transportiert und abschließend mit ».« separiert in strIPAddress gepackt. Public Function HostByName(ByVal strName As String) As String 'Argumente: ' strName: DNS-Name des Rechners Dim byteIPSegements(3) As Byte 'enthält die 4 Segmente Dim iSegment As Long 'Zeiger in byteIPSegements Dim lngHostAddress As Long 'Adresse der HOSTENT-Kopie Dim lngHostIP As Long 'Adresse des Byte-Arrays aus Dim strIPAddress As String 'zusammengesetzte IP-Adresse Dim udtHOSTENT As HOSTENT 'HOSTENT-Struktur If Not bInitialized Then Me.Initialize 'Adresse des Prozess-HOSTENT ermitteln lngHostAddress = GetHostByName(strName) If lngHostAddress = 0 Then Err.Raise 9999, , strName & " konnte keine IP-Adresse zuge..." End If 'Prozess-HOSTENT duplizieren Call CopyMemory(udtHOSTENT, lngHostAddress, LenB(udtHOSTENT)) 'Network Byte Order über Long in Byte-Array kopieren Call CopyMemory(lngHostIP, udtHOSTENT.hAddrList, 4) Call CopyMemory(byteIPSegements(0), lngHostIP, 4) 'String aus Byte-Array zusammenstellen For iSegment = 0 To 3 strIPAddress = strIPAddress & byteIPSegements(iSegment) & "." Next HostByName = Left(strIPAddress, Len(strIPAddress) – 1) End Function
Die interessanten Stellen sind hervorgehoben.
5.4.7
IPAddress-Eigenschaft
Das eigentlich Aufregende haben wir nun hinter uns gelassen, und die IP-Adresse ist nur noch Formsache: Public Property Get IPAddress() As String If Not bInitialized Then Me.Initialize IPAddress = HostByName(Me.DNSName) End Property
An die HostByName-Methode wird der DNS-Name des eigenen Rechners übergeben und die Rückgabe weitergereicht.
96
5
Systeminformationen
5.5 GUIDs Globally Unique Identifiers können überall dort eingesetzt werden, wo eine eindeutige ID benötigt wird. Das können Objekt-IDs sein, aber auch zum Beispiel Boundaries in einem Multipart MIME-Header.
5.5.1
CoCreateQuid-Funktion
Diese Funktion erzeugt die GUID: Declare Function CoCreateGuid Lib "OLE32.DLL" (pGUID As GUID) As Long
pGUID Eine GUID-Struktur, die jedoch nur aus einem 16 Byte großen Byte-Array besteht.
Rückgabe 0 im Erfolgsfall
5.5.2
GUID-Struktur
Diese Struktur besteht nur aus einem Element:
bytes(15) Byte, Array zur Aufnahme der 16 Bytes
5.5.3
StringFromGUID2-Funktion
StringFromGUID2 konvertiert eine GUID-Struktur in eine lesbare GUID. Declare Function StringFromGUID2 Lib "OLE32.DLL" (pGUID As GUID, ByVal lpszString As String, ByVal lMax As Long) As Long
pGUID gefüllte GUID-Struktur
lpszString Mit 78 Zeichen vorbelegter String. Die Zahl setzt sich zusammen aus 16 je 2-stelligen Hexwerten + 4 Bindestrichen + 2 geschweifte Klammern + ein abschließendes NULL. Das macht 39, und da die Rückgabe ein Unicode-String ist, muss der Wert verdoppelt werden: 78
GUIDs
97
lMax Größe des Arguments lpszString in Byte
Rückgabe Anzahl der benötigten Unicode-Zeichen, also 39
5.5.4
GetNewGUID-Methode
Die Methode selbst erzeugt nun eine GUID-Struktur und wandelt diese anschließend in einen String um, von dem dann abschließend noch die geschweiften Klammern entfernt werden: Public Function GetNewGUID() As String Dim udtGUID As GUID 'GUID-Struktur Dim strGUID As String 'GUID-String Dim nSize As Long 'Bruttolänge des Unicode-GUID strGUID = Space(78) 'GUID erzeugen CoCreateGuid udtGUID 'nach ANSI konvertieren nSize = StringFromGUID2(udtGUID, strGUID, Len(strGUID)) strGUID = Left(StrConv(strGUID, vbFromUnicode), nSize – 1) 'geschweifte Klammern entfernen GetNewGUID = Mid(strGUID, 2, Len(strGUID) – 2) End Function
6 Laufwerke Bei den Laufwerken handelt es sich um ein vergleichsweise übersichtliches Thema. Die Fragen, die einem Rechner in Bezug auf seine Laufwerke gestellt werden können, sind: 왘 Wie viele Laufwerke sind verfügbar? 왘 Wie heißt Laufwerk i? 왘 Ist Laufwerk i bereit? 왘 Wie viele CD-Laufwerke sind verfügbar? 왘 Wie heißt das i-te CD-Laufwerk? 왘 Wie groß ist Laufwerk i? 왘 Wie viel Platz ist auf Laufwerk i noch verfügbar?
Abbildung 6.1: EinTreeView mit Laufwerken
Abbildung 6.1 zeigt das Ergebnis der Komponente clsDrives.
100
6
Laufwerke
Die verfügbaren Funktionen legten es nahe, die Namen der verfügbaren Laufwerke beim Initialisieren der Klasse in einem nullbasierten String-Array abzulegen. Dessen um 1 erhöhte Obergrenze (UBound) ergäbe schon einmal die Anzahl der verfügbaren Laufwerke. Es stellte sich die Frage, ob der Zugriff auf die Laufwerke über den Namen (»C:\«) oder den Index (1) erfolgen soll. Ich entschied mich für den Index.
6.1 Initialisierung der Arrays Neben dem Array, das die Laufwerksbezeichner aufnehmen wird, existiert ein zweites, in dem Typnamen der Laufwerke abgelegt werden. Dieser Typname ermöglicht den Zugriff auf ein gleichnamiges Image einer ImageList, um beispielsweise einen TreeView zu füllen. Diese Arrays sollen beim Initialisieren der Komponente geladen werden. Nun legt die Routine Class_Initialize die unangenehme Eigenheit an den Tag, dass Sie einen eventuell auftretenden Fehler generell mit 440 überbügelt. Mir ist zwar kein Fehler untergekommen, doch wer kann dies schon ausschließen. Aus diesem Grund ist die Initialisierung in eine eigene Prozedur ausgelagert worden, die im Fehlerfalle von anderer Stelle aus erneut aufgerufen werden kann, um den Fehler zu provozieren. Doch dazu später. Wenn man sich auf die Liste der verfügbaren Laufwerke beschränkt, reichen die folgenden fünf Konstanten aus, um den Laufwerkstyp zu bestimmen: Konstante
Wert Typname
Bedeutung
DRIVE_REMOVABLE
2
FloppyDrive
Floppy, Zip oder sonstige Wechselträgermedien
DRIVE_FIXED
3
LocalDrive
lokale Festplatte
DRIVE_REMOTE
4
NetDrive
Netzplatte
DRIVE_CDROM
5
CDDrive
CD-ROM-Laufwerk
DRIVE_RAMDISK
6
LocalDrive
RAM-Disk
Tabelle 6.1: Konstanten und Typnamen der Laufwerkstypen
Zur Ermittlung der verfügbaren Laufwerke stehen zwei Funktionen zur Verfügung. Die erste (GetLogicalDrives) gibt einen Long-Wert zurück, dessen Bits 0 bis 25 für die 26 möglichen Laufwerke A bis Z stehen. Ist Bit 3 gleich True, so existiert Laufwerk D. Die zweite Funktion gibt einen String zurück, in dem NULL-separiert die Namen der Laufwerke stehen. Das hört sich um einiges komfortabler an als die zu erwartende Maskierung, der wir uns bei Verwendung der GetLogicalDrives wohl hätten unterziehen müssen. Die Funktion heißt übrigens GetLogicalDriveStrings.
Initialisierung der Arrays
6.1.1
101
GetLogicalDriveStrings-Funktion
Declare Function GetLogicalDriveStrings Lib "kernel32" Alias "GetLogicalDriveStringsA" (ByVal nBufferLength As Long, ByVal lpBuffer As String) As Long
nBufferLength Dieser Wert signalisiert der Funktion, wie groß das Argument lpBuffer ist.
lpBuffer Dieser String muss für potenziell 26 Laufwerke ausreichend ausgelegt sein. Ein Laufwerksname lautet beispielsweise »C:\«. Mit Trennzeichen und dem abschließenden doppelten NULL ergibt dies 105 Zeichen.
Rückgabe Die Funktion gibt die Länge der tatsächlich genutzten Zeichenkette zurück, ohne das letzte NULL.
6.1.2
Die Prozedur InitializeArrays
Im ersten Teil der Prozedur wird GetLogicalDriveStrings aufgerufen und die so ermittelte Zeichenkette mittels Split in ein Array verwandelt. Private Sub InitializeArrays() ' Arrays initialisieren Dim strBuffer As String 'String mit Laufwerksbezeichnern Dim nSize As Long 'Bruttogröße von strBuffer On Error GoTo errHandler 'Drives-Array füllen strBuffer = Space(200) nSize = GetLogicalDriveStrings(200, strBuffer) strDrives = Split(Left(strBuffer, nSize – 1), vbNullChar) 'DriveTypes-Array füllen strDriveTypes(2) = "FloppyDrive" strDriveTypes(3) = "LocalDrive" strDriveTypes(4) = "NetDrive" strDriveTypes(5) = "CDDrive" strDriveTypes(6) = "LocalDrive" bClassReady = True Exit Sub errHandler: Err.Raise Err.Number, "clsSysDrives:InitializeArrays" End Sub
102
6
Laufwerke
Die Variable bClassReady wird später von allen Eigenschaftsroutinen abgefragt. Ist sie auf False, wird InitializeArrays erneut aufgerufen. Da wir uns dann außerhalb der Prozedur Class_Initialize befinden, bleiben Nummer, Text und Kontext des Fehlers erhalten. Die Eigenschaftsprozeduren, die im Prinzip einzeilig sind, sind nicht mit einem eigenen ErrorHandler ausgestattet und unterstellen somit, dass die aufrufende Stelle ihrerseits abgesichert ist.
6.2 Die Eigenschaft DrivesCount Die um 1 erhöhte obere Grenze des Arrays enthält den gesuchten Wert. Public Property Get DrivesCount() As Long If Not bClassReady Then Call InitializeArrays DrivesCount = UBound(strDrives) + 1 End Property
6.3 Die Eigenschaft DriveName Hinter dieser Eigenschaft steckt der jeweilige Eintrag aus dem Array strDrives. Public Property Get DriveName(ByVal Index As Long) As String If Not bClassReady Then Call InitializeArrays DriveName = strDrives(Index – 1) End Property
6.4 Die Eigenschaft DriveType Diese Eigenschaft ist ein wenig tricky, denn die Rückgabe der Funktion GetDriveType ist als Enumeration ausgelegt, deren Elemente den DRIVE-Konstanten aus Tabelle 6.1 entsprechen. Public Property Get DriveType(ByVal Index As Long) As enumDriveType If Not bClassReady Then Call InitializeArrays DriveType = GetDriveType(strDrives(Index – 1)) End Property
Hier nun die Enumeration: Public Enum enumDriveType enumDriveTypeNotExisting = 1 enumDriveTypeRemoveable = 2 enumDriveTypeLocal = 3 enumDriveTypeRemote = 4
Die Eigenschaft DriveTypeName
103
enumDriveTypeCDRom = 5 enumDriveTypeRamDisk = 6 End Enum
6.4.1
GetDriveType-Funktion
Declare Function GetDriveType Lib "kernel32" Alias "GetDriveTypeA" (ByVal nDrive As String) As Long
nDrive Dieser String enthält den Laufwerksbezeichner. Dieser muss nicht im Root-Verzeichnis enden, solange das übergebene Unterverzeichnis existiert und mit einem BackSlash abgeschlossen ist, können auch Verzeichnis-Strings übergeben werden: "C:\" "C:\WINNT" "C:\WINNT\" "C:\Trullala\"
ergibt ergibt ergibt ergibt
eine eine eine eine
3 1 3 1
als als als als
Rückgabe Rückgabe (Backslash fehlt) Rückgabe Rückgabe (Verzeichnis existiert nicht)
Rückgabe Die Rückgabe ist eine der Konstanten aus Tabelle 6.1, eine 1 für nicht existierende Laufwerke oder eine 0, wenn der Laufwerkstyp nicht bestimmt werden kann. Die beiden letzten Fälle können wir allerdings ausschließen, da GetLogicalDriveStrings die Basis unserer Anfragen ist und ein nicht existierendes oder nicht bestimmbares Laufwerk im Prinzip nur durch einen Hardware-Defekt hervorgerufen werden könnte.
6.5 Die Eigenschaft DriveTypeName Diese Routine ähnelt der unter 6.4 vorgestellten. Allerdings wird die Rückgabe der GetDriveType als Zeiger für das Array strDriveTypes verwendet. Public Property Get DriveTypeName(ByVal Index As Long) As String If Not bClassReady Then Call InitializeArrays DriveTypeName = strDriveTypes(GetDriveType(strDrives(Index – 1))) End Property
6.6 Die Eigenschaft DriveIndex Da die anderen Eigenschaften über den Index des Laufwerks gesteuert werden, benötigen wir eine Funktion, die aus einer Laufwerksbezeichnung den Index ermittelt. Dies erfolgt über den Vergleich des übergebenen Ausdrucks mit allen Elementen des Arrays strDrives.
104
6
Laufwerke
Public Property Get DriveIndex(ByVal strDrive As String) As Long Dim iDrive As Long 'Zeiger in strDrives() On Error GoTo errHandler If Not bClassReady Then Call InitializeArrays For iDrive = 0 To UBound(strDrives) If strDrive = strDrives(iDrive) Then DriveIndex = iDrive + 1 Exit Property End If Next Exit Property errHandler: If InStr(1, Err.Source, ":") = 0 Then Err.Source = "clsSysDrives:DriveIndex" End If Err.Raise Err.Number End Property
6.7 Die Eigenschaft DriveReady Die Bereitschaft eines Laufwerks kann recht einfach durch einen Testzugriff mittels der Dir-Funktion erfolgen. War dieser fehlerfrei, so ist das Laufwerk bereit. Public Property Get DriveReady(ByVal Index As Long) As Boolean If Not bClassReady Then Call InitializeArrays On Error Resume Next Dir strDrives(Index – 1) DriveReady = (Err.Number = 0) End Property
6.8 Die Eigenschaft CDDrivesCount Die Anzahl der verfügbaren CD-Laufwerke wird dadurch ermittelt, dass alle in strDrives enthaltenen Laufwerke über GetDriveType auf ihren Typ hin untersucht werden. Übereinstimmungen mit DRIVE_CDROM werden in einer Variablen mitgezählt. Public Property Get CDDrivesCount() As Long Dim iDrive As Long 'Zeiger in strDrives() Dim nDrives As Long 'Zwischenvariable On Error GoTo errHandler If Not bClassReady Then Call InitializeArrays For iDrive = 0 To UBound(strDrives) If GetDriveType(strDrives(iDrive)) = DRIVE_CDROM Then nDrives = nDrives + 1 End If Next CDDrivesCount = nDrives
Die Eigenschaft CDDriveName
105
Exit Property errHandler: If InStr(1, Err.Source, ":") = 0 Then Err.Source = "clsSysDrives:CDDrivesCount" End If Err.Raise Err.Number End Property
6.9 Die Eigenschaft CDDriveName Diese Eigenschaft gibt den Namen des i-ten CD-Laufwerks zurück. Wie bei CDDrivesCount werden alle Laufwerke aus strDrives auf ihren Typ hin untersucht und Übereinstimmungen mit DRIVE_CDROM werden mitgezählt, bis Element i erreicht ist. Public Property Get CDDriveName(ByVal Index As Long) As String Dim iDrive As Long 'Zeiger in strDrives() Dim nDrives As Long 'Zwischenvariable On Error GoTo errHandler If Not bClassReady Then Call InitializeArrays If Index > Me.CDDrivesCount Then Err.Raise 9 For iDrive = 0 To UBound(strDrives) If GetDriveType(strDrives(iDrive)) = DRIVE_CDROM Then nDrives = nDrives + 1 If nDrives = Index Then CDDriveName = strDrives(iDrive) Exit Property End If End If Next Exit Property errHandler: If InStr(1, Err.Source, ":") = 0 Then Err.Source = "clsSysDrives:CDDriveName" End If Err.Raise Err.Number End Property
6.10 Die Eigenschaft DriveFreeSpace Bei DriveFreeSpace und DriveTotalSpace wird’s noch einmal spannend, denn da erwartet uns eine Reihe von Problemen.
106
6
Laufwerke
6.10.1 GetDiskFreeSpace oder GetDiskFreeSpaceEx? In Windows NT inkl. 2000, 98 und 95 ab SR-2 kann die GetDiskFreeSpaceEx-Funktion verwendet werden. In Windows 95 kleiner SR-2 existiert die Funktion schlicht und ergreifend nicht. Es muss also geprüft werden, ob die Funktion GetDiskFreeSpaceEx in der Bibliothek Kernel32 enthalten ist. Die Funktion GetProcAddress gibt die Speicheradresse einer Funktion innerhalb einer Bibliothek zurück. Existiert die Funktion nicht, so erhalten wir eine 0. GetProcAddress benötigt allerdings ein Module Handle der gewünschten Bibliothek, das wiederum von der Funktion GetModuleHandle erzeugt werden kann:
6.10.2 GetModuleHandle-Funktion Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long
lpModuleName Die Funktion erwartet hier den Namen der Bibliothek, z.B. »kernel32.dll«.
Rückgabe Die Rückgabe enthält ein so genanntes Module Handle, ein Handle auf die Instanz der Programmdatei bzw. die gemappte Programmdatei innerhalb des eigenen Prozesses. Im Gegensatz zur Funktion LoadLibrary inkrementiert GetModuleHandle nicht den Referenzzähler, weshalb wir auch kein FreeLibrary hinterherschicken müssen.
6.10.3 GetProcAddress-Funktion Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
hModule Ein Module Handle der gewünschten Datei
lpProcName Der Alias der Prozedur
Rückgabe Konnte die angeforderte Prozedur in der Programmdatei lokalisiert werden, so gibt GetProcAddress die Speicheradresse der Funktion zurück. Gelingt dies nicht, weil zum Beispiel die Funktion gar nicht existiert, so gibt sie 0 zurück.
Die Eigenschaft DriveFreeSpace
107
Wenn festgestellt werden soll, ob die Funktion GetDiskFreeSpaceEx (Alias GetDisk FreeSpaceExA) in der Bibliothek Kernel32.dll existiert, so ist das durch diesen Ausdruck möglich: lngResult = GetProcAddress(GetModuleHandle("kernel32.dll"), _ "GetDiskFreeSpaceExA") If lngResult = 0 Then 'Win 95 vor SR 2 ... Else 'der Rest ... End If
6.10.4 GetDiskFreeSpace-Funktion Für Windows 95 Systeme kleiner SR-2 muss diese Funktion eingesetzt werden: Declare Function GetDiskFreeSpace Lib "kernel32" Alias "GetDiskFreeSpaceA" (ByVal lpRootPathName As String, lpSectorsPerCluster As Long, lpBytesPerSector As Long, lpNumberOfFreeClusters As Long, lpTotalNumberOfClusters As Long) As Long
lpRootPathName In diesem Argument erwartet die Funktion einen gültigen Laufwerksbezeichner. Bezüglich Unterverzeichnissen gelten die Bemerkungen aus Abschnitt 6.4.1.
lpSectorsPerCluster In dieses Argument schreibt die Funktion die Anzahl der Sektoren je Cluster.
lpBytesPerSector Hier erwarten uns die Anzahl der Bytes je Sektor.
lpNumberOfFreeClusters Die Gesamtzahl der freien Cluster wird hier übermittelt.
lpTotalNumberOfClusters Und hier die Zahl der verfügbaren Clusters.
Rückgabe Im Fehlerfalle gibt die Funktion 0 zurück, andernfalls einen Wert ungleich 0.
108
6
Laufwerke
Folgende Variablen werden benötigt: Dim Dim Dim Dim
nSectorsPerCluster As Long nBytesPerSector As Long nFreeClusters As Long nTotalClusters As Long
'Anzahl der 'Anzahl der 'Anzahl der 'Gesamtzahl
Sektoren je Cluster Bytes je Sektor freien Clusters der Cluster
Aufruf und Ermittlung des freien Speicherplatzes sieht wie folgt aus: lngResult = GetDiskFreeSpace(strDrives(Index – 1), _ nSectorsPerCluster, nBytesPerSector, nFreeClusters, _ nTotalClusters) DriveFreeSpace = CDec(nFreeClusters * nSectorsPerCluster * _ nBytesPerSector)
Der einzige Datentyp, der mit Gigabytes oder gar Terabytes fertig wird, ist der Decimal-Untertyp des Variants, weshalb das Produkt mit CDec konvertiert wird.
6.10.5 GetDiskFreeSpaceEx-Funktion Für NT inkl. 2000, Win 98 und Win 95 ab SR-2 steht die GetDiskFreeSpaceEx zu Gebote: Declare Function GetDiskFreeSpaceEx Lib "kernel32" Alias "GetDiskFreeSpaceExA" (ByVal lpDirectoryName As String, lpFreeBytesAvailable As ULARGE_INTEGER, lpTotalNumberOfBytes As ULARGE_INTEGER, lpTotalNumberOfFreeBytes As ULARGE_INTEGER) As Long
lpDirectoryName In diesem Argument erwartet die Funktion einen gültigen Laufwerksbezeichner. Im Übrigen gelten die Bemerkungen aus Abschnitt 6.4.1.
lpFreeBytesAvailable Vorzeichenloser 64 Bit Integer, in dem die Anzahl der dem aktuellen Anwender verfügbaren Bytes ausgegeben wird. In Windows 2000 kann dieser Wert kleiner sein als die Zahl der freien Bytes, die in lpTotalNumberOfFreeBytes enthalten sind.
lpTotalNumberOfBytes Vorzeichenloser 64 Bit Integer mit der Kapazität des Datenträgers in Bytes.
lpTotalNumberOfFreeBytes Vorzeichenloser 64 Bit Integer, der die Anzahl der verfügbaren Bytes des Datenträger enthält.
Die Eigenschaft DriveFreeSpace
109
Rückgabe Im Fehlerfalle gibt die Funktion 0 zurück.
6.10.6 Behandlung des 64 Bit-Integers Auffallend sind zuerst einmal die drei vorzeichenlosen 64 Bit Integers, die so in VB oder VBA nicht zur Verfügung stehen. Die MSDN schlägt in einem Beispiel vor, diesen Datentyp so nachzubauen: Private Type ULARGE_INTEGER 'Unsigned Large Integer LowPart As Long HighPart As Long End Type
Der Trick besteht also darin, den 64 Bit Wert aus zwei 32 Bit Long-Werten zusammenzusetzen. Folgende Variablen werden benötigt: Dim ulAvail As ULARGE_INTEGER Dim ulFree As ULARGE_INTEGER Dim ulTotal As ULARGE_INTEGER
'Anzahl der verfügbaren Bytes 'Größe des Datenträgers 'Freie Bytes
Der Aufruf der Funktion erfolgt durch lngResult = GetDiskFreeSpaceEx(strDrives(Index – 1), ulAvail, _ ulTotal, ulFree)
Bei der Auswertung der ulFree-Variablen erwartet uns noch ein Problem. Der vorzeichenbehaftete VB-Long wird negativ, sobald der vorzeichenlose C-Datentyp einen Wert größer oder gleich 231 annimmt. Ist der VB-Long negativ, so muss 232 hinzu addiert werden: If ulFree.LowPart < 0 Then 'Konvertierung der negativen Werte in Unsigned-Werte DriveFreeSpace = CDec(ulFree.LowPart + 2 ^ 32 + _ ulFree.HighPart * 2 ^ 32) Else DriveFreeSpace = CDec(ulFree.LowPart + ulFree.HighPart * 2 ^ 32) End If
Diese Lösung birgt aber noch ein Problem, denn theoretisch müssten wir ulFree.HighPart ebenfalls auf Vorzeichen prüfen; in der Praxis jedoch beträfe dies Laufwerke mit einer Kapazität ab 263 Bytes, also 9,2 * 1018 Bytes. Und das ist noch eine Weile hin. Aber neben dieser nicht ganz sauberen Lösung, den 64 Bit Wert in zwei Longs zu packen, steht uns noch ein völlig anderer Weg offen. Der Datentyp mit der größten Flexibilität am API ist der Byte-Datentyp oder besser das Byte-Array. Und die Vorzüge liegen auf der Hand, denn der Byte-Datentyp ist vorzeichenlos. Es entfällt die Prüfung
110
6
Laufwerke
auf ein eventuelles Vorzeichen, und wir benötigen den ULARGE_INTEGER nicht mehr. Einen Nachteil sollten wir allerdings nicht verschweigen, denn wir fangen uns eine Deklaration As Any ein: Declare Function GetDiskFreeSpaceEx Lib "kernel32" Alias "GetDiskFreeSpaceExA" (ByVal lpDirectoryName As String, lpFreeBytesAvailable As Any, lpTotalNumberOfBytes As Any, lpTotalNumberOfFreeBytes As Any) As Long
Wir benötigen nun andere Variablen: Dim byteAvail(7) As Byte Dim byteFree(7) As Byte Dim byteTotal(7) As Byte
'Array zur Aufnahme der verfügbaren Bytes 'Array zur Aufnahme der freien Kapazität 'Array zur Aufnahme der Datenträgergröße
und einen Zeiger Dim iByte As Long
'Zeiger in byteFree()
Im Funktionsaufruf muss jeweils ein Zeiger auf das Element 0 des Arrays übergeben werden: lngResult = GetDiskFreeSpaceEx(strDrives(Index – 1), byteAvail(0), _ byteTotal(0), byteFree(0))
Zur Umrechnung der Rückgabe werden die Exponenten zur Basis 2 in 8er-Schritten durchlaufen: For iByte = 0 To 7 DriveFreeSpace = CDec(DriveFreeSpace + _ byteFree(iByte) * 2 ^ (8 * iByte)) Next
Das Ergebnis ist im Vergleich zur MSDN-Lösung mit den beiden Longs einfacher und unzweifelhaft auch eleganter. Zum Abschluss zeigen wir die Eigenschaftsprozedur noch einmal am Stück: Public Property Get DriveFreeSpace(ByVal Index As Long) As Variant Dim lngResult As Long 'Rückgabe aus Funktionen 'Variablen für Win 95 vor SR 2 (GetDiskFreeSpace) Dim nSectorsPerCluster As Long 'Anzahl der Sektoren je Cluster Dim nBytesPerSector As Long 'Anzahl der Bytes je Sektor Dim nFreeClusters As Long 'Anzahl der freien Clusters Dim nTotalClusters As Long 'Gesamtzahl der Cluster 'Variablen für Rest (GetDiskFreeSpaceEx) Dim byteAvail(7) As Byte 'Array für verfügbare Bytes Dim byteFree(7) As Byte 'Array für freie Kapazität Dim byteTotal(7) As Byte 'Array für Datenträgergröße Dim iByte As Long 'Zeiger in byteFree() On Error GoTo errHandler If Not bClassReady Then Call InitializeArrays 'Prüfen, ob GetDiskFreeSpaceEx existiert lngResult = GetProcAddress(GetModuleHandle("kernel32.dll"), _
Die Eigenschaft DriveTotalSpace
111
"GetDiskFreeSpaceExA") If lngResult = 0 Then 'Win 95 vor SR 2 lngResult = GetDiskFreeSpace(strDrives(Index – 1), _ nSectorsPerCluster, nBytesPerSector, _ nFreeClusters, nTotalClusters) DriveFreeSpace = CDec(nFreeClusters * nSectorsPerCluster * _ nBytesPerSector) Else 'der Rest lngResult = GetDiskFreeSpaceEx(strDrives(Index – 1), _ byteAvail(0), byteTotal(0), byteFree(0)) For iByte = 0 To 7 DriveFreeSpace = CDec(DriveFreeSpace + _ byteFree(iByte) * 2 ^ (8 * iByte)) Next End If Exit Property errHandler: If InStr(1, Err.Source, ":") = 0 Then Err.Source = "clsSysDrives:DriveFreeSpace" End If Err.Raise Err.Number End Property
6.11 Die Eigenschaft DriveTotalSpace Im Vergleich zu DriveFreeSpace sind nur zwei kleine Unterschiede zu beobachten. Der erste betrifft die Ermittlung der Laufwerkskapazität durch GetDiskFreeSpace, denn anstelle von nFreeClusters kommt natürlich nTotalClusters zu Ehren: DriveTotalSpace = CDec(nTotalClusters * nSectorsPerCluster * _ nBytesPerSector)
Bei der Auswertung der Elemente der GetDiskFreeSpaceEx-Funktion kommt logischerweise auch die Variable zum Einsatz, mit der die Kapazität beschrieben wird: lngResult = GetDiskFreeSpaceEx(strDrives(Index – 1), _ byteAvail(0), byteTotal(0), byteFree(0)) For iByte = 0 To 7 DriveTotalSpace = CDec(DriveTotalSpace + _ byteTotal(iByte) * 2 ^ (8 * iByte)) Next
7 Dateisystem Visual Basic bietet eine ganze Menge an Funktionen rund um Dateien, und dennoch gibt es auch hier ein paar Kleinigkeiten, die ohne das Win32 API nicht sauber gelöst werden können. Dazu gehört eine Boolean-Eigenschaft, die signalisiert, ob eine Datei geöffnet ist. Ist ein Dateityp registriert, so ist in der Registry unter HKEY_CLASSES_ROOT ein Beziehungsgeflecht zwischen Dateierweiterung, dem Klassennamen und der dazugehörigen ClassID aufgebaut. Innerhalb dieses Geflechts hat man Zugriff auf verschiedenste Informationen zu diesen Dateitypen, zum Beispiel den Typenamen oder sogar ein Handle des dem Dateityp zugeordneten Icons. In Kapitel 8 werden wir noch sehen, wie man an diese Informationen in der Registry herankommt. In der Shell aber steckt die Funktion SHGetFileInfo, die ohne umfangreiche Registry-Zugriffe diese Werte quasi mit einer Programmzeile eruiert. Hebt man die Decke ein wenig an, so sieht man, dass SHGetFileInfo im Prinzip auch in der Registry nachschaut. Allerdings werden Änderungen beim Prozessstart abgefragt, eine Änderung des Typnamens in der Registry, während der Prozess läuft, schlägt offensichtlich nicht durch. Über SHGetFileInfo werden wir Eigenschaften für Typnamen und Icon-Handle bilden.
7.1 FileInUse-Eigenschaft Gelegentlich ist es erforderlich, Dateien exklusiv zu öffnen. Voraussetzung hierzu ist es, dass die Datei von niemandem sonst geöffnet ist. Wir versuchen also unsererseits, die betreffende Datei exklusiv zu öffnen. Und wenn das funktioniert, ist die Datei folglich von niemand anderem geöffnet. Kann die Datei hingegen nicht geöffnet werden, so muss sie bereits geöffnet sein. Die Funktion zum Öffnen einer Datei heißt CreateFile und kann – das sagt uns bereits der Name – auch Dateien erzeugen.
114
7.1.1
7
Dateisystem
CreateFile-Funktion
Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByVal lpSecurityAttributes As Long, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
lpFileName Vollständiger Name der zu öffnenden Datei
dwDesiredAccess Art des Zugangs zu der Datei: Konstante
DELETE
Wert
Bedeutung
0
nur Query-Rechte
&H10000
Löschrechte (Bestandteil der Standard Access Rights)
GENERIC_READ
&H80000000
Leserechte
GENERIC_WRITE
&H40000000
Schreibrechte
&HC0000000 Lese- und Schreibrechte
Alles größer Null erfordert exklusiven Zugang. Somit reicht ein GENERIC_READ für unsere Zwecke aus.
dwShareMode Share-Modus für alle nachfolgenden Zugriffe. Konstante
Wert Bedeutung 0
Datei kann nicht geöffnet werden, solange wir sie nicht geschlossen haben.
FILE_SHARE_READ
1
Datei kann geöffnet werden, wenn sie mit Leserechten geöffnet wird.
FILE_SHARE_WRITE
2
Datei kann geöffnet werden, wenn sie mit Schreibrechten geöffnet wird.
FILE_SHARE_DELETE
4
Datei kann geöffnet werden, wenn sie mit Löschrechten geöffnet wird.
Wir übergeben eine 0.
FileInUse-Eigenschaft
115
lpSecurityAttributes Hier erwartet die Funktion eine SECURITY_ATTRIBUTES-Struktur. Wird 0 übergeben, so legt die Funktion unter NT oder 2000 einen Standard-Security-Descriptor an. In Win 95 und 98 ist dieses Argument ohnehin ohne Bedeutung. Wir übergeben 0.
dwCreationDisposition Steuert das Verhalten der Funktion mit einer der folgenden Konstanten: Konstante
Wert Bedeutung
CREATE_NEW
1
Erzeugt eine neue Datei. Existiert die Datei bereits, schlägt CreateFile fehl.
CREATE_ALWAYS
2
Erzeugt eine neue Datei. Existiert die Datei bereits, wird sie überschrieben.
OPEN_EXISTING
3
Öffnet eine Datei. Existiert sie nicht, schlägt CreateFile fehl.
OPEN_ALWAYS
4
Öffnet eine Datei und legt sie an, sollte sie nicht existieren.
TRUNCATE_EXISTING
5
Öffnet eine Datei und leert sie. Existiert diese Datei, schlägt die Funktion fehl.
Wir übergeben OPEN_EXISTING.
dwFlagsAndAttributes Eine der folgenden Konstanten oder eine gültige Kombination: Konstante
Wert
Bedeutung
FILE_ATTRIBUTE_ARCHIVE
&H20
Archive-Flag gesetzt
FILE_ATTRIBUTE_ENCRYPTED
&H40
Die Datei ist verschlüsselt
FILE_ATTRIBUTE_HIDDEN
2
Hidden-Flag gesetzt
FILE_ATTRIBUTE_NORMAL
&H80
Normale Datei ohne Mitwirkung einer der anderen Konstanten.
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
&H2000 Datei wird nicht von Content Indexing Service berücksichtigt.
FILE_ATTRIBUTE_OFFLINE
&H1000 Signalisiert, dass die Datei physikalisch auf einen Offline-Speicherplatz verschoben wurde. Dies ist ein Windows 2000 Feature.
FILE_ATTRIBUTE_READONLY
1
Read-Only-Flag ist gesetzt.
116
7
Dateisystem
Konstante
Wert
Bedeutung
FILE_ATTRIBUTE_SYSTEM
4
System-Flag ist gesetzt
FILE_ATTRIBUTE_TEMPORARY
&H100
Die Datei wird derzeit vom System genutzt und befindet sich im temporären Speicher.
Wir übergeben FILE_ATTRIBUTE_NORMAL.
hTemplateFile Handle einer Vorlagendatei zur Erstellung neuer Dateien. Wir übergeben 0.
Rückgabe Handle der Datei oder INVALID_HANDLE_VALUE (-1) bei Fehler.
7.1.2
CloseHandle-Funktion
Und wie so oft, wenn Handles im Spiel sind, müssen diese nach dem Öffnen auch wieder geschlossen werden. CloseHandle dient dazu, ein beliebiges Handle zu schließen. Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
jObject Handle des zu schließenden Objekts
Rückgabe 0 im Fehlerfalle
7.1.3
FileInUse-Eigenschaft
Im Prinzip ist die Eigenschaft recht einfach. Nach dem Aufruf der CreateFile wird die Rückgabe unter die Lupe genommen. Ist diese INVALID_HANDLE_VALUE, so wird der Win32 Error Code ermittelt. Bei ERROR_SHARING_VIOLATION ist die Datei bereits geöffnet, und die Rückgabe wird True. Alle anderen Fehler werden führen dazu, dass der Fehlertext von der Funktion GetErrorMessage aus der System Message Table ausgelesen wird. Konnte die Datei hingegen geöffnet werden, so ist sie offenbar derzeit nicht in Benutzung. In diesem Falle muss sie aber geschlossen werden. Public ' Dim Dim
Property Get FileInUse(ByVal strFile As String) As Boolean strFile: zu prüfende Datei hFile As Long 'Datei-Handle lngError As Long 'Fehler-Code
SHGetFileInfo und SHFILEINFO
117
On Error GoTo Errhandler 'Datei öffnen hFile = CreateFile(strFile, GENERIC_READ, 0, 0, OPEN_EXISTING, _ FILE_ATTRIBUTE_NORMAL, 0) If hFile = INVALID_HANDLE_VALUE Then 'Win32 Error Code ermitteln lngError = Err.LastDllError If lngError = ERROR_SHARING_VIOLATION Then FileInUse = True Else Err.Raise 10000 + lngError, , GetErrorMessage(lngError) End If Else CloseHandle hFile End If Exit Property Errhandler: If hFile = INVALID_HANDLE_VALUE Then CloseHandle hFile Err.Raise Err.Number, "clsFiles:FileInUse" End Property
7.2 SHGetFileInfo und SHFILEINFO Die SHGetFileInfo-Funktion und die SHFILEINFO-Struktur bieten Zugang zu einer ganzen Reihe von Informationen zu einzelnen Dateien.
7.2.1
SHGetFileInfo-Funktion
Declare Function SHGetFileInfo Lib "shell32.dll" Alias "SHGetFileInfoA" (ByVal pszPath As String, ByVal dwFileAttributes As Long, psfi As SHFILEINFO, ByVal cbFileInfo As Long, ByVal uFlags As Long) As Long
pszPath Name der Datei, der absolut angegeben werden kann, aber auch relativ zum Verzeichnis des aufrufenden Clients.
dwFileAttributes Ein Wert oder eine Kombination von Werten aus der Reihe der FILE_ATTRIBUTE-Konstanten. Dieses Argument wird aber ignoriert, wenn SHGFI_USEFILEATTRIBUTES nicht in uFlags enthalten ist. Wir übergeben 0.
118
7
Dateisystem
psfi Eine SHFILEINFO-Struktur, die von der Funktion gemäß den Flags gefüllt wird.
cbFileInfo Länge der SHFILEINFO-Struktur, die als psfi übergeben wird.
uFlags Eine oder mehrere dieser Konstanten (Auszug): Konstante
Wert
Bedeutung
SHGFI_DISPLAYNAME
&H200
Anzeigename (Dateiname ohne Pfad)
SHGFI_ICON
&H100
Icon-Handle
SHGFI_TYPENAME
&H400
Typnamen (z.B. Microsoft Excel-Arbeitsmappe)
Rückgabe 0 im Fehlerfalle, je nach Konstante aber auch ein Wert. Also einfach ignorieren.
7.2.2
SHFILEINFO-Struktur
hIcon Long, ein Handle des Icons, sofern Sie SHGFI_ICON oder eine andere Icon-Konstante in den Flags übergeben haben. Wenn dies so ist, dann beachten Sie bitte, dass dieses Handle wieder mit der Funktion DestroyHandle explizit zerstört werden muss.
iIcon Long, Index des Icons in der System Image List, sofern Sie ein Handle angefordert haben. Der Index wird offensichtlich je Prozess gepflegt. Und diese Liste wird durch nicht zerstörte Handles aufgebläht.
dwAttributes Long, Bit-Array mit Attributen der Datei.
szDisplayName String * MAX_PATH (also 260), Anzeigename der Datei, sofern SHGFI_DISPLAY NAME als uFlags übergeben wurde. Aus »D:\Projekte\.....\Test.xls« wird »Test.xls«.
DisplayName-Eigenschaft
119
szTypeName String * 80, Typename der Datei, sofern SHGFI_TYPENAME als uFlags übergeben wurde. Nachdem die SHGetFileInfo-Funktion nebst SHFILEINFO-Struktur vorgestellt sind, werden wir nun die drei Eigenschaften konstruieren.
7.3 DisplayName-Eigenschaft Diese Eigenschaft gibt den Anzeigenamen der übergebenen Datei zurück: Public Property Get DisplayName(ByVal strFile As String) As String 'Argumente: ' strFile: zu prüfende Datei Dim lngReturn As Long 'Rückgabe der Funktion Dim udtSHFILEINFO As SHFILEINFO 'SHFILEINFO-Struktur lngReturn = SHGetFileInfo(strFile, 0, udtSHFILEINFO, _ LenB(udtSHFILEINFO), SHGFI_DISPLAYNAME) DisplayName = Left(udtSHFILEINFO.szDisplayName, _ InStr(1, udtSHFILEINFO.szDisplayName, vbNullChar) – 1) End Property
Die Unterschiede zu den beiden folgenden Eigenschaften sind hervorgehoben.
7.4 TypeName-Eigenschaft TypeName gibt den Typnamen der übergebenen Datei zurück: Public Property Get TypeName(ByVal strFile As String) As String 'Argumente: ' strFile: zu prüfende Datei Dim lngReturn As Long 'Rückgabe der Funktion Dim udtSHFILEINFO As SHFILEINFO 'SHFILEINFO-Struktur lngReturn = SHGetFileInfo(strFile, 0, udtSHFILEINFO, _ LenB(udtSHFILEINFO), SHGFI_TYPENAME) TypeName = Left(udtSHFILEINFO.szTypeName, _ InStr(1, udtSHFILEINFO.szTypeName, vbNullChar) – 1) End Property
Auch hier sind die Unterschiede zu DisplayName und IconHandle hervorgehoben.
7.5 IconHandle Diese Eigenschaft gibt ein Handle auf das der übergebenen Datei zugeordnete Icon zurück.
120
7
Dateisystem
Public Property Get IconHandle(ByVal strFile As String) As Long 'Argumente: ' strFile: zu prüfende Datei Dim lngReturn As Long 'Rückgabe der Funktion Dim udtSHFILEINFO As SHFILEINFO 'SHFILEINFO-Struktur lngReturn = SHGetFileInfo(strFile, 0, udtSHFILEINFO, _ LenB(udtSHFILEINFO), SHGFI_ICON) IconHandle = udtSHFILEINFO.hIcon End Property
Die zu den beiden vorangegangenen Eigenschaften verschiedenen Stellen sind hervorgehoben.
8 Registry Die Registry – Fluch oder Segen? Denken wir an die dunkle Zeit der Ini-Dateien zurück, so erscheint uns die Registry geradezu in gleißendem Lichte. Vor dem Einzug der Registry hatten wir es mit Dutzenden von Ini-Dateien zu tun, die zwar teils mit einer Gliederung daherkamen, aber im Grunde kaum als hierarchisch angesehen werden konnten. Die Registry bietet eine zentrale, hierarchisch gegliederte Anlaufstelle für alle Arten von Informationen. Systemparameter werden darin ebenso abgelegt wie anwendungsübergreifende oder auch anwendungsinterne Informationen. Und genauso sieht sie gelegentlich auch aus, denn viele Programme scheinen beleidigt zu sein, wenn man sie deinstalliert, und hinterlassen trotzig der Nachwelt ihren Müll in der Registry. Die Registry-Struktur hier en détail durchzukauen macht wohl keinen Sinn. Nicht zuletzt auch deshalb, weil ich sie gar nicht in der Tiefe kenne. Wir werden aber im weiteren Verlauf dieses Kapitels noch ein paar interessante Zusammenhänge diskutieren, die im Schlüssel HKEY_CLASSES_ROOT zu finden sind. In Abschnitt 8.1 werden nach einer kurzen Vorstellung der Root-Keys die wichtigsten Datentypen vorgestellt. In 8.2 bis 8.5 sind die Techniken zum Lesen, Erzeugen und Schreiben sowie Löschen von Werten durchgesprochen. In 8.6 folgen noch ein paar Überlegungen zur Fehlerbehandlung im Zusammenhang mit Registry-Zugriffen. Die erarbeiteten Methoden werden in Abschnitt 8.7 in eine universelle Settings-Schnittstelle gegossen, mit der wir uns von der Enge des Schlüssels »VB and VBA Program Settings« befreien. Abschnitt 8.8 stellt die Technik zum Enumerieren von Registry-Schlüsseln vor. Schließlich werden wir in den Abschnitten 8.9 bis 8.13 an zwei etwas komplexeren und praxisbezogenen Beispielen eine Reise durch HKEY_CLASSES_ROOT unternehmen. Im Übrigen wird unterstellt, dass Sie im Umgang mit RegEdit versiert sind und bereits die eine oder andere Stunde mit der Registry zugebracht haben.
122
8
Registry
8.1 Struktur und Datentypen 8.1.1
Struktur
Die Registry besteht aus ein paar Root-Keys, die als grobes Ordnungsschema der Informationen dienen. Dies sind die folgenden:
HKEY_CLASSES_ROOT Hier stehen alle erdenklichen Informationen zu installierten Programmen, seien es nun Exe-Files oder DLLs. Dort sind die Klassennamen zu finden, die zugehörigen CLSIDs, Pfade und Namen der betreffenden Dateien, im Explorer darzustellende Icons, ShellKommando-Strukturen, Dateierweiterungen. Zugänge existieren wahlweise über Klassennamen, CLSIDs und Dateierweiterungen.
HKEY_USERS mit HKEY_CURRENT_USER Alle erforderlichen Informationen zu allen verfügbaren Profilen sind in HKEY_USERS hinterlegt. Die den aktuell angemeldeten User betreffenden Daten werden in dem virtuellen Schlüssel HKEY_CURRENT_USER zusammengefasst. Änderungen in HKEY_CURRENT_USER oder HKEY_USERS sind transparent.
HKEY_LOCAL_MACHINE mit HKEY_CURRENT_CONFIG Unter diesem Schlüssel sind maschinenbezogene Daten zu finden wie beispielsweise Informationen über Hardware, aber auch Software-Konfiguration und das Betriebssystem betreffende Daten. Aktuelle Veränderungen gegenüber den Daten in HKEY_LOCAL_MACHINE sind im Schlüssel HKEY_CURRENT_CONFIG herausgezogen.
HKEY_DYN_DATA Hier sind unter Win95 und Win98 dynamische Daten zu finden, die im RAM gehalten werden. Unser Begehr liegt wohl ausschließlich in den beiden Keys HKEY_CLASSES_ROOT und HKEY_CURRENT_USER.
8.1.2
Datentypen
Die Registry enthält zwölf verschiedene Datentypen, von denen jedoch lediglich die in der folgenden Tabelle dargestellten fünf Datentypen von Interesse sind. So existieren beispielsweise noch zwei weitere für 64-Bit-Werte, ein symbolischer Link, von dessen Verwendung Microsoft abrät, und so weiter.
Struktur und Datentypen
123
Hier unsere fünf Favoriten: Konstante
Wert VB-Datentyp
typischer Inhalt
REG_SZ
1
String
Datei- und Pfadangaben, String-Optionen
REG_EXPAND_SZ
2
String
potenziell expandable Datei- und Pfadangaben
REG_MULTI_SZ
7
String(-Array)
Kombinationen von REG_SZ-Werten
REG_BINARY
3
String, Byte-Array
binäre Daten, Treiberdaten
REG_DWORD
4
Long
Konstanten, Koordinaten, logische Werte
Tabelle 8.1: Registry-Datentypen
Abbildung 8.1 zeigt Beispieleinträge dieser fünf Datentypen.
Abbildung 8.1: Darstellung der fünf Datentypen
Bei den reinen String-Datentypen REG_SZ und REG_EXPAND_SZ handelt es sich intern um Unicode-Strings, was wir nur beim Auslesen berücksichtigen müssen. Dieser für die Ewigkeit (und asiatische Sprachen) entworfene Datentyp enthält zwei Byte je Zeichen, wovon das zweite in der westlichen Hemisphäre stets mit einem NULL (Chr(0)) belegt ist.
REG_SZ Dies ist der Standarddatentyp für Zeichenketten in der Registry. Zum Beispiel CLSIDs fallen darunter, Klassennamen, Datei- und Verzeichnisnamen und sonstige Programmeinstellungen alphanumerischer Natur, etwa Fontnamen oder Recent-Files-Listen.
124
8
Registry
REG_EXPAND_SZ Auch dieser Typ enthält Strings. Der Unterschied zu REG_SZ liegt aber darin, dass sich potenziell hinter diesen Zeichenketten Environment-Variablen verbergen. Hierbei handelt es sich um eine Hand voll vielfach verwendeter Parameter, die von Besonderheiten des jeweiligen Rechners, Betriebssystems oder auch der Installation handeln. %SystemRoot% ist beispielsweise der Platzhalter für das Windows-Verzeichnis.
REG_MULTI_SZ Wenn mehrere String-Werte variabler Teillängen abgespeichert werden sollen, so kommt dieser Typ zur Anwendung. Die einzelnen Teile sind per Chr(0) voneinander getrennt und mit zwei NULLs abgeschlossen. In der MSDN ist zu lesen, dass dieser Datentyp für human readable text gedacht ist. Die Vermutung, dass mich in der Registry nun ein Klartext erwartete, war falsch, denn meine als REG_MULTI_SZ gespeicherten Werte präsentierten sich als Binärdaten. Ein Vergleich mit anderen Systemwerten, die in der MSDN als REG_MULTI_SZ deklariert erscheinen, ergab natürlich, dass diese auch nicht human readable waren.
REG_BINARY Wie der Name schon sagt, kennzeichnet dieser Datentyp binäre Daten. Sollten Sie binäre Daten geringen Volumens ablegen wollen, so wäre das der passende Datentyp.
REG_DWORD Als Long-Datentyp bietet REG_DWORD sich an, alle ganzzahligen Programmwerte aufzunehmen. Hierunter fallen beispielsweise der WindowState einer Form, aber auch Top-, Left-, Width- oder Height-Werte. Viele Programme legen logische Werte als REG_DWORD ab, wobei für True allerdings der in C übliche Wert von +1 verwendet wird, was sich für uns relativ einfach gestaltet: lngMeinLong = Clng(bValue) * -1
wobei bValue ein boolescher Wert ist. False (= 0) bleibt 0 und True (= -1) wird durch die Multiplikation mit -1 zu +1. Das Auslesen erfolgt ähnlich: bValue = cBool(lngMeinLong * -1)
8.2 Öffnen und Schließen eines Schlüssels In Abbildung 8.2 ist der Weg eines Zugriffs auf einen Wert in der Registry aufgezeigt. Gäbe es nicht noch eine Fülle weiterer Gründe für eine Registry-Klasse mit einfachen Schnittstellen, so wäre er spätestens durch die in Abb. 8.2 dargestellten Schritte gegeben.
Öffnen und Schließen eines Schlüssels
125
Abbildung 8.2: Anatomie eines Registry-Zugriffs auf einen Schlüssel oder einen Wert
Der erste Schritt ist also das Öffnen des dem gewünschten Wert unmittelbar übergeordneten Schlüssels. RegOpenKey gibt ein Handle zurück, das für alle weiteren Zugriffe auf den geöffneten Schlüssel oder seiner Unterschlüssel oder Werte dient. Mit diesem Handle rufen wir nun unter Angabe des Schlüsselnamens die entsprechende Funktion auf. Dieses Handle wird selbstredend auch zum Schließen des Schlüssels benötigt. Abbildung 8.3 zeigt den Zugriff auf einen Wert an einem Beispiel.
Abbildung 8.3: Beispiel eines Zugriffs auf einen Wert
8.2.1
Öffnen eines Schlüssels (RegOpenKeyEx)
Zum Öffnen eines Schlüssels steht uns die Funktion RegOpenKeyEx zur Verfügung, die wie folgt deklariert wird.
126
8
Registry
Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" (ByVal hKey As Long, ByVal lpSubKey As String, ByVal ulOptions As Long, ByVal samDesired As Long, ByRef phkResult As Long) As Long
hKey Hier erwartet die Funktion den Root-Schlüssel, also zum Beispiel HKEY_CURRENT_ USER.
lpSubKey Den zu öffnenden Schlüssel übergeben wir in diesem Argument. Dieser String muss den kompletten Bezug beinhalten. Im Beispiel aus Abb. 8.3 wäre das »Software\APITest«.
ulOptions Dieses Argument ist reserviert und muss 0 sein.
samDesired Mit diesem Long-Wert werden die gewünschten Zugriffsrechte für den Schlüssel definiert. Auch hier ergeben die einzelnen Komponenten eine Bitmaske. Die folgende Tabelle gibt Aufschluss über die erforderlichen Konstanten zum Auslesen eines Werts: Konstante
Wert
Bedeutung
STANDARD_RIGHTS_READ
&H1F0000
Standardleserechte
KEY_SET_VALUE
2
Schreiben eines Wertes
Tabelle 8.2: Konstanten für Lesezugriff
Der erforderliche Wert KEY_READ ergibt sich nun als: KEY_READ = (STANDARD_RIGHTS_READ Or KEY_QUERY_VALUE)
Die Rechte für den Schreibzugriff fallen erwartungsgemäß etwas umfangreicher aus: Konstante
Wert
Bedeutung
STANDARD_RIGHTS_ALL
&H20000
Standardleserechte
KEY_QUERY_VALUE
1
Zugriff über RegQueryValueEx
SYNCHRONIZE
&H100000
irgendwelche Synchronisationen
Tabelle 8.3: Konstanten für Schreibzugriff
Öffnen und Schließen eines Schlüssels
127
Die Schreibrechtskonstante KEY_WRITE wird wie folgt gebildet: KEY_WRITE = ((STANDARD_RIGHTS_ALL Or KEY_SET_VALUE) And _ (Not SYNCHRONIZE))
Um einen Wert zu löschen, benötigt man dieselben Rechte wie zum Verändern, woraus sich die Konstante KEY_DELETE wie folgt ergibt: KEY_DELETE = ((STANDARD_RIGHTS_ALL Or KEY_SET_VALUE) And _ (Not SYNCHRONIZE))
In der MSDN werden Sie noch eine Reihe weiterer Konstanten finden, die für Schreibrechte Verwendung finden. So sind die Konstanten KEY_READ und KEY_DELETE namentlich meine Erfindung und inhaltlich das Ergebnis von Versuchen. Die MSDN kennt für verändernde Zugriffe nur KEY_ALL_ACCESS, dessen Wert so gebildet wird: KEY_ALL_ACCESS = ((STANDARD_RIGHTS_ALL Or KEY_QUERY_VALUE Or KEY_SET_VALUE Or KEY_CREATE_SUB_KEY Or _ KEY_ENUMERATE_SUB_KEYS Or KEY_NOTIFY Or _ KEY_CREATE_LINK) And (Not SYNCHRONIZE))
Auch KEY_READ ist in der MSDN wesentlich umfangreicher deklariert. Haben die bei Microsoft nun einfach alle Konstanten in einen Topf geworfen, die sie kannten, oder liegt da ein tieferer Sinn dahinter, der sich meinen Experimenten nicht erschloss? Würden Sie eine Anwendung ausliefern, bei der in wesentlichen Pogrammteilen eine Diskrepanz zwischen Anschein und Dokumentation liegt? Wenn Sie kalte Füße bekommen, sollten Sie generell KEY_ACCESS_ALL verwenden.
phkResult In diesem Argument steckt der tiefere Sinn des Schlüsselöffnens, das Handle des übergebenen Schlüssels.
Rückgabe War das Öffnen erfolgreich, so ist die Rückgabe 0. Aussagekräftige Fehlertexte können mit FormatMessage (siehe 8.6 Fehlerbehandlung) erzeugt werden. Ein kurzes Beispiel mag hier genügen, da wir der Funktion noch begegnen werden: hResult = RegOpenKeyEx(lngHKEY, strKey, 0&, KEY_READ, hKey)
8.2.2
Schließen eines Schlüssels (RegCloseKey)
Hier haben wir es mit einem der seltenen Fällen zu tun, in denen sich die Verwendung einer Funktion wirklich einfach gestaltet: Declare Function RegCloseKey Lib "advapi32.dll" (ByVal hKey As Long) As Long
128
8
Registry
hKey Dies ist das Handle, welches als phkResult von der RegOpenKeyEx ermittelt wurde.
Rückgabe 0 signalisiert Erfolg und alles andere einen Fehler. Doch welcher Fehler kann beim Schließen eines Schlüssels auftreten? Im Prinzip keiner. Eine Auswertung ist, also überflüssig, wodurch sich der Funktionsaufruf so gestalten lässt: RegCloseKey hKey
8.3 Werte auslesen Nachdem wir nun das Öffnen und Schließen eines Schlüssels im Prinzip abgehandelt haben, können wir uns dem Auslesen eines Wertes zuwenden. Hierzu bedienen wir uns der Funktion RegQueryValueEx.
8.3.1
RegQueryValueEx
Declare Function RegQueryValueEx Lib "advapi32.dll" Alias "RegQueryValueExA" (ByVal hKey As Long, ByVal lpValueName As String, ByVal lpReserved As Long, ByRef lpType As Long, lpData As Any, ByRef lpcbData As Long) As Long
hKey Das ist das Handle des Keys, das die RegOpenKeyEx-Funktion zurückgibt.
lpValueName Als lpValueName übergeben wir den Namen des Schlüssels. Wenn der (Standard)-Wert eines Schlüssels ausgelesen werden soll, muss lpValueName als »« übergeben werden.
lpReserved Wie für alle reservierten Argumente übergeben wir hier 0.
lpType lpType als einer der unter 8.1.2 vorgestellten Datentypkonstanten, signalisiert der Funktion, welchen Typs der Wert ist. RegQueryValueEx kann aber dazu aufgefordert werden, den Datentyp anhand des Namens selbst zu ermitteln und zurückzugeben.
Werte auslesen
129
lpData In diesem als Any deklarierten Argument nehmen wir den Inhalt des angeforderten Wertes zurück. Die Deklaration und Vorbelegung ist natürlich vom Datentyp abhängig. In der folgenden Tabelle sind Deklaration, Vorbelegung, Übergabe und Länge von lpData (in lpcbData enthalten) für die fünf Standarddatentypen dargestellt: Datentyp
Deklaration
Vorbelegung
Übergabe
lpcbData
REG_SZ
strData As String
Space(nSize)
ByVal strData
nSize – 1
REG_EXPAND_SZ
strData As String
Space(nSize)
ByVal strData
nSize – 1
REG_MULTI_SZ
strData As String
Space(nSize)
ByVal strData
nSize
REG_BINARY
strData As String
Space(nSize)
ByVal strData
nSize
REG_DWORD
lngData As Long
keine
lngData
4
Tabelle 8.4: Deklaration, Vorbelegung, Übergabe und Länge der Standarddatentypen
lpcbData Dieses Argument enthält die Nettobreite des Arguments lpData. Die Übergabe ist in Tabelle 8.3 zu sehen. Rufen wir die Funktion mit lplcData mit dem Wert 0 auf, so gibt die Funktion in lpbcData die Bruttobreite des angefragten Wertes zurück.
Rückgabe War die Funktion erfolgreich, so ist die Rückgabe 0. Alle anderen Werte können über die FormatMessage-Funktion in Fehlertexte konvertiert werden.
8.3.2
Die Methode GetRegistryValue
Werfen wir zuerst einen Blick auf den Pseudo-Code der Methode: Öffne Schlüssel (Rückgabe hKey) Ermittle Datentyp und Länge (Rückgabe lngType und nSize) Belege Datenvariable vor Ermittle Wert Schließe Schlüssel
Die Methodensignatur sieht so aus: Public Function GetRegistryValue(ByVal lngHKEY As enumHKEY, _ ByVal strKey As String, ByVal strValue As String) As Variant ' lngHKey: KEY (z.B. HKEY_CURRENT_USER) ' strKey: Key (z.B. "Software\APITool") ' strValue: Name des Wertes (z.B. "Top") End Function
130
8
Registry
Die Rückgabe ist entweder der ausgelesene Wert oder Empty, falls der Wert nicht vorhanden ist. Folgende Variablen werden benötigt: Dim Dim Dim Dim Dim Dim
strData As String lngData As Long nSize As Long hResult As Long hKey As Long lngType As Long
'Rückgabe bei Strings und REG_BINARY 'Rückgabe bei REG_DWORD 'Länge der Rückgabe 'Rückgabe der Funktionen 'Key-Handle 'Datentyp
Zuerst wird nun der Schlüssel geöffnet und ein eventueller Fehler beim Öffnen entsprechend verarbeitet: hResult = RegOpenKeyEx(lngHKEY, strKey, 0&, KEY_READ, hKey) If hResult = ERROR_FILE_NOT_FOUND Then GetRegistryValue = Empty Exit Function ElseIf hResult <> ERROR_SUCCESS Then Err.Raise hResult, , GetErrorMessage(hResult) End If
Der Schlüssel wurde also erfolgreich geöffnet, und wir beauftragen die RegQuery ValueEx mit der Ermittlung des Datentyps und der erforderlichen Größe. Dies wird über nSize gesteuert, da sie zu diesem Zeitpunkt noch den Wert 0 hat. Die Funktion gibt diese Informationen in den Variablen lngType und nSize zurück: hResult = RegQueryValueEx(hKey, strValue, 0&, lngType, _ ByVal strData, nSize) If hResult <> ERROR_SUCCESS Then Err.Raise hResult, , GetErrorMessage(hResult) End If
Nun können wir die RegQueryValueEx mit einem entsprechend deklarierten und vorbelegten Argument lpData aufrufen: Select Case lngType Case REG_SZ 'Text strData = Space(nSize) hResult = RegQueryValueEx(hKey, strValue, 0&, lngType, _ ByVal strData, nSize) GetRegistryValue = Left(strData, nSize – 1) Case REG_EXPAND_SZ 'potenziell zu expandierender Text strData = Space(nSize) hResult = RegQueryValueEx(hKey, strValue, 0&, lngType, _ ByVal strData, nSize) GetRegistryValue = ExpandString(Left(strData, nSize – 1)) Case REG_MULTI_SZ 'NULL-getrennte Strings strData = Space(nSize) hResult = RegQueryValueEx(hKey, strValue, 0&, lngType, _
Werte auslesen
131
ByVal strData, nSize) GetRegistryValue = Split(Left(strData, nSize – 2), Chr(0)) Case REG_BINARY 'binäre Daten strData = Space(nSize) hResult = RegQueryValueEx(hKey, strValue, 0&, lngType, _ ByVal strData, nSize) GetRegistryValue = strData Case REG_DWORD 'Long hResult = RegQueryValueEx(hKey, strValue, 0&, lngType, _ lngData, 4) GetRegistryValue = lngData End Select
Auch hier müssen wir die Rückgabe sicherheitshalber überprüfen: If hResult <> ERROR_SUCCESS Then Err.Raise hResult, , GetErrorMessage(hResult) End If
Bleibt noch, den Schlüssel zu schließen: RegCloseKey hKey
Im ErrorHandler müssen wir natürlich auch damit rechnen, dass der Schlüssel erfolgreich geöffnet wurde, sodass wir ihn auch hier schließen müssen: errHandler: RegCloseKey hKey If InStr(1, Err.Source, ":") = "" Then Err.Source = "cSysRegistry:GetRegistryValue" End If Err.Raise Err.Number
Wie Sie sich noch erinnern, enthält der REG_EXPAND_SZ-Datentyp potenziell (nicht notwendigerweise!) eine Environment-Variable. Das Expandieren dieses Wertes wurde in der folgenden Zeile eingeleitet: GetRegistryValue = ExpandString(Left(strData, nSize – 1))
Und diesem Expandieren widmet sich der nächste Abschnitt.
8.3.3
Expandieren eines Strings
Was fangen wir nun mit einem zu expandierenden String wie dem folgenden an, der den Schlüssel DefaultIcon der Klasse dllfile beschreibt: %SystemRoot%\System32\shell32.dll,-154
Nun, eine Möglichkeit wäre, %SystemRoot% zu extrahieren und an die etwas merkwürdige Environ-Funktion zu verfüttern. Dafür gibt es aber keine Punkte, denn das API stellt uns eine wesentlich elegantere Funktion zur Verfügung:
132
8
Registry
Declare Function ExpandEnvironmentStrings Lib "kernel32" Alias "ExpandEnvironmentStringsA" (ByVal lpSrc As String, ByVal lpDst As String, ByVal nSize As Long) As Long
lpSrc Als lpSrc erwartet die Funktion die zu expandierende Zeichenkette mit einem abschließenden NULL.
lpDst In lpDst gibt die Funktion die expandierte Zeichenkette NULL-terminiert zurück.
nSize In diesem Argument teilen wir der Funktion die Größe des Arguments lpDst mit.
Rückgabe ExpandEnvironmentStrings gibt die Bruttogröße des expandierten Strings in Byte zurück. In Windows NT 4 gibt die Funktion die Länge des Unicode-Strings zurück, was unter Q234874 in der MSDN beschrieben ist. Der dortigen Empfehlung, die Länge des Strings über StrLen zu ermitteln, ist sinnvoll. Damit ergibt sich für uns die Gelegenheit, die Funktion mit einer 0 als nSize aufzurufen, danach lpDst auf die betreffende Größe zu erweitern und ExpandEnvironmentStrings nun mit der eigentlichen Expandierung zu betrauen. Man muss allerdings bedenken, dass die Funktion geneigt ist, einen Chr(0) hinter der expandierten lpDst zu ergänzen. Wenn wir sie nun mit nSize = 0 aufrufen, so ergibt sich als Nettogröße des expandierten Strings 0. Übergeben wir beispielsweise in diesem Falle lpDst mit einer mit Space(3), also hexadezimal 20 20 20, vorbelegten String, so erhalten wir hexadezimal 00 20 20. Was passiert nun, wenn lpDst als vbNullstring übergeben wird? Verkneift sich die Funktion, hinter diesem Nullstring ein Chr(0) anzuhängen? Oder tut sie es doch? Wir sollten uns vor Augen halten, dass alles, was wir an das API übergeben, ein Zeiger ist. Unsere leere Variable ist also in Wirklichkeit eine Speicheradresse in der Form von 1237996. Überschreibt nun die Funktion mit Chr(0) die folgende Speicheradresse? Und warum langweile ich Sie damit? Im Umgang mit dem API ist Vorsicht die Mutter der Porzellankiste. Ich persönlich schlafe besser, wenn ich solche Unsicherheiten ausschließen kann. Also übergebe ich der Funktion ein Blank und sie kommt erst gar nicht in Versuchung, irgendetwas dahinter zu schreiben.
Werte schreiben
133
Die Funktion ExpandString Nach diesen etwas theoretischen Betrachtungen schauen wir uns die Funktion ExpandString an, die eine eventuell vorhandene Environment-Variable durch den realen Wert ersetzt. Private Function ExpandString(ByVal strPath As String) As String 'Argumente: ' strPath: zu expandierender String Dim nSize As Long 'Länge der strExpanded Dim strExpanded As String 'expandierter Pfad If InStr(1, strPath, "%") = 0 Then 'keine Environment Variable enthalten ExpandString = strPath Else 'Environment Variable vorhanden strExpanded = " " strPath = strPath & Chr(0) nSize = ExpandEnvironmentStrings(strPath, strExpanded, 0) strExpanded = Space(nSize / 2) nSize = ExpandEnvironmentStrings(strPath, strExpanded, nSize) ExpandString = Left(strExpanded, StrLen(strExpanded)) End If End Function
Unter NT 4 wird strExpanded als doppelt so groß angelegt wie erforderlich. Doch das birgt keine Probleme, solange lpDst und nSize übereinstimmen. Um jedoch den expandierten String für die Rückgabe zurecht zu schneiden, wird die tatsächliche Länge benötigt. Und die wiederum kann die StrLen-Funktion ermitteln, die folgendermaßen deklariert wird: Declare Function StrLen Lib "kernel32" Alias "lstrlenA" (ByVal Ptr As String) As Long
Der String wird als Ptr übergeben, und die Rückgabe ist die Nettobreite des Strings.
8.4 Werte schreiben Die Vorgehensweise beim Schreiben von Werten ist ähnlich der beim Lesevorgang. Beim ersten Lesezugriff auf einem Rechner existieren natürlich weder Schlüssel noch Werte in der Registry. Was man von dem Windows-API eigentlich gar nicht erwartet, ist, dass mit einem Aufruf von RegCreateKeyEx gar mehrere fehlende Elemente einer Schlüsselkette angelegt werden. Existieren beispielsweise weder APITool noch der darunter anzusiedelnde Schlüssel Settings, so legt RegCreateKeyEx beide in einem Aufwasch an, wenn wir den Schlüssel »Software\APITool\Settings« übergeben.
134
8
Registry
Wenn ein Wert nicht existiert, so müssen wir diesen nicht anlegen, denn die RegSetValueEx erledigt das automatisch beim Versuch, den Wert zu verändern. Werfen wir jedoch zuvor einen Blick oder zwei auf die RegCreateKeyEx-Funktion.
8.4.1
RegCreateKeyEx
In der folgenden Deklaration sind bereits einige bekannte Elemente zu sehen, was ihr ein wenig den Schrecken nimmt: Declare Function RegCreateKeyEx Lib "advapi32.dll" Alias "RegCreateKeyExA" (ByVal hKey As Long, ByVal lpSubKey As String, ByVal Reserved As Long, ByVal lpClass As String, ByVal dwOptions As Long, ByVal samDesired As Long, ByVal lpSecurityAttributes As Long, phkResult As Long, ByVal lpdwDisposition As Long) As Long
hKey Hier erwartet die Funktion die dem Root-Key zugeordnete Konstante, die wohl zumeist HKEY_CURRENT_USER sein wird.
lpSubKey Dies ist der vollständige anzulegende Schlüsselpfad, also etwa »Software\APITool\Settings«.
Reserved 0&
lpClass Die Erklärung dieses Arguments erspare ich uns, da Microsoft ohnehin empfiehlt, einen Nullstring zu übergeben.
dwOptions Folgende Optionen stehen zur Verfügung: Konstante
Wert
Bedeutung
REG_OPTION_NON_VOLATILE
0
Wert wird gespeichert und steht somit nach dem nächsten Systemstart zur Verfügung.
REG_OPTION_VOLATILE
1
Wert ist flüchtig und steht nach dem nächsten Systemstart nicht zur Verfügung. (NT/2000)
Tabelle 8.5: Optionen beim Anlegen eines Schlüssels
Werte schreiben
135
Konstante
Wert
Bedeutung
REG_OPTION_BACKUP_RESTORE
4
Ermöglicht ein Importieren und Exportieren des Schlüssels nebst aller Unterelemente von bzw. in eine separate Datei. In diesem Falle wird samDesired ignoriert und der Schlüssel mit allen erforderlichen Rechten angelegt und geöffnet. (NT/2000)
Tabelle 8.5: Optionen beim Anlegen eines Schlüssels (Fortsetzung)
Die beiden unteren Optionen stehen in Win95 und Win98 nicht zur Verfügung, was es angeraten erscheinen lässt, hier einfach eine 0& zu übergeben.
samDesired In diesem Long-Wert erteilen wir die erforderlichen Rechte zum Anlegen eines Schlüssels und Schreiben eines Wertes. Folgender Wert reicht aus: KEY_WRITE = ((STANDARD_RIGHTS_ALL Or KEY_SET_VALUE) And _ (Not SYNCHRONIZE))
Interessant ist, dass die Funktion bereit ist, einen Key oder SubKey anzulegen, obwohl die Konstante KEY_CREATE_SUB_KEY nicht enthalten ist. Anhänger des ruhigen Schlafs werden auch hier KEY_ALL_ACCESS vorziehen.
lpSecurityAttributes Normalerweise verbirgt sich hinter diesem Argument eine Deklaration mit einer Struktur, in der ein so genannter SecurityDescriptor enthalten ist. Hört sich kompliziert an und ist es vermutlich auch. Wenn wir 0& übergeben, dürfen wir den Schlüssel nicht vererben, womit wir vermutlich recht gut leben können.
phkResult Richtig nett ist es, dass die Funktion nicht nur den oder die Schlüssel anlegt, sondern uns in diesem Argument sogar ein Handle darauf zurückgibt.
lpdwDisposition Mit diesem Argument teilt uns die Funktion mit, ob der Schlüssel angelegt wurde oder ob er nur geöffnet wurde, da er bereits existierte. Bei unserer Vorgehensweise, nämlich den Schlüssel mit RegOpenKeyEx versuchsweise zu öffnen und nur dann über RegCreateKeyEx anzulegen, wenn er nicht existiert, interessiert uns diese Information nicht. Wir übergeben einfach 0&, denn wenn der Funktion eine 0 anstelle eines Zeigers übergeben wird, verzichtet sie darauf, lpdwDisposition zu beschreiben. Wenn Sie auf
136
8
Registry
die Rückgabe Wert legen sollten, so sollten Sie bei der Deklaration das ByVal entfernen, sonst knallt’s, weil Windows diese 0 als Speicheradresse auffassen und an die Adresse 0 im Speicher die Rückgabe schreiben würde!!
Rückgabe Auch hier ist die Rückgabe im Erfolgsfalle 0. Fehler können über FormatMessage ausgewertet werden (siehe 8.6).
8.4.2
RegSetValueEx
Auch RegSetValueEx erwartet nicht, dass der zu beschreibende Wert bereits existiert. Sollte er tatsächlich nicht vorhanden sein, so wird er angelegt und natürlich beschrieben. Declare Function RegSetValueEx Lib "advapi32.dll" Alias "RegSetValueExA" (ByVal hKey As Long, ByVal lpValueName As String, ByVal Reserved As Long, ByVal dwType As Long, lpValue As Any, ByVal cbData As Long) As Long
hKey Hier übergeben wir das Handle des dem Wert übergeordneten Schlüssels. Dieses Handle erhalten wir entweder von RegOpenKeyEx oder RegCreateKeyEx.
lpValueName Der Name des Wertes, der beschrieben werden soll.
Reserved 0&
dwType Das ist der gewünschte Registry-Datentyp.
lpValue Als lpValue wird der zu schreibende Wert übergeben, der natürlich in gewisser Abhängigkeit von dem Registry-Datentyp steht. Die Variable selbst wird als Variant von der Methode entgegengenommen, was bei der Übergabe korrigiert werden muss.
Werte schreiben
Datentyp
137
VB-Typ
Übergabe
cbData
REG_SZ
String
ByVal CStr(varValue)
Len(varValue)
REG_EXPAND_SZ
String
ByVal CStr(varValue)
Len(varValue)
REG_MULTI_SZ
String
ByVal CStr(varValue)
Len(varValue)
REG_BINARY
String
ByVal CStr(varValue)
Len(varValue)
REG_DWORD
Long
CLng(varValue)
4
Tabelle 8.6: VB-Datentyp, Übergabe und Länge der Standarddatentypen
Je nach Datentyp muss das eine oder andere Chr(0) noch ergänzt werden, was unter 8.4.3 Berücksichtigung findet.
cbData Das ist die Bruttolänge des übergebenen Wertes.
Rückgabe Auch RegSetValueEx gibt ERROR_SUCCESS im Erfolgsfall zurück. Eine andere Rückgabe kann über FormatString zum Sprechen gebracht werden (siehe 8.6).
8.4.3
Die Methode SetRegistryValue
Der Pseudo-Code dieser Methode ermöglicht einen ersten Überblick über den Aufbau: Öffne Schlüssel (Rückgabe hKey) Wenn Schlüssel nicht existiert, so lege ihn an Schreibe Wert in Abhängigkeit des Datentyps Schließe Schlüssel
Und hier die Methodensignatur: Public Sub SetRegistryValue(ByVal lngHKEY As enumHKEY, _ ByVal strKey As String, ByVal strValueName As String, _ lngType As enumDataType, ByVal varValue As Variant) ' lngHKey: KEY (z.B. HKEY_CURRENT_USER) ' strKey: Key (z.B. "Software\APITool") ' strValueName: Name des Wertes (z.B. "Top") ' lngType: Datentyp (z.B. REG_SZ) ' varValue: zu schreibender Wert End Function
Folgende Variablen werden benötigt: Dim lngResult As Long Dim hKey As Long
'Rückgabe aus Funktion 'Handle des zu öffnenden Keys
138
8
Registry
Auch hier wird zuerst versucht, den Schlüssel zu öffnen: lngResult = RegOpenKeyEx(lngHKEY, strKey, 0, KEY_WRITE, hKey)
Allerdings fällt die Auswertung der Rückgabe etwas anders aus, denn wenn die Rückgabe 2 ausfällt, wird der bzw. werden die Schlüssel angelegt. Andere Werte ungleich ERROR_SUCCESS führen zu einem dann zu erzeugenden Fehler. If lngResult = ERROR_FILE_NOT_FOUND Then 'Schlüssel erzeugen lngResult = RegCreateKeyEx(lngHKEY, strKey, 0&, vbNullString, _ 0&, KEY_WRITE, 0&, hKey, 0&) If lngResult <> ERROR_SUCCESS Then Err.Raise lngResult, , GetErrorMessage(lngResult) End If ElseIf lngResult <> ERROR_SUCCESS Then Err.Raise lngResult, , GetErrorMessage(lngResult) End If
An dieser Stelle verfügen wir über ein Handle auf den geöffneten Schlüssel und können abhängig vom Registry-Datentyp den Wert schreiben: Select Case lngType Case REG_SZ, REG_EXPAND_SZ varValue = varValue & Chr(0) lngResult = RegSetValueEx(hKey, strValueName, 0&, lngType, _ ByVal CStr(varValue), Len(varValue)) Case REG_MULTI_SZ varValue = Join(varValue, Chr(0)) & Chr(0) & Chr(0) lngResult = RegSetValueEx(hKey, strValueName, 0&, lngType, _ ByVal CStr(varValue), Len(varValue)) Case REG_BINARY lngResult = RegSetValueEx(hKey, strValueName, 0&, lngType, _ ByVal CStr(varValue), Len(varValue)) Case REG_DWORD lngResult = RegSetValueEx(hKey, strValueName, 0&, lngType, _ CLng(varValue), 4) End Select
REG_BINARY und REG_DWORD dürften klar sein. Die beiden String-Typen REG_SZ und REG_EXPAND_SZ müssen mit einem abschließenden Chr(0) versehen werden. Bei REG_MULTI_SZ wäre es nett, wenn man ein Array an die Methode übergeben könnte und diese daraus eine mit zwei (!) Chr(0) terminierte Zeichenkette herstellt. Vielleicht verwirren die drei Chr(0) in der hervorgehobenen Zeile, aber das erste ist das Trennzeichen des Arrays und die beiden anderen schließen diesen Datentyp ab. Es fehlt nun noch die Auswertung der Rückgabe aus RegSetValueEx If lngResult <> ERROR_SUCCESS Then Err.Raise lngResult, , GetErrorMessage(lngResult) End If
Werte löschen
139
und das Schließen des Keys RegCloseKey hKey
Im ErrorHandler muss der Schlüssel noch sicherheitshalber geschlossen und der Fehler weitergeleitet werden: errHandler: RegCloseKey hKey If InStr(1, Err.Source, ":") = "" Then Err.Source = "cSysRegistry:SetRegistryValue" End If Err.Raise Err.Number
8.5 Werte löschen Zur Grundausstattung mit Registry-Werten fehlt noch das Löschen von Werten.
8.5.1
RegDeleteValue
Im Gegensatz zu den bislang behandelten Funktionen kann RegDeleteValue jedoch als vergleichsweise übersichtlich bezeichnet werden. Declare Function RegDeleteValue Lib "advapi32.dll" Alias "RegDeleteValueA" (ByVal hKey As Long, ByVal lpValueName As String) As Long
hKey In diesem Argument wird das Handle des geöffneten Keys übergeben, unter dem der zu löschende Wert angesiedelt ist.
lpValueName Das ist der Name des Schlüssels.
Rückgabe Im Erfolgsfalle wird ERROR_SUCCESS zurückgegeben, andernfalls ein Wert, den wir an die FormatMessage-Funktion verfüttern können.
8.5.2
Die Methode DeleteRegistryValue
Die Methode ist wie folgt strukturiert: Public Sub DeleteRegistryValue(ByVal lngHKEY As enumHKEY, _ ByVal strKey As String, ByVal strValueName As String) ' lngHKey: KEY (z.B. HKEY_CURRENT_USER)
140
8 ' '
Registry
strKey: Key (z.B. "Software\APITool") strValueName: Name des Wertes (z.B. "Top")
End Sub
Der Bedarf an Variablen hält sich mit zweien im erträglichen Bereich: Dim lngResult As Long Dim hKey As Long
'Rückgabe aus Funktion 'Handle des zu öffnenden Keys
Auch hier wird zuerst versucht, den Schlüssel zu öffnen: lngResult = RegOpenKeyEx(lngHKEY, strKey, 0, KEY_ALL_ACCESS, hKey)
Das Löschen des Werts erfolgt, wenn der Schlüssel geöffnet werden konnte. Und auch nur dann muss er wieder geschlossen werden: If lngResult = ERROR_SUCCESS Then lngResult = RegDeleteValue(hKey, strValueName) If lngResult <> ERROR_SUCCESS Then Err.Raise lngResult, , GetErrorMessage(lngResult) End If RegCloseKey hKey End If
Der ErrorHandler ist auch Standard, aber der Vollständigkeit halber sei er trotzdem gezeigt: errHandler: RegCloseKey hKey If InStr(1, Err.Source, ":") = "" Then Err.Source = "cSysRegistry:DeleteRegistryValue" End If Err.Raise Err.Number
8.6 Fehlerbehandlung Einige API-Funktionen erzeugen Fehler, die über einen ErrorHandler abgefangen werden können. Andere Funktionen als die hier vorgestellten, machen ihrem Unmut lediglich über die Rückgabe Luft. Aber nicht alle ReturnCodes ungleich ERROR_SUCCESS stellen einen Fehler dar, wie im Beispiel der Methode SetRegistryValue zu sehen war. Ist die Rückgabe gleich ERROR_FILE_NOT_FOUND, so wird versucht, den Schlüssel anzulegen. Obwohl es eine ganze Reihe von Fehlerkonstanten gibt, kommen wir hier mit den beiden schon vielfach gezeigten Werten aus: Private Const ERROR_SUCCESS = 0 Private Const ERROR_FILE_NOT_FOUND = 2
Fehlerbehandlung
141
Nun aber zu der sehnsüchtig erwarteten Funktion, die aus den nichts sagenden ReturnCodes sprechende Texte macht:
8.6.1
FormatMessage
Declare Function FormatMessage Lib "kernel32" Alias "FormatMessageA" (ByVal dwFlags As Long, ByVal lpSource As Long, ByVal dwMessageId As Long, ByVal dwLanguageId As Long, ByVal lpBuffer As String, ByVal nSize As Long, Arguments As Long) As Long
dwFlags Hinter diesen Flags verbirgt sich eine Reihe von Konstanten, mit der die Art der Eingangsgröße und die Quelle angegeben wird, die zur Auflösung des Fehlertexts herangezogen werden soll. Im Zusammenhang mit Registry-Zugriffen können wir uns auf FORMAT_MESSAGE_FROM_SYSTEM als einzige Konstante beschränken, die FormatMessage auf die System Message Table verweist. Das gestaltet auch den Aufruf der Methode einfacher, da wir nur noch den ReturnCode übergeben müssen.
lpSource Dieses Argument kann sowohl ein (Module-)Handle zu einer über LoadLibrary geladenen Bibliothek mit Fehlerdefinitionen sein als auch ein Zeiger auf eine so genannte Message Definition. Wir dürfen großzügig ByVal eine 0& übergeben.
dwMessageId Dies ist die Fehlernummer, nach der in der System Message Table oder der betreffenden per lpSource referenzierten Bibliothek gesucht werden soll.
dwLanguageId In diesem Argument erwartet die Funktion entweder eine gültige Language-ID (LANGID, siehe Kapitel Locale Settings) oder 0. Letzteres hat zur Folge, dass ausgehend von der Systemsprache nacheinander bis zu fünf Language-IDs herangezogen werden, um den Text in der betreffenden Sprache wiederzugeben. 0 wird also vermutlich die Sprache hervorbringen, die der Anwender eingestellt hat.
lpBuffer In diesen mit 256 Zeichen wohl ausreichend vorbelegten String schreibt die Funktion den Fehlertext.
142
8
Registry
nSize nSize wird mit der vorbelegten Länge von lpBuffer übergeben.
Arguments Hier stehen uns einige recht kryptische Formatierungskommandos zur Verfügung, die vom System bei der Rückgabe des Fehlertextes berücksichtigt werden. Eine 0 macht dem Spuk jedoch ein frühzeitiges Ende.
Rückgabe Die Bruttolänge von lpBuffer erwartet uns hier.
8.6.2
Die Methode GetErrorMessage
Wie im vorangehenden Abschnitt zu sehen war, kann die Ermittlung eines Fehlertextes recht kompliziert werden. Um so angenehmer kann empfunden werden, dass wir uns nicht in externe Bibliotheken einklinken müssen. Da die Methode recht übersichtlich ist, müssen wir sie nicht häppchenweise verarbeiten: Private Function GetErrorMessage(ByVal lngMessageID As Long) As String ' lngMessageID: FehlerCode in System Message Table Dim strError As String * 256 'Fehlertext Dim lngResult As Long 'Rückgabe Dim nSize As Long 'Länge von strError [IN] On Error GoTo errhandler nSize = 256 lngResult = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0&, _ lngMessageID, 0&, strError, nSize, 0&) If lngResult = 0 Then If Err.Number = 0 Then Err.Raise 9999 Else GetErrorMessage = Err.Description & vbCrLf & Err.LastDllError End If Else GetErrorMessage = Left(strError, lngResult – 1) End If Exit Function errhandler: Err.Raise 9999, , "Der Fehlertext zu ReturnCode " & _ lngReturnCode & " konnte nicht ermittelt werden." End Function
Eine Settings-Schnittstelle
143
Eine entscheidende Frage ist, ob hier eine lngMessageID entgegengenommen werden könnte, die zu einem leeren Fehlertext, also lngResult = 0, führt. Die MSDN-Beispiele schließen es auch nicht aus. Der hervorgehobene Block kümmert sich genau um diesen hoffentlich hypothetischen Fall. Die fehlende Source-Eigenschaft des Err-Objekts veranlasst die aufrufende Methode dazu, ihren Stempel in die Source zu prägen. Beim Testen der Funktion bleibt die eine oder andere Überraschung nicht aus, wenn beispielsweise eine offensichtlich nicht definierte lngMessageID an FormatMessage übergeben wird. Excel war reichlich platt, als ich zu Testzwecken in einer For-NextSchleife die Werte 1 bis 100 erzeugte und an die Methode übergab.
8.7 Eine Settings-Schnittstelle Die Funktionen GetSetting, SaveSetting und DeleteSetting bieten eine einfache und leistungsfähige Schnittstelle zur Registry. Die Schachtelungstiefe ist in der Regel ausreichend, denn wir können beliebige Sections unter dem Application-Key anlegen und beliebige Schlüssel in jeder Section. Die Beschränkung auf den String-Datentyp ist kein wirkliches Manko, denn schließlich gibt es ja reichlich Konvertierungsfunktionen. Als nachteilig ließe sich verzeichnen, dass die Sections und Werte generell unter dem Schlüssel »Software\VB and VBA Program Settings« in HKEY_CURRENT_USER angelegt werden. Als störend empfinde ich, dass das Löschen eines Wertes über DeleteSetting den Fehler 5 produziert, sobald dieser Wert nicht mehr existiert. In der späteren Applikation können wir diesen Fehler über eine Fehlerbehandlung natürlich ausschalten, wenn wir das Programm in der Entwicklungsumgebung jedoch gezielt mit der Einstellung »bei jedem Fehler unterbrechen« gestartet haben, dann nervt das schon auf Dauer. Hier könnten Sie anmerken, dass man diesen Fall mit einem Debug-Schalter umgehen könnte, aber von dieser Technik halte ich persönlich nicht sonderlich viel. Sei’s drum, wir bauen eine Settings-Schnittstelle, die gleichermaßen einfach und flexibel ist: 왘 Sie muss Werte erzeugen und ggf. anlegen können, auslesen und auch wieder löschen gehören ebenfalls dazu. 왘 Das recht nette Feature DefaultValue der GetSetting-Funktion soll sie ebenfalls bieten. 왘 Der oberste Schlüssel wird unter Software in HKEY_CURRENT_USER angelegt. 왘 Die bekannten Methodennamen SaveSetting, GetSetting und DeleteSetting finden Verwendung und dienen als einfache Schnittstellen zu den komplexeren Methoden GetRegitryValue, SetRegistryValue und DeleteRegistryValue.
144
8
Registry
Beim Start der Applikation stellen wir die AppName-Eigenschaft der Klasse ein, die sich während des Programmlaufs ja in der Regel nicht verändert: cSysReg.AppName = "APITest"
Ein lesender Zugriff wird mit der Section-Eigenschaft eingeleitet. Danach wird die GetSetting-Methode nur noch mit dem Namen des Wertes und ggf. dem Default-Wert aufgerufen: cSysReg.Section = "frmMain" Me.Top = cSysReg.GetSetting("Top", 300) Me.Left = cSysReg.GetSetting("Left", 300)
Beim Schreiben stellen wir auch zuerst die Section-Eigenschaft ein. cSysReg.Section = "frmMain"
Nun geben wir den Datentyp an und übergeben Wertname und Wert. cSysReg.DataType = enumREG_ZS cSysReg.SaveSetting "Caption", Me.Caption
Da der Datentyp in der Klasse gespeichert ist, können Gruppen von Werten gleichen Datentyps vereinfacht werden: cSysReg.DataType = enumREG_DWORD cSysReg.SaveSetting "Top", Me.Top cSysReg.SaveSetting "Left", Me.Left
Was bleibt, ist das Löschen eines Wertes: cSysReg.Section = "frmMain" cSysReg.DeleteSetting "Top"
Wenn die Section bereits eingestellt ist, kann die obere Zeile natürlich entfallen. Die benötigten Eigenschaftsroutinen gestalten sich recht einfach: Public Property Let AppName(ByVal NewAppName As String) strAppName = NewAppName End Property Public Property Let Section(ByVal NewSection As String) strSection = NewSection End Property Public Property Get Section() As String Section = strSection End Property Public Property Let DataType(ByVal NewDataType As enumDataType) lngDataType = NewDataType End Property
Eine Settings-Schnittstelle
145
Lediglich die Section ist in Schreib- und Leserichtung ausgelegt, die beiden anderen Eigenschaften werden nur geschrieben. Hinter DataType steckt die folgende Enumeration: Public Enum enumDataType enumREG_ZS = 1 enumREG_EXPAND_SZ = 2 enumREG_BINARY = 3 enumREG_DWORD = 4 enumREG_MULTI_SZ = 7 End Enum
Zum Schreiben der Werte steht die Methode SaveSetting bereit, die sich natürlich ganz auf die SetRegistryValue-Methode verlässt. Dieser übergibt sie den hKEY, stellt den Pfad zusammen und übergibt Schlüsselname, Datentyp und den eigentlichen Wert: Public Sub SaveSetting(ByVal ValueName As String, _ ByVal Value As Variant) 'Argumente: ' ValueName: Name des Wertes ' Value: Neuer Wert Me.SetRegistryValue enumHKEY_CURRENT_USER, _ "Software\" & strAppName & "\" & strSection, _ ValueName, lngDataType, Value End Sub
Das Lesen gestaltet sich eine Spur aufwändiger, da der Default-Wert verarbeitet werden muss. Public Function GetSetting(ByVal ValueName As String, _ Optional ByVal DefaultValue As Variant) As Variant 'Argumente: ' ValueName: Name des Wertes ' DefaultValue: Default-Wert Dim varSetting As Variant 'Zwischenvariable varSetting = Me.GetRegistryValue(enumHKEY_CURRENT_USER, _ "Software\" & strAppName & "\" & strSection, ValueName) If IsEmpty(varSetting) And Not IsEmpty(DefaultValue) Then varSetting = DefaultValue End If GetSetting = varSetting End Function
Die Rückgabe der GetRegistryValue-Methode wird in einer Zwischenvariablen gespeichert. Ist diese Empty und ein Default-Wert vorhanden, so wird der Default-Wert zurückgegeben.
146
8
Registry
Bleibt noch die Löschroutine DeleteSetting, die ebenfalls wie SaveSetting nur aus einer Programmzeile besteht: Public Function DeleteSetting(ByVal ValueName As String) 'Argumente: ' ValueName: Name des Wertes Me.DeleteRegistryValue enumHKEY_CURRENT_USER, _ "Software\" & strAppName & "\" & strSection, _ ValueName End Function
8.8 Enumeration von Schlüsseln und Werten Bislang sind wir davon ausgegangen, dass sowohl der Name des Schlüssels als auch der des Wertes bekannt sind. Das ist auch die Regel bei Registry-Zugriffen. Das Registry-API stellt jedoch Funktionen bereit, um Unterschlüssel und Werte eines Schlüssels zu ermitteln, indem an die betreffende Enumerationsfunktion (RegEnumKeyEx) der nullbasierte Index des betreffenden Unterschlüssels oder Wertes übergeben wird. In der MSDN sind Beispiele zu sehen, in denen in einer Do-Loop-Schleife ein Indexwert so lange inkrementiert wird, bis die betreffende Funktion einen Fehler meldet, also auf ein Element versucht wurde zuzugreifen, das schlicht nicht existiert. Hmmm, kann man so machen. Eleganter ist es aber zweifellos, vorher die Anzahl der Unterelemente zu ermitteln und den Index an dieser Obergrenze zu orientieren. Diese Vorgehensweise bietet noch den unbestreitbaren Vorteil, dass bei dieser Gelegenheit auch die Größe des längsten Namens und Wertes in Erfahrung gebracht werden kann. Wie groß würden wir die Strings vorbelegen, wenn wir die tatsächliche Größe nicht kennen? Der folgende Satz aus der MSDN hilft auch nicht viel weiter: Windows 95 / 98: No registry subkey or value name may exceed 255 characters. Was ist mit NT und Windows 2000? Schauen wir uns einmal die RegQueryInfoKeyFunktion an, die uns diese Informationen liefert.
8.8.1
RegQueryInfoKey
Die Funktion wird wie folgt deklariert: Declare Function RegQueryInfoKey Lib "advapi32.dll" Alias "RegQueryInfoKeyA" (ByVal hKey As Long, ByVal lpClass As Long, lpcbClass As Long, ByVal lpReserved As Long, lpcSubKeys As Long, lpcbMaxSubKeyLen As Long, lpcbMaxClassLen As Long, lpcValues As Long, lpcbMaxValueNameLen As Long, lpcbMaxValueLen As Long, lpcbSecurityDescriptor As Long, lpftLastWriteTime As FILETIME) As Long
Enumeration von Schlüsseln und Werten
147
hKey Der Handle auf den geöffneten Key
lpClass Zeiger auf eine Klasse, die im Moment noch nicht unterstützt wird, weshalb wir 0& übergeben.
lpcbClass Länge von lpClass, auch 0&
lpReserved Und noch einmal 0&
lpcSubKeys Anzahl der Unterschlüssel
lpcbMaxSubKeyLen Nettogröße des längsten Unterschlüsselnamens in Byte
lpcbMaxClassLen Größe des längsten Klassennamens. Da diese noch nicht unterstützt werden, übergeben wir 0&.
lpcValues Anzahl der Werte des Schlüssels. Der Wert (Standard) wird nicht mitgezählt.
lpcbMaxValueNameLen Nettogröße des längsten Wertnamens in Byte
lpcbMaxValueLen Größe des längsten Wertes in Byte
lpcbSecurityDescriptor Dahinter verbirgt sich das recht komplizierte Thema security descriptors und access-control lists (ACLs), dessen Registry-Relevanz mir noch einigermaßen unklar ist. Alle Zugriffe mit einem schlichten 0& an dieser Stelle funktionierten, weshalb wir uns den Rest hier großzügig schenken.
148
8
Registry
lpftLastWriteTime Hier erwartet die Funktion einen FILETIME-Datentyp, in dem Datum und Uhrzeit des letzten schreibenden Zugriffs auf diesen Schlüssel oder eines seiner Elemente, also Unterschlüssel oder Werte, eingetragen werden. Lediglich Windows NT führt hierüber Buch, Win95 und 98 schreiben eine 0 hinein.
Rückgabe Die Rückgabe ist auch hier 0 im Erfolgsfalle und eben ungleich 0 bei Fehlern.
8.8.2
RegEnumKeyEx
Diese Funktion dient dazu, über einen NULL-basierten Index jeweils einen Schlüsselnamen zu ermitteln: Declare Function RegEnumKeyEx Lib "advapi32.dll" Alias "RegEnumKeyExA" (ByVal hKey As Long, ByVal dwIndex As Long, ByVal lpName As String, lpcbName As Long, ByVal lpReserved As Long, ByVal lpClass As Long, lpcbClass As Long, lpftLastWriteTime As FILETIME) As Long
hKey Der Handle auf den geöffneten Key
dwIndex Der NULL-basierte Index des betreffenden Unterschlüssels
lpName String, der mit dem Namen des Unterschlüssels gefüllt wird
lpcbName Bruttolänge des lpName. Wird mit dem um 1 erhöhten Wert des Arguments lpcbMaxSubKeyLen der Funktion RegQueryInfoKey vorbelegt.
lpReserved Wir übergeben 0&
lpClass Zeiger auf eine Klasse, die im Moment noch nicht unterstützt wird, weshalb wir 0& übergeben.
Enumeration von Schlüsseln und Werten
149
lpcbClass Länge von lpClass, auch 0&
lpftLastWriteTime Hier erwartet die Funktion einen FILETIME-Datentyp, in dem Datum und Uhrzeit des letzten schreibenden Zugriffs auf diesen Schlüssel oder eines seiner Elemente, also Unterschlüssel oder Werte, vermerkt werden. Lediglich Windows NT führt hierüber Buch, Win95 und 98 schreiben eine 0 hinein. Der FILETIME-Datentyp ist wie folgt aufgebaut: Private | Public Type FILETIME dwLowDateTime As Long dwHighDateTime As Long End Type
Rückgabe Die Rückgabe ist 0 im Erfolgsfalle und ungleich 0 bei Fehlern.
8.8.3
Die Methode EnumKeys
Nachdem die beiden verwendeten Funktionen vorgestellt wurden, können wir uns der Methode widmen, die die Namen aller Unterschlüssel des übergebenen Schlüssels in einem String-Array zurückgibt. Die Rückgabe ist Empty, wenn keine Unterschlüssel vorhanden sind. Public Function EnumKeys(ByVal lngHKEY As enumHKEY, _ ByVal strKey As String) As Variant ' SubKeys von strKey zurückgeben 'Argumente: ' lngHKey: KEY (z.B. HKEY_CURRENT_USER) ' strKey: Key (z.B. "Software\APITool") Dim lngResult As Long 'Rückgabe aus Funktion Dim hKey As Long 'Handle des zu öffnenden Keys Dim nSubKeys As Long 'Anzahl der SubKeys Dim iSubKey As Long 'Zeiger in nSubKeys Dim strSubKey As String 'jeweiliger SubKey Dim nSize As Long 'Länge von strKey Dim lngMaxSize As Long 'größte Länge der SubKeys Dim udtTime As FILETIME 'FILETIME-Struktur Dim lngDummy As Long 'Dummy für alle nicht benötigten Rückgaben Dim strKeys() As String 'temporäres Array On Error GoTo errhandler 'Schlüssel öffnen lngResult = RegOpenKeyEx(lngHKEY, strKey, 0, KEY_ALL_ACCESS, hKey) If lngResult = ERROR_SUCCESS Then
150
8
Registry
'Größen ermitteln lngResult = RegQueryInfoKey(hKey, 0&, 0&, 0&, nSubKeys, _ lngMaxSize, lngDummy, lngDummy, lngDummy, lngDummy, _ lngDummy, udtTime) If lngResult <> ERROR_SUCCESS Then Err.Raise lngResult, , GetErrorMessage(lngResult) End If 'Abbruch, wenn keine SubKeys vorhanden If nSubKeys = 0 Then EnumKeys = Empty RegCloseKey hKey Exit Function End If ReDim strKeys(nSubKeys – 1) 'SubKeys ermitteln For iSubKey = 0 To nSubKeys – 1 'Variablen vorbelegen strSubKey = Space(lngMaxSize + 1) nSize = lngMaxSize + 1 'Funktionsaufruf lngResult = RegEnumKeyEx(hKey, iSubKey, strSubKey, nSize, _ 0&, 0&, 0&, udtTime) If lngResult <> ERROR_SUCCESS Then Err.Raise lngResult, , GetErrorMessage(lngResult) End If 'SubKey in Array schreiben strKeys(iSubKey) = Left(strSubKey, nSize) Next EnumKeys = strKeys Else EnumKeys = Empty End If 'Schlüssel schließen RegCloseKey hKey Exit Function errhandler: RegCloseKey hKey If InStr(1, Err.Source, ":") = "" Then Err.Source = "cSysRegistry:EnumKeys" End If Err.Raise Err.Number End Function
Beachten Sie bitte, dass die von RegQueryInfoKey retournierten Größenangaben Nettowerte sind, also ohne das abschließende NULL. Die beiden Variablen strSubKey und nSize sind wiederum Bruttowerte, weshalb in den beiden hervorgehobenen Zeilen eine 1 addiert werden muss. Alle nicht benötigten Werte aus RegQueryInfoKey werden durch lngDummy abgefangen. Dieser Wert wird von der Funktion eben mehrfach beschrieben.
Enumeration von Schlüsseln und Werten
151
Im folgenden Codeauszug sehen Sie die Verwendung dieser Methode: strItems = cSysReg.EnumValues(HKEY_CLASSES_ROOT, "Software") If Not IsEmpty(strItems) Then For iKey = 0 To UBound(strItems) lstItems.AddItem strItems(iKey) Next End If
8.8.4
RegEnumValue
Ähnlich der Funktion RegEnumKeyEx wird diese Funktion über einen NULL-basierten Index gesteuert und gibt uns Namen, Typ und Inhalt des jeweiligen Wertes zurück. Declare Function RegEnumValue Lib "advapi32.dll" Alias "RegEnumValueA" (ByVal hKey As Long, ByVal dwIndex As Long, ByVal lpValueName As String, lpcbValueName As Long, ByVal lpReserved As Long, lpType As Long, ByVal lpData As String, lpcbData As Long) As Long
hKey Der Handle auf den geöffneten Key
dwIndex Der NULL-basierte Index des betreffenden Unterschlüssels
lpValueName String, der mit dem Namen des Wertes gefüllt wird
lpcbValueName Bruttolänge des lpValueName. Wird mit dem um 1 erhöhten Wert des Arguments lpcbMaxValueNameLen der Funktion RegQueryInfoKey vorbelegt
lpReserved Wir übergeben 0&
lpType Registry-Datentyp des Wertes
152
8
Registry
lpData String zur Entgegennahme des Wertes. Auch wenn der jeweilige Werte nicht interessiert, müssen dennoch lpData und lpcbData entsprechend vorbelegt übergeben werden.
lpcbData Bruttolänge des lpData, was mit dem um 1 erhöhten Wert des Arguments lpcbMaxValueLen der Funktion RegQueryInfoKey vorbelegt.
Rückgabe Die Rückgabe ist 0 im Erfolgsfalle und ungleich 0 bei Fehlern.
8.8.5
Die Methode EnumValues
Diese Methode benötigt drei Variablen mehr als die EnumKeys-Methode, da neben den Namen auch noch Datentyp und Wert transportiert werden. Was geschieht mit den Datentypen und Werten? Sollen sie ebenfalls zurückgegeben werden? Das ist eine gute Frage. Die GetRegistryValue-Methode zeigte, dass wir je nach Datentyp die Rückgabe anders verarbeiten müssen. Es ist nun Geschmackssache, ob die typgerechte Aufbereitung des Wertes integriert wird oder nicht. Ich persönlich habe mich dagegen entschieden, was Sie jedoch nicht daran hindern sollte, neben Namen und Wert auch Datentyp in dem Array zurückzuliefern. Da die Namen der Werte von dieser Methode zurückgeliefert werden, kann die aufrufende Programmstelle mit Schlüssel- und Wertnamen die GetRegistryValue-Methode aufrufen. Diese zusätzliche Rechenzeit dürfte verschmerzbar sein, zumal ja in der Regel keine Hunderte von Werten hinter einem Schlüssel stehen. Public Function EnumValues(ByVal lngHKEY As enumHKEY, _ ByVal strKey As String) As Variant ' Values von strKey zurückgeben 'Argumente: ' lngHKey: KEY (z.B. HKEY_CURRENT_USER) ' strKey: Key (z.B. "Software\APITool") Dim lngResult As Long 'Rückgabe aus Funktion Dim hKey As Long 'Handle des zu öffnenden Keys Dim nValues As Long 'Anzahl der Values Dim iValue As Long 'Zeiger in nValues Dim lngMaxNameSize As Long 'größte Länge der ValueNames Dim strValueName As String 'jeweiliger ValueName Dim nValueNameSize As Long 'Länge von strValueName Dim lngMaxValueSize As Long 'größte Länge der Values Dim strValue As String 'jeweiliger Value Dim nValueSize As Long 'Länge von strValue
Enumeration von Schlüsseln und Werten Dim lngType As Long 'Registry-Datentyp Dim udtTime As FILETIME 'FILETIME-Struktur Dim lngDummy As Long 'Dummy für nicht benötigte Rückgaben Dim strValueNames() As String 'temporäres Array On Error GoTo errhandler 'Schlüssel öffnen lngResult = RegOpenKeyEx(lngHKEY, strKey, 0, KEY_ALL_ACCESS, hKey) If lngResult = ERROR_SUCCESS Then 'Größen ermitteln lngResult = RegQueryInfoKey(hKey, 0&, 0&, 0&, lngDummy, _ lngDummy, lngDummy, nValues, lngMaxNameSize, _ lngMaxValueSize, lngDummy, udtTime) If lngResult <> ERROR_SUCCESS Then Err.Raise lngResult, , GetErrorMessage(lngResult) End If 'Abbruch, wenn keine Werte vorhanden If nValues = 0 Then EnumValues = Empty RegCloseKey hKey Exit Function End If ReDim strValueNames(1, nValues – 1) 'Werte ermitteln For iValue = 0 To nValues – 1 'Variablen vorbelegen strValueName = Space(lngMaxNameSize + 1) nValueNameSize = lngMaxNameSize + 1 strValue = Space(lngMaxValueSize + 1) nValueSize = lngMaxValueSize + 1 'Funktionsaufruf lngResult = RegEnumValue(hKey, iValue, strValueName, _ nValueNameSize, 0&, lngType, strValue, nValueSize) If lngResult <> ERROR_SUCCESS Then Err.Raise lngResult, , GetErrorMessage(lngResult) End If 'Werte in Array schreiben strValueNames(0, iValue) = Left(strValueName, nValueNameSize) strValueNames(1, iValue) = lngType Next EnumValues = strValueNames Else EnumValues = Empty End If 'Schlüssel schließen RegCloseKey hKey Exit Function errhandler: RegCloseKey hKey If InStr(1, Err.Source, ":") = "" Then Err.Source = "cSysRegistry:EnumValues"
153
154
8
Registry
End If Err.Raise Err.Number End Function
8.9 Registry-Viewer Marke Eigenbau Unsere Klasse soll ihre Funktionsfähigkeit und Praktikabilität an einem konkreten Beispiel unter Beweis stellen. Abbildung 8.4 zeigt das Ergebnis, das wir nun angehen wollen. Bei den dargestellten Inhalten handelt es sich um alte Bekannte, die bereits in Abbildung 8.1 erschienen. Der Registry-Datentyp DWORD präsentiert sich mit einem eigenen Icon.
Abbildung 8.4: Registry-Viewer Marke Eigenbau
Die Einträge werden in der Form_Load-Prozedur erzeugt: Private Sub Form_Load() cSysReg.SetRegistryValue enumHKEY_CURRENT_USER, _ "Software\APITest\Settings", _ "SZ-String", enumREG_ZS, "erster String" cSysReg.SetRegistryValue enumHKEY_CURRENT_USER, _ "Software\APITest\Settings", _ "EXPAND_SZ-String", enumREG_EXPAND_SZ, _ "%SystemRoot%\Test.ini" cSysReg.SetRegistryValue enumHKEY_CURRENT_USER, _ "Software\APITest\Settings", _ "MULTI_SZ-String", enumREG_MULTI_SZ, _ Array("String1", "String2")
Registry-Viewer Marke Eigenbau
155
cSysReg.SetRegistryValue enumHKEY_CURRENT_USER, _ "Software\APITest\Settings", "BINARY", _ enumREG_BINARY, Chr(10) & Chr(20) & Chr(30) cSysReg.SetRegistryValue enumHKEY_CURRENT_USER, _ "Software\APITest\Settings", "DWORD", _ enumREG_DWORD, 1234 Call Tvw_Load End Sub
In der Tvw_Load-Prozedur, die von Form_Load aufgerufen wird, werden die Hauptknoten erzeugt und die rekursive Tvw_LoadChildren mit HKEY_CURRENT_USER gestartet: Private Sub Tvw_Load() Dim actNode As MSComctlLib.Node Tvw.Nodes.Add , tvwFirst, "Root", "Arbeitsplatz", "MyComp" Set actNode = Tvw.Nodes.Add("Root", tvwChild, "K-2147483648", "HKEY_CLASSES_ROOT", "Closed", "Open") Set actNode = Tvw.Nodes.Add("Root", tvwChild, "K-2147483647", "HKEY_CURRENT_USER", "Closed", "Open") Tvw_LoadChildren &H80000001, actNode, "" Set actNode = Tvw.Nodes.Add("Root", tvwChild, "K-2147483646", "HKEY_LOCAL_MACHINE", "Closed", "Open") Set actNode = Tvw.Nodes.Add("Root", tvwChild, "K-2147483645", "HKEY_USERS", "Closed", "Open") Set actNode = Tvw.Nodes.Add("Root", tvwChild, "K-2147483643", "HKEY_CURRENT_CONFIG", "Closed", "Open") Tvw.Nodes("Root").Expanded = True End Sub
_ _
_ _ _
Tvw_LoadChildren lädt die Children des aktuellen Knotens und ruft sich jeweils mit jedem neuen Knoten selbst auf: Private Sub Tvw_LoadChildren(lngKey As Long, _ actNode As MSComctlLib.Node, strKey As String) Dim NewNode As MSComctlLib.Node Dim varSubKeys As Variant Dim iSubKey As Long varSubKeys = cSysReg.EnumKeys(lngKey, strKey) If LenB(strKey) > 0 Then strKey = strKey & "\" If Not IsEmpty(varSubKeys) Then For iSubKey = 0 To UBound(varSubKeys) Set NewNode = Tvw.Nodes.Add(actNode.Key, tvwChild, _ "K" & Tvw.Nodes.Count, varSubKeys(iSubKey), _ "Closed", "Open") Tvw_LoadChildren lngKey, NewNode, strKey & NewNode.Text Next End If End Sub
156
8
Registry
Das war’s, der Baum ist geladen. Die Node_Click-Prozedur sorgt ihrerseits dafür, dass der ListView geladen wird: Private Sub Tvw_NodeClick(ByVal Node As MSComctlLib.Node) Dim actItem As MSComctlLib.ListItem Dim varValues As Variant Dim iValue As Long Dim varValue As Variant Dim strKey As String Dim lngKey As Long Dim iChar As Long Lvw.ListItems.Clear If Node.Key = "Root" Then Exit Sub 'Key-String bilden strKey = GetKeyString(Node) 'Root-Key-Wert ermitteln lngKey = GetRootKey(Node) 'Standard-Wert erzeugen Set actItem = Lvw.ListItems.Add(, , "(Standard)", "String", _ "String") 'Inhalt des Standard-Wertes ermitteln und eintragen varValues = cSysReg.GetRegistryValue(enumHKEY_CURRENT_USER, _ strKey, "") If IsEmpty(varValues) Then actItem.SubItems(1) = "(Wert nicht gesetzt)" Else actItem.SubItems(1) = varValues End If 'alle Wertenamen und -typen ermitteln ... varValues = cSysReg.EnumValues(lngKey, strKey) If Not IsEmpty(varValues) Then For iValue = 0 To UBound(varValues, 2) '... und nacheinander eintragen Set actItem = Lvw.ListItems.Add(, , varValues(0, iValue)) 'den jeweiligen erfragen ... varValue = cSysReg.GetRegistryValue(lngKey, strKey, _ varValues(0, iValue)) '... und typabhängig darstellen Select Case varValues(1, iValue) Case 1, 2 actItem.SmallIcon = "String" actItem.SubItems(1) = varValue Case 7 actItem.SmallIcon = "String" actItem.SubItems(1) = Replace(Join(varValue, Chr(0)), _ Chr(0), ";") Case 3 actItem.SmallIcon = "Binary" For iChar = 1 To Len(varValue) actItem.SubItems(1) = actItem.SubItems(1) & " " & _ Right(Hex(Asc(Mid(varValue, iChar, 1)) + 256), 2)
Und das Ganze in VBA
157
Next Case 4 actItem.SmallIcon = "Long" actItem.SubItems(1) = Hex(varValue) & " (" & varValue & ")" End Select Next End If 'und noch etwas AutoSizing im ListView SendMessage Lvw.hwnd, LVM_SETCOLUMNWIDTH, 0, _ LVSCW_AUTOSIZE_USEHEADER SendMessage Lvw.hwnd, LVM_SETCOLUMNWIDTH, 1, _ LVSCW_AUTOSIZE_USEHEADER End Sub
Es fehlen noch die beiden Routinen, die ausgehend vom aktuellen Knoten den Schlüssel bilden und den Key-Wert aus der zweitobersten Ebene entnehmen, der dort als Node.Key eingetragen wurde: Private Function GetKeyString(Node As MSComctlLib.Node) As String Dim actNode As MSComctlLib.Node Set actNode = Node Dim strKey As String Do Until actNode.Parent.Key = "Root" strKey = actNode.Text & "\" & strKey Set actNode = actNode.Parent Loop GetKeyString = strKey End Function Private Function GetRootKey(Node As MSComctlLib.Node) As Long Dim actNode As MSComctlLib.Node Set actNode = Node Do Until actNode.Parent.Key = "Root" Set actNode = actNode.Parent Loop GetRootKey = CLng(Right(actNode.Key, Len(actNode.Key) – 1)) End Function
Ich gestehe, in dieses kleine Beispielprogramm nicht sonderlich viel Energie gesteckt zu haben, was man nicht nur am fehlenden ErrorHandler erkennen kann. Die Children eines angeklickten Knotens müssten beispielsweise dynamisch geladen werden.
8.10 Und das Ganze in VBA Auch für VBA möchte ich noch ein Beispiel präsentieren, das naturgemäß nicht in solch elegantem Äußeren daherkommt, wie Abbildung 8.5 zeigt:
158
8
Registry
Abbildung 8.5: Registry-Viewer in VBA
Die Prozedur UserForm_Initialize übernimmt das Laden der ComboBox. Außerdem wird ein Array gefüllt, aus dem über den ListIndex der ComboBox der RootKey-Long festgestellt werden kann: Private Sub UserForm_Initialize() lngRootKey(0) = &H80000000 lngRootKey(1) = &H80000001 lngRootKey(2) = &H80000002 lngRootKey(3) = &H80000003 lngRootKey(4) = &H80000005 cmbKeys.AddItem "HKEY_CLASSES_ROOT" cmbKeys.AddItem "HKEY_CURRENT_USER" cmbKeys.AddItem "HKEY_LOCAL_MACHINE" cmbKeys.AddItem "HKEY_USERS" cmbKeys.AddItem "HKEY_CURRENT_CONFIG" cmbKeys.ListIndex = 1 End Sub
Das Click-Ereignis der ComboBox wird dazu verwendet, die Laderoutine für die ListBox aufzurufen: Private Sub cmbKeys_Click() lblKey.Caption = "" LoadSubKeys End Sub
Und das Ganze in VBA
159
Diese Laderoutine LoadSubKeys übernimmt das eigentliche Laden der ListBox: Private Sub LoadSubKeys() Dim strItems As Variant Dim iKey As Long lstItems.Clear If lblKey.Caption <> "" Then lstItems.AddItem ".." End If '(Standard)-Wert lstItems.AddItem "(V) (Standard)" 'SubKeys strItems = cSysReg.EnumKeys(lngRootKey(cmbKeys.ListIndex), _ lblKey.Caption) If Not IsEmpty(strItems) Then For iKey = 0 To UBound(strItems) lstItems.AddItem "(K) " & strItems(iKey) Next End If 'Values strItems = cSysReg.EnumValues(lngRootKey(cmbKeys.ListIndex), _ lblKey.Caption) If Not IsEmpty(strItems) Then For iKey = 0 To UBound(strItems, 2) lstItems.AddItem "(V) " & strItems(0, iKey) Next End If End Sub
Die ListBox muss die Schlüsselnamen und die Wertenamen aufnehmen, die zur Unterscheidung ein (K) für Key und (V) für Value vorangestellt bekommen. Um eine Navigation zu ermöglichen, wird ein »..« als erster Eintrag erzeugt. Ein einfacher Klick in der ListBox lädt das Label lblValue, das den Inhalt des Wertes anzeigt, sofern der aktuelle Eintrag ein Wert ist. Wir kennen den Registry-Typ nicht, weshalb andere Lösungen gesucht werden müssen. Mit IsArray und IsNumeric können wir zwei Fälle sicher erkennen und verarbeiten. Lediglich das Erkennen eines Binärwertes erfordert ein näheres Hinsehen. In einer Schleife werden alle einzelnen Zeichen daraufhin untersucht, ob ihr ANSI-Wert kleiner als 32 ist. Wurde ein solcher gefunden, erfolgt die aus Abschnitt 8.9 bekannte Konvertierung in ihre hexadezimalen Zeichencodes. Private Sub lstItems_Click() Dim varValue As Variant Dim strValueName As String Dim iChar As Long If Left(lstItems.Text, 3) = "(V)" Then strValueName = Right(lstItems.Text, Len(lstItems.Text) – 4) varValue = _ cSysReg.GetRegistryValue(lngRootKey(cmbKeys.ListIndex), _
160 lblKey.Caption, strValueName) If IsArray(varValue) Then lblValue.Caption = Replace(Join(varValue, Chr(0)), Chr(0), _ ";") ElseIf IsNumeric(varValue) Then lblValue.Caption = Hex(varValue) & " (" & varValue & ")" Else For iChar = 1 To Len(varValue) If Asc(Mid(varValue, iChar, 1)) < 32 Then Exit For Next If iChar > Len(varValue) Then lblValue.Caption = varValue Else lblValue.Caption = "" For iChar = 1 To Len(varValue) lblValue.Caption = lblValue.Caption & " " & _ Right(Hex(Asc(Mid(varValue, iChar, 1)) + 256), 2) Next End If End If Else lblValue.Caption = "" End If End Sub
Ein Doppelklick in der ListBox wiederum löst die Navigation aus: Private Sub lstItems_DblClick(ByVal Cancel As MSForms.ReturnBoolean) Dim strNetKey As String If Left(lstItems.Text, 1) = "(" Then strNetKey = Right(lstItems.Text, Len(lstItems.Text) – 4) Else strNetKey = lstItems.Text End If If lblKey.Caption = "" Then lblKey.Caption = strNetKey ElseIf lstItems.Text = ".." Then If InStr(lblKey.Caption, "\") = 0 Then lblKey.Caption = "" Else lblKey.Caption = Left(lblKey.Caption, _ InStrRev(lblKey.Caption, "\") – 1) End If Else lblKey.Caption = lblKey.Caption & "\" & strNetKey End If LoadSubKeys End Sub
8
Registry
Von .doc zum Word-Dokument
161
Neben dem Aufruf der LoadSubKeys ist es die Aufgabe dieser Prozedur, die Caption des Labels zu erzeugen, die in LoadSubKeys als Key an die Registry-Klasse übergeben wird.
8.11 Von .doc zum Word-Dokument In Kapitel 4 hatten wir bereits ein konkretes Beispiel, in dem über die Dateierweiterung der Name des Dokumenttyps ermittelt wurde. Abbildung 8.6 zeigt diesen Zusammenhang.
Abbildung 8.6: Der Weg zum Namen des Dokumenttyps
Wir haben Glück, denn beide gesuchten Werte sind Standardwerte des jeweiligen Schlüssels. Der Klassenname ist Standardwert des Dateierweiterungseintrags und der Dokumenttypname Standardwert des Klassennamens.
8.11.1
Die Methode ExtensionToFileType
Wie Abbildung 8.7 zeigt, benötigen wir zwei Zugriffe. Der erste ermittelt den Klassennamen »Word.Document.8«. Im zweiten Zugriff wird der Klassenname als Schlüssel gesucht und dessen Standardwert beinhaltet bereits den gesuchten Begriff »Microsoft Word-Dokument«.
162
8
Registry
Public Function ExtensionToFileType (ByVal strExtension _ As String) As Variant ' Dateityp der übergebenen Dateierweiterung zurückgeben und ' Empty, wenn er nicht existiert. 'Argumente: ' strExtension: Dateierweiterung Dim strClass As Variant 'Klassenname On Error GoTo Errhandler strClass = GetRegistryValue(HKEY_CLASSES_ROOT, "." & strExtension, _ "") If IsEmpty(strClass) Then ExtensionToFileType = Empty Exit Function End If ExtensionToFileType = GetRegistryValue(HKEY_CLASSES_ROOT, _ strClass, "") Exit Function Errhandler: If InStr(1, Err.Source, ":") = 0 Then Err.Source = "cSysRegistry: ExtensionToFileType " End If Err.Raise Err.Number End Function
8.12 Von .doc zu ContentType Der ContentType kennzeichnet in MIME-Headern den Typ des Anhangs einer Mail. Bei Word-Dokumenten lautet er beispielsweise »application/msword«. Auch hier bietet uns der Dateierweiterungseintrag unter HKEY_CLASSES_ROOT einen Zugang, der in Abbildung 8.8 am Beispiel der Dateierweiterung .doc zu sehen ist. Der Bezug vom Content Type zur Klasse kann natürlich auch wieder hergestellt werden, denn unter »HKEY_CLASSES_ROOT\MIME\Database\Content Type« wiederum finden sich die Einträge, die ihrerseits mindestens zur Dateierweiterung führen. Manche führen auch die Class-ID (CLSID) der betreffenden Anwendung. Man könnte vermuten, dass es sich beim Auftreten einer CLSID um den Content Type nicht registrierter Dateitypen handelt, aber das kann zumindest nicht generell behauptet werden. Ist die Dateierweiterung erst mal bekannt, so stehen auch alle anderen Informationen bereit.
8.12.1
Die Methode ExtensionToContentType
Kann vom Sender kein Content Type ermittelt werden, so wird üblicherweise im MIME-Header »application/octet-stream« als Typ eingetragen.
Von .doc zu ContentType
Abbildung 8.7: Content Type unter HKEY_CLASSES_ROOT\.doc
Abbildung 8.8: Content Type unter HKEY_CLASSES_ROOT\MIME Public Function ExtensionToContentType(ByVal strExtension As String) _ As String ' MIME-Content-Type ermitteln 'Argumente: ' strExtension: Dateierweiterung On Error GoTo Errhandler If Me.HasKey(enumHKEY_CLASSES_ROOT, "." & strExtension, _ "Content Type") Then ExtensionToContentType = _ Me.GetRegistryValue(enumHKEY_CLASSES_ROOT, _ "." & strExtension, "Content Type") Else ExtensionToContentType = "application/octet-stream"
163
164
8
Registry
End If Exit Function Errhandler: If InStr(1, Err.Source, ":") = 0 Then Err.Source = "cSysRegistry:ExtensionToContentType" End If Err.Raise Err.Number End Function
8.13 Vom Dateinamen zum DefaultIcon Das dürfte wohl einer der elegantesten Zugriffe in HKEY_CLASSES_ROOT sein, denn die Registry kann sogar dazu angehalten werden, ein Handle auf das einer Datei zugeordnete Icon zu erzeugen. Mit diesem Handle können wir über einen kleinen Umweg ein Image zur Laufzeit in eine ImageList fabrizieren, und dort wiederum kann es etwa von einem ListView-Steuerelement referenziert werden. Handelt es sich um einen registrierten Dateityp, so wird das diesem Typ zugeordnete Icon ermittelt. Ist der Dateityp nicht registriert, wie zum Beispiel bei einer Exe, so wird versucht, aus der Datei selbst ein Icon zu extrahieren.
Abbildung 8.9: Dateien mit Icons in ListView
Vom Dateinamen zum DefaultIcon
165
Sie ahnen, worauf das hinausläuft? Per aspera ad astra, wussten schon die Römer zu sagen. Ganz so einfach wird die Sache diesmal nicht, da es offensichtlich mehrere Möglichkeiten gibt, wie registrierte Dateitypen die Icon-Information ablegen können. Jedenfalls beginnt die Suche in der Registry mit der Dateierweiterung, die aus dem Dateinamen extrahiert wird.
8.13.1
Die Konstellationen in der Registry
Fall 1 Unter dem Klassennamen, zum Beispiel Word.Document.8, befindet sich ein Schlüssel namens DefaultIcon, dessen Standardwert einen Bezug zu der Datei und der IconNummer beinhaltet. Abbildung 8.10 zeigt diesen Fall.
Abbildung 8.10: DefaultIcon unter Klassennamen
Das DefaultIcon ist also durch ein Komma vom Dateinamen getrennt.
Fall 2 Steht unter dem Schlüssel DefaultIcon des Klassennamens nur eine Nummer, so muss die Datei, aus der dieses Icon extrahiert werden muss, auf einem anderen Weg in Erfahrung gebracht werden. Zu diesem Zweck wird die CLSID herausgelesen, denn in diesem Fall stehen Dateiname und Icon-Nummer unter der betreffenden CLSID, wie Abbildung 8.11 verdeutlicht. Das DefaultIcon ist auch hier durch ein Komma vom Dateinamen getrennt.
166
8
Registry
Abbildung 8.11: DefaultIcon unter CLSID
Fall 3 Es existiert zwar ein DefaultIcon-Eintrag, der enthält jedoch nur eine Icon-Nummer, mit oder ohne einem vorangestellten »%«-Zeichen. Nun wird geprüft, ob dem Dateityp eine CLSID zugeordnet ist. Wenn ja (gewissermaßen Fall 3a), so wird das DefaultIcon aus dem CLSID-Schlüssel gelesen. Andernfalls handelt es sich vermutlich um eine Exe, und das Icon wird aus der Exe selbst extrahiert (3b).
Fall 4 Im letzten Regelfall existiert im Schlüssel des Klassennamens kein Eintrag DefaultIcon, wie zum Beispiel bei der Klasse »MSGraph.Chart.8«. Nun wird direkt im CLSIDSchlüssel nach dem InProcServer32 oder dem LocalServer32 gesucht und dort das Standard-Icon (0) extrahiert.
Fall 5 Bei ScreenSaver-Dateien (*.scr) beispielsweise existiert weder ein DefaultIcon unter .scr noch unter dem Klassennamen scrfile. Letzterer kennt auch keine CLSID. Vermutlich gibt es aber auch hier einen Weg, denn der Explorer zeigt ein Icon an.
8.13.2
ExtractIcon
Um das Handle auf ein in einer Datei abgelegtes Icon zu erhalten, bietet sich die Funktion ExtractIcon an. Declare Function ExtractIcon Lib "shell32.dll" Alias "ExtractIconA" (ByVal hInst As Long, ByVal lpszExeFileName As String, ByVal nIconIndex As Long) As Long
Vom Dateinamen zum DefaultIcon
167
hInst Das Handle auf die Instanz der aufrufenden Anwendung. In VB-Applikationen ist dieser Wert in dem Ausdruck App.hInstance enthalten. In VBA haben wir keinen Handle auf die Instanz der gastgebenden Applikation zur Verfügung. Aber die Funktion GetModuleHandle mit einem Nullstring als Argument aufgerufen, gibt einen Module Handle der laufenden Applikation zurück, und nichts anderes ist offensichtlich dieses Argument.
lpszExeFileName Name der Datei, aus der das Icon extrahiert werden soll. Das muss übrigens nicht immer eine Exe sein, denn auch DLLs enthalten Icons, auf die diese Funktion den Zugriff ermöglicht. Ein typisches Beispiel ist die shell32.dll, die eine Reihe von Standard-Icons enthält, wie das für DLLs selbst zum Beispiel.
nIconIndex Dieses Argument wird bei Werten ab null als Index auf das Icon interpretiert. -1 wiederum veranlasst die Funktion, die Anzahl der Icons zurückzugeben, die in der Datei enthalten sind. Negative Werte kleiner -1 werden als Zeiger auf den so genannten Resource Identifier verstanden, der für DLLs beispielsweise -154 lautet.
Rückgabe Die Funktion gibt im Erfolgsfalle das Handle auf das Icon zurück. Dieses Icon-Handle muss mit DestroyIcon wieder freigegeben werden, wenn es nicht mehr benötigt wird!
8.13.3
Die Methode FileToIconHandle
Wie so oft begann alles mit einer kleinen, übersichtlichen Methode. Aber recht schnell wuchs sie mit der Anzahl der Fälle, die abgedeckt werden sollten. Nun hat sie 75 teils recht lange Zeilen, wodurch es eher dienlich ist, stellvertretend einen der Fälle zu behandeln. Die Zugriffskomponenten sind in allen Fällen gleich. Die Signatur sieht wie folgt aus: Public Function FileToIconHandle(ByVal strFile As String, Optional _ ByVal iIcon As Long = 0) As Long 'Argumente: ' strFile: Dateierweiterung ' iIcon: Icon-Index End Function
168
8
Registry
Folgende Variablen werden benötigt: Dim Dim Dim Dim Dim
strExtension As String strClass As String strCLSID As Variant strServer As String hModule As Long
'Dateierweiterung 'Klassenname 'CLSID 'Server-Applikation 'Module Handle der Applikation
Zuerst wird hModule ermittelt: hModule = GetModuleHandle(vbNullString)
Nun werden strExtension und strClass zugewiesen: strExtension = Right(strFile, Len(strFile) – InStrRev(strFile, ".")) strClass = Me.GetRegistryValue(enumHKEY_CLASSES_ROOT, _ "." & strExtension, "")
Jetzt wird geprüft, ob unter dem Klassennamen ein DefaultIcon-Schlüssel existiert: If Me.HasKey(enumHKEY_CLASSES_ROOT, strClass & "\DefaultIcon") Then
Wenn ja, wird der Name des Servers ausgelesen, der sich im (Standard)-Wert des Server-Schlüssels befindet: strServer = Me.GetRegistryValue(enumHKEY_CLASSES_ROOT, _ strClass & "\DefaultIcon", "")
Taucht in diesem Server-String ein Komma auf, so sind darin Server und Index des darzustellenden Icons vorhanden: If InStr(1, strServer, ",") > 0 Then iIcon = CLng(Right(strServer, Len(strServer) – _ InStr(1, strServer, ","))) strServer = Left(strServer, InStr(1, strServer, ",") – 1)
Wir haben also nun den Server-String und den Icon-Index in zwei verschiedenen Variablen, die an die ExtractIcon-Funktion übergeben werden können: FileToIconHandle = ExtractIcon(hModule, strServer, iIcon)
Die restlichen 50 Zeilen ersparen wir uns an dieser Stelle, es ist nichts Neues darin zu finden. Viel spannender ist hingegen die Frage, wie das Icon nun so weiterverarbeitet werden kann, dass ein ListView-Steuerelement damit arbeiten kann. Die hier verwendete Methode HasKey birgt keine Überraschungen: Public Function HasKey(ByVal lngHKEY As enumHKEY, ByVal strKey As _ String, Optional ByVal strValue As Variant) As Boolean ' Prüfen, ob der angeforderte Schlüssel und ' evt. auch der Wert existiert 'Argumente: ' lngHKey: KEY ' strKey: Key
Vom Dateinamen zum DefaultIcon
169
' strValue: Name des Wertes Dim strData As String 'Rückgabe bei String- und Binary-Werten Dim nSize As Long 'Länge der Rückgabe Dim hResult As Long 'Rückgabe der API Dim hKey As Long 'Key-Handle Dim lngType As Long 'Datentyp On Error GoTo Errhandler 'Schlüssel öffnen hResult = RegOpenKeyEx(lngHKEY, strKey, 0&, KEY_READ, hKey) If hResult = 0 Then If Not IsMissing(strValue) Then hResult = RegQueryValueEx(hKey, strValue, 0&, lngType, _ ByVal strData, nSize) If hResult = 0 Then HasKey = True Else HasKey = False End If Else HasKey = True End If Else HasKey = False End If RegCloseKey hKey Exit Function Errhandler: RegCloseKey hKey Err.Raise Err.Number, "cSysRegistry:HasKey" End Function
8.13.4
Die weitere Verarbeitung des Icon-Handles
Ein ListView-Steuerelement benötigt eine ImageList, um Icons darstellen zu können. So steht’s zumindest in der MSDN. Geben wir uns an dieser Stelle damit zufrieden. Mit dem Icon-Handle alleine kommen wir bei der ImageList auch nicht zum Erfolg, weshalb wir einen kleinen Umweg machen müssen. Und der führt über eine PictureBox. Eine geniale Funktion aus dem API ermöglicht es, ein Icon in einen Device Context zu zeichnen, wobei wir nicht näher darüber nachdenken, wie denn eine PictureBox zu einem Gerätekontext kommen kann. Bevor wir abschließend die Prozedur unter die Lupe nehmen, die das ListItem erzeugt und mit dem entsprechenden Icon versieht, werfen wir noch einen Blick auf zwei benötigte Funktionen.
170
8.13.5
8
Registry
DrawIcon
Declare Function DrawIcon Lib "user32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByVal hIcon As Long) As Long
hdc Der Handle eines Device Context, in das gezeichnet werden soll
x X-Koordinate der oberen linken Ecke in Pixel
y Y-Koordinate der oberen linken Ecke in Pixel
hIcon Handle auf das zu zeichnende Icon
Rückgabe Null bei einem Fehler
8.13.6
DestroyIcon
Declare Function DestroyIcon Lib "user32" (ByVal hIcon As Long) As Long
hIcon n. gef. H:\AWL\WIN32_Softwareentw\Handle des Icons, dessen Ressource freigegeben werden soll
Rückgabe Auch hier 0 bei einem Fehler, andernfalls (zumeist) eine 1
8.13.7
Die Prozedur AddFile
Die Inline-Kommentare der Prozedur müssten ausreichen: Private Sub AddFile(ByVal strFile As String) ' Hinzufügen einer neuen Datei Dim actListItem As MSComctlLib.ListItem Dim actListImage As ListImage Dim hIcon As Long 'Handle des der Datei zugeordneten Icons
Vom Dateinamen zum DefaultIcon
171
'ListItem mit Standard-Icon aus ImageList hinzufügen Set actListItem = lvwAttachments.ListItems.Add(, , _ cUtil.ExtractFile(strFile), "File") 'vollständigen Namen in Tag ablegen actListItem.Tag = strFile 'PictureBox leeren Set picAttachment.Picture = LoadPicture("") picAttachment.AutoRedraw = True 'Icon-Handle ermitteln hIcon = cSysReg.FileToIconHandle(strFile) If hIcon <> 0 Then 'Icon zeichnen und freigeben DrawIcon picAttachment.hdc, 0, 0, hIcon DestroyIcon hIcon 'PictureBox refreshen picAttachment.AutoRedraw = False picAttachment.Refresh 'ListImage in ImageList ergänzen ... Set actListImage = imlAttachments.ListImages.Add(, "X" & _ imlAttachments.ListImages.Count, picAttachment.Image) '... und ListItem zuweisen actListItem.Icon = actListImage.Key End If End Sub
Auf der Buch-CD finden Sie ein VB-Projekt namens FileIcon.vbp, das ein Extrakt des in Abbildung 8.9 dargestellten Mail-Clients ist. Sie finden auch einen CommandButton, der das komplette Windows-Verzeichnis einliest. Das Ergebnis sehen Sie in Abbildung 8.12.
Abbildung 8.12: FileIcon-Applikation mit Inhalt des Windows-Verzeichnisses
172
8.13.8
8
Registry
Grenzen dieser Vorgehensweise
Wie bereits erwähnt, entstanden die Routinen dieses Beispiels bei der Erstellung eines SMTP-Mail-Clients, der in ein anderes Projekt integriert war. Es ging also darum, einzelne Dateien über den GetOpenFileName-Dialog (siehe Kapitel 4) hinzuzufügen. Das Icon jeder Datei einfach in der ImageList anzuhängen, ist bei dieser überschaubaren Menge von Anhängen in Ordnung. Wenn Sie diese Routinen aber nutzen wollen, um den Windows-Explorer nachzubauen, so werden Sie auf ein Problem stoßen, denn die Kapazität der ImageList ist endlich. Dreimal nacheinander das System32-Verzeichnis anzuzeigen könnte schon genügen, um diese Grenze kennen zu lernen. In diesem Falle müssen Sie entweder die ImageList vor jedem Anzeigen eines Verzeichnisses leeren oder die geladenen Icons irgendwie verwalten, damit Sie ein identisches Icon nicht zweimal laden. Das Handle taugt dazu nicht, denn ein identisches Icon kommt in der Regel zu verschiedenen Zeitpunkten mit verschiedenen Handles. Diese Techniken haben jedoch nichts mit dem API zu tun, weshalb wir das Kapitel Registry hiermit beschließen können. Sie haben alle Techniken kennen gelernt, die erforderlich sind, um die Registry über das hinaus zu erschließen, was in diesem Kapitel vorgestellt wurde. Viel Spaß dabei.
9 Drucker Der Zugriff auf die verfügbaren Drucker stellt in Visual Basic dank der Printers-Collection kein Problem dar. Aber in Visual Basic for Applications sieht das schon anders aus, denn da steht die Printers-Collection nicht zur Verfügung. In VBA sind Sie auf das API angewiesen, wenn Sie einen Drucker auswählen wollen. Das kann aber eigentlich nicht so schwierig sein. Oder doch? Wir lernten bislang eine Reihe von Techniken kennen, die mit wenigen Codezeilen zum Ziel führten. Welches Betriebssystem hierbei eingesetzt wurde, interessierte uns nicht. Bei Druckern macht es hingegen einen Unterschied, ob wir es mit Win 9x oder NT zu tun haben. Wollen wir gar den eingestellten Default-Drucker über das API ermitteln, spielt es sogar eine Rolle, welche NT-Version vorliegt. Doch das ist nicht das einzige Problem. Viel gravierender ist die unterschiedliche Implementierung des Themas Drucker in den einzelnen Office-Applikationen unter teilweiser Vernachlässigung der guten Sitten in Windows. Doch dieses Kapitel endet nicht bei der Druckerauswahl. In Abschnitt 9.2 werden wir den Default-Drucker auslesen und verändern. In 9.4 wird die DEVMODE-Struktur untersucht, mit der wir Druckereinstellungen auslesen und auch verändern können. Der Anlass für meine Beschäftigung mit dem Printers-API war übrigens eine VB-Applikation, die auch Labels bedruckte, allerdings keine Auswahl der Papierzufuhr bot. Wie denn auch, mit Bordmitteln von VB ging das nicht. Papierzufuhr und Papierausrichtung werden in 9.6 und 9.7 jeweils ausgelesen und auch verändert. Weitere Eigenschaften für andere Einstellungen können Sie auf Basis der hier vorgestellten Techniken jederzeit ergänzen. Bitte vergessen Sie dann nicht, mir eine Kopie zuzumailen ;-) Mit der Rückgabe des Gerätekontexts in 9.8 wird dieses Kapitel abgeschlossen.
9.1 Druckerauswahl Mit Enumeration ist eine Aufzählung zumeist gleichartiger Dinge gemeint. Laut Duden-Hotline wird enumerieren übrigens immer noch mit einem »m« geschrieben, obwohl nummerieren inzwischen zwei m aufweist. Konzeptionell weist die Recht-
174
9
Drucker
schreibreform also durchaus Elemente des Drucker-API’s auf: Ungereimtheiten allenthalben. Die Ermittlung der verfügbaren Drucker erfolgt über den Aufruf der EnumPrinters.
9.1.1
EnumPrinters-Funktion
Declare Function EnumPrinters Lib "winspool.drv" Alias "EnumPrintersA" (ByVal flags As Long, ByVal name As String, ByVal Level As Long, pPrinterEnum As Long, ByVal cdBuf As Long, pcbNeeded As Long, pcReturned As Long) As Long
Flags Das Argument Flags steuert im Wesentlichen, welche Art von Druckern zurückgegeben werden sollen, also beispielsweise die lokalen Drucker beziehungsweise Netzwerkdrucker. Wie so oft bei Flags stecken in diesem Argument Konstanten aus der Reihe der Zweierpotenzen, die miteinander kombiniert werden können. Wir verwenden die durch ein Or miteinander verknüpften PRINTER_ENUM_CONNECTIONS sowie PRINTER_ENUM_LOCAL, wodurch alle auch im Windows-Explorer angezeigten Drucker verfügbar sind.
Name In diesem Argument kann der Name einer Domäne angegeben werden, in der die Drucker enthalten sind. Wir übergeben vbNullString.
Level Hinter Level verbirgt sich die Information, welche PRINTER_INFO-Struktur die Funktion verwenden soll. Das ist ein reichlich verwirrendes Thema, denn es stehen insgesamt fünf verschiedene Datentypen zur Verfügung, wobei in Win 9x nur 1, 2 und 5 eingesetzt werden dürfen.
pPrinterEnum Als pPrinterEnum kommt nun die eigentliche PRINTER_INFO-Struktur zum Einsatz. Doch hier steckt der Teufel im Detail, wie wir noch sehen werden.
cdBuf Das Argument cdBuf erwartet die Größe des Arguments pPrinterEnum.
Druckerauswahl
175
pcbNeeded Ist cdBuf jedoch zu klein, um die gewünschte Information aufzunehmen, so enthält das folgende Argument pcbNeeded die erforderliche Größe des Arguments pPrinterEnum. Diesen Mechanismus werden wir uns noch zunutze machen.
pPrinterEnum Als letztes Argument übergibt uns die Funktion die Zahl der verfügbaren Drucker.
Rückgabe 0 im Fehlerfalle. Das Problem ist nun, dass EnumPrinters eigentlich ein Array von PRINTER_INFO-Strukturen beschreibt, und wir in VBA keine Chance haben, ein Array von beispielsweise PRINTER_INFO_4-Strukturen zu verwalten. Wie müssen wir uns eigentlich ein Array von Strukturen vorstellen? PRINTER_INFO_4 besteht aus den drei Elementen pPrinterName, pServerName und Attributes, ist also durchaus einem eindimensionalen Array vergleichbar. Ein Array dieser Struktur ist also einem zweidimensionalen Array vergleichbar. Nun ist aber ein Speicher nicht zweidimensional, sondern eher einer Kette ähnlich. Und ebenso wird ein zweidimensionales Array abgelegt, wie Abbildung 9.1 zeigt.
Abbildung 9.1: Aufbau zweidimensionaler Arrays
Schön, aber wie kommt man nun an die einzelnen Array-Elemente? Sicherlich nicht als PRINTER_INFO_4-Array. Wir benötigen einen Datentyp variabler Länge, der in der Lage ist, beliebige Inhalte darzustellen. Das könnte im Prinzip ein String sein (ich hab’s probiert!), allerdings wird das Handling der Array-Elemente etwas komplizierter. Da die Funktion ohnehin einen Zeiger auf die Struktur erwartet, bietet sich ein Long-Array geradezu an. EnumPrinters signalisiert im Argument pcbNeeded die Größe (in Bytes), mit der dieses Long-Array vorbelegt werden muss. Das Long-Array, nennen wir es lngBuffer(), wird also mit ReDim lngBuffer(lngRequiredSize \ 4)
176
9
Drucker
ausreichend dimensioniert, denn das erste Element trägt schließlich den Index 0. Der folgende Ausdruck hätte auch funktioniert: ReDim lngBuffer(CLng(lngRequiredSize / 4))
Das Handling beim Funktionsaufruf ist unspektakulär, denn die Funktion erwartet einen Zeiger auf einen zusammenhängenden Speicherbereich, und wir übergeben ganz einfach das erste Array-Element: EnumPrinters(..., lngBuffer(0), ...)
Die Funktion EnumPrinters schreibt nun ein PRINTER_INFO_4-Array in den Speicher, und unser einziger Zugang ist ein Long-Array, dessen Element 0 an die Funktion übergeben wurde. Die meisten Datentypen haben eine feste Größe, wodurch die Speicheradresse recht einfach berechenbar ist. Strings aber zeichnen sich durch ihre variable Länge aus. Wenn nun eine Bibliothek zur Positionierung auf Element i einer Struktur mit Strings umfangreiche Längenberechnungen durchführen muss, so wäre diese Vorgehensweise sicherlich suboptimal zu nennen. Ergiebiger hingegen ist es, die ArrayElemente möglichst so anzuordnen, dass man mit einem einfachen Algorithmus zu Element i gelangt und im Zweifel etwas rechnen muss, um zu den Daten eines Strings zu gelangen. Das Ergebnis sehen Sie in Abbildung 9.2.
Abbildung 9.2: Speicherorganisation von Struktur-Arrays
Im ersten Teil des Speicherbereichs liegen 3 Long-Werte je Drucker, wobei die ersten beiden Adressen die zughörigen Strings enthalten und der dritte den Wert selbst. Die Druckernamen liegen also in den Elementen 0, 3, 6 etc. Die PRINTER_INFO_5-Struktur besteht hingegen aus 5 Elementen, wobei allerdings auch hier das erste Element den gesuchten Drucknamen enthält; wir benötigen also die Zeiger 0, 5, 10 etc. Bleiben wir in unserem Beispiel aber bei PRINTER_INFO_4.
Druckerauswahl
177
Um einen Zeiger – und darum handelt es sich ja bei den Inhalten der Array-Elemte 0, 3 etc. – in einen String umzuwandeln, können wir die Funktion PtrToStr verwenden.
9.1.2
PtrToStr-Funktion (lstrcpyA)
Declare Function PtrToStr Lib "kernel32" Alias "lstrcpyA" (ByVal RetVal As String, ByVal Ptr As Long) As Long
RetVal Mit RetVal erhält die Funktion eine hinreichend große Zeichenkette, in die sie den String kopiert, der sich hinter dem Zeiger Ptr verbirgt.
Ptr Ptr ist ein Zeiger auf das erste Zeichen der Zeichenkette, die in RetVal kopiert werden soll.
Rückgabe 0 im Fehlerfalle, sonst Adresse von RetVal. Zur Ermittlung der erforderlichen Größe der Variablen strTemp(iPrinter) kommt die strlen-Funktion zum Einsatz. strTemp(iPrinter) = Space(strlen(lngBuffer(iPrinter * 3))) lngResult = PtrToStr(strTemp(iPrinter), lngBuffer(iPrinter * 3))
9.1.3
strlen-Funktion
Declare Function strlen Lib "kernel32" Alias "lstrlenA" (ByVal Ptr As Long) As Long
Ptr Sie erwartet in Ptr einen Zeiger auf den String.
Rückgabe Die Nettolänge des Strings.
9.1.4
Die Methode EnumeratePrinters
Je nach Betriebssystem kommen Struktur 4 oder 5 zum Einsatz. Erstere besteht aus drei Elementen und die zweite aus 5. Zumindest diese beiden Parameter sollten eine allgemeingültige Methode entgegennehmen. Aber auch die Flags, mit denen die Druckerart festgelegt wird, sollten übergeben werden.
178
9
Drucker
Die Rückgabe der Methode ist ein einfacher String, in dem durch Join die Teilstrings zusammengesetzt werden. Die Rückgabe wird über Split wieder in ein Array zurückverwandelt. Durch die Vermeidung von String-Arrays ist die Methode auch in Office 97 lauffähig, sofern Sie zwei Funktionen namens Join und Split schreiben. Private Function EnumeratePrinters(ByVal lngFlags As Long, _ ByVal iLevel As Long, _ ByVal nElements As Long) As String 'lngFlags: Flags, denen die Drucker entsprechen müssen 'iLevel: Struktur-Kennung (PRINTER_INFO_?) 'nElements: Anzahl der Elemente der Struktur Dim lngResult As Long 'Rückgabe aus Funktion Dim lngRequiredSize As Long 'von der Funktion ermittelte Buffergröße Dim lngBuffer() As Long 'Daten-Array Dim iPrinter As Long 'Druckerzeiger Dim strTemp() As String 'Zwischenvariable für Rückgabe 'Größe des Buffers bestimmen lngResult = EnumPrinters(lngFlags, vbNullString, iLevel, 0&, 0&, _ lngRequiredSize, nPrinters) 'Buffer einstellen und Funktion erneut aufrufen ReDim lngBuffer(lngRequiredSize \ 4) lngResult = EnumPrinters(lngFlags, vbNullString, iLevel, _ lngBuffer(0), lngRequiredSize, _ lngRequiredSize, nPrinters) 'Anzahl der Drucker prüfen If nPrinters > 0 Then 'Drucker-Array dimensionieren ReDim strTemp(nPrinters – 1) For iPrinter = 0 To nPrinters – 1 'Array-Element mit ausreichender Länge vorbelegen ... strTemp(iPrinter) = Space(strlen(lngBuffer(iPrinter * _ nElements))) lngResult = PtrToStr(strTemp(iPrinter), _ lngBuffer(iPrinter * nElements)) Next End If EnumeratePrinters = Join(strTemp, ";") End Function
Wenden wir uns nun noch der Klassenschnittstelle zu. Das Initialize-Ereignis der Klasse sorgt dafür, dass ein privates String-Array mit den Namen der verfügbaren Drucker gefüllt wird. Private Sub Class_Initialize() Const lngFlags As Long = PRINTER_ENUM_CONNECTIONS Or _ PRINTER_ENUM_LOCAL Dim strReturn As String 'Rückgabe aus EnumeratePrinters If cSysInfo.WinNT Then 'Aufruf mit PRINTER_INFO_4, 3 Elemente strReturn = EnumeratePrinters(lngFlags, 4, 3)
Druckerauswahl
179
Else 'Aufruf mit PRINTER_INFO_5, 5 Elemente strReturn = EnumeratePrinters(lngFlags, 5, 5) End If 'Rückgabe in Array umwandeln If LenB(strReturn) > 0 Then strPrinters = Split(strReturn, ";") End If strPrinter = Me.DefaultPrinter End Sub
Class_Terminate wiederum löscht einen eventuell erzeugten Gerätekontext: Private Sub Class_Terminate() If lngDC <> 0 Then DeleteDC lngDC End If End Sub
Die PrinterCount-Eigenschaft gibt Aufschluss über die Anzahl der Drucker. Public Property Get PrinterCount() As Long PrinterCount = nPrinters End Property
PrinterName mit einem Index gibt den Namen des jeweiligen Druckers zurück. Public Property Get PrinterName(ByVal Index As Long) As String PrinterName = strPrinters(Index – 1) End Property
Im folgenden Listing sehen Sie die Verwendung der Klasse zum Füllen einer Combobox. Beim Laden der Drucker wird der Listindex auf den Drucker eingestellt, der als Default-Drucker eingestellt ist. Private Sub cmdLoadPrinters_Click() Dim iPrinter As Long Dim iDefault As Long Dim strDefault As String strDefault = cSysPrinter.DefaultPrinter For iPrinter = 1 To cSysPrinter.PrinterCount lstPrinters.AddItem cSysPrinter.PrinterName(iPrinter) If cSysPrinter.PrinterName(iPrinter) = strDefault Then iDefault = iPrinter – 1 End If Next lstPrinters.ListIndex = iDefault End Sub
180
9
Drucker
9.2 Default-Drucker Von einem Druckerdialog kann man erwarten, dass der Default-Drucker automatisch ausgewählt wird. Und das wollen wir auch so halten und der Printer-Klasse eine Eigenschaft des Namens DefaultPrinter spendieren. Hier beginnt man zum ersten Mal zu staunen, denn für Win 9x, NT 4 und NT 2000 gibt es völlig verschiedene Ansätze. In Win 95 und Win 98 wird der Default-Printer über die Funktion EnumPrinters mit dem Flag PRINTER_ENUM_DEFAULT aufgerufen, wofür wir unsere Methode verwenden können. In NT 4 existiert unter HKEY_CURRENT_USER\ Software\Microsoft\Windows NT\CurrentVersion\Windows ein Wert namens Device, der den Drucker enthält. NT 2000 wiederum unterhält unter dem Namen GetDefaultPrinter eigens eine Funktion, die den Default-Drucker zurück gibt.
9.2.1
GetDefaultPrinter-Funktion
Declare Function GetDefaultPrinter Lib "winspool.drv" Alias "GetDefaultPrinterA" (ByVal pszBuffer As String, pcchBuffer As Long) As Long
pszBuffer Ausreichend vorbelegter String zur Aufnahme des Druckernamens.
pcchBuffer Bruttogröße des in pszBuffer enthaltenen Druckernamens. Ist pszBuffer oder pcchBuffer zu klein, so enthält pcchBuffer die benötigte Bruttogröße des Puffers; psz Buffer wird in diesem Falle nicht beschrieben.
Rückgabe 0 im Fehlerfalle
9.2.2
Property Get DefaultPrinter
Hier nun die Eigenschaftsroutine für die Leserichtung: Public Property Get DefaultPrinter() As String Dim strValue As String 'Default-Printer Dim nSize As Long 'Bruttogröße der Rückgabe Dim lngResult As Long 'Rückgabe aus GetDefaultPrinter If cSysInfo.Win95 Or cSysInfo.Win98 Then 'in Win 95 und 98 Enumerieren mit Default-Kostante und Struktur 5 DefaultPrinter = EnumeratePrinters(PRINTER_ENUM_DEFAULT, 5, 5) ElseIf cSysInfo.WinNT Then Select Case cSysInfo.NTVersion
Default-Drucker
181
Case 4 'in NT bis Version 4 in Registry nachsehen strValue = cSysReg.GetRegistryValue(enumHKEY_CURRENT_USER, _ "Software\Microsoft\Windows NT\CurrentVersion\Windows", _ "Device") If InStr(1, strValue, ",") > 0 Then DefaultPrinter = Left(strValue, InStr(1, strValue, ",") – 1) Else DefaultPrinter = strValue End If Case 5 'in Windows 2000ff neue Funktion nutzen lngResult = GetDefaultPrinter(strValue, nSize) strValue = Space(nSize) lngResult = GetDefaultPrinter(strValue, nSize) DefaultPrinter = Left(strValue, nSize) End Select End If End Property
9.2.3
SetDefaultPrinter-Funktion
Windows 2000 bietet eigens eine Funktion zur Änderung des Default-Drucker. Declare Function SetDefaultPrinter Lib "winspool.drv" Alias "SetDefaultPrinterA" (ByVal pszPrinter As String) As Long
pszPrinter Name des neuen Default-Druckers
Rückgabe 0 im Fehlerfalle
9.2.4
OpenPrinter-Funktion
Für Zugriffe unter Windows 95 oder 98 muss ein Drucker-Handle erzeugt werden, was mittels der Funktion OpenPrinter erfolgt. Declare Function OpenPrinter Lib "winspool.drv" Alias "OpenPrinterA" (ByVal pPrinterName As String, phPrinter As Long, pDefault As Any)
pPrinterName Name des zu öffnenden Druckers
182
9
Drucker
phPrinter Von der Funktion ermitteltes Handle
pDefault Eine PRINTER_DEFAULTS-Struktur (siehe 9.2.5) oder 0
Rückgabe 0 im Fehlerfalle
9.2.5
PRINTER_DEFAULTS-Struktur
Die PRINTER_DEFAULTS enthält u.a. Initialisierungswerte und Zugriffsrechte.
pDatatype String, dahinter verbirgt sich der Standard-Datentyp des Druckers, also »EMF« für PCL-, »RAW« für PostScript-Drucker oder TEXT für Textmodus.
pDevMode Long, ein Zeiger auf eine entsprechend parametrierte DEVMODE-Struktur oder 0.
DesiredAccess Long, eine Kombination aus den folgenden Konstanten: Konstante
Wert
Bedeutung
STANDARD_RIGHTS_REQUIRED
&HF0000000
Basisrecht bestehend aus DELETE, READ_CONTROL, WRITE_DAC und WRITE_OWNER
PRINTER_ACCESS_ADMINISTER
&H4
Administrationsrechte für SetPrinter
PRINTER_ACCESS_USE
&H8
Basisrechte
PRINTER_ALL_ACCESS
&HF000C000
(STANDARD_RIGHTS_REQUIRED Or PRINTER_ACCESS_ADMINISTER Or PRINTER_ACCESS_USE)
Tabelle 9.1: DesiredAccess-Konstanten
9.2.6
ClosePrinter-Funktion
ClosePrinter kommt zum Einsatz, um ein mit OpenPrinter geöffnetes Handle zu schließen.
Default-Drucker
183
Declare Function ClosePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
hPrinter Handle des geöffneten Druckers
Rückgabe 0 im Fehlerfalle
9.2.7
Property Let DefaultPrinter
In Schreibrichtung kommt bei Windows 2000 SetDefaultPrinter zum Einsatz. In NT 4 wird der oben beschriebene Wert in der Registry verändert, was die Methode SetRegistryValue der Klasse clsSysRegistry erledigt. Nur in Win 9x ist die Änderung des Default-Druckers etwas komplizierter. Nach bewährtem Muster wird der Drucker mit OpenPrinter geöffnet, die aktuellen Einstellungen in eine PRINTER_INFO_5-Struktur eingelesen. In den Attributes wird über eine Oder-Verknüpfung das Attribut PRINTER_ATTRIBUTE_DEFAULT ergänzt, und danach die ganze Struktur per SetPrinter aktualisiert. In allen Fällen werden mittels SendMessage alle laufenden Anwendungen über den Wechsel informiert. Public Property Let DefaultPrinter(ByVal strNewValue As String) Dim hPrinter As Long 'Drucker-Handle Dim lngRequSize As Long 'erforderliche Variablengröße Dim lngResult As Long 'Rückgabe aus GetDefaultPrinter Dim udtDefaults As PRINTER_DEFAULTS 'Struktur mit Zugangsrechten Dim lngInfo() As Long 'aktuelle PRINTER_INFO-Daten If cSysInfo.Win95 Or cSysInfo.Win98 Then 'Rechte einstellen udtDefaults.pDatatype = 0& udtDefaults.DesiredAccess = PRINTER_ALL_ACCESS 'Drucker öffnen lngResult = OpenPrinter(strNewValue, hPrinter, udtDefaults) If lngResult = 0 Then Err.Raise 9999, , "Der Drucker " & strNewValue & _ " konnte nicht geöffnet werden." End If 'Anzahl der benötigten Bytes bestimmen und Drucker auslesen lngResult = GetPrinter(hPrinter, 5, ByVal 0&, 0&, lngRequSize) ReDim lngInfo((lngRequSize \ 4)) lngResult = GetPrinter(hPrinter, 5, lngInfo(0), lngRequSize, _ lngRequSize) lngInfo(2) = lngInfo(2) Or PRINTER_ATTRIBUTE_DEFAULT lngResult = SetPrinter(hPrinter, 5, lngInfo(0), 0&) If lngResult = 0 Then
184
9
Drucker
MsgBox "SetPrinter Failed." End If ClosePrinter hPrinter ElseIf cSysInfo.WinNT Then Select Case cSysInfo.NTVersion Case 4 'in NT bis Version 4 in Registry eintragen cSysReg.SetRegistryValue enumHKEY_CURRENT_USER, _ "Software\Microsoft\Windows NT\CurrentVersion\Windows", _ "Device", enumREG_ZS, strNewValue lngResult = SendMessage(HWND_BROADCAST, WM_WININICHANGE, 0, 0) Case 5 lngResult = SetDefaultPrinter(strNewValue & vbNullChar) End Select End If End Property
9.3 Drucker in MS-Office Es existiert kein Office-übergreifender Modus, einen Drucker einzustellen. Teils kann der Drucker über ein meist ActivePrinter genanntes Argument einer PrintOutMethode eingestellt werden. Allerdings gibt es auch eine ActivePrinter-Eigenschaft des Application-Objekts, die jedoch so ihre Besonderheiten aufweist. In Word funktioniert ActivePrinter vorbildlich, in Excel muss der Druckname beim Zuweisen eine mühsam zu ermittelnde Ergänzung in Form von »auf NeXX:« aufweisen, in PowerPoint ist ActivePrinter schreibgeschützt und in Access und Outlook geht man wiederum völlig andere Wege. Kaum zu glauben, dass dies aus einem Hause kommt. Die ActivePrinter-Eigenschaft ergänzt den eigentlichen Druckernamen durch Anhängsel wie »auf LPT1:« oder »auf Ne00:«. Man könnte vermuten, dieses bei NT anzutreffende Ne00: sei einer Netzwerkressource zugeordnet, aber weit gefehlt, denn bei zwei Druckern auf einem Server kann der erste »Ne00« heißen und der zweite »Ne01:«. Das ist aber noch nicht alles, denn zwischen dem Druckernamen und dieser Resource steckt landessprachlich »auf«, »on« oder »sur«, eben je nach Land. Hierfür ist jedoch keineswegs die eingestellte Windows-Sprache verantwortlich, sondern das Language Pack, mit dem Office installiert wurde. In Word können wir der ActivePrinter-Eigenschaft einen Druckernamen in der Form zuweisen, in der die Druckernamen über EnumPrinters ermittelt wurden. Word gerät danach kurz in Wallung und ermittelt die Net-Resource selbst. Excel hingegen produziert den Laufzeitfehler 1004 und besteht auf dem Zusatz. Zu allem Überfluss existiert auch hier keine einheitliche Linie, vielmehr unterscheiden sich die Inhalte und die Aufbewahrungsorte dieser Verbindungsbezeichner.
Drucker in MS-Office
9.3.1
185
Die Methode GetPrinterConnection
Diese Methode wird beim Einsatz in Excel benötigt und liest den gewünschten Verbindungszusatz aus der Registry aus. Als Besonderheit, nennen wir es Sahnehäubchen, unterscheiden sich in Win 9x und NT auch noch die Druckernamen grundsätzlich. Während NT die Namen verwendet, die auch durch EnumPrinter ans Tageslicht gelockt wurden, darf in Win 95 der Namen nicht mit dem UNC-Pfad beginnen. Im API steckt jedoch in der Variable pDriverName der PRINTER_INFO_2-Struktur der Nettoname ohne UNC-Pfad. Die private Methode GetPrinter_2_Value gibt einzelne Variablen der PRINTER_INFO_2-Struktur zurück. Die Technik hinter dieser Methode ist im Prinzip bereits erklärt, weshalb wir GetPrinter_2_Value hier großzügig übergehen können. Die Methode GetRegistryValue der Klasse cSysPrinter ermittelt jeweils den gewünschten Wert, z.B. »LPT1:« oder »Ne00:«. Zum Abschluss müssen die beiden Literale durch »auf« oder »on« zu beispielsweise »\\NTDC\HP LaserJet 2100 Series PCL 6 auf Ne00:« verbunden werden. Interessanterweise ist es nicht die Systemsprache aus den Locale Settings, die für die Sprache verantwortlich ist, es ist vielmehr, wie bereits erwähnt, die Sprache des Language Packs, mit dem Office installiert wurde, so Microsoft. In Office 2000 wird das wohl so sein, aber in Office 97 wurde bereits eine Variante gesichtet, die in Excel »auf« verwendete und in Word »on«! Auf einem Rechner! Public Function GetPrinterConnection(ByVal strPrinter As String, _ ByVal lngLangID As Long) As String 'Argumente: ' strPrinter: Druckername aus EnumPrinters ' lngLangID: Language-ID Dim strRes As String 'Ressource (z.B. Ne00: bei NT) If cSysInfo.Win95 Or cSysInfo.Win98 Then 'in Win 95 und 98 Enumerieren mit Default-Kostante und Struktur 5 strPrinter = GetPrinter_2_Value(strPrinter, 4) strRes = cSysReg.GetRegistryValue(enumHKEY_LOCAL_MACHINE, _ "System\CurrentControlSet\Control\Print\Printers\" & _ strPrinter, "Port") ElseIf cSysInfo.WinNT Then strRes = cSysReg.GetRegistryValue(enumHKEY_CURRENT_USER, _ "Software\Microsoft\Windows NT\CurrentVersion\Devices", _ strPrinter) strRes = Right(strRes, Len(strRes) – InStr(1, strRes, ",")) End If Select Case lngLangID Case 1031 GetPrinterConnection = strPrinter & " auf " & strRes Case 1033
186
9
Drucker
GetPrinterConnection = strPrinter & " on " & strRes End Select End Function
9.3.2
Die Unterschiede innerhalb von MS-Office
In PowerPoint gelang mir die Änderung des Druckers nicht. Excel unterstützt nur die Verwendung der ActivePrinter-Eigenschaft inklusive »auf xyz:«, eine Änderung des DefaultPrinter über das API führt dazu, dass Excel etwas von einem unbekannten Drucker faselt. Word wiederum verträgt »auf xyz:« nicht, möchte also lediglich den reinen Druckernamen. Die Änderung des DefaultPrinters wird von Word einfach ignoriert. Access, Outlook und Visio kennen beispielsweise keine ActivePrinter-Eigenschaft. Aber sie reagieren auf die Änderung des Default-Druckers über das API, wie man leicht an dem Drucker-Dialog überprüfen kann.
9.4 DEVMODE-Elemente lesen und schreiben Begehrlichkeiten bezüglich Druckereinstellungen enden früher oder später bei der Auswahl der Papierzufuhr. Diese und ähnliche Werte stecken in der DEVMODEStruktur. Ich will nicht verhehlen, dass ich geraume Zeit auf der falschen Fährte verbrachte, auf die mich die MSDN mit der DocumentProperties-Funktion lockte. Damit lässt sich zwar auch eine DEVMODE-Struktur ausgeben und verändern, doch diese Änderungen wirken sich offenbar nur solange aus, bis der Drucker mit ClosePrinter geschlossen wird. Und das ist genau nicht das, was wir wollen, obwohl es so schön einfach gewesen wäre. Unser Begehr ist die dauerhafte Änderung eines Wertes, weil der Ausdruck anschließend über die Print-Methode eines Objekts vonstatten geht oder über das PrinterObjekt erfolgt. Und da wird es uns nicht gelingen, ein aktives Handle zu übergeben. Die DEVMODE-Struktur ist aber auch als Teil der PRINTER_INFO_2-Struktur unterwegs, und diese kann per GetPrinter- gelesen und SetPrinter-Funktion geschrieben werden.
DEVMODE-Elemente lesen und schreiben
9.4.1
187
DEVMODE-Struktur
26 Elemente weist DEVMODE auf.
dmDeviceName String (32 Byte), der friendly Name des Druckers, der unter den Gerätetreibern eindeutig ist.
dmSpecVersion Integer, laut MSDN sollen wir hier die Konstante DM_SPECVERSION = &H320 eintragen, die anzeigt, auf welcher Spezifikationsnummer die DEVMODE_Struktur basiert, wenn wir eine erzeugen. Lässt man sich die Nummer hingegen ausgeben, so kommt &H401 heraus. Zum Glück kann man dieses Element einfach so lassen, wie es ist.
dmDriverVersion Integer, Versionsnummer des Druckertreibers
dmSize Integer, Größe der DEVMODE-Struktur ohne dmDriverExtra
dmDriverExtra Integer, Zahl der treiberspezifischen Bytes, die hinter der offiziellen DEVMODE-Struktur anzutreffen sind.
dmFields Long, eine Kombination der folgenden Konstanten, wobei ein enthaltener Wert bedeutet, dass der betreffende Drucker über das Merkmal verfügt: If pdevmode.dmFields and DM_COLOR = DM_COLOR Then 'Farbdrucker Else 'kein Farbdrucker End IF
Hier nun die Konstanten: Konstante
Wert
Bedeutung
DM_ORIENTATION
&H1&
Papierausrichtung
DM_PAPERSIZE
&H2&
Papiergröße (siehe DMPAPER-Konstanten)
DM_PAPERLENGTH
&H4&
Papierlänge in mm -1
Tabelle 9.2: Merkmalskonstanten in dmFields-Argument
188
9
Drucker
Konstante
Wert
Bedeutung
DM_PAPERWIDTH
&H8&
Papierbreite in mm -1
DM_SCALE
&H10&
Skalierung bezogen auf 100
DM_COPIES
&H100&
Anzahl der Kopien
DM_DEFAULTSOURCE
&H200&
Papierzufuhr
DM_PRINTQUALITY
&H400&
Anzahl der DPI oder eine der DMRES-Konstanten
DM_COLOR
&H800&
DMCOLOR_MONOCHROME oder DMCOLOR_COLOR
DM_DUPLEX
&H1000&
beidseitiges Drucken
DM_YRESOLUTION
&H2000&
Anzahl der DPI in Y-Richtung
DM_TTOPTION
&H4000&
Handling von TrueType-Schriften
DM_COLLATE As Long
&H8000&
Download bei Mehrfachkopien
DM_FORMNAME As Long
&H10000000
bei NT Formularname
DM_ICMMETHOD
&H20000000
Unterstützung von Image-Color-Management (Win 9x und 2000)
DM_ICMINTENT
&H40000000
Art der ICM-Unterstützung (Win 9x und 2000)
DM_MEDIATYPE
&H80000000
Art des Druckmediums (Win 9x und 2000)
DM_DITHERTYPE
&H10000000
Art des Dithering (Win 9x und 2000)
Tabelle 9.2: Merkmalskonstanten in dmFields-Argument (Fortsetzung)
Dies sind nicht alle Konstanten, die dmFields enthalten kann, aber alle, die bei Druckern möglich sind.
dmOrientation Integer, eine der beiden folgenden Konstanten sind für die Papierausrichtung möglich: Konstante
Wert
Bedeutung
DMORIENT_PORTRAIT
1
Hochformat
DMORIENT_LANDSCAPE
2
Querformat
Tabelle 9.3: Papierausrichtungskonstanten
dmPaperSize Integer, Papiergröße, die sich zumeist in einer der folgenden Konstanten ausdrückt. Der Wert kann jedoch auch 0 sein, wobei dann die Elemente dmPaperLength und dmPaperWidth die Papiergröße wiedergeben. Wir beschränken uns hier auf die metrischen Werte.
DEVMODE-Elemente lesen und schreiben
189
Konstante
Wert
Bedeutung
DMPAPER_A3
8
A3 (297 x 420 mm)
DMPAPER_A4
9
A4 (210 x 297 mm)
DMPAPER_A5
11
A5 (148 x 210 mm)
DMPAPER_B4
12
B4 (250 x 354 mm)
DMPAPER_B5
13
B5 (182 x 257 mm)
DMPAPER_QUARTO
15
Quarto (215 x 275 mm)
DMPAPER_ENV_C3
29
Umschlag C3 (324 x 458 mm)
DMPAPER_ENV_C4
30
Umschlag C4 (229 x 324 mm)
DMPAPER_ENV_C5
28
Umschlag C5 (162 x 229 mm)
DMPAPER_ENV_C6
31
Umschlag C6 (114 x 162 mm)
DMPAPER_ENV_C65
32
Umschlag C65 (114 x 229 mm)
DMPAPER_ENV_B4
33
Umschlag B4 (250 x 353 mm)
DMPAPER_ENV_B5
34
Umschlag B5 (176 x 250 mm)
DMPAPER_ENV_B6
35
Umschlag B6 (176 x 125 mm)
DMPAPER_ENV_DL
27
Umschlag DL (110 x 220 mm)
DMPAPER_ENV_ITALY
36
Umschlag
110 x 230 mm
Tabelle 9.4: Metrische Papiergrößenkonstanten
Darüber hinaus existieren noch über 20 zöllige Formate sowie Sondergrößen aus dem Asiatischen Raum.
dmPaperLength Integer, Papierhöhe in mm -1. Dieser Wert überschreibt dmPaperSize.
dmPaperWidth Integer, Papierbreite in mm -1. Dieser Wert überschreibt dmPaperSize.
dmScale Integer, Skalierungsfaktor des Ausdrucks bezogen auf 100. Ein Wert von 50 resultiert in einem Druck halber Höhe und Breite, also einem Viertel der Originalfläche.
dmCopies Integer, Anzahl der Kopien je Blatt. Siehe hierzu auch dmCollate.
190
9
Drucker
dmDefaultSource Integer, Konstante für Papierzufuhr. Dies kann eine der folgenden diskreten Konstanten sein, die mit DMBIN_USER oder größer einen druckerspezifische Wert annehmen. Konstante
Wert
Bedeutung
DMBIN_ONLYONE
1
nur ein Behälter vorhanden
DMBIN_UPPER
1
oberer Behälter
DMBIN_LOWER
2
unterer Behälter
DMBIN_MIDDLE
3
mittlerer Behälter
DMBIN_MANUAL
4
manuelle Blattzufuhr
DMBIN_ENVELOPE
5
Behälter für Umschläge
DMBIN_ENVMANUAL
6
manuelle Umschlagzufuhr
DMBIN_AUTO
7
automatische Blattzufuhr
DMBIN_TRACTOR
8
Traktoreinzug
DMBIN_SMALLFMT
9
Behälter für kleine Formate
DMBIN_LARGEFMT
10
Behälter für große Formate
DMBIN_LARGECAPACITY
11
Behälter für große Kapazitäten
DMBIN_CASSETTE
14
Papierkassette
DMBIN_USER
256
Grenze für treiberspezifische Werte
Tabelle 9.5: Papierzufuhrkonstanten
dmPrintQuality Integer, eine der folgenden (negativen) Druckauflösungskonstanten oder die Zahl der dots per inch (DPI), wenn die Zahl positiv ist: Konstante
Wert
Bedeutung
DMRES_DRAFT
-1
Entwurfsqualität
DMRES_HIGH
-4
Hohe Auflösung
DMRES_LOW
-2
niedrige Auflösung
DMRES_MEDIUM
-3
mittlere Auflösung
Tabelle 9.6: Druckauflösungskonstanten
DEVMODE-Elemente lesen und schreiben
191
dmColor Integer, zeigt an, ob der Drucker Farbdruck unterstützt: Konstante
Wert
Bedeutung
DMCOLOR_MONOCHROME
1
Monochrom-Drucker
DMCOLOR_COLOR
2
Farbdrucker
Tabelle 9.7: Farbdruckkonstanten
dmDuplex Integer, steuert ein- oder beidseitigen Druck: Konstante
Wert
Bedeutung
DMDUP_SIMPLEX
1
nur einseitiger Druck
DMDUP_VERTICAL
2
beidseitig, Bindung an Schmalseite
DMDUP_HORIZONTAL
3
beidseitig, Bindung an Breitseite
Tabelle 9.8: Duplexdruck-Konstanten
dmYResolution Integer, vertikale Druckauflösung in dots per inch (DPI). Ist dieser Wert in dmFields initialisiert worden, so enthält dmPrintQuality die Auflösung in horizontaler Richtung.
dmTTOption Integer, steuert die Handhabung der TrueType-Schriften mit einer der folgenden Konstanten: Konstante
Wert
Bedeutung
DMTT_BITMAP
1
TrueType-Fonts als Grafik drucken Voreinstellung für Matrixdrucker
DMTT_DOWNLOAD
2
TrueType-Fonts als Soft Fonts in Drucker laden Voreinstellung für HP-Drucker, die die Printer Control Language (PCL) unterstützen
DMTT_SUBDEV
3
Geräte-Fonts für TrueType-Fonts ersetzen Voreinstellung für PostScript-Drucker
DMTT_DOWNLOAD_OUTLINE Tabelle 9.9: TrueType-Konstanten
4
TrueType-Fonts als Outline Soft Fonts laden
192
9
Drucker
dmCollate Integer, steuert den Datenversand bei Mehrfachkopien Konstante
Wert
Bedeutung
DMCOLLATE_FALSE
0
Daten bei Kopien für jede Kopie senden
DMCOLLATE_TRUE
1
Daten bei Kopien nur einmal senden
Tabelle 9.10: Collate-Konstanten
dmFormName String (32 Byte), Formularname. Die vom Drucker unterstützen Formulare können über die EnumForms-Funktion ermittelt werden.
dmUnusedPadding Integer, wird derzeit nicht verwendet.
dmBitsPerPel Long, darf bei Druckern nicht verwendet werden.
dmPelsWidth Long, darf bei Druckern nicht verwendet werden.
dmPelsHeight Long, darf bei Druckern nicht verwendet werden.
dmDisplayFlags Long, darf bei Druckern nicht verwendet werden.
dmDisplayFrequency Long, darf bei Druckern nicht verwendet werden.
9.4.2
Von PRINTER_INFO_2 zu DEVMODE
Heterogenen Strukturen rückt man am besten mit Byte-Arrays zuleibe, und dann wird alles gut. Diese bewährte API-Weisheit liegt auch hier voll im Trend. Doch lassen Sie uns zuvor einen Blick auf das eigentliche Problem werfen. Abbildung 9.3 zeigt den Aufbau der Struktur mit dem Fokus auf den kleinen Unterschied Zeiger bzw. Wert.
DEVMODE-Elemente lesen und schreiben
193
Abbildung 9.3: Die Wahrheit über die PRINTER_INFO_2-Struktur
Die ersten 13 Elemente der PRINTER_INFO_2-Struktur sind Zeiger, hiervon verweisen elf auf Strings, einer auf eine SECURITY_DESCRIPTOR-Struktur und der letzte auf eine DEVMODE-Struktur. Was nicht funktioniert, ist eine Variable auf die Printer-Struktur zu erzeugen und mit dieser die lesenden und schreibenden Zugriffe auf dmOrientation oder dmDefaultSource zu realisieren. Was erhalten wir überhaupt von der GetPrinter-Funktion? Nun, wir bekommen einen Zeiger auf das erste Element einer PRINTER_INFO_2-Struktur, also auf pServerName. ... = GetPrinter(hPrinter, 2, aDevMode(0), UBound(aDevMode), nSize)
pDevMode wiederum ist der achte Long-Wert der Struktur. Wenn wir also ein LongArray mit acht Elementen erzeugen und dort die ersten 32 Byte aus aDevMode kopieren, sind wir einen Schritt weiter. Dim lngAddr(7) As Long 'Adress-Array für die ersten 8 Elemente Call CopyMemory(lngAddr(0), aDevMode(0), 32)
Nach diesem Kopiervorgang steckt in lngAddr(7) die Adresse des ersten Elements der DEVMODE-Struktur. CopyMemory hilft uns hier nicht weiter, aber lstrcpyA kann den Inhalt eines Zeigers in einen »String« kopieren. Um nun an den Inhalt von lngAddr(7) zu gelangen, können wir das Schlüsselwort ByVal einsetzen. So einfach ist das.
194
9
Drucker
Das Element dmOrientation ist 44 Bytes von dem Zeiger auf die DEVMODE-Struktur entfernt. Der Lesezugriff sieht so aus: lngResult = PtrToPtr(intOrientation, ByVal (lngAddr(7) + 44))
Die Funktion lstrcpyA haben wir bislang unter dem Namen PtrToStr mit folgender Deklaration eingesetzt: Declare Function PtrToStr Lib "kernel32" Alias "lstrcpyA" (ByVal RetVal As String, ByVal Ptr As Long) As Long
Doch wir benötigen die Freiheit, mit ByRef und ByVal zu arbeiten, was mit dieser Deklaration nicht funktionieren würde. Unter dem Namen PtrToPtr muss eine neue Deklaration her: Declare Function PtrToPtr Lib "kernel32" Alias "lstrcpyA" (RetVal As Any, Ptr As Any) As Long
Schauen wir uns noch den schreibende Zugriff an, bei dem intValue den neuen Wert enthält: lngResult = PtrToByte(ByVal (lngAddr(7) + 44), intValue) lngResult = SetPrinter(hPrinter, 2, aDevMode(0), 0)
Gegenüber dem Lesezugriff muss der Wert natürlich noch an SetPrinter übergeben werden. Da wir jedoch durch die neu gewonnene Funktion PtrToPtr direkt im Originalspeicher geschrieben haben, erübrigt sich ein weiteres Kopieren der Struktur mit CopyMemory.
9.4.3
Eine Lesemethode für DEVMODE
Hier noch mal die private Methode GetDevModeValue im Zusammenhang: Private Function GetDevModeValue(ByVal strPrinter As String, _ ByVal lngOffset As Long) As Integer 'Argumente: ' strPrinter: Druckernamen ' lngOffset: Offset zu DEVMODE-Basisadresse Dim nSize As Long 'Puffergröße nach Ansicht der 'GetPrinter-Funktion Dim hPrinter As Long 'Drucker-Handle Dim pd As PRINTER_DEFAULTS 'Zugangsrechte Dim aDevMode(3046) As Byte 'Daten aus GetPrinter-Funktion Dim lngAddr(7) As Long 'Adress-Array für die ersten 8 'Elemente der PRINTER_INFO_2 Dim lngResult As Long 'Rückgabe aus Funktion pd.DesiredAccess = PRINTER_ALL_ACCESS Or pd.DesiredAccess lngResult = OpenPrinter(strPrinter, hPrinter, pd) lngResult = GetPrinter(hPrinter, 2, aDevMode(0), UBound(aDevMode), _ nSize) Call CopyMemory(lngAddr(0), aDevMode(0), 32)
DeviceCapabilities
195
lngResult = PtrToPtr(GetDevModeValue, ByVal (lngAddr(7) + _ lngOffset)) lngResult = ClosePrinter(hPrinter) End Function
9.4.4
Eine Schreibmethode für DEVMODE
Die zentrale Schreibmethode sieht so aus: Private Sub SetDevModeValue(ByVal strPrinter As String, _ ByVal lngOffset As Long, ByVal intValue As Integer) 'Argumente: ' strPrinter: Druckernamen ' lngOffset: Offset zu DEVMODE-Basisadresse ' intValue: einzustellender Wert Dim nSize As Long 'Puffergröße nach Ansicht der 'GetPrinter-Funktion Dim hPrinter As Long 'Drucker-Handle Dim pd As PRINTER_DEFAULTS 'Zugangsrechte Dim aDevMode(3046) As Byte 'Daten aus GetPrinter-Funktion Dim lngAddr(7) As Long 'Adress-Array für die ersten 8 'Elemente der PRINTER_INFO_2 Dim lngResult As Long 'Rückgabe aus Funktion pd.DesiredAccess = PRINTER_ALL_ACCESS Or pd.DesiredAccess lngResult = OpenPrinter(strPrinter, hPrinter, pd) 'Schreiben nur, wenn Drucker auch geöffnet werden kann If lngResult > 0 Then lngResult = GetPrinter(hPrinter, 2, aDevMode(0), _ UBound(aDevMode), nSize) Call CopyMemory(lngAddr(0), aDevMode(0), 32) lngResult = PtrToPtr(ByVal (lngAddr(7) + lngOffset), intValue) lngResult = SetPrinter(hPrinter, 2, aDevMode(0), 0) lngResult = ClosePrinter(hPrinter) End If End Sub
9.5 DeviceCapabilities Bevor wir zu den richtig spannenden Themen kommen, müssen wir noch eine Funktion beleuchten, die Informationen über die Fähigkeiten eines Druckers bereithält.
9.5.1
DeviceCapabilities-Funktion
Declare Function DeviceCapabilities Lib "winspool.drv" Alias "DeviceCapabilitiesA" (ByVal lpDeviceName As String, ByVal lpPort As String, ByVal iIndex As Long, lpOutput As Any, ByVal dev As Long) As Long
196
9
Drucker
lpDeviceName NULL-terminierter String mit dem Namen des Druckers
lpPort NULL-terminierter String mit Drucker-Port
iIndex Dieses Argument steuert, welche Information über den Drucker zurückgegeben werden soll. In der MSDN finden Sie insgesamt 33 verschiedene Konstanten für genauso viele Informationen, wovon wir jedoch nur die beiden folgenden einsetzen: Konstante
Wert
Bedeutung
DC_BINS
6
Bewirkt eine Rückgabe eines Integer-Arrays mit Papierzufuhrkonstanten aus Tabelle 9.4.
DC_BINNAMES
12
Veranlasst die Funktion, die Namen in einem NULL-separierten StringArray zurückzugeben. Diese Zeichenkette beinhaltet 24 Bytes brutto je Eintrag, wobei nicht genutzte Zeichen durch NULLs dargestellt sind.
Tabelle 9.11: Konstanten zur Steuerung der DeviceCapabilities-Funktion
Ist lpOutput = 0, so gibt die Funktion bei beiden Konstanten die Anzahl der verfügbaren Papierzufuhren zurück.
lpOutput Zeiger auf ein Array mit den angeforderten Daten, wobei der Datentyp des Arrays von iIndex abhängig ist.
dev Zeiger auf eine DEVMODE-Struktur oder 0. Übergeben wir 0, so ermittelt die Funktion die Default-Werte des Druckers. Übergeben wir hingegen einen gültigen Zeiger auf eine DEVMODE-Struktur, so ermittelt DeviceCapabilities die Einstellungen dieser Struktur. Wir übergeben eine glatte 0.
Rückgabe Hat alles geklappt, ist die Rückgabe positiv. 0 wiederum signalisiert, dass der Aufruf zwar fehlerfrei war, aber nicht richtig zum Erfolg führte, weil beispielsweise der Drucker das angeforderte Feature nicht unterstützt. Kam es hingegen zu einem Fehler, so gibt die Funktion -1 zurück.
Klassenschnittstelle für Papierzufuhr
9.5.2
197
Die Methode EnumerateBins
Die folgende Prozedur verwendet die DeviceCapabilities-Funktion, um zwei Arrays mit Informationen über die verfügbaren Papierzufuhren zu füllen. Private Sub EnumerateBins(strPrinter As String, _ ByVal strPort As String) 'Argumente: ' strPrinter: Druckernamen ' strPort: Port Dim nBins As Long 'Anzahl der Bins des Druckers Dim iBin As Long 'Zeiger in nBins Dim strBins As String 'String mit allen verfügbaren Bin-Namen 'NULL anhängen strPrinter = strPrinter & vbNullChar strPort = strPort & vbNullChar 'Anzahl der Bins ermitteln nBins = DeviceCapabilities(strPrinter, strPort, DC_BINS, _ ByVal vbNullString, 0) 'Arrays dimensionieren ReDim strPaperBins(nBins – 1) ReDim intPaperBins(nBins – 1) 'String vorbelegen strBins = String(24 * (nBins), 0) 'Bin-Konstanten-Array füllen lassen nBins = DeviceCapabilities(strPrinter, strPort, DC_BINS, _ intPaperBins(0), 0) 'Bin-Namen-Array füllen lassen nBins = DeviceCapabilities(strPrinter, strPort, DC_BINNAMES, _ ByVal strBins, 0) – 1 For iBin = 0 To nBins 'Bin-Namen in Namens-Array übertragen strPaperBins(iBin) = Mid(strBins, 24 * iBin + 1, 24) strPaperBins(iBin) = Left(strPaperBins(iBin), _ InStr(1, strPaperBins(iBin), Chr(0)) – 1) Next End Sub
9.6 Klassenschnittstelle für Papierzufuhr Bevor eine PaperBins-Schnittstelle bedient werden kann, müssen die beiden Arrays intPaperBins und strPaperBins gefüllt werden. Da diese an dem Drucker ausgerichtet sind, bietet es sich an, eine Printer-Eigenschaft einzurichten: Public Property Let Printer(ByVal strNewValue As String) 'evt. vorhandenen Gerätekontext freigeben If lngDC <> 0 Then DeleteDC lngDC lngDC = 0
198
9
Drucker
End If strPrinter = strNewValue EnumerateBins strPrinter, GetPrinter_2_Value(strPrinter, 3) End Property
Der neue Wert wird in der privaten strPrinter abgelegt und anschließend die EnumerateBins mit Druckernamen und Port aufgerufen; letzterer wird von der privaten Methode GetPrinter_2_Value zurückgegeben.
9.6.1
PaperBin-Eigenschaft
Ein Eigenschaftspärchen ist angelegt worden, um den Namen der Papierzufuhr zu ermitteln, beziehungsweise neu einzustellen: Public Property Get PaperBin() As String Dim pDevMode As DEVMODE 'DEVMODE-Struktur zur Ermittlung des Offset Dim lngOffset As Long 'Adress-Offset in DEVMODE-Struktur Dim intBin As Integer 'aktuelle Bin-Konstante Dim iBin As Long 'Zeiger in strPaperBins() 'Offset ermitteln lngOffset = VarPtr(pDevMode.dmDefaultSource) – _ VarPtr(pDevMode.dmSpecVersion) + 32 'aktuelle Bin ermitteln intBin = GetDevModeValue(strPrinter, lngOffset) 'intBin mit intPaperBins()-Elementen vergleichen ... For iBin = 0 To UBound(strPaperBins) If intBin = intPaperBins(iBin) Then '... und dazugehörigen Namen zurückgeben PaperBin = strPaperBins(iBin) Exit For End If Next End Property
Wie zu erwarten, nutzt diese Eigenschaft die Lesemethode GetDevModeValue, die in 9.4.3 beschrieben ist. Der dort benötigte Offset als Differenz zwischen dem dmDefaultSourceElement der DEVMODE-Struktur und der Basisadresse der beliebigen DEVMODE-Struktur wird in lngOffset abgelegt. Leider können wir den Offset nicht als Differenz von beiden bilden, da der String im ersten Element bereits zu einem BSTR mutiert ist und als Unicode-String die doppelte Länge aufweist. Wir müssten also 32 abziehen, und da ist es ebenso sinnvoll, den ersten numerischen Wert, somit den zweiten insgesamt, anzupeilen und zu dieser Differenz 32 hinzuzuzählen. Und das geht immerhin solange gut, wie Microsoft DEVMODE nicht strukturell verändert, und davon können wir ausgehen. Schauen wir uns noch die Schreibrichtung der Eigenschaft an: Public Property Let PaperBin(ByVal strNewValue As String) Dim pDevMode As DEVMODE 'DEVMODE-Struktur zur Ermittlung des Offset Dim lngOffset As Long 'Adress-Offset in DEVMODE-Struktur
Papierausrichtung
199
Dim iBin As Long 'Zeiger in strPaperBins() 'Offset ermitteln lngOffset = VarPtr(pDevMode.dmDefaultSource) – _ VarPtr(pDevMode.dmSpecVersion) + 32 'Bin-Konstante ermitteln ... For iBin = 0 To UBound(strPaperBins) If strNewValue = strPaperBins(iBin) Then '... und damit DEVMODE ändern SetDevModeValue strPrinter, lngOffset, intPaperBins(iBin) Exit For End If Next End Property
9.6.2
PaperBinCount-Eigenschaft
Diese Eigenschaft gibt die Anzahl der Papierzufuhren zurück: Public Property Get PaperBinCount() As Long PaperBinCount = UBound(intPaperBins) + 1 End Property
9.6.3
GetPaperBinName-Methode
GetPaperBinName erwartet einen 1-basierten Index und gibt das entsprechende Element aus dem PaperBin-String-Array zurück: Public Function GetPaperBinName(ByVal iBin As Long) As String GetPaperBinName = strPaperBins(iBin – 1) End Function
9.7 Papierausrichtung Die Papierausrichtung ist noch eine Größe, die sinnvollerweise als eigene Schnittstelle herausgebildet wurde.
9.7.1
Orientation-Eigenschaft
Ebenso wie bei der Papierzufuhr werden auch hier die beiden Methoden verwendet, die in 9.4 vorgestellt wurden. Public Property Get Orientation() As enumOrientation Dim pDevMode As DEVMODE 'DEVMODE-Struktur zur Ermittlung des Offset Dim lngOffset As Long 'Adress-Offset in DEVMODE-Struktur 'Offset ermitteln lngOffset = VarPtr(pDevMode.dmOrientation) – _ VarPtr(pDevMode.dmSpecVersion) + 32
200
9
Drucker
'aktuelle Ausrichtung ermitteln Orientation = GetDevModeValue(strPrinter, lngOffset) End Property
Im Gegensatz zur Papierzufuhr ist diese Einstellungen recht einfach, was auch für die Schreibrichtung gilt: Public Property Let Orientation(ByVal lngNewValue As enumOrientation) Dim pDevMode As DEVMODE 'DEVMODE-Struktur zur Ermittlung des Offset Dim lngOffset As Long 'Adress-Offset in DEVMODE-Struktur 'Offset ermitteln lngOffset = VarPtr(pDevMode.dmOrientation) – _ VarPtr(pDevMode.dmSpecVersion) + 32 'neue Ausrichtung übergeben SetDevModeValue strPrinter, lngOffset, lngNewValue End Property
In beiden Fällen kommt eine Enumeration zum Einsatz, die wie folgt deklariert ist: Public Enum enumOrientation enumOrientationPortrait = DMORIENT_PORTRAIT enumOrientationLandscape = DMORIENT_LANDSCAPE End Enum
9.8 Gerätekontext hDC Gelegentlich benötigt man den Gerätekontext eines Druckers. In Kapitel 10 beispielsweise werden neben den Systemfonts auch die des aktuellen Druckers gesammelt, wobei der Gerätekontext des aktuellen Druckers zum Einsatz kommt.
9.8.1
CreateDC-Funktion
Declare Function CreateDC Lib "gdi32" Alias "CreateDCA" (ByVal lpDriverName As String, ByVal lpDeviceName As String, ByVal lpOutput As String, ByVal lpInitData As Long) As Long
lpDriverName Unter NT ist dies üblicherweise »winspool«, bei Win 95 und 98 müssen wir einen NullString übergeben.
lpDeviceName Name des Druckers
lpOutput Wird unter Win32 ignoriert und sollte 0 sein.
Gerätekontext hDC
201
lpInitData Zeiger zu einer DEVMODE-Struktur mit Initialisierungsdaten oder 0
Rückgabe 0 im Fehlerfalle, sonst der Gerätekontext
9.8.2
DeleteDC-Funktion
Ein mit CreateDC erzeugter Gerätekontext sollte mit DeleteDC wieder gelöscht werden: Declare Function DeleteDC Lib "gdi32" (ByVal hdc As Long) As Long
hdc Zu löschender Gerätekontext.
Rückgabe 0 im Fehlerfalle.
9.8.3
hDC-Eigenschaft
Diese Eigenschaft gibt nun den Gerätekontext des aktuellen Druckers zurück, wobei die Variable strPrinter als aktueller Drucker verwendet wird: Public Property Get hdc() As Long Dim strDriverName As String 'Name des Druckertreibers If cSysInfo.WinNT Then strDriverName = "winspool" End If hdc = CreateDC(strDriverName, strPrinter, 0, 0) End Property
10 Fonts In Visual Basic sind wir in Sachen Fonts bestens gerüstet. Zum einen stellt uns das Screen-Objekt eine umfangreiche Unterstützung für Drucker zur Verfügung und bietet den Zugang zu allen Systemschriftarten. Zum anderen ermöglichen die PrintersCollection und das daraus resultierende Printer-Objekt den Zugriff auf druckerspezifische Schriftarten. In VBA indes sieht dies anders aus, denn dort erwarten uns weder das Screen-Objekt noch ein Printer-Objekt oder gar die Printers-Collection. Somit ist dies vorwiegend – aber nicht ausschließlich – ein Kapitel für VBA-Entwickler. Doch auch VB-Entwickler können von den hier vorgestellten Techniken profitieren, denn die Darstellung von Fonts in einem Winkel relativ zur Horizontalen erfordern die Verwendung von Font-Funktionen aus dem Win32-API. Dies werden wir anhand einer Methode untersuchen, die einen Text senkrecht in einer PictureBox darstellt.
Abbildung 10.1: Der Fonts-Beispieldialog
204
10
Fonts
Doch auch die Default-GUI-Schrift und deren Größe bleibt uns ohne das API verborgen. In Abbildung 10.1 sehen Sie das Ziel dieses Kapitels.
Begriffliches Das GDI (Graphical Device Interface) ist die Windows-Schnittstelle für alle grafischen Ausgabegeräte, sei es nun zur Anzeige über das Graphics Subsystem oder zum Druck über das Printing Subsystem. Sinn und Zweck dieser Übung ist das Entkoppeln der idealisierten Theorie in theoretisch unendlicher Auflösung in mehr profane Hardware. In Bezug auf Fonts bietet das GDI eingangsseitig eine Schnittstelle für logische Fonts und übersetzt diese zusammen mit den jeweiligen Treibern in physikalische Fonts. Windows hält als Stock Objects eine Reihe vordefinierter Objekte bereit, die von allen Programmen genutzt werden können. Hierunter fallen graphische Werkzeuge wie Pen, Brush, Palette, Bitmap, Region und natürlich der Font. Mit der GetStockObjectFunktion kann ein Handle auf ein solches Stock-Object erzeugt und an einen Gerätekontext übergeben werden, und diese Objekte gelten dann dort bis zur nächsten Änderung. Lediglich das System stellt insofern eine Ausnahme dar, weil die dortigen Einstellungen nicht persistent sind und bei jedem Start von Windows durch die Standards ersetzt werden. Als Schriftartenstandard des Systems ist das Stock-Object SYSTEM_FONT definiert. Bei den Stock Objects handelt es sich übrigens um logische Objekte, die also idealisiert beschrieben sind und im Anwendungsfall durch ein mehr oder weniger passendes physikalisches Objekt ersetzt werden. Mit Ausnahme von Bitmap und Region hat jeder Objekttyp ein Standardobjekt. Diese Objekte gelten jeweils für einen Gerätekontext, der wiederum für alle Steuerelemente ermittelt werden kann, die ein Fenster-Handle bereithalten. Dass also das Picture-Steuerelement den Gerätekontext hDC herausbildet, ist demnach nichts, was dieses Steuerelement grundsätzlich von anderen Fensterklassen unterscheidet. Die DrawIcon-Funktion akzeptiert auch einen CommandButton, dessen Gerätekontext über die GetWindowDC-Funktion ermittelt werden kann.
10.1 Fonts enumerieren Diese Aufgabe konfrontiert uns nun erstmalig mit der Technik des Callback. Bei diesem Verfahren übergeben wir Windows die Adresse einer Public Function, und Windows wiederum ruft diese Funktion auf und übergibt bei jedem Aufruf die Daten eines Fonts. Aus dieser Vorgehensweise ergibt sich jedoch ein Nachteil, der in der Art dieser Public Function begründet liegt. Da COM sich nicht auf das Risiko einlässt, eine mögli-
Fonts enumerieren
205
cherweise nicht instanziierte Prozedur aufzurufen, muss die Funktion in einem Modulblatt liegen. Das widerspricht ein wenig unserer bislang favorisierten Technik der Codekapselung in Klassen, denn hier müssen wir ein Modul hinzunehmen. Gestartet wird die Operation mit der Funktion EnumFontFamiliesEx.
10.1.1
EnumFontFamiliesEx-Funktion
Declare Function EnumFontFamiliesEx Lib "gdi32" Alias "EnumFontFamiliesExA" (ByVal hdc As Long, lpLogFont As LOGFONT, ByVal lpEnumFontProc As Long, ByVal lparam As Long, ByVal dwFlags As Long) As Long
hdc Handle des Gerätekontexts, dessen Schriften enumeriert werden sollen. Unter einem Gerätekontext (Device Context) versteht man genau genommen Datenstrukturen, die graphische Attribute eines Gerätes darstellen. Die Art des Stifts fällt hierunter, seine Farbe, Hintergrundfarben und Muster und natürlich der Font. Es gibt Gerätekontexte für reale Geräte wie Drucker oder Bildschirm, aber auch Fenster und Fensterausschnitte können über einen Gerätekontext verfügen. Das Picture-Steuerelement ist das Einzige aus der Riege der Standardsteuerelemente, das über einen Gerätekontext angesprochen werden kann, und ist somit für manche graphische Funktionen per API schlechterdings unverzichtbar.
lpLogFont Zeiger auf eine LOGFONT-Struktur, die Informationen über zu enumerierende Fonts enthält.
lpEnumFontProc Adresse der Callback-Funktion
lparam Hier können wir einen anwendungsspezifischen Wert übergeben, den die Funktion an die Callback-Funktion durchreicht.
dwFlags Dieses Argument wird zur Zeit nicht genutzt und muss 0 sein.
Rückgabe Der letzte Wert, der von der Callback-Funktion zurückgegeben wurde.
206
10.1.2
10
Fonts
LOGFONT-Struktur
Die LOGFONT-Struktur beschreibt einen logischen Font (logical font). Dieser logische Font enthält eine ideale Beschreibung eines Fonts mit Höhe, Breite, Ausrichtung und weiteren Attributen. Ein logischer Font kann nicht verwendet werden und muss in einen physikalischen Font übersetzt werden, der dem logischen Font nahe kommt. Die Argumente LfHeight und LfWidth werden in logical units angegeben. Diese logical unit ist ebenfalls ein theoretisches Maß, das in das physikalische Maß des konkreten Gerätes übersetzt werden muss. Windows geht a priori von einem Verhältnis von 1 zu 1 von logical zu physical units aus.
lfHeight Long, Höhe des Fonts in logical units
lfWidthin Long, Breite des Fonts in logical units
lfEscapement Long, Winkel der Fluchtlinie eines Zeichens zur X-Achse in Zehntelgrad
lfOrientation Long, Winkel der Grundlinie eines Zeichens zur X-Achse in Zehntelgrad
lfWeight Long, Maß für die Strichstärke zwischen 0 und 1000 Konstante
Wert
Bemerkung
FW_DONTCARE
0
nicht erstellen
FW_THIN
100
dünne Schrift
FW_EXTRALIGHT
200
auch FW_ULTRALIGHT extraleichte
FW_LIGHT
300
leichte Schrift
FW_NORMAL
400
normale Schrift (auch FW_REGULAR)
FW_MEDIUM
500
mittlere Schrift
FW_SEMIBOLD
600
(auch FW_DEMIBOLD) halbfette Schrift
FW_BOLD
700
fette Schrift
FW_EXTRABOLD
800
(auch FW_ULTRABOLD) extra fette Schrift
Tabelle 10.1: Font-Weight-Konstanten
Fonts enumerieren
207
Konstante
Wert
Bemerkung
FW_HEAVY
900
(auch FW_BLACK) »schwarze« Schrift
Tabelle 10.1: Font-Weight-Konstanten (Fortsetzung)
lfItalic Byte, 0 nicht kursiv und 1 kursiv
lfUnderline Byte, 0 nicht unterstrichen und 1 unterstrichen
lfStrikeOut Byte, 0 nicht durchgestrichen und 1 durchgestrichen
lfCharSet Byte, Konstante, mit der die Zeichensatzherkunft bezeichnet wird. Es gibt beispielsweise Konstanten für kyrillische, griechische oder gar chinesische Zeichensätze. Die MSDN empfiehlt allerdings, nur DEFAULT_CHARSET (1) zu verwenden, um unerwartete Ergebnisse zu vermeiden.
lfOutPrecision Byte, legt fest, ob beispielsweise ein Rasterfont, ein TrueType- oder geräteabhängiger Font gewählt werden soll, wenn mehrere Fonts des angegebenen Namens existieren.
lfClipPrecision Byte, steuert die Clipping-Präzision
lfQuality Byte, steuert die Genauigkeit, mit der die anderen Parameter von dem ausgewählten Font eingehalten werden müssen. Folgende Konstanten sind definiert: Konstante
Wert Bedeutung
ANTIALIASED_QUALITY
4
Antialiasing wird vorgenommen, wenn der Font es unterstützt und die Größe nicht zu extrem ist.
DEFAULT_QUALITY
0
Erscheinungsform des Fonts ohne Bedeutung.
Tabelle 10.2: Font-Quality-Konstanten
208
10
Konstante
Wert Bedeutung
DRAFT_QUALITY
1
Erscheinungsform des Fonts weniger wichtig als bei PROOF_QUALITY
NONANTIALIASED_QUALITY
3
Kein Antialiasing
PROOF_QUALITY
2
Erscheinungsform des Fonts von hoher Bedeutung
Fonts
Tabelle 10.2: Font-Quality-Konstanten (Fortsetzung)
lfPitchAndFamily Byte, beeinflusst die Schriftfamilie und den Pitch. Die folgenden Konstanten stehen für Pitch: Konstante
Wert
Bedeutung
DEFAULT_PITCH
0
keine Angabe
FIXED_PITCH
1
konstanter Abstand
VARIABLE_PITCH
2
variabler Abstand
Tabelle 10.3: Pitch-Konstanten
Die Schriftfamilie kann mit einer der folgenden Konstanten festgelegt werden: Konstante
Wert
Bedeutung
FF_DECORATIVE
80
Romanschriftart, z.B. Old English
FF_DONTCARE
0
egal
FF_MODERN
48
nicht-proportionale Schrift, z.B. Pica, Elite, and CourierNew
FF_ROMAN
16
proportionale Serifenschrift, z.B. MS Serif
FF_SCRIPT
64
Schrift im Stile von Handschriften, z.B. Script
FF_SWISS
32
proportionale serifenlose Schrift, z.B. MS Sans Serif
Tabelle 10.4: Font-Familien-Konstanten
lfFaceName Byte-Array mit 32 Elementen, Name der Schriftart inklusive des abschließenden NULL.
10.1.3
EnumFonts-Prozedur
Solchermaßen vorbereitet können wir nun an die eigentliche Aufgabe des Font-Enumerierens herangehen.
Fonts enumerieren
209
Zuerst wird die LOGFONT-Struktur versorgt, indem dort DEFAULT_CHARSET übergeben wird. Danach wird die Enumeration mit dem Gerätekontext des Systems gestartet, wofür das Desktop-Fenster herangezogen wird. Anschließend wird eine neue Enumeration mit dem Device Context des Default-Druckers in Angriff genommen. Der AddressOf-Operator übergibt jeweils die Adresse der CallBack-Funktion EnumFontCallBack aus dem Modul. Private Sub EnumFonts() Dim hDC As Long 'Device Context des Desktops Dim hWnd As Long 'Handle des Desktops Dim LF As LOGFONT 'LOGFONT-Struktur 'LOGFONT parametrieren LF.lfCharSet = DEFAULT_CHARSET 'Device Context für System ermitteln und Fonts enumerieren hWnd = GetDesktopWindow() hDC = GetWindowDC(hWnd) EnumFontFamiliesEx hdc, LF, AddressOf EnumFontCallBack, 0&, 0& 'Device Context für Default-Printer ermitteln und Fonts enumerieren hDC = cSysPrinter.hdc EnumFontFamiliesEx hDC, LF, AddressOf EnumFontCallBack, 0&, 0& End Sub
10.1.4
ENUMLOGFONTEX-Struktur
Diese Struktur, die in der Callback-Funktion ankommt, enthält alle Informationen über einen Font.
elfLogFont Eine LOGFONT-Struktur (siehe 10.1.2)
elfFullName Byte-Array (64 Elemente), Schriftname: z.B. Arial, Courier New, Century Gothic
elfStyle Byte-Array (32 Elemente), Stil: z.B. Standard, Regular, Fett, Kursiv, Fett Kursiv, Bold, Antiqua, Roman, Medium.
elfScript Byte-Array (32 Elemente), Schrifttyp: z.B. OEM/DOS, Symbol, Mitteleuropäisch, Westlich, Griechisch, Baltisch, Kyrillisch, Türkisch, Hebräisch. Einzelne Schriften tauchen unter demselben Namen (elfFullName) mit verschiedenen Schrifttypen (elfScript) auf.
210
10.1.5
10
Fonts
EnumFontCallBack-Funktion
Diese Funktion muss einer festen Signatur gehorchen, in der je ein Zeiger auf eine Struktur zur Beschreibung des logischen und des physikalischen Fonts übergeben wird. Im Argument FontType steckt eine der folgenden Konstanten: Konstante
Wert
Bedeutung
DEVICE_FONTTYPE
2
gerätespezifischer Font
RASTER_FONTTYPE
1
Raster-Font
TRUETYPE_FONTTYPE
4
TrueType-Font
Tabelle 10.5: FontType-Konstanten
Das letzte Argument entspricht dem Argument, das als lParam der EnumFontFamiliesEx-Funktion beim Aufruf übergeben wurde. Public Function EnumFontCallBack(lpLFX As ENUMLOGFONTEX, _ lpTMX As Long, ByVal FontType As Long, lParam As Long) As Long cSysFont.AddFont ClearAPIString(StrConv(lpLFX.elfFullName, _ vbUnicode)) EnumFontCallBack = 1 End Function
Die Methode AddFont der Font-Klasse wird mit dem von Unicode konvertierten Fontnamen aufgerufen. Die Callback-Funktion selbst erhält 1 als Rückgabe, was bedeutet, dass die Enumeration fortgeführt werden soll; 0 würde sie beenden.
10.1.6
AddFont-Methode
Die Font-Klasse verfügt über ein privates Font-Array des Namens strFonts, welches in dieser Methode gefüllt wird. Da die Fonts jedoch nicht sortiert ankommen, erfolgt gleichzeitig eine Sortierung in dieser Methode. Da dies jedoch reiner VB-Code ist, können wir uns die Methode hier en détail schenken. Wir verfügen jedenfalls über ein wohlgefülltes und sortiertes String-Array mit den verfügbaren Fontnamen, für das wir nun noch eine Schnittstelle entwerfen müssen.
10.2 DefaultGUIFontName-Eigenschaft Hinter dieser Eigenschaft verbirgt sich der eingestellte Systemfont. Wenn Sie sich an irgendeiner Stelle an diese Einstellung anlehnen wollen, so bietet Ihnen diese Eigenschaft den Namen der Schriftart.
DefaultGUIFontName-Eigenschaft
10.2.1
211
Die Eigenschaftsprozedur
In dieser Prozedur wird die private Methode GetDeviceFontName aufgerufen und der Gerätekontext des Systems übergeben, der wiederum als Eigenschaft der WindowKomponente herausgebildet ist. Diese Klasse wird in Kapitel 11 vorgestellt. Public Property Get DefaultGUIFontName() As String DefaultGUIFontName = GetDeviceFontName(cSysWnd.SystemHDC) End Property
10.2.2
GetDeviceFontName-Prozedur
In dieser Prozedur wird GetTextFace aufgerufen, die ausgestattet mit dem Gerätekontext den Namen des aktuellen Fonts zurückgibt. In jedem Gerätekontext ist zur Textdarstellung zu jedem Zeitpunkt immer nur ein Font-Objekt vorhanden oder besser referenziert. Private Function GetDeviceFontName(ByVal hDC As Long) As String Dim lpFaceName As String * 100 'String für Fontnamen Dim nSize As Long 'Nettolänge des Fonts 'Font-Namen ermitteln nSize = GetTextFace(hDC, 100, lpFaceName) 'Font auf evt. Mapping prüfen ... GetDeviceFontName = GetMappedFont(Left(lpFaceName, nSize)) End Function
In der letzten Zeile wird der Prozedur die Rückgabe der GetMappedFont-Prozedur zugewiesen, die unter 10.3.4 noch vorgestellt wird.
10.2.3
GetTextFace-Funktion
Declare Function GetTextFace Lib "gdi32" Alias "GetTextFaceA" (ByVal hdc As Long, ByVal nCount As Long, ByVal lpFaceName As String) As Long
GetTextFace gibt den Namen der Schriftart zurück, die im übergebenen Gerätekontext gewählt ist.
hdc Handle auf den Gerätekontext
nCount Größe des Arguments lpFaceName
lpFaceName Mit nCount-Zeichen vorbelegter String zur Aufnahme des Schriftnamens
212
10
Fonts
Rückgabe Nettobreite des Schriftnamens in lpFaceName oder 0 im Fehlerfalle
10.2.4
GetMappedFont-Prozedur
Seit Version 3 bietet Windows ein so genanntes Font Mapping für logische Fonts. Das Prinzip ist einfach. Einem Font kann ein logischer Font zugeordnet werden, gegen den ersterer generell auszutauschen ist. Der auszutauschende Font existiert hierbei zumeist nicht und wird durch einen realen logischen Font ersetzt. Die Windows 2000 Fonts »MS Shell Dlg« und »MS Shell Dlg 2« sind Beispiele für zu ersetzende Schriften. Die Festlegungen finden Sie in der Registry in Software\Microsoft\Windows NT\Current Version\FontSubstitutes unter dem Key HKEY_LOCAL_MACHINE. Die Registry-Komponente aus Kapitel 8 prüft, ob Schlüssel und Wert vorhanden sind und gibt den Namen des neuen Fonts zurück, falls ein solcher existiert. Private Function GetMappedFont(ByVal strFont As String) As String Const strMapKey As String = "Software\Microsoft\Windows NT\... If cSysReg.HasKey(enumHKEY_LOCAL_MACHINE, strMapKey, strFont) Then strFont = cSysReg.GetRegistryValue(enumHKEY_LOCAL_MACHINE, _ strMapKey, strFont) Else GetMappedFont = strFont End If End Function
10.3 DefaultGUIFontSize-Eigenschaft Diese Eigenschaft wiederum gibt die Größe des Default-GUI-Fonts zurück. Lassen Sie uns auch hier mit der Eigenschaftsprozedur beginnen:
10.3.1
Die Eigenschaftsprozedur
In dieser Prozedur wird ebenso wie in DefaultGUIFontname der Gerätekontext des Desktop-Fensters stellvertretend für das System ermittelt und an die GetDeviceFontSize-Prozedur übergeben: Public Property Get DefaultGUIFontSize() As Single DefaultGUIFontSize = GetDeviceFontSize(cSysWnd.SystemHDC) End Property
DefaultGUIFontSize-Eigenschaft
10.3.2
213
GetDeviceFontSize-Prozedur
Zuerst wird der Map-Modus über SetMapMode auf Twips eingestellt und dann die Textgeometrie mittels GetTextMetrics ermittelt. Abschließend wird der Map-Modus wieder auf das Maß eingestellt, das wir antrafen. Private Function GetDeviceFontSize(ByVal hDC As Long) As Single Dim udtTM As TEXTMETRIC 'TextMetric-Type Dim lngPrevMapMode As Long 'vorangehender MapMode Const MM_TWIPS = 6 lngPrevMapMode = SetMapMode(hDC, MM_TWIPS) GetTextMetrics hDC, udtTM lngPrevMapMode = SetMapMode(hDC, lngPrevMapMode) GetDeviceFontSize = udtTM.tmHeight / 20 End Function
10.3.3
SetMapMode-Funktion
Declare Function SetMapMode Lib "gdi32" (ByVal hdc As Long, ByVal nMapMode As Long) As Long
hdc Handle des Gerätekontexts
nMapMode Eine der folgenden Map-Modus-Konstanten: Konstante
Wert
Bedeutung
MM_ANISOTROPIC
8
Logical Units werden in ungleichmäßig skalierte Achsen übersetzt, deren Maße per SetWindowExtEx and SetViewportExtEx eingestellt werden können.
MM_HIENGLISH
5
Jede Logical Unit korrespondiert mit 0,001 Zoll. Positive X-Werte liegen rechts und positive Y-Werte oben.
MM_HIMETRIC
3
Jede Logical Unit korrespondiert mit 0,01 Millimeter. Positive XWerte liegen rechts und positive Y-Werte oben.
MM_ISOTROPIC
7
Logical Units werden in gleichmäßig skalierte Achsen übersetzt, deren Maß per SetWindowExtEx und SetViewportExtEx eingestellt werden kann.
MM_LOENGLISH
4
Jede Logical Unit korrespondiert mit to 0,01 Zoll. Positive X-Werte liegen rechts und positive Y-Werte oben.
MM_LOMETRIC
2
Jede Logical Unit korrespondiert mit 0,1 Millimeter. Positive X-Werte liegen rechts und positive Y-Werte oben.
Tabelle 10.6: Map-Mode-Konstanten
214
10
Fonts
Konstante
Wert
Bedeutung
MM_TEXT
1
Jede Logical Unit korrespondiert mit einem Pixel. Positive X-Werte liegen rechts und positive Y-Werte unten.
MM_TWIPS
6
Jede Logical Unit korrespondiert mit einem Zwanzigstel-Punkt (1/ 1440 Zoll). Positive X-Werte liegen rechts und positive Y-Werte oben.
Tabelle 10.6: Map-Mode-Konstanten (Fortsetzung)
Rückgabe 0 im Fehlerfalle
10.3.4
GetTextMetrics
Declare Function GetTextMetrics Lib "gdi32" Alias "GetTextMetricsA" (ByVal hdc As Long, lpMetrics As TEXTMETRIC) As Long
hdc Handle des Gerätekontexts
lpMetrics Zeiger auf TEXTMETRIC-Struktur
Rückgabe 0 im Fehlerfalle
10.3.5
TEXTMETRIC-Struktur
Die TEXTMETRIC-Struktur beinhaltet Informationen über den Aufbau einer Schrift. Mit Ausnahme von tmWeight hängen die Maße vom aktuellen map mode ab.
tmHeight Long, Gesamthöhe der Schrift inklusive Oberlänge und Unterlänge
tmAscent Long, Anteil der Höhe oberhalb der Grundlinie, also Körper und Oberlänge
tmDescent Long, Anteil der Höhe unterhalb der Grundlinie, also Unterlänge
DefaultGUIFontSize-Eigenschaft
215
tmInternalLeading Long, Höhe innerhalb des Zeichens für Akzente (kann 0 sein)
tmExternalLeading Long, Abstand zwischen zwei Zeilen (kann 0 sein)
tmAveCharWidth Long, mittlere Breite eines Zeichens (üblicherweise identisch mit dem Zeichen »x«) in normalem Schriftschnitt, also ohne kursive oder fette Eigenheiten.
tmMaxCharWidth Long, Breite des breitesten Zeichens
tmWeight Long, Wert zwischen 0 und 1000, der die Strichstärke beschreibt (siehe auch das lfWeight-Element der LOGFONT-Struktur).
tmOverhang Long, Maß für die Verdickung eines fetten zu einem normalen Zeichen oder den Überhang, den ein kursives Zeichen gegenüber einem normalen Zeichen aufweist.
tmDigitizedAspectX Long, horizontales Lagemaß des Gerätes, für den der Font entworfen wurde
tmDigitizedAspectY Long, vertikales Lagemaß des Gerätes, für den der Font entworfen wurde
tmFirstChar Byte, ANSI-Code des ersten Zeichen des Fonts (i. d. R. 32 für Leerzeichen)
tmLastChar Byte, ANSI-Code des letzten Zeichens des Fonts (i. d. R. 255)
216
10
Fonts
tmDefaultChar Byte, ANSI-Code des Zeichens, das dargestellt werden soll, wenn ein Zeichen angefordert wurde, das außerhalb der Grenzen von tmFirstChar und tmLastChar liegt.
tmBreakChar Byte, ANSI-Code des Zeichens, das Wörter voneinander trennt
tmItalic Byte, ungleich 0 bei kursiver Schrift
tmUnderlined Byte, ungleich 0 bei unterstrichener Schrift
tmStruckOut Byte, ungleich 0 bei durchgestrichener Schrift
tmPitchAndFamily Byte, Bitmaske, deren Bits 0 bis 3 folgende Information tragen: Konstante
Wert
Bedeutung
TMPF_FIXED_PITCH
1
Ist dieses Bit gesetzt, so handelt es sich um einen proportionalen (!) Font.
TMPF_VECTOR
2
Dieses Bit kennzeichnet eine Vektorschrift.
TMPF_TRUETYPE
4
Dieses Bit kennzeichnet eine TrueType-Schrift.
TMPF_DEVICE
8
Dieses Bit kennzeichnet eine geräteabhängige Schrift.
Tabelle 10.7: TextMetric-PitchAndFamiliy-Konstanten
TrueType- und PostScript-Fonts bedingen auch das TMPF_VECTOR-Bit. Eine PostScriptSchrift auf einem Drucker-Gerätekontext beinhaltet die Bits TMPF_VECTOR, TMPF_TRUETYPE und TMPF_DEVICE. Das höherwertige Halbbyte kennzeichnet die Schriftfamilie. Siehe hierzu auch das Element lfPitchAndFamily der LOGFONT-Struktur.
Schriften außerhalb der Horizontalen
217
tmCharSet Byte, Konstante, mit der die Zeichensatzherkunft bezeichnet wird. So gibt es zum Beispiel Konstanten für kyrillische oder griechische Zeichensätze. Wir erhalten 0 für ANSI_CHARSET.
10.4 Schriften außerhalb der Horizontalen Die Klasse clsSysFonts enthält eine Methode namens WriteVerticalText, die in einem Picture-Steuerelement senkrechte, von rechts lesbare Texte erzeugt. Diese Methode entstand zu Anfang als quasi universelle Methode, die nicht auf ein spezielles Steuerelement beschränkt war. Doch diesem Buch liegt der praktische Ansatz zugrunde, und wenn man eine senkrechte Schrift in einem Dialog benötigt, so ist eben ein PictureSteuerelement durchaus angebracht. Gleiches gilt für den Winkel, in dem die Schrift dargestellt werden soll. Die Regel ist wohl eine senkrechte Anordnung. Wenn Sie einen anderen Winkel benötigen, so werden sie in diesem Abschnitt alles Erforderliche vorfinden, um diese Aufgabe zu bewältigen. Die Vorgehensweise folgt diesem Muster: 왘 LOGFONT-Struktur parametrieren, 왘 logischen Font erzeugen (CreateFontIndirect-Funktion) und 왘 diesen Font im Gerätekontext aktivieren (SelectObject-Funktion), 왘 neue Koordinaten einstellen und 왘 Text ausgeben, 왘 vorherigen Font wieder einstellen (SelectObject-Funktion) und 왘 Font-Handle löschen (DeleteObject-Funktion). Schauen wir uns zuerst die Funktionen an, die hier erstmalig zum Einsatz kommen. Die verwendete LOGFONT-Struktur ist in 10.2.2 beschrieben.
10.4.1
CreateFontIndirect-Funktion
Declare Function CreateFontIndirect Lib "gdi32" Alias "CreateFontIndirectA" (lpLogFont As LOGFONT) As Long
lpLogFont Zeiger auf eine LOGFONT-Struktur, in der die Charakteristiken des gewünschten Fonts beschrieben sind.
218
10
Fonts
Rückgabe 0 im Fehlerfalle
10.4.2
SelectObject-Funktion
Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Diese Funktion stellt ein neues Objekt im übergebenen Gerätekontext ein.
hdc Handle des Gerätekontexts
hObject Handle auf das neue Objekt des Typs Bitmap, Brush, Pen, Region oder Font
Rückgabe Ein Handle auf das vorherige Objekt oder 0 bzw. -1 im Fehlerfalle
10.4.3
DeleteObject-Funktion
Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
hObject Handle eines erzeugten Objekts des Typs Pen, Brush, Bitmap, Region, Palette oder Font
Rückgabe 0 im Fehlerfalle, das heißt, das Handle ist ungültig oder das Objekt ist zur Zeit in einem Gerätekontext aktiv.
10.4.4
Die WriteVerticalText-Methode
Kompliziert ist unser Ansinnen keineswegs, wie der nachfolgende Code zeigt: Public Sub WriteVerticalText(ByRef actPic As PictureBox, _ ByVal strFontName As String, _ ByVal sngFontSize As Single, _ ByVal strText As String) 'Argumente: ' actPic: PictureBox-Steuerelement ' strFontName: Fontname
Zum Schluss ' '
219 sngFontSize: strText:
Fontgröße Text
Dim udtFont As LOGFONT 'LOGFONT-Struktur Dim lngPrevFont As Long 'vorheriger Font Dim lngPrevX As Long 'vorherige CurrentX-Position Dim lngPrevY As Long 'vorherige CurrentY-Position Dim hFont As Long 'Handle auf neuen Font Dim lngReturn As Long 'Rückgabe aus Funktion 'Hintergrund löschen actPic.Picture = LoadPicture() 'aktuelle Koordinaten sichern lngPrevX = actPic.CurrentX lngPrevY = actPic.CurrentY 'LOGFONT parametrieren udtFont.lfEscapement = 900 udtFont.lfFaceName = strFontName & Chr(0) udtFont.lfHeight = (sngFontSize * -20) / 15 '15 = Twips per Pixel 'logischen Font erzeugen und für Control auswählen hFont = CreateFontIndirect(udtFont) lngPrevFont = SelectObject(actPic.hDC, hFont) 'neue Koordinaten einstellen und Text ausgeben actPic.CurrentX = 0 actPic.CurrentY = actPic.ScaleHeight – 60 actPic.Print strText 'vorherigen Font wieder einstellen und Font-Handle löschen lngReturn = SelectObject(actPic.hDC, lngPrevFont) lngReturn = DeleteObject(hFont) 'vorherige Koordinaten einstellen actPic.CurrentX = lngPrevX actPic.CurrentY = lngPrevY End Sub
Es ist keineswegs zwingend, die Werte der CurrentX- und CurrentY-Eigenschaften zwischenzuspeichern und am Ende wieder zuzuweisen, aber vielleicht Kennzeichen guten Stils. Gleiches gilt für das vormals selektierte Form-Objekt, das in lngPrevFont abgelegt wird.
10.5 Zum Schluss Man kann über Sinn und Nutzen der Font-Programmierung mittels API trefflich streiten, aber dieses Thema schärft das Verständnis für Windows in nicht unerheblicher Art und Weise. Ich hoffe, dass auch Sie diesen Nutzen davontragen werden. Der Gerätekontext dürfte an Konturen gewonnen haben. Mich hat zum Beispiel der Begriff des Font-Objekts anfangs irritiert, das in einem Gerätekontext aktiviert oder besser referenziert wird. Ein Objekt ist eine Kombination aus Code und Daten, haben wir gelernt, und der Font bestand für mich nur aus einer
220
10
Fonts
Datenstruktur. Doch ergibt nicht eine VB-Klasse, deren Code sich lediglich aus Eigenschaftsroutinen zusammensetzt, genau so ein Konstrukt, das einer Datenstruktur gleichzusetzen ist? Und wer sagt, dass neben oder hinter der Struktur nicht noch massig Code steckt, der angesichts der blanken Struktur nicht notwendigerweise ins Auge springt? Einem reinen VB- oder VBA-Entwickler erschließt sich das API nicht unbedingt auf den ersten Blick. Doch das ist gerade der Reiz an der Sache.
11 Windows Der Name Windows ist Programm, im wahrsten Sinne des Wortes. Denn als dieser Absatz geschrieben wurde, verzeichnete mein Windows bei zehn gestarteten Applikationen exakt 700 Fenster. Allein Word beanspruchte (mit einer geladenen Datei) 31 Fenster. ActiveX-Steuerelemente werden in der Regel in jeweils eigenen Fenstern dargestellt. Wenn wir also in VB ein Formular mit zwei CommandButtons, einem Label und einer TextBox eingerichtet haben, so haben wir es mit vier Fenstern zu tun (Form, 2 Buttons, Textbox), lediglich das Label-Steuerelement beansprucht kein eigenes Fenster für sich. Aber alles, was bedienbar ist, besitzt ein eigenes Fenster. In VBA übrigens verbirgt die UserForm die Steuerelemente vor Windows und somit auch vor uns, wodurch wir bezüglich des Einsatzes des Windows-API leider recht eingeschränkt sind.
11.1 hWnd und hDC Die Klasse clsSysWindow widmet sich dem Thema Handles für Fenster und deren Gerätekontexten. Der Code befindet sich in der Klasse clsSysWindow. Hierbei werden wir zuerst die Funktionen vorstellen und danach den Klassencode.
11.1.1
GetDesktopWindow
Declare Function GetDesktopWindow Lib "user32" () As Long
Diese Funktion gibt ein Handle auf das Desktop-Fenster zurück. Dieses Fenster wird von einigen Funktionen als Platzhalter für das System angesehen, so auch von der SelectObject-Funktion, die weiter unten behandelt wird.
11.1.2
GetWindowDC
Declare Function GetWindowDC Lib "user32" (ByVal hwnd As Long) As Long
222
11
Windows
hwnd Handle des Fensters, dessen Gerätekontext benötigt wird
Rückgabe Handle auf den Gerätekontext (Device Context). GDI-Funktionen verwenden in der Regel einen Gerätekontext zur Durchführung ihre Aufgaben.
11.1.3
ReleaseDC
Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As Long
ReleaseDC gibt einen Verweis auf einen Gerätekontext wieder frei, der beispielsweise mit GetWindowDC angefordert wurde.
hwnd Handle des Fensters
hdc Handle des Gerätekontexts
Rückgabe 1 bei Erfolg und 0 im Fehlerfalle
11.1.4
SystemHWnd-Eigenschaft
Diese Eigenschaft gibt ein Handle auf das Desktop-Fenster zurück: Public Property Get SystemHWnd() As Long SystemHWnd = GetDesktopWindow() End Property
11.1.5
SystemHDC-Eigenschaft
SystemHDC gibt ein Handle auf den Gerätekontext des Desktop-Fensters zurück: Public Property Get SystemHDC() As Long SystemHDC = GetDC(GetDesktopWindow()) End Property
Das TOPMOST-Problem
11.1.6
223
GetDC-Methode
Diese Methode nimmt ein Fenster-Handle entgegen und erzeugt einen dazugehörigen Gerätekontext. Damit die Klasse am Ende die Handles wieder freigeben kann, werden beide, also jeweils das Handle für Fenster und Gerätekontext, in Arrays gespeichert. Public Function GetDC(ByVal hWnd As Long) As Long Dim hDC As Long 'Gerätekontext 'Gerätekontext erzeugen hDC = GetWindowDC(hWnd) GetDC = hDC 'Handles in Arrays ergänzen lngHDCs(UBound(lngHDCs)) = hDC lngHWnds(UBound(lngHDCs)) = hWnd ReDim Preserve lngHDCs(UBound(lngHDCs) + 1) ReDim Preserve lngHWnds(UBound(lngHWnds) + 1) End Function
11.2 Das TOPMOST-Problem Wenn frmMain als erstes Formular gestartet wird und an alle weiteren Formulare als Owner übergeben wird, so kann ausgeschlossen werden, dass ein nicht modaler Dialog in VB-Programmen gewissermaßen verschwindet und nur noch durch Minimieren aller Fenster erreicht werden kann. Und modale Dialoge sind immer sichtbar. Wozu also TOPMOST? Die einzigen Formulare, die Schwierigkeiten bereiten könnten, sind die, welche vor frmMain gestartet werden, und da fallen mir gleich zwei ein, die in Abbildung 11.1 zu sehen sind: nämlich Splash und Login.
11.2.1
SetWindowPos-Funktion
Mit dieser Funktion kann die Reihenfolge eines Fensters beeinflusst werden. Declare Function SetWindowPos Lib "user32" (ByVal hWnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
hWnd Fenster-Handle
hWndInsertAfter Handle des Fensters, vor dem das Fenster positioniert werden soll (also quasi ein Handle des Owner-Fensters) oder eine der folgenden Konstanten:
224
11
Windows
Abbildung 11.1: Splash und Login Konstante
Wert
Bedeutung
HWND_BOTTOM
1
Ordnet das Fenster an hinterster Stelle an.
HWND_NOTOPMOST
-2
Ordnet das Fenster an erster Stelle hinter den Topmost-Fenstern an. Die Operation hat keine Auswirkung, wenn das Fenster nicht zu den Topmost-Fenstern gehört.
HWND_TOP
0
Ordnet das Fenster an vorderster Stelle an.
HWND_TOPMOST
-1
Ordnet das Fenster an vorderster Stelle an. Diese Position wird beibehalten, auch wenn das Fenster deaktiviert wird.
Tabelle 11.1: Konstanten zur Positionierung eines Fensters
x Linker Rand des Fensters in »client coordinates«
Das TOPMOST-Problem
225
y Oberer Rand des Fensters in »client coordinates«
cx Breite des Fensters in Pixel
cy Höhe des Fensters in Pixel
wFlags Steuert die Größe des Fensters sowie seine Anordnung mit einer Kombination von insgesamt 15 SetWindowPos-Konstanten (SWP), von denen hier zwei wiedergegeben sind: Konstante
Wert
Bedeutung
SWP_NOSIZE
1
Argumente cx und cy ignorieren
SWP_NOMOVE
2
Argumente x und y ignorieren
Tabelle 11.2: SetWindowPos-Konstanten
Rückgabe 0 im Fehlerfalle
11.2.2
Einsatz der Funktion
Wir müssen nur noch dafür sorgen, dass die beiden Fenster auch nach vorne gebracht werden, und zwar in der richtigen Reihenfolge: Public Sub Main() Dim fLogin As frmLogin Dim fSplash As frmSplash 'Splash-Dialog anzeigen Set fSplash = New frmSplash fSplash.Show fSplash.Refresh Sleep 1000 'Loagin-Dialog anzeigen Set fLogin = New frmLogin fLogin.Password = "LossMiNai" fLogin.Show vbModal Unload fSplash If fLogin.Tag = "OK" Then Unload fLogin
'Instanz des Login-Dialogs 'Instanz des Splash-Dialogs
226
11
Windows
frmMain.Show vbModeless Else Unload fLogin End If End Sub
Die Sleep-Funktion verhilft uns zu einer kleinen Unterbrechung, damit der Anwender Muße hat, in aller Ruhe Ihre Werbung zu bestaunen. Mein Konterfei aus Abbildung 11.1 ist natürlich in keiner meiner Anwendungen zu sehen; ich möchte die Leute ja schließlich nicht erschrecken. Und in der Form_Load-Ereignisprozedur beider Forms steht diese Zeile: SetWindowPos Me.hWnd, HWND_TOPMOST, 0, 0, 0, 0, _ SWP_NOMOVE Or SWP_NOSIZE
Diese Einstellung muss am Ende nicht aufgehoben werden, da beide Fenster ohnehin aus dem Speicher entfernt werden.
11.3 Undo – oder lieber doch nicht Es sind Firmen wie Microsoft mit Produkten wie Excel und Word, die Standards setzen; und es sind die Anwender dieser Programme, die uns gelegentlich fragen, wieso in unserem Programm keine Möglichkeit besteht etwas rückgängig zu machen. Ist der Anwender gleich der Auftraggeber, so können wir ihn fragen, wie viel ihm eine Rückgängig-Funktion wert ist, und das Thema ist vermutlich schnell vom Tisch. Und wenn nicht, dann haben Sie ein Problem – ein recht großes Problem zwar, aber ein lösbares. Selbst wenn Sie Änderungen bereits in der Datenbank vorgenommen haben, können Sie ein zweidimensionales String-Array aufbauen, und für jede Operation ein passendes Gegengift in Form einer oder mehrerer SQL-Anweisungen hinterlegen und die LIFO (last in first out) abarbeiten, wenn der Anwender sich entschließt, seine Aktivitäten wieder rückgängig zu machen. Das wollen wir jetzt nicht näher untersuchen, vielmehr begeben wir uns daran, ein paar TextBoxes in einem Dialog mittels einer Collection und der Nachricht WM_UNDO dazu zu bewegen, sich selbst um das Rückgängigmachen zu kümmern. Im VB-Projekt Windows auf der beiliegenden CD finden Sie eine frmMain, die über zwei TextBoxes verfügt sowie einen Menüeintrag und einen Symbolleistenknopf, um die Rückgängig-Operation einzuleiten. Menüeintrag und Symbolleistenknopf sind nur dann bedienbar, wenn es auch etwas rückgängig zu machen gibt. Das heißt natürlich, dass wir sorgfältig darauf achten müssen, dass die Bedienelemente nicht verfügbar sind, wenn noch nichts verändert wurde oder die Collection leer ist, in der die TextBoxes versammelt sind, die für »rückgängig« vorgemerkt sind.
Undo – oder lieber doch nicht
11.3.1
227
SendMessage-Funktion
Diese Funktion sendet eine Nachricht an ein Fenster und wartet hierbei, bis die Nachricht abgearbeitet ist. Die Nachricht landet bei der jeweiligen Window Procedure der betreffenden Fensterklasse. Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
hWnd Handle des Ziel-Fensters
wMsg Die zu sendende Nachricht, also WM_UNDO
wParam Zusätzlicher, nachrichtenspezifischer Parameter
lParam Zusätzlicher, nachrichtenspezifischer Parameter
Rückgabe Nachrichtenabhängiger Wert (0 im Fehlerfall und 1 im Erfolgsfall bei WM_UNDOMessage)
11.3.2
Ereignisroutine für TextBoxes
In dem Dialog tummelt sich möglicherweise ein ganze Reihe TextBoxes. Wir werden also darauf achten müssen, dass nicht in jeder Ereignisroutine ein mehr oder weniger identischer Code steht. Eine gemeinsame Routine bietet sich hier an, an die ein Verweis auf die jeweilig auslösende TextBox übergeben wird: Private Sub CommonTextBoxRoutine(ByRef txtAct As TextBox) Dim iText As Long 'Zeiger in colText 'Abbruch, wenn Änderung durch Rückgängig-Operation verursacht If bForgetIt Then Exit Sub If colText.Count = 0 Then 'TextBox ergänzen, da Collection leer ist colText.Add txtAct ElseIf colText(colText.Count) <> txtAct Then 'TextBox ergänzen, da das letzte Element eine andere TextBox ist colText.Add txtAct
228
11
Windows
End If If colText(colText.Count) = txtAct And colText.Count > 1 Then 'Wenn TextBox ergänzt wurde, ... For iText = colText.Count – 1 To 1 Step -1 '... alle Verweise auf diese TextBox suchen ... If colText(iText) = txtAct Then '... und entfernen. colText.Remove iText End If Next End If 'Bedienelemente aktivieren mnuEUndo.Enabled = True tlbStandard.Buttons("Undo").Enabled = True End Sub
Die Ereignisroutine einer TextBox enthält nur noch den Aufruf der CommonTextBoxRoutine: Private Sub txtName_Change() Call CommonTextBoxRoutine(txtName) End Sub
11.3.3
Das Rückgängigmachen
Werfen wir abschließend einen Blick auf die Menü-Ereignisroutine. Diese wird natürlich auch von der Ereignisroutine der ToolBar aufgerufen. Private Sub mnuEUndo_Click() 'Abbruch-Variable aktivieren bForgetIt = True 'SendMessage auf letzte TextBox loslassen ... SendMessage colText(colText.Count).hWnd, WM_UNDO, 0, 0 '... und diese aktivieren. colText(colText.Count).SetFocus 'die letzte TextBox aus Collection entfernen colText.Remove colText.Count 'Abbruch-Variable deaktivieren bForgetIt = False 'Wenn Collection leer ist, ... If colText.Count = 0 Then '... Bedienung deaktivieren. mnuEUndo.Enabled = False tlbStandard.Buttons("Undo").Enabled = False End If End Sub
Ein RTF-ToolTip-Fenster mit AutoSize
229
11.4 Ein RTF-ToolTip-Fenster mit AutoSize In einem Programm sollte zu einem Produkt jeweils ein Bild, technische Eckdaten sowie erläuternde Texte angezeigt werden. Das Bild und die technischen Daten unterzubringen, war kein Problem. Nur fehlte der Platz für die Texte. Als ideal stellte sich die Idee eines ToolTip-Fensters heraus, das optional eingeblendet werden konnte. Wenn dieses sogar noch dem Cursor gefolgt wäre, also durch Bewegen der Maus hätte verschoben werden können, so wäre die Lösung perfekt gewesen (siehe Abbildung 11.2).
Abbildung 11.2: ToolTip-Fenster mit RTF-Control
Bei dem Fenster handelte es sich um eine normale Form, deren BorderStyle auf 0 – kein eingestellt wurde, RTF-Control und Form waren kongruent. Das Bewegen der Form, die als Instanz gestartet wurde, gestaltete sich über die MouseMove-Ereignisse der betroffenen Steuerelemente recht einfach. Die jeweiligen Textgrößen variierten erheblich und waren durch interne Formatierungen schlichtweg unberechenbar, wodurch eine Größenbestimmung des RTF-Steuerelements nur näherungsweise mit großer Toleranz gestaltet werden konnte. Je nach Inhalt und Formatierung belegten diese nur zwei Drittel des zur Verfügung stehenden Platzes. Eine AutoSize-Funktion wäre die Lösung gewesen, aber das RTF-Steuerelement unterstützte dies nicht.
230
11
Windows
Eine Analyse des Nachrichtenverkehrs zwischen Windows und dem RTF-Steuerelement beim Laden des Texts ergab, dass die Auswertung eines Hooks zwischen beiden kein Spaziergang werden würde. Der Zufall brachte es an den Tag, dass die User32.dll einige Funktionen bereithält, um ScrollBars zu manipulieren und den Status abzufragen. Denn die komplizierte Auswertung per Hook scheint auch irgendjemanden bei Microsoft dazu inspiriert zu haben, hierfür Funktionen bereit zu stellen.
11.4.1
ScrollBar-Funktionen
Gleich neun Funktionen dienen der Manipulation und Statusabfrage einer ScrollBar: Funktion
Zweck
EnableScrollBar
aktiviert oder deaktiviert einen oder beide ScrollBar-Pfeile
GetScrollBarInfo
füllt einen SCROLLBARINFO-Datentyp mit der Geometrie einer ScrollBar
GetScrollInfo
füllt einen SCROLLINFO-Datentyp mit Parametern einer ScrollBar
GetScrollPos
ermittelt den aktuellen Wert (Value-Eigenschaft) der ScrollBar
GetScrollRange
ermittelt Min und Max der ScrollBar
SetScrollInfo
übergibt einen SCROLLINFO-Datentyp mit Parametern
SetScrollPos
übergibt einen neuen Value an eine ScrollBar
SetScrollRange
übergibt neue Min- oder Max-Werte an eine Scrollbar
ShowScrollBar
zeigt eine ScrollBar an oder versteckt sie
Der Vollständigkeit halber seien die folgenden Funktionen erwähnt, die zwar scrollen, aber keinen unmittelbaren Bezug zu ScrollBars selbst haben: Funktion
Zweck
ScrollDC
scrollt einen rechteckigen Bereich in einem DeviceContext horizontal und/oder vertikal
ScrollWindowEx
scrollt die ClientArea eines Fensters
GetScrollBarInfo verwendet, wie bereits erwähnt, die Struktur SCROOLBARINFO, um Angaben über die Geometrie zu übermitteln. In VB oder VBA dürften wir kaum Verwendung für diese Information haben. Ein Blick auf die SCROLLINFO-Struktur hingegen erleichtert die Auswahl der betreffenden GetScroll-Funktion, weshalb wir diese etwas näher ansehen.
Ein RTF-ToolTip-Fenster mit AutoSize
11.4.2
231
SCROLLINFO-Struktur
Private Type SCROLLINFO cbSize As Long fMask As Long nMin As Long nMax As Long nPage As Long nPos As Long nTrackPos As Long End Type
cbSize In diesem Element erwartet die aufgerufene Funktion die Größe der Struktur, die wie folgt zugewiesen werden kann: Dim ScrInfo as SCROLLINFO ScrInfo.cbSize = Len(ScrInfo)
fMask Hier legen Sie fest, welche Parameter eingestellt oder ermittelt werden sollen, die da sind: Konstante
Wert
Bedeutung
SIF_RANGE
1
Min- und Max-Werte
SIF_PAGE
2
Seitengröße beim Scrollen
SIF_POS
4
aktueller Wert
SIF_TRACKPOS
&H10
aktueller Wert vor Anwender-Aktion
SIF_ALL
&H17
SIF_RANGE bis SIF_TRACKPOS
SIF_DISABLENOSCROLL
8
ScrollBar disabled beibehalten, wenn sie nicht mehr erforderlich ist.
nMin Min-Wert der ScrollBar
nMax Max-Wert der ScrollBar
232
11
Windows
nPage Seitengröße der ScrollBar. Das ist die Größe, um die sich der Wert bei einem Klick auf den Schenkel der ScrollBar verändert.
nPos Aktueller Wert der Scrollbar. Dieser Wert verändert sich nicht, wenn der so genannnte Thumb der ScrollBar gerade per Maus verändert wird. Dieser Wert wird erst nach Abschluss dieser Drag-Operation aktualisiert.
nTrackPos Aktueller Wert bei laufender Anwenderaktion (Dragging). Dieser Wert ist nur dann von Interesse, wenn die Struktur als Argument einer SendMessage-Funktion übermittelt wird und interessiert bei einer statischen Betrachtung nicht. Um zu erkennen, ob ein Steuerelement nun gerade eine ScrollBar eingeblendet hat, eignet sich die Abfrage des nMax-Wertes. nMax ist 0, wenn die ScrollBar nicht existiert, und > 0 im eingeblendeten Zustand.
11.4.3
GetScrollInfo-Funktion
Die universelle GetScrollInfo gibt eine SCROLLINFO-Struktur mit allen Statusinformationen der ScrollBar zurück. Declare Function GetScrollInfo Lib "user32.dll" (ByVal hWnd As Long, ByVal fnBar As Long, lpsi As SCROLLINFO) As Long
hWnd Das Handle des zu untersuchenden Steuerelements.
fnBar Eine der folgenden Konstanten: Konstante
Wert Bedeutung
SB_CTL
2
signalisiert der Funktion, dass hWnd sich auf ein ScrollBar-Steuerelement selbst bezieht
SB_HORZ
0
fordert die Werte der horizontalen ScrollBar an
SB_VERT
1
fordert die Werte der vertikalen ScrollBar an
Ein RTF-ToolTip-Fenster mit AutoSize
233
lpsi eine SCROLLINFO-Struktur
Rückgabe 0 bei Fehler Im folgenden Beispielcode fragen wir den nMax der vertikalen ScrollBar eines RTFSteuerelements ab: Dim ScrInfo As SCROLLINFO 'ScrollInfo-Struktur Dim hReturn As Long 'Rückgabe der GetScrollInfo 'ScrollInfo-Struktur mit dem Nötigsten versorgen ScrInfo.cbSize = Len(ScrInfo) ScrInfo.fMask = SIF_RANGE 'ScrollInfo-Struktur füllen lassen hReturn = GetScrollInfo(RTF.hwnd, SB_VERT, ScrInfo) 'Rückgabe prüfen und ggf. Fehler erzeugen If hReturn = 0 Then Err.Raise Err.LastDllError End If 'Prüfen, ob ScrollBar sichtbar ist If ScrInfo.nMax = 0 Then 'vertikale ScrollBar nicht sichtbar Else 'vertikale ScrollBar sichtbar Endif
11.4.4
GetScrollPos- und GetScrollRange-Funktion
Obwohl sich die Verwendung der GetScrollInfo-Funktion als recht einfach herausstellte, stehen uns über GetScrollPos und GetScrollRange zwei Funktionen zur Verfügung, um gezielt den nPos-Wert sowie den Range nMin und nMax abzufragen. Man ist geneigt, dies als puren Luxus einzustufen. Die Ermittlung des aktuellen Wertes der ScrollBar über GetScrollPos könnte so aussehen: Declare Function GetScrollPos Lib "user32.dll" (ByVal hWnd As Long, ByVal nBar As Long) As Long Private Sub ... lngPos = GetScrollPos(hWnd, SB_VERT)
Die Rückgabe der GetScrollPos-Funktion enthält also die gesuchte nPos der ScrollBar. Wir werden noch auf diese nPos zurückkommen, denn sooo einfach, wie es auf den ersten Blick erscheint, ist die Sache mit der Position auch nicht.
234
11
Windows
Nun ein Beispiel für die GetScrollRange, die mit zwei zurückzugebenden Parametern die Rückgabe der Funktion selbst nicht mehr nutzen kann: Declare Function GetScrollRange Lib "user32.dll" (ByVal hWnd As Long, ByVal nBar As Long, lpMinPos As Long, lpMaxPos As Long) As Long Private Sub ... hResult = GetScrollRange(hWnd, SB_VERT, lngMin, lngMax)
Die beiden Variablen lngMin und lngMax enthalten die Grenzen der Scrollbar.
11.4.5
AutoSize des RTF-Steuerelements
Nachdem wir nun das API-Rüstzeug für das AutoSizing erarbeitet haben, wollen wir das Problem etwas näher untersuchen. Wir vergrößern die Height-Eigenschaft des RTF-Steuerelements so lange, bis die GetScrollRange-Funktion einen nMax von 0 zurückliefert. Soweit die Theorie. Die Praxis zeigt hingegen, dass nach Erreichen einer ausreichenden Höhe die ScrollBar nicht verschwindet. Das RTF-Steuerelement beansprucht vielmehr einen kleinen Freiraum unterhalb der letzten Zeile, bevor es bereit ist, die ScrollBar auszublenden. Und dieser zusätzliche Platz wiederum stört, denn man erwartet am unteren Ende zwangsläufig einen Abstand der dem entspricht, der auch zwischen oberem Rand und erster Zeile vorherrscht. Zum Glück verhält sich das RTF-Steuerelement beim Verkleinern der Höhe anders, denn da wird die ScrollBar erst eingeblendet, wenn sie wirklich erforderlich ist. Die Lösung ist also, die Höhe in einer größeren Schrittweite so lange zu erhöhen, bis die ScrollBar verschwindet. Nennen wir diese Höhe h1. Danach reduzieren wir die Höhe mit einer kleineren Schrittweite, bis die ScrollBar wieder auftaucht. Die optimale Höhe h2 ist nun also die unmittelbar vorher zugewiesene Höhe, also die aktuelle Höhe plus kleinere Schrittweite. Kompliziert, nicht wahr? Doch was passiert, wenn wir diese errechnete Höhe zuweisen? Das RTF-Steuerelement blendet die ScrollBar nicht aus, denn beim Vergrößern legt sie auf einen gewissen Abstand wert, wie wir bereits sahen. Also weisen wir nacheinander h1 und h2 zu und das RTF-Steuerlement hat die gewünschte Höhe. Das Ganze klingt etwas kompliziert, aber zusammen mit dem Code dürfte das Verfahren einleuchten: Private Sub AutoSizeControl() Dim hReturn As Long 'Rückgabe der GetScrollInfo Dim lngHOpt As Long 'Wert, bei dem Scrollbar noch unsichtbar war Dim lngHGone As Long 'Wert, bei dem ScrollBar verschwand Dim lngMin As Long 'Min-Wert der Scrollbar aus Funktion Dim lngMax As Long 'Max-Wert der Scrollbar aus Funktion On Error GoTo errHandler Do 'Range ermitteln hReturn = GetScrollRange(RTF.hWnd, SB_VERT, lngMin, lngMax)
Ein RTF-ToolTip-Fenster mit AutoSize
235
If hReturn = 0 Then Err.Raise Err.LastDllError End If 'Prüfen, ob ScrollBar unsichtbar wurde If lngMax = 0 Then 'diese Größe merken ... lngHGone = RTF.Height '... und schrittweise Höhe reduzieren, bis ... Do RTF.Height = RTF.Height – 50 hReturn = GetScrollRange(RTF.hWnd, SB_VERT, lngMin, _ lngMax) If hReturn = 0 Then Err.Raise Err.LastDllError End If If lngMax > 0 Then '... sie wieder erscheint 'Letzte unbedenkliche Höhe merken lngHOpt = RTF.Height + 50 'nacheinander lngHGone und lngHOpt einstellen RTF.Height = lngHGone RTF.Height = lngHOpt Exit Sub End If Loop End If 'RTF vergrößern RTF.Height = RTF.Height + 100 Loop Exit Sub errHandler: Err.Raise Err.Number, "frmInfo:AutoSizeControl" End Sub
Bleibt noch, die Höhe der Form auf die aktuelle Höhe des RTF-Steuerlements zu setzen, und die Info-Form ist fast perfekt. Störend ist lediglich, dass unser RTF während des AutoSizing ein wenig flackert. In besagtem Projekt, das den Ausgangspunkt dieser Lösung darstellte, blendete ich ein Picture-Steuerelement für die Dauer dieses Vorgangs in den Vordergrund, und der Spuk war vorüber. Das fällt noch nicht einmal auf, da das AutoSizing ohnehin nur beim Laden einer neuen Textdatei erfolgt. Das fertige Formular finden Sie als frmInfo auf der Buch-CD.
11.4.6
Kann man der nPos trauen?
Der Fragestellung nach zu urteilen, kann man ihr nicht trauen. Doch der Reihe nach. Es gibt Zeitgenossen, die sofort nach dem Messer greifen und eine Kerbe in die Maus ritzen, wenn sich ein Wert nicht mit der Erwartung deckt. So ging es mir auch, als der
236
11
Windows
nPos-Wert nicht dem nMax entsprach, obwohl sich der Thumb am unteren Anschlag befand. Einen Moment zweifelte ich sogar an der Umsetzung des Wertes, der schließlich als unsigned integer, also vorzeichenloser 32-Bit-Wert ankommt. Aber so lange vorzeichenlose Werte kleiner als die obere Grenze des vorzeichenbehafteten Äquivalents sind, ist die Konvertierung problemlos. Was ist also passiert? Nun, die Funktionen GetScrollPos und GetScrollInfo geben offensichtlich den Wert zurück, der an der oberen Kante des Thumbs liegt. Und da der Thumb immer eine reelle Höhe aufweist, kann dieser Wert zwar klein, aber nie 0 werden. Der Fehler ist also proportional zur relativen Höhe des Thumbs, also dem Verhältnis von ThumbHöhe und Höhe der Verschiebefläche der ScrollBar, wie Abb. 11.3 zeigt:
Abbildung 11.3: nPos ohne Korrektur
Um nPos in der gewohnten Weise zwischen nMin und nMax verarbeiten zu können, müssen wir also einen Korrekturwert einbringen, der sich an der relativen Position des Thumbs orientiert, was in Abb. 11.4 wiedergegeben ist.
Abbildung 11.4: nPos mit Korrektur
SubClassing am Beispiel des RTF-Controls
237
Ideal wäre eine Codekomponente, die eine Änderung der ScrollBar in Form eines Ereignisses an beispielsweise die Form weitermelden würde. Damit die Codekomponente über die Änderung der eigentlich unbekannten ScrollBar unterrichtet werden kann, muss eine besondere Technik zur Anwendung kommen, die man SubClassing nennt.
11.5 SubClassing am Beispiel des RTF-Controls Unter SubClassing versteht man die Erstellung einer eigenen Komponente in Form einer Klasse oder DLL, die für ein bestimmtes Steuerelement dynamische Funktionalität ergänzt, indem beispielsweise ein neues Ereignis erzeugt wird. Damit die Komponente diese Dynamik leisten kann, müssen die Nachrichten zwischen Windows und dem Steuerelement durch diese Komponente geschleust werden. Durch Auswertung dieser Nachrichten und deren Parameter werden die Informationen gesammelt, die diese neue Funktionalität erst ermöglichen. Eine SubClassing-Komponente arbeitet also selbstständig. Unsere Komponente, die dem RTF-Steuerelement ein wenig auf die Sprünge helfen soll, wird ein neues Ereignis erzeugen, das uns die genaue, also korrigierte, Position der ScrollBar in einem Wertebereich zwischen 0 und 1 übergibt. Denn die Übergabe eines ganzzahligen Wertes, wie wir es von ScrollBars gewohnt sind, mach keinen Sinn, wenn wir den Max nicht einstellen können. Abbildung 11.5 zeigt die Architektur des SubClassing in unserem RTF-Projekt. In dem Modul modRTF befindet sich die Public Function RTFWindowProc, die nach der Umleitung des Nachrichtenstroms alle für das RTF bestimmten Nachrichten von Windows entgegennimmt. Diese so genannte WindowProc darf nicht in einer Klasse stehen, da sich COM nicht auf das Experiment einlässt, Nachrichten an eine möglicherweise nicht geladene COM-Code-Komponente zu senden. Und Module sind nun einmal per definitionem immer geladen. Die RTFWindowProc wiederum ruft die Methode CallBackProc der Klasse clsRTF auf und übergibt ihr die Nachricht nebst ihren Argumenten. Die Methode CallBackProc leitet die Nachricht an die ursprüngliche Window-Prozedure weiter und wertet danach die Nachricht aus. Sollten sich aus der Auswertung neue Erkenntnisse ergeben, die ein Ereignis rechtfertigen, so löst die Klasse clsRTF, die in frmMain als cRTF WithEvents deklariert wurde, das betreffende Ereignis aus. Nach wie vor erhält frmMain natürlich die Standard-Ereignisse des RTF-Steuerelements, wofür stellvertretend das Change-Ereignis steht.
238
11
Windows
Abbildung 11.5: Architektur des RTF-SubClassing
Diese Abbildung birgt bei genauerem Hinsehen noch ein kleines Problem, denn clsRTF WithEvents unter dem Namen cRTF in frmMain deklariert, stellt der außen stehenden modMain natürlich keine Schnittstelle zur Verfügung. Dennoch muss RTFWindowProc in modMain die Methode CallBackProc aufrufen. Die Lösung dieses Problem liegt in der Verwendung zweier Objektvariablen, wie Abb. 11.6 zeigt.
Abbildung 11.6: Referenzierung der clsRTF
Von modRTF aus wird die Klasse als gRTF referenziert. Innerhalb frmMain ist dieselbe Klasseninstanz WithEvents als cRTF bekannt, welches in der Form_Load auf die Adresse der dann bereits bekannten gRTF verweist. Wenn Sie das Ganze in einer DLL kapseln, ist diese doppelte Referenz natürlich obsolet, weil die globale Instanz unbekannt sein wird.
SubClassing am Beispiel des RTF-Controls
11.5.1
239
Hook und Unhook
Eine Umleitung wird üblicherweise als Hook bezeichnet, also das Einhaken in eine laufende Kommunikation. Mit Unhook ist das Zurücksetzen auf die ursprüngliche Verbindung gemeint. Für das Einrichten eines Hooks benötigen wir das Window-Handle des betreffenden Fensters und die Zieladresse der von Windows aufzurufenden Window-Prozedur. Das Window-Handle, oder kurz hWnd, eines Steuerelements erhalten wir auf Anfrage vom Steuerelement selbst, denn diese verfügen in der Regel über eine hWnd-Eigenschaft. Fehlt noch die Adresse der neuen Window-Prozedur, die wir in modRTF eingerichtet haben. Diesen Long-Wert liefert der AddressOf-Operator. Die Window-Prozedur muss mit einer genau festgelegten Methodensignatur ausgestattet sein, damit sie von Windows als Window-Prozedur akzeptiert wird: Public Function RTFWindowProc(ByVal hWnd As Long, ByVal uMsg As Long, _ ByVal wParam As Long, ByVal lParam As Long) As Long End Function
Als hWnd wird das Handle des Zielfensters übergeben, während in uMsg die Nachricht selbst kodiert ist. wParam und lParam sind zwei Argumente, die der Nachricht uMsg zugeordnet sind. Der CallBackProc-Methode der Klasse müssen wir das Handle nicht übergeben, denn dieses muss der Klasse ohnehin bekannt sein. Sie benötigt aber drei Long-Argumente, um uMsg, wParam und lParam entgegennehmen zu können: Public Function CallBackProc(ByVal uMsg As Long, ByVal wParam As Long,_ ByVal lParam As Long) As Long 'Aufruf der eigentlichen Window-Prozedur des Steuerelements End Function
Nun ist die Adresse der Window-Prozedur ein Attribut des betreffenden Fensters. Und dieses Attribut gilt es zu ändern, um die Umleitung in Kraft zu setzen. Zur Änderung eines Fensterattributs steht uns eine Funktion zur Verfügung, die auf den Namen SetWindowLong hört: Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
hWnd hWnd ist das Handle des Fensters, dessen Attribut geändert werden soll.
240
11
Windows
nIndex Mit nIndex legen wir fest, welches Attribut geändert werden soll. Es existieren insgesamt neun Konstanten, wovon allerdings in der Regel nur die Verwendung findet, die uns die Änderung der Window-Prozedur ermöglicht: Const GWL_WNDPROC = -4
dwNewLong In diesem Argument nimmt das Fenster den neuen Wert des zu ändernden Attributs entgegen, in unserem Falle also die neue Adresse.
Rückgabe War die Änderung erfolgreich, so gibt SetWindowLong den vorherigen Wert des geänderten Attributs zurück, andernfalls 0. Die Rückgabe der SetWindowLong benötigen wir, um die Nachricht an die ursprüngliche Window-Prozedur weiterleiten zu können, aber auch um den Hook am Ende wieder zurückzusetzen. Dies ist auf den ersten Blick nicht ganz einsichtig, denn man könnte erwarten, dass das Fenster das Zeitliche segnet, bevor die Applikation, die es schließlich erst erzeugt hat, aufhört zu existieren. Doch dies ist ein Trugschluss, der Ihrer Applikation ein unerwartet schnelles Ende bereiten wird, denn sie wird eine Speicherverletzung verursachen. Die Fenster werden erst ordnungsgemäß abgeräumt, wenn der VB-Code längst abgearbeitet ist. Wir werden noch darauf zurückkommen. Das Einrichten eines Hooks wiederum gestaltet sich recht einfach: Private Sub Hook() lpPrevWndProc = SetWindowLong(hWnd, GWL_WNDPROC, _ AddressOf RTFWindowProc) End Sub
lpPrevWndProc ist eine private Variable in clsRTF, in der die Ursprungsadresse der Window-Prozedur abgelegt wird. Das Aufheben des Hooks ist logischerweise auch nur eine Änderung des Attributs GWL_WNDPROC des Fensters: Private Sub Unhook() SetWindowLong hWnd, GWL_WNDPROC, lpPrevWndProc End Sub
Sobald der Hook eingerichtet ist, prasseln die Nachrichten mitunter im Millisekundenrhythmus auf unsere Funktion in modMain ein. Einen Eindruck davon können Sie bekommen, wenn Sie mit dem Visual Studio beiliegenden Spy-Tool ein Fenster untersuchen. Am Ende dieses Abschnitts werden Sie aber auch in der Lage sein, den Nachrichtenverkehr selbst mitzuprotokollieren.
SubClassing am Beispiel des RTF-Controls
241
Die eigentliche Auswertung des Nachrichtenstroms findet in der Methode CallBackProc der Klasse clsRTF statt. Doch die erste Amtshandlung in der Funktion sollte die Weiterleitung der Nachricht an ihr ursprüngliches Ziel sein. Dessen nimmt sich die Funktion CallWindowProc an, die folgendermaßen deklariert ist: Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndProc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Warum das erste Argument in den Microsoft-Quellen nun auf einmal lpPrevWndFunc heißt, obwohl an anderer Stelle die Rede von lpPrevWndProc ist, ist unklar. Der Einheitlichkeit wegen habe ich das Argument in lpPrevWndProc umgetauft. Unsere CallBackProc-Methode muss nun so aussehen, um die Nachricht wieder an die Zieladresse loszuwerden: Public Function CallBackProc(ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long CallBackProc = CallWindowProc(lpPrevWndProc, hWnd, uMsg, wParam, _ lParam) ... End Function
Der Hook ist fertig, doch noch zu nichts nutze. Es geht also nun darum, uMsg auszuwerten, damit wir auf die gewünschte Nachricht reagieren können. Es gibt rund 1000 verschiedene Nachrichten, die von Windows zu Fenstern gesendet werden. Der überwiegende Teil davon ist Steuerelemente-spezifisch, andere wiederum gelten für alle Fensterarten. Im Schnitt werden wohl potentiell zwischen 100 und 200 verschiedene Nachrichten je Fensterklasse ausgetauscht. Diese Zahl erschreckt ein wenig, doch meist sind wir gezielt auf der Suche nach Nachrichten zu einer fest umrissenen Aufgabe, wodurch wir es eigentliche nur mit einer Handvoll Nachrichten zu tun haben.
11.5.2
Auswertung der VSCROLL-Nachricht
Eine Änderung der vertikalen ScrollBar eines Steuerelements können wir recht einfach und sicher daran erkennen, dass VSCROLL-Nachrichten zwischen Windows und der Window Procedure ausgetauscht werden. Zuerst wird geprüft, ob es sich um eine Vertical-Scroll-Nachricht handelt. Liegt diese vor und handelt es sich zusätzlich um das Ende eines Scroll-Vorgangs, so wird zuerst eine SCROLLINFO-Struktur angefordert. Bei Win 98, NT 4 ab SP 6 und Windows 2000 können wir neben der etwas groben SCROLLINFO-Struktur auch eine SCROLLBARINFOStruktur erhalten, die uns genauere Daten über die Geometrie der ScrollBar liefert. Public Function CallBackProc(ByVal uMsg As Long, _ ByVal wParam As Long, _ ByVal lParam As Long) As Long
242
11
Windows
Dim lngResult As Long Dim lngScrollHeight As Long Dim lngThumbTop As Long Dim lngThumbHeight As Long 'Originalnachricht weiterleiten CallBackProc = CallWindowProc(lpPrevWndProc, hWnd, uMsg, wParam, _ lParam) 'Vertical-Scroll-Nachricht suchen If uMsg = WM_VSCROLL Then 'Ende eines Scroll-Vorgangs suchen If wParam = SB_ENDSCROLL Then 'SCROLLINFO-Struktur anfordern lngResult = GetScrollInfo(hWnd, SB_VERT, ScrInfo) 'Scroll-Position ohne Korrektur ermitteln sngScroll = ScrInfo.nPos / (ScrInfo.nMax – ScrInfo.nMin) If bSP6 Then 'Scroll-Position mit Korrektur ermitteln lngResult = GetScrollBarInfo(hWnd, OBJID_VSCROLL, _ ScrBarInfo) lngScrollHeight = ScrBarInfo.rcScrollBar.Bottom – _ ScrBarInfo.rcScrollBar.Top – 32 lngThumbTop = ScrBarInfo.xyThumbTop – 16 lngThumbHeight = ScrBarInfo.xyThumbBottom – 16 – _ lngThumbTop sngScroll = (lngThumbTop + lngThumbHeight * lngThumbTop / _ (lngScrollHeight – lngThumbHeight)) / _ lngScrollHeight End If 'Ereignis auslösen RaiseEvent Scroll(sngScroll) End If End If Exit Function errHandler: Call Unhook Err.Raise Err.Number, "clsRTF:CallBackProc" End Function
11.5.3
Das Scroll-Ereignis
In der Form steht uns dieses Scroll-Ereignis natürlich zur Verfügung, indem WithEvents eine Klasseninstanz erzeugt wird: Private WithEvents cRTF As clsRTF
Die Ereignisprozedur kann so genutzt werden: Private Sub cRTF_Scroll(ByVal RelativePosition As Single) lblScroll.Caption = RelativePosition End Sub
SubClassing am Beispiel des RTF-Controls
243
Wir erhalten also einen Wert zwischen 0 und 1, der exakt der Position des Thumbs in Bezug zur ScrollBar entspricht. Sie werden diesen Wert vermutlich nicht in einem Label ausgeben, aber hier geht es ums Prinzip.
11.5.4
Zum Schluss
Ich möchte nicht verhehlen, dass ich SubClassing als eine heiße Technik ansehe und sie meide, wo es nur geht. Aber wenn sie gründlich getestet ist, steht ihrer Verwendung eigentlich nichts im Wege, und Sie erhalten eine neue Funktionalität, die Ihnen auch neue Impulse in Ihren Anwendungen liefern kann. Lassen Sie es mich wissen, wenn Sie eine tolle Lösung oder auch einfach nur Ideen haben.
12 Shell Die Klasse cSysShell kümmert sich im Wesentlichen um das Starten und Beenden eines Programmes sowie das Positionieren des Programmfensters. Sie entstand im Rahmen eines Projektes, in dem ein in C geschriebener CAD-Viewer in ein VB-Programm eingeblendet werden sollte. Der Anwender sollte hierbei den Eindruck gewinnen, als seien beide Programme eines (siehe Abbildung 12.1).
Abbildung 12.1: Beispiel für Shell-Klasse
246
12
Shell
12.1 Programm starten Das Starten eines Programmes erledigt die ShellExecute-Funktion für uns.
12.1.1
ShellExecute-Funktion
Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" (ByVal hWnd As Long, ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long
hWnd Handle des Parent-Window. Da allerdings kein dem Owner-Objekt vergleichbarer Mechanismus dahinter steckt, können wir ohne funktionale Einbußen zu riskieren einfach eine Null übergeben.
lpOperation Eines der üblichen Shell-Kommandos (Verbs). Tabelle 12.1 zeigt eine Auswahl an Standardverben. Verb
Bedeutung
edit
Startet einen Editor und öffnet die Datei zum Editieren
find
Startet eine Suche
open
Startet eine Anwendung. Handelt es sich bei der übergebenen Datei nicht um eine Anwendung, so wird die der Datei zugeordnete Anwendung gestartet.
print
Druckt die Datei
properties
Zeigt den Eigenschaftsdialog der Datei an
Tabelle 12.1: Auswahl an Standardverben
lpFile Dieser String enthält einen Dateinamen, der quasi als Objekt des Satzes anzusehen ist, in dem lpOperation das Verb darstellt.
lpParameters Ist lpFile eine Anwendung, so können in diesem String zusätzliche Informationen stehen, die an die Anwendung weitergereicht werden. Das können der Name einer zu ladenden Datei sein und sonstige Startargumente. Stellt lpFile hingegen ein Dokument dar, so sollte lpParameters ein Null-String sein.
Programm starten
247
lpDirectory Ein Default-Verzeichnis oder vbNullString.
nShowCmd Dieses Argument steuert, wie das Anwendungsfenster erscheinen soll, wenn die Anwendung gestartet wird. Folgende Konstanten sind definiert: Konstante
Wert
VbAppWinStyle
Bedeutung
SW_HIDE
0
vbHide
Versteckt das Fenster und aktiviert ein anderes.
SW_MAXIMIZE
3
vbMaximizedFocus
Maximiert das Fenster.
SW_MINIMIZE
6
vbMinimizedNoFocus
Minimiert das Fenster und aktiviert das nächste in Z-Richtung.
SW_RESTORE
9
Aktiviert das Fenster und zeigt es somit an. Ist das Fenster minimiert oder maximiert, so wird die Originalgröße und -position wieder hergestellt. Dieses Flag sollte gesetzt werden, wenn ein minimiertes Fenster wieder hergestellt werden soll.
SW_SHOW
5
Aktiviert das Fenster und zeigt es in der aktuellen Größe und Position.
SW_SHOWDEFAULT
10
Startet die Anwendung mit dem Flag, das in der STARTUPINFO-Struktur an die CreateProcess-Funktion übergeben wird.
SW_SHOWMAXIMIZED
3
vbMaximizedFocus
Aktiviert das Fenster und zeigt es maximiert an.
SW_SHOWMINIMIZED
2
vbMinimizedFocus
Aktiviert das Fenster und zeigt es minimiert an.
SW_SHOWMINNOACTIVE
7
Zeigt das Fenster minimiert an. Das aktive Fenster bleibt hierbei aktiv.
SW_SHOWNA
8
Zeigt das Fenster in seiner aktuellen Größe und Position an. Das aktive Fenster bleibt hierbei aktiv.
SW_SHOWNOACTIVATE
4
Tabelle 12.2: Shell-Show-Konstanten
vbNormalNoFocus
Zeigt das Fenster in seiner letzten Größe und Position an. Das aktive Fenster bleibt hierbei aktiv.
248
12
Shell
Konstante
Wert
VbAppWinStyle
Bedeutung
SW_SHOWNORMAL
1
vbNormalFocus
Aktiviert das Fenster und zeigt es an. Ist das Fenster minimiert oder maximiert, wird es in Originalgröße und -position angezeigt.
Tabelle 12.2: Shell-Show-Konstanten (Fortsetzung)
Die dritte Spalte stellt die VB-Konstanten gegenüber, die in der Enumeration VbAppWinStyle enthalten sind. Dort, wo sie übereinstimmen, stimmen sie auch betragsmäßig überein. Deshalb können wir an der Klassenschnittstelle VbAppWinStyle deklarieren und das Argument dennoch an die Funktion übergeben.
Rückgabe Ein Wert größer 32 oder eines der folgenden Ergebnisse: Konstante
Wert
Bedeutung
0
Speicher oder Ressourcen sind zu Ende.
ERROR_FILE_NOT_FOUND
2
Die angegebene Datei wurde nicht gefunden.
ERROR_PATH_NOT_FOUND
3
Das angegebene Verzeichnis wurde nicht gefunden.
ERROR_BAD_FORMAT
11
Die Exe ist keine gültige Win32-Exe oder es ist ein Fehler im Exe-Image aufgetreten.
SE_ERR_ACCESSDENIED
5
Der Zugriff auf die Datei wurde verweigert.
SE_ERR_ASSOCINCOMPLETE
27
Die Zuordnung der Dateierweiterung ist fehlerhaft oder unvollständig.
SE_ERR_DDEBUSY
30
Die DDE-Transaktion konnte nicht abgeschlossen werden, da andere DDE-Transaktionen durchgeführt wurden.
SE_ERR_DDEFAIL
29
Die DDE-Transaktion schlug fehl.
SE_ERR_DDETIMEOUT
28
Die DDE-Transaktion konnte wegen eines Timeout nicht abgeschlossen werden.
SE_ERR_DLLNOTFOUND
32
Die angegebene DLL wurde nicht gefunden.
SE_ERR_FNF
2
siehe ERROR_FILE_NOT_FOUND
SE_ERR_NOASSOC
31
Der Dateierweiterung ist keine Anwendung zugeordnet. Dieser Fehler wird auch erzeugt, wenn versucht wird, eine nicht druckbare Datei zu drucken.
SE_ERR_OOM
8
Es besteht nicht genügend Speicher, um die Operation zu Ende zu führen.
SE_ERR_PNF
3
siehe ERROR_PATH_NOT_FOUND
SE_ERR_SHARE
26
Ein Dateizugriffsfehler ist aufgetreten.
Tabelle 12.3: Shell-Fehlerkonstanten
Programm starten
249
Es wäre schön, wenn die ShellExecute-Funktion ein Handle auf die gestarteten Applikationen in Form eines Handles auf das Top-Level-Fenster der Anwendung zurückgeben würde. Aber das tut sie leider nicht. Da wir jedoch ein Handle benötigen, müssen wir dieses auf anderem Wege besorgen.
12.1.2
FindWindow-Funktion
FindWindow versucht ein Top-Level-Fenster zu finden, das dem übergebenen Klassennamen und dem Fenstertitel entspricht. Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
lpClassName Name der Fensterklasse oder vbNullString. Bei der Fensterklasse muss es sich um eine registrierte Fensterklasse oder ein Steuerelement handeln.
lpWindowName Name des Fenstertitels oder vbNullString. Hier liegt aber ein kleiner Hund begraben, wie wir noch sehen werden.
Rückgabe 0 im Fehlerfalle oder ein gültiges Fenster-Handle.
12.1.3
Die Methode LaunchApplication
Die folgenden privaten Variablen werden benötigt: Private Private Private Private Private
hWndApp As Long strAppTitle As String bAppIsrunning As Boolean bTopMost As Boolean rectWnd As RECT
'Window-Handle der SATViewers 'Titel in Titelleiste 'Wenn True, läuft die Anwendung 'Wenn True, dann TOPMOST einstellen 'RECT-Struktur
Zuerst wird die ShellExecute mit den entsprechenden Argumenten gestartet und die Rückgabe an die Prozedur CheckShellExecuteReturnCode verfüttert. Im Fehlerfalle wandelt sie den Fehlercode in eine entsprechende Fehlernummer und einen Fehlertext um. Wurde ein strWindowTitle an die Methode übergeben, so wird nun versucht ein Handle auf das Top-Level-Fenster zu erhaschen. Gelegentlich muss man einen Moment warten, bis sich die Existenz des Fenster in allen Ecken von Windows herumgesprochen hat. Dies wird durch einen Sleep mit 100 Millisekunden erreicht.
250
12
Shell
Public Sub LaunchApplication(ByVal strApp As String, _ ByVal lngWindowState As VbAppWinStyle, _ ByVal bSetTopMost As Boolean, _ Optional ByVal strParam As String, _ Optional strWindowTitle As Variant) 'Argumente: ' strApp: Name der zu startenden Anwendung ' lngWindowState: WindowState ' bSetTopMost: wenn True, dann TopMost ' strParam: an strApp zu übergebender String ' strWindowTitle: Fenstertitel Dim iFind As Long 'Zählt die FindWindow-Anläufe hWndApp = 0 CheckShellExecuteReturnCode ShellExecute(0, "open", strApp, _ strParam, vbNullString, lngWindowState), strApp bAppIsrunning = True bTopMost = bSetTopMost If Not IsMissing(strWindowTitle) Then strAppTitle = strWindowTitle Do While hWndApp = 0 Sleep 100 hWndApp = FindWindow(vbNullString, strWindowTitle) iFind = iFind + 1 If iFind > 20 Then Exit Sub Loop Me.ResizeApplicationWindow End If End Sub
Konnte ein Fenster-Handle ermittelt werden, werden die Größe und Position des Fensters eingestellt.
12.2 Größe und Position des Fensters bestimmen Diese Aufgabe besteht lediglich darin, das Fenster in Z-Richtung anzuordnen und eventuelle Korrekturen an Größe und Position per SetWindowPos vorzunehmen. So sagt es uns zumindest der Verstand. Bei der Arbeit mit dem CAD-Viewer, der ja dieser Komponente zugrunde liegt, stellt sich jedoch heraus, dass alte Fensterschatten zu beobachten waren. Abhilfe schuf eine WM_PAINT-Nachricht an das Fenster, die vor der Korrektur abgeschickt wurde. Gelegentlich war auch zu beobachten, dass die Größenänderungen nicht griffen. Das Problem verschwand, nachdem eine RedrawWindow-Funktion nach der SetWindowPos sich das Fenster noch einmal vornahm.
Größe und Position des Fensters bestimmen
12.2.1
251
SetWindowPos-Funktion
Mit dieser Funktion kann die Reihenfolge eines Fensters beeinflusst werden. Declare Function SetWindowPos Lib "user32" (ByVal hWnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
hWnd Fenster-Handle
hWndInsertAfter Handle des Fensters, vor dem das Fenster positioniert werden soll, oder eine der folgenden Konstanten: Konstante
Wert
Bedeutung
HWND_BOTTOM
1
Ordnet das Fenster an hinterster Stelle an.
HWND_NOTOPMOST
-2
Ordnet das Fenster an erster Stelle hinter den TopMost-Fenstern an. Die Operation hat keine Auswirkung, wenn das Fenster nicht zu den TopMost-Fenstern gehört.
HWND_TOP
0
Ordnet das Fenster an vorderster Stelle an.
HWND_TOPMOST
-1
Ordnet das Fenster an vorderster Stelle an. Diese Position wird beibehalten, auch wenn das Fenster deaktiviert wird.
Tabelle 12.4: Konstanten zur Positionierung eines Fensters
x Linker Rand des Fensters in client coordinates
y Oberer Rand des Fensters in client coordinates
cx Breite des Fensters in Pixel
cy Höhe des Fensters in Pixel
252
12
Shell
wFlags Steuert die Größe des Fensters sowie seine Anordnung mit einer Kombination von insgesamt 15 SetWindowPos-Konstanten (SWP), von denen hier zwei wiedergegeben sind: Konstante
Wert
Bedeutung
SWP_NOSIZE
1
Argumente cx und cy ignorieren
SWP_NOMOVE
2
Argumente x und y ignorieren
Tabelle 12.5: SetWindowPos-Konstanten
Rückgabe 0 im Fehlerfalle
12.2.2
SendMessage-Funktion
Diese Funktion sendet eine Nachricht an ein Fenster und wartet hierbei, bis die Nachricht abgearbeitet ist. Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
hWnd Handle des Ziel-Fensters
wMsg Die zu sendende Nachricht, also WM_PAINT
wParam Zusätzliche, nachrichtenspezifische Parameter oder 0
lParam Zusätzliche, nachrichtenspezifische Parameter oder 0
12.2.3
RedrawWindow-Funktion
Diese Funktion erzwingt ein Update eines Bereichs oder einer Region innerhalb eines Fensters.
Größe und Position des Fensters bestimmen
253
Declare Function RedrawWindow Lib "user32" (ByVal hWnd As Long, lprcUpdate As RECT, ByVal hrgnUpdate As Long, ByVal fuRedraw As Long) As Long
hWnd Handle des Fensters
lprcUpdate RECT-Struktur mit Koordinaten des Bereichs oder 0, falls hrgnUpdate ein gültiges Handle auf eine Region beinhaltet.
hrgnUpdate Handle auf eine Region innerhalb der Client-Area des Fensters.
fuRedraw Dieses Argument steuert das Verhalten des Fensters. Die beiden folgenden Konstanten werden benötigt: Konstante
Wert
Bedeutung
RDW_UPDATENOW
&H100 Sendet WM_NCPAINT, WM_ERASEBKGND und WM_PAINT zu allen spezifizierten Fenstern (RDW_ALLCHILDREN oder RDW_NOCHILDREN).
RDW_ALLCHILDREN
&H80
Sorgt dafür, dass die Operation zu allen untergeordneten Fenstern gesendet wird.
Tabelle 12.6: Verwendete Redraw-Konstanten
Rückgabe 0 im Fehlerfalle
12.2.4
RECT-Struktur
Diese Struktur beschreibt ein Rechteck durch Angabe der oberen linken und unteren rechten Koordinaten in Pixel, deren 15-Faches wiederum unsere Twips sind.
Left Long, X-Komponente der oberen linken Ecke
Top Long, Y-Komponente der oberen linken Ecke
254
12
Shell
Right Long, X-Komponente der unteren rechten Ecke
Bottom Long, Y-Komponente der unteren rechten Ecke
12.2.5
Die Methode ResizeApplicationWindow
Nachdem sichergestellt ist, dass ein gültiges Fenster-Handle existiert, werden die Parameter zusammengestellt und die Funktion aufgerufen: Public Sub ResizeApplicationWindow() Dim lngResult As Long 'Rückgabe Dim lngPos As Long 'TopMost oder nicht Dim lngFlags As Long 'Flags If hWndApp = 0 Then Err.Raise 9999, "Es konnte kein Fenster-Handle ermittelt werden." End If If Me.AppIsRunning Then 'Verhindert "Schatten" des Fensters lngResult = SendMessage(hWndApp, WM_PAINT, 0&, 0&) 'TopMost oder nicht einstellen If bTopMost Then lngPos = HWND_TOPMOST Else lngPos = HWND_NOTOPMOST End If 'Flags einstellen If rectWnd.Right * rectWnd.Bottom = 0 Then lngFlags = SWP_NOMOVE Or SWP_NOSIZE End If 'Funktionen aufrufen lngResult = SetWindowPos(hWndApp, lngPos, rectWnd.Left / 15, _ rectWnd.Top / 15, (rectWnd.Right – rectWnd.Left) / _ 15, (rectWnd.Bottom – rectWnd.Top) / 15, lngFlags) lngResult = RedrawWindow(hWndApp, rectWnd, 0&, _ RDW_UPDATENOW Or RDW_ALLCHILDREN) End If End Sub
12.3 Programm beenden Das Beenden des Programmes erfolgt dadurch, dass eine WM_CLOSE-Nachricht an das Top-Level-Fenster gesendet wird. Und dieses Top-Level-Fenster kennen wir unter dem Namen hWndApp. Hier kommt allerdings die PostMessage-Funktion zusammen mit WaitForSingleObject zum Einsatz.
Programm beenden
12.3.1
255
PostMessage-Funktion
Im Gegensatz zu SendMessage liefert PostMessage die Nachricht ab und wartet nicht, bis diese abgearbeitet ist. Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Die Argumente sind identisch mit denen der SendMessage-Funktion (siehe 12.2.2).
12.3.2
WaitForSingleObject-Funktion
WaitForSingleObject wartet für einen anzugebenden Zeitraum, bis das Objekt hinter dem Handle seinen Zustand gewechselt hat. Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long
hHandle Handle des zu überwachenden Objekts (Fenster oder Prozess)
dwMilliseconds Anzahl der Millisekunden oder INFINITE (&HFFFFFFFF)
Rückgabe 0 im Fehlerfalle
12.3.3
Die Methode CloseApplication
Zuerst wird also die WM_CLOSE-Nachricht abgesetzt. Danach stellt WaitForSingleObject sicher, dass sie abgearbeitet wurde. Public Sub CloseApplication() Dim lngResult As Long 'Rückgabe der PostMessage If hWndApp = 0 Then Err.Raise 9999, "Es konnte kein Fenster-Handle ermittelt werden." End If lngResult = PostMessage(hWndApp, WM_CLOSE, 0&, 0&) Sleep 100 If lngResult = 0 Then Err.Raise 9999, , "Die Nachricht konnte nicht ..." End If lngResult = WaitForSingleObject(hWndApp, INFINITE) If lngResult = 0 Then Err.Raise 9999, , "Die Anwendung konnte nicht beendet werden." End If bAppIsrunning = False End Sub
13 Maus und Tastatur Die beiden Key-Ereignisse KeyDown und KeyUp übergeben ein Shift-Argument und die drei Maus-Ereignisse MouseDown, MouseMove und MouseUp neben Shift auch noch das Button-Argument, wodurch uns die Zustände der Maustasten, der Steuerungs-, Umschalt- und Alt-Taste signalisiert werden. Wozu also sollten wir diese Zustände über das Windows-API abfragen? Sie werden sehen, dass es ein paar Gelegenheiten gibt, bei denen man nicht daran vorbeikommt, den aktuellen Zustand einer Taste der Tastatur oder Maus in Erfahrung zu bringen.
13.1 Maus Da ist das Drag&Drop zu nennen, das manuell gestartet wird. In meiner Laufbahn habe ich ein halbes Dutzend Techniken ausprobiert und wieder verworfen. Die sauberste ist meiner Ansicht nach die Verwendung des MouseDown-Ereignisses, in dem zeitverzögert geprüft wird, ob die linke Maustaste immer noch gedrückt ist. Und genau das kann das Button-Argument des Ereignisses selbst nicht leisten, denn das gibt lediglich den Status der Maustasten zu dem Zeitpunkt wieder, in dem das Ereignis aufgerufen wurde. Wenn wir Entwickler von der rechten Maustaste reden, so meinen wir in der Regel damit die rechte Maustaste, die das Kontextmenü hervorbringen soll. Bei einem Rechtshänder stimmt dies auch mit der Wirklichkeit überein. Ist die Tastenkonfiguration der Maus aber in der Systemsteuerung als linkshändig konfiguriert worden, so wird die physikalisch linke Maustaste zur logischen rechten Maustaste. Wenn wir also von der Taste reden, die das Kontextmenü anzeigen soll, so ist dies die logische rechte Maustaste und die ist unabhängig von der tatsächlichen physikalischen Taste. Die Maus selbst kann aber nur mit einem Bitmuster die physikalischen Tasten codieren, die Zuordnung zu den logischen Tasten müssen wir selbst vornehmen.
258
13
13.1.1
Maus und Tastatur
GetSystemMetrics-Funktion
Die GetSystemMetrics-Funktion erteilt Auskunft über eine Reihe an der Hardware festgemachter Werte wie etwa die Geometrie der Monitore. Aber auch Systemeinstellungen können in Erfahrung gebracht werden, darunter auch, ob die Tastenkonfiguration der Maus geändert wurde. Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
nIndex Dieses Argument steuert, welchen der rund 80 Werte die Funktion zurückgeben soll.
Rückgabe 0 im Fehlerfalle und andernfalls der angeforderte Wert. Bei logischen Werten bedeutet 0 False und alles andere True.
13.1.2
Class_Initialize-Routine
Es dürfte ausreichen, wenn das Initialize-Ereignis dazu herangezogen wird, die aktuelle Mauskonfiguration (SM_SWAPBUTTON) festzustellen. Wir definieren zwei Variablen für die logische Tastenbelegung und nutzen dieses Ereignis, um ihnen die physikalischen Werte zuzuweisen: Private lngLogicalLeft As Long Private lngLogicalRight As Long
'logische linke Maustaste 'logische rechte Maustaste
Nun der Code der Initialize-Ereignisprozedur: Private Sub Class_Initialize() If (GetSystemMetrics(SM_SWAPBUTTON) = 0) Then lngLogicalLeft = VK_LBUTTON lngLogicalRight = VK_RBUTTON Else lngLogicalLeft = VK_RBUTTON lngLogicalRight = VK_LBUTTON End If End Sub
13.1.3
MouseButtonsSwapped-Eigenschaft
Es könnte vielleicht von Interesse sein, die Umkonfiguration der Maustaste als Eigenschaft herauszuziehen: Public Property Get MouseButtonsSwapped() As Boolean MouseButtonsSwapped = (GetSystemMetrics(SM_SWAPBUTTON) <> 0) End Property
Maus
13.1.4
259
GetAsyncKeyState-Funktion
Diese Funktion ermittelt, ob eine Taste der Tastatur oder Maus derzeit gedrückt ist: Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
vKey Der Virtual-Key Code der gewünschten Taste. Hier eine Auswahl der Virtual Keys, die auch in der Komponente Verwendung fanden: Konstante
Wert
Bedeutung
VK_CONTROL
&H11
eine der beiden Steuerungstasten
VK_MENU
&H12
eine der beiden Alt-Tasten
VK_SHIFT
&H10
eine der beiden Shift-Tasten
VK_LCONTROL
&HA2
linke Steuerungstaste
VK_LMENU
&HA4
linke Alt-Taste
VK_LSHIFT
&HA0
linke Shift-Taste
VK_RCONTROL
&HA3
rechte Steuerungstaste
VK_RMENU
&HA5
rechte Alt-Taste
VK_RSHIFT
&HA1
rechte Shift-Taste
VK_LBUTTON
&H1
linke Maustaste (physikalisch)
VK_MBUTTON
&H4
mittlere Maustaste
VK_RBUTTON
&H2
rechte Maustaste (physikalisch)
Tabelle 13.1: Auswahl Virtual-Key Codes
Wie Sie hier sehen können, wird bei Steuerungs-, Umschalt- und Alt-Taste zwischen linken und rechten Tasten unterschieden. Die Alt-Gr-Taste beispielsweise beinhaltet VK_RMENU und VK_RCONTROL.
Rückgabe Der Rückgabe können wir entnehmen, ob die Taste derzeit gedrückt ist und sogar, ob sie seit dem letzten Abfragen des Zustands verändert wurde. Da das MSB (most significant bit) dafür steht, ob die Taste gedrückt ist, können wir auf < 0 abfragen.
13.1.5
MouseLeftButtonPressed-Eigenschaft
Exemplarisch für die Maustasteneigenschaften sei eine gezeigt. Damit die Maus-Eigenschaften in der alphabetisch sortierten Liste nacheinander erscheinen, wurde der Terminus Mouse vor Left, Middle und Right eingeordnet.
260
13
Maus und Tastatur
Public Property Get MouseLeftButtonPressed() As Boolean MouseLeftButtonPressed = (GetAsyncKeyState(lngLogicalLeft) < 0) End Property
13.2 Tastatur Gelegentlich habe ich den »TreeView« aus dem Hause Sheridan im Einsatz. Dass der Font der einzelnen Knoten formatiert werden kann, steht hierbei nicht so sehr im Vordergrund. Wichtiger ist, dass dieser Kandidat einen MultiSelect beherrscht. Angekündigt wird der MultiSelect durch das BeforeNodeClick-Ereignis und ist in bester Tradition aller Before-Ereignisse über ein Cancel-Argument abbrechbar. Dieses Ereignis meldet sich allerdings auch, wenn ein einfacher Klick erfolgte. In beiden Fällen kann tvw.SelectedNodes.Count = 1 sein. Liegt nun ein MultiSelect vor, so muss geprüft werden, ob die neue Auswahl zu der bisherigen Auswahl passt, und dieser MultiSelect offenbart sich nur über die gedrückte Steuerungstaste. Auch bedienen wir uns der GetAsyncKeyState-Funktion, die ja bereits beschrieben ist. Hier nun eine Eigenschaft als Beispiel: Public Property Get ControlKeyPressed() As Boolean ControlKeyPressed = (GetAsyncKeyState(VK_CONTROL) < 0) End Property
Neben den funktionalen Tasten wie Steuerungstaste, Return oder den Cursortasten können natürlich auch die normalen Tasten abgeprüft werden. Übergeben Sie hierzu einfach den Key-Code, wie er auch als VB-Konstante vorliegt. Im folgenden Beispiel wird der Zustand einer beliebigen Taste geprüft: Public Property Get KeyPressed(ByVal lngKeyCode As Long) As Boolean KeyPressed = (GetAsyncKeyState(lngKeyCode) < 0) End Property
Der Aufruf zur Prüfung, ob beispielsweise die A-Taste gedrückt ist, könnte so aussehen: If cSysKeyboard.KeyPressed(vbKeyA) Then
14 Funktionsreferenz 14.1 CallWindowProc CallWindowProc leitet Nachrichten im Stile einer Message (SendMessage oder PostMessage) an eine Window Procedure weiter.
14.1.1
Deklaration und Signatur
Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndProc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Argumente: lpPrevWndProc Adresse der Original Window Procedure hWnd
Handle des Ziel-Fensters
Msg
Die zu sendende Nachricht
wParam
Zusätzlicher, nachrichtenspezifischer Parameter
lParam
Zusätzlicher, nachrichtenspezifischer Parameter
Rückgabe: nachrichtenabhängiger Wert
Das ist der Torso einer CallBack-Prozedur. Die Message und die beiden Parameter werden übergeben, lpPrevWndProc und Fenster-Handle sind bekannt. Public Function CallBackProc(ByVal uMsg As Long, ByVal wParam As _ Long, ByVal lParam As Long) As Long CallBackProc = CallWindowProc(lpPrevWndProc, hWnd, uMsg, wParam, _ lParam) End function
262
14.1.2
14
Funktionsreferenz
Bemerkungen
Die Umleitung des Nachrichtenstroms wird mit der SetWindowLong-Funktion eingeleitet, die bei der Gelegenheit auch lpPrevWndProc ermittelt. Die Umleitung muss wieder aufgehoben werden, sonst ernten Sie eine Speicherschutzverletzung. Siehe auch SetWindowLong-Funktion.
14.2 ChooseColor (APIChooseColor) Diese Funktion startet den Farbdialog und gibt die Auswahl als RGB-Long zurück. Zur Unterscheidung ihres Alias und der CHOOSECOLOR-Struktur wird sie als APIChooseColor deklariert.
14.2.1
Deklaration und Signatur
Declare Function APIChooseColor Lib "comdlg32.dll" Alias "ChooseColorA" (pCHOOSECOLOR As CHOOSECOLOR) As Long
Argumente: pCHOOSECOLOR
Zeiger auf CHOOSECOLOR-Struktur
Rückgabe: 0, wenn der Dialog abgebrochen wurde, und 1 bei OK
Weitere Informationen finden Sie im Kapitel 4.2 Farbendialog.
14.3 CloseHandle CloseHandle schließt ein beliebiges Handle.
14.3.1
Deklaration und Signatur
Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Argumente: hObject Rückgabe: 0 im Fehlerfalle
Handle des zu schließenden Objekts
ClosePrinter
263
Dieses Beispiel schließt ein Datei-Handle: CloseHandle hFile
14.4 ClosePrinter ClosePrinter kommt zum Einsatz, um ein mit OpenPrinter geöffnetes Handle zu schließen.
14.4.1
Deklaration und Signatur
Declare Function ClosePrinter Lib "winspool.drv" (ByVal hPrinter As Long) As Long
Argumente: hPrinter
Handle des zu schließenden Druckers
Rückgabe: 0 im Fehlerfalle
Hier wird der per hPrinter referenzierte Drucker geschlossen: ClosePrinter hPrinter
14.5 CoCreateGUID CoCreateGUID erzeugt eine weltweit eindeutige ID (globally unique ID) und steckt diese Zahl in ein 16 Byte großes Byte-Array.
14.5.1
Deklaration und Signatur
Declare Function CoCreateGuid Lib "OLE32.DLL" (pGUID As GUID) As Long
Argumente: pGUID
GUID-Struktur, die eigentlich nur ein Byte-Array ist
Rückgabe: 0 im Erfolgsfall
Siehe auch StringFromGUID2
264
14
Funktionsreferenz
14.6 CoFileTimeNow Diese Funktion gibt die Anzahl der 100-ns-Intervalle zurück, die seit dem 1. Januar 1601 vergangen sind.
14.6.1
Deklaration und Signatur
Declare Function CoFileTimeNow Lib "ole32.dll" (lpFileTime As Any) As Long
Argumente: lpFileTime
Eigentlich eine FILETIME-Struktur, doch wir verwenden ein Byte-Array
Rückgabe: 0 im Erfolgsfall
In diesem Beispiel wird die Rückgabe in einem Decimal-Datentyp verarbeitet: Dim byteTime(7) As Byte Dim lngReturn As Long Dim varTime As Variant Dim i As Long lngReturn = CoFileTimeNow(byteTime(0)) For i = 0 To 7 varTime = CDec(varTime + byteTime(i) * 2 ^ (8 * i)) Next
14.7 CopyMemory Diese Funktion ermöglicht das Kopieren eines Speicherbereichs an eine Zieladresse.
14.7.1
Deklaration und Signatur
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (hpvDest As Any, ByVal hpvSource As Long, ByVal cbCopy As Long)
Argumente: hpvDest
Zieladresse
hpvSource
Quelladresse
cbCopy
Anzahl der zu kopierenden Bytes
CreateDC
265
Im folgenden Beispiel wird eine Kopie der HOSTENT-Struktur angelegt, die Adresse der zu kopierenden Struktur wird von der GetHostByName-Funktion in lngHostAddress abgelegt: lngHostAddress = GetHostByName(strName) Call CopyMemory(udtHOSTENT, lngHostAddress, LenB(udtHOSTENT))
Eine gewisse Gefahr liegt in der Deklaration von hpvDest As Any. Wenn die Zieladresse ein Zeiger auf mehr als einen Datentyp sein kann, dann muss mit As Any quasi die Obermenge dieser verschiedenen Datentypen deklariert werden. Sie müssen nur darauf achten, ein ByVal vor eine String-Variable zu setzen oder diese mit StrPtr() zu übergeben: Call Call Call Call
CopyMemory(ByVal strTemp, lngPointer, nSize) CopyMemory(StrPtr(strTemp), lngPointer, nSize) CopyMemory(lngTemp, lngPointer, nSize) CopyMemory(byteTemp(0), lngPointer, nSize)
Siehe auch lstrcpy.
14.8 CreateDC Diese Funktion erzeugt einen Gerätekontext eines Druckers.
14.8.1
Deklaration und Signatur
Declare Function CreateDC Lib "gdi32" Alias "CreateDCA" (ByVal lpDriverName As String, ByVal lpDeviceName As String, ByVal lpOutput As String, ByVal lpInitData As Long) As Long
Argumente: lpDriverName Unter NT ist dies üblicherweise »winspool«, bei Win 95 und 98 müssen wir einen NullString übergeben. lpDeviceName Name des Druckers lpOutput
Wird unter Win32 ignoriert und sollte 0 sein
lpInitData
Zeiger zu einer DEVMODE-Struktur mit Initialisierungsdaten oder 0
Rückgabe: 0 im Fehlerfalle, sonst der Gerätekontext
Die folgende Zeile gibt ein Handle auf den Gerätekontext des Druckers zurück: lngDC = CreateDC(strDriverName, strPrinter, 0, 0)
266
14
Funktionsreferenz
14.9 CreateFile CreateFile ermöglicht das Öffnen und Erzeugen einer Datei.
14.9.1
Deklaration und Signatur
Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFileName As String, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByVal lpSecurityAttributes As Long, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long Argumente: lpFileName
Vollständiger Name der zu öffnenden Datei
dwDesiredAccess
Art des Zugangs zu der Datei
dwShareMode
Share-Modus für alle nachfolgenden Zugriffe
lpSecurityAttributes
Zeiger auf eine SECURITY_ATTRIBUTES-Struktur oder 0
dwCreationDisposition Steuert das Verhalten der Funktion, also Öffnen oder Erzeugen der Datei dwFlagsAndAttributes
Im Wesentlichen Dateiattribute
hTemplateFile
Handle einer Vorlagendatei oder 0
Rückgabe: Handle der Datei oder INVALID_HANDLE_VALUE bei Fehler
Im folgenden Beispiel wird ein Handle auf die übergebene Datei angefragt. hFile = CreateFile(strFile, GENERIC_READ, 0, 0, OPEN_EXISTING, _ FILE_ATTRIBUTE_NORMAL, 0)
Siehe hierzu auch Kapitel 7.1
14.10 CreateFontIndirect CreateFontIndirect erzeugt einen logischen Font auf Basis der übergebenen LOGFONTStruktur und gibt ein Handle darauf zurück.
14.10.1
Deklaration und Signatur
Declare Function CreateFontIndirect Lib "gdi32" Alias "CreateFontIndirectA" (lpLogFont As LOGFONT) As Long Argumente: lpLogFont Rückgabe: 0 im Fehlerfalle
Zeiger auf eine LOGFONT-Struktur
DeleteDC
267
Im folgenden Codeauszug wird eine LOGFONT-Struktur auf den Font strFontName eingestellt, dieser u.a. um 90 ° gedreht und erzeugt: Dim udtFont As LOGFONT 'LOGFONT-Struktur udtFont.lfEscapement = 900 udtFont.lfFaceName = strFontName & Chr(0) udtFont.lfHeight = (sngFontSize * -20) / 15 hFont = CreateFontIndirect(udtFont)
14.11 DeleteDC DeleteDC löscht einen per CreateDC erzeugten Handle auf einen Gerätekontext.
14.11.1
Deklaration und Signatur
Declare Function DeleteDC Lib "gdi32" (ByVal hdc As Long) As Long
Argumente: hdc
Handle des zu löschenden Gerätekontexts
Rückgabe: 0 im Fehlerfalle
Die folgende Zeile löscht einen Gerätekontext: DeleteDC lngDC
14.12 DeleteObject DeleteObject löscht ein logisches Objekt des Typs Pen, Brush, Font, Bitmap, Region oder Palette und gibt seine Ressourcen frei.
14.12.1
Deklaration und Signatur
Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
Argumente: hObject
Handle des zu löschenden Objekts
Rückgabe: 0 im Fehlerfalle, was bedeutet, das Handle ist ungültig oder das Objekt ist zurzeit in einem Gerätekontext aktiv
268
14
Funktionsreferenz
Im folgenden Beispiel wird ein per CreateFontIndirect erzeugtes Font-Objekt gelöscht: hFont = CreateFontIndirect(udtFont) ... lngReturn = DeleteObject(hFont)
14.13 DestroyIcon DestroyIcon zerstört ein Icon und gibt den Speicherbereich des Icons frei.
14.13.1
Deklaration und Signatur
Declare Function DestroyIcon Lib "user32" (ByVal hIcon As Long) As Long
Argumente: hIcon
Handle des Icons
Rückgabe: 0 im Fehlerfalle
Dieses Beispiel zerstört ein Icon: DestroyIcon hIcon
14.14 DeviceCapabilities Diese Funktion stellt Informationen über die Fähigkeiten eines Druckers zur Verfügung.
14.14.1
Deklaration und Signatur
Declare Function DeviceCapabilities Lib "winspool.drv" Alias "DeviceCapabilitiesA" (ByVal lpDeviceName As String, ByVal lpPort As String, ByVal iIndex As Long, lpOutput As Any, ByVal dev As Long) As Long
Argumente: lpDeviceName
NULL-terminierter String mit dem Namen des Druckers
lpPort
NULL-terminierter String mit Drucker-Port
iIndex
Dieses Argument steuert, welche Information über den Drucker zurückgegeben werden soll. Die Konstanten beginnen mit DC_.
DrawIcon
269
Argumente: lpOutput
Zeiger auf ein Array mit den angeforderten Daten, wobei der Datentyp des Arrays von iIndex abhängig ist.
dev
Zeiger auf eine DEVMODE-Struktur oder 0. Übergeben wir 0, so ermittelt die Funktion die Default-Werte des Druckers. Übergeben wir hingegen einen gültigen Zeiger auf eine DEVMODE-Struktur, so ermittelt DeviceCapabilities die Einstellungen dieser Struktur.
Rückgabe: Hat alles geklappt, ist die Rückgabe positiv. 0 wiederum signalisiert, dass der Aufruf zwar fehlerfrei war, aber nicht richtig zum Erfolg führte, weil beispielsweise der Drucker das angeforderte Feature nicht unterstützt. Kam es hingegen zu einem Fehler, so gibt die Funktion -1 zurück.
Konstante
Wert
Bedeutung
DC_BINS
6
Bewirkt eine Rückgabe eines Integer-Arrays mit Papierzufuhrkonstanten (DMBIN_*-Konstanten)
DC_BINNAMES
12
Veranlasst die Funktion, die Namen in einem NULL-separierten String-Array zurückzugeben. Diese Zeichenkette beinhaltet 24 Bytes Brutto je Eintrag, wobei nicht genutzte Zeichen durch NULLs dargestellt sind.
DC_COPIES
18
Bewirkt die Rückgabe der maximalen Zahl von Kopien, die der Drucker produzieren kann
DC_DUPLEX
7
Unterstützt der Drucker Duplexdruck, so gibt die Funktion 1 zurück, andernfalls 0.
Tabelle 14.1: Konstanten zur Steuerung der DeviceCapabilities-Funktion
In diesem Beispiel wird geprüft, ob der Drucker doppelseitigen Druck unterstützt und das Ergebnis der logischen Variablen bDuplex zugewiesen wird: bDuplex = DeviceCapabilities(strPrinter, strPort, DC_DUPLEX, 0, 0) * -1
Die folgenden Zeilen ermitteln die Anzahl der Papierzufuhren und füllen ein Array mit den jeweiligen Konstanten: nBins = DeviceCapabilities(strPrinter, strPort, DC_BINS, ByVal 0&, 0) ReDim intPaperBins(nBins – 1) nBins = DeviceCapabilities(strPrinter, strPort, DC_BINS, _ intPaperBins(0), 0)
14.15 DrawIcon DrawIcon zeichnet ein Icon oder einen Cursor in ein über einen Device Context verfügbares Objekt.
270
14
Funktionsreferenz
14.15.1 Deklaration und Signatur Declare Function DrawIcon Lib "user32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByVal hIcon As Long) As Long
Argumente: hdc
Handle des Device Contexts, in den das Icon bzw. der Cursor gezeichnet werden soll
x
Logische X-Koordinate gemäß MapMode (i. d. R. 1 je Pixel)
y
Logische Y-Koordinate gemäß MapMode (i. d. R. 1 je Pixel)
hIcon
Handle des zu zeichnenden Icons
Rückgabe: 0 im Fehlerfalle
Dieses Beispiel zeichnet ein Icon in ein Picture-Steuerelement: DrawIcon picAttachment.hdc, 0, 0, hIcon
14.16 EnumFontFamiliesEx EnumFontFamiliesEx startet einen Vorgang, in dem Windows für jede Schriftart eine anzugebende Callback-Funktion aufruft.
14.16.1
Deklaration und Signatur
Declare Function EnumFontFamiliesEx Lib "gdi32" Alias "EnumFontFamiliesExA" (ByVal hdc As Long, lpLogFont As LOGFONT, ByVal lpEnumFontProc As Long, ByVal lparam As Long, ByVal dwFlags As Long) As Long
Argumente: hdc
Handle des Gerätekontexts, dessen Schriften enumeriert werden sollen. Unter einem Gerätekontext (Device Context) versteht man genau genommen Datenstrukturen, die graphische Attribute eines Gerätes darstellen. Die Art des Stifts fällt hierunter, seine Farbe, Hintergrundfarben und Muster und natürlich der Font. Es gibt Gerätekontexte für reale Geräte wie Drucker oder Bildschirm, aber auch Fenster und Fensterausschnitte können über einen Gerätekontext verfügen.
lpLogFont
Zeiger auf eine LOGFONT-Struktur, die Informationen über zu enumerierende Fonts enthält
lpEnumFontProc
Adresse der Callback-Funktion
lparam
Hier können wir einen anwendungsspezifischen Wert übergeben, den die Funktion an die Callback-Funktion durchreicht.
dwFlags
Dieses Argument wird zurzeit nicht genutzt und muss 0 sein.
EnumPrinters
271
Argumente: Rückgabe: Der letzte Wert, der von der Callback-Funktion zurückgegeben wurde
Im folgenden Beispiel werden die Enumerationen der Systemschriften von Windows gestartet: LF.lfCharSet = DEFAULT_CHARSET hDC = cSysWnd.SystemHDC EnumFontFamiliesEx hDC, LF, AddressOf EnumFontCallBack, 0&, 0&
14.17 EnumPrinters Diese Funktion gibt eine Struktur mit Informationen zu verfügbaren Druckern zurück.
14.17.1
Deklaration und Signatur
Declare Function EnumPrinters Lib "winspool.drv" Alias "EnumPrintersA" (ByVal flags As Long, ByVal name As String, ByVal Level As Long, pPrinterEnum As Long, ByVal cdBuf As Long, pcbNeeded As Long, pcReturned As Long) As Long
Argumente: flags
Das Argument Flags steuert im Wesentlichen, welche Art von Druckern zurückgegeben werden sollen, also beispielsweise die lokalen Drucker beziehungsweise Netzwerkdrucker. Wir verwenden durch ein Or miteinander verknüpft PRINTER_ENUM_CONNECTIONS sowie PRINTER_ENUM_LOCAL, wodurch alle auch im Windows-Explorer angezeigten Drucker verfügbar sind.
name
Domäne, in der die Drucker zu finden sind. Wir übergeben vbNullString.
Level
Wert zwischen 1 und 5, der angibt, welche PRINTER_INFO-Struktur gefüllt werden soll.
pPrinterEnum
Verweis auf die eigentliche PRINTER_INFO-Struktur
cdBuf
Größe des Arguments pPrinterEnum
pcbNeeded
Ist cdBuf zu klein, so enthält das folgende Argument pcbNeeded die erforderliche Größe des Arguments pPrinterEnum.
pcReturned
Zahl der verfügbaren Drucker
Rückgabe: 0 im Fehlerfalle
272
14
Funktionsreferenz
Im folgenden Codeauszug wird EnumPrinters genötigt, die erforderliche Größe des Long-Arrays anzugeben. Danach wird die Funktion mit einem hinreichend dimensionierten Array aufgerufen: 'Größe des Buffers bestimmen lngResult = EnumPrinters(lngFlags, vbNullString, iLevel, 0&, 0&, _ lngRequiredSize, nPrinters) 'Buffer einstellen und Funktion erneut aufrufen ReDim lngBuffer(lngRequiredSize \ 4) As Long lngResult = EnumPrinters(lngFlags, vbNullString, iLevel, _ lngBuffer(0), lngRequiredSize, _ lngRequiredSize, nPrinters)
Siehe hierzu auch Kapitel 9.1.1.
14.18 ExpandEnvironmentStrings Die Registry kann im Datentyp REG_EXPAND_SZ Strings beinhalten, die eine Art rechnerabhängige Platzhalter darstellen. %SystemRoot% ist zum Beispiel so ein Wert. Ein solcher String kann über die Funktion ExpandEnvironmentStrings expandiert werden.
14.18.1
Deklaration und Signatur
Declare Function ExpandEnvironmentStrings Lib "kernel32" Alias "ExpandEnvironmentStringsA" (ByVal lpSrc As String, ByVal lpDst As String, ByVal nSize As Long) As Long
Argumente: lpSrc
Zu expandierende Zeichenkette mit einem abschließenden NULL
lpDst
NULL-terminierte Zeichenkette für Rückgabe
nSize
Größe von lpDst
Rückgabe: Bruttogröße des expandierten Strings in Bytes, unter NT4 bezogen auf Unicode
Im folgenden Codeauszug wird die erforderliche Größe für die Rückgabe ermittelt und danach die Funktion erneut aufgerufen. nSize = ExpandEnvironmentStrings(strPath, strExpanded, 0) strExpanded = Space(nSize) nSize = ExpandEnvironmentStrings(strPath, strExpanded, nSize) ExpandString = Left(strExpanded, StrLen(strExpanded))
ExtractIcon
273
Die API-Funktion StrLen wird hierzu verwendet, um die tatsächliche Größe des expandierten Strings zu ermitteln.
14.19 ExtractIcon ExtractIcon ermöglicht es, ein Handle auf ein in einer Datei enthaltenes Icon zu ermitteln.
14.19.1
Deklaration und Signatur
Declare Function ExtractIcon Lib "shell32.dll" Alias "ExtractIconA" (ByVal hInst As Long, ByVal lpszExeFileName As String, ByVal nIconIndex As Long) As Long
Argumente: hInst
Handle auf die Instanz der aufrufenden Anwendung
lpszExeFileName
Name der Datei, aus der das Icon extrahiert werden soll
nIconIndex
Dieses Argument wird bei Werten ab Null als Index auf das Icon interpretiert. -1 wiederum veranlasst die Funktion, die Anzahl der Icons zurückzugeben, die in der Datei enthalten sind.
Rückgabe: Handle auf das Icon. Das Handle muss mit DestroyIcon wieder aufgelöst werden.
Ein Beispiel: hModule = GetModuleHandle(vbNullString) hIcon = ExtractIcon(hModule, strFile, iIcon)
Das Module-Handle wird über GetModuleHandle ermittelt.
14.20 FindWindow FindWindow versucht, ein Top-Level-Fenster zu finden, das dem übergebenen Klassennamen und dem Fenstertitel entspricht.
14.20.1
Deklaration und Signatur
Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
274
14
Funktionsreferenz
Argumente: lpClassName
Name der Fensterklasse oder vbNullString. Bei der Fensterklasse muss es sich um eine registrierte Fensterklasse oder ein Steuerelement handeln.
lpWindowName
Name des Fenstertitels oder vbNullString. Hier liegt aber ein kleiner Hund begraben, wie wir noch sehen werden.
Rückgabe: 0 im Fehlerfalle oder ein gültiges Fenster-Handle
Die folgende Zeile produziert ein Handle auf die erste, leere Notepad-Instanz: hWnd = FindWindow(vbNullString, "Unbenannt – Editor")
14.21 FormatMessage Die Funktion FormatMessage ermittelt die Klartextfehlermeldungen aus der System Message Table, die hinter den Return-Codes einiger API-Funktionen stecken.
14.21.1
Deklaration und Signatur
Declare Function FormatMessage Lib "kernel32" Alias "FormatMessageA" (ByVal dwFlags As Long, ByVal lpSource As Long, ByVal dwMessageId As Long, ByVal dwLanguageId As Long, ByVal lpBuffer As String, nSize As Long, Arguments As Long) As Long
Argumente: dwFlags
FORMAT_MESSAGE_FROM_SYSTEM, um auf die System Message Table zu kommen
lpSource
Ein Module-Handle zu einer über LoadLibrary geladenen Bibliothek oder 0, wenn System Message Table verwendet wird
dwMessageId
Fehlernummer
dwLanguageId
Eine gültige Language-ID oder 0
lpBuffer
Mit 256 Zeichen vorbelegter String für Fehlertext
nSize
Größe von lpBuffer
Arguments
0
Rückgabe: Bruttolänge von lpBuffer
GetAsyncKeyState
275
Ein Beispiel: lngResult = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0&, _ lngMessageID, 0&, strError, nSize, 0&)
14.22 GetAsyncKeyState GetAsyncKeyState teilt uns mit, ob eine bestimmte Taste der Tastatur oder der Maus aktuell gedrückt ist.
14.22.1
Deklaration und Signatur
Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Long) As Integer
Argumente: vKey
Der Virtual-Key Code der gewünschten Taste
Rückgabe: Das MSB (most significant bit) zeigt, ob die Taste gedrückt ist. Im LSB (least significant bit) steckt zudem die Information, ob die Taste seit dem letzten Aufruf von GetAsyncKeyState gedrückt wurde.
Diese Eigenschaft prüft, ob eine Steuerungstaste gedrückt ist: Public Property Get ControlKeyPressed() As Boolean ControlKeyPressed = (GetAsyncKeyState(VK_CONTROL) < 0) End Property
14.22.2
Hinweise
Im Zusammenhang mit Maustasten kann es entscheidend sein, ob die Maustasten auf linkshändig umkonfiguriert wurden, was wiederum über die GetSystemMetrics-Funktion in Erfahrung gebracht werden kann.
14.23 GetComputerName GetComputerName gibt den NetBIOS-Namen des Rechners zurück.
14.23.1
Deklaration und Signatur
Declare Function GetComputerName Lib "kernel32" Alias "GetComputerNameA" (ByVal lpBuffer As String, nSize As Long) As Long
276
14
Funktionsreferenz
Argumente: lpBuffer
Windows-Anmeldename
nSize
[in] die Größe der Vorbelegung und [out] Nettogröße des NetBIOS-Namens
Rückgabe: 0 im Fehlerfalle
Die folgende Zeile ermittelt den NetBIOS-Namen: lngResult = GetComputerName(strBuffer, nSize) NetBIOSName = Left(strBuffer, nSize)
Bitte beachten Sie, dass nSize hier die Nettogröße zurückgibt.
14.24 GetDesktopWindow Diese Funktion gibt ein Handle auf das Desktop-Fenster zurück. Dieses Fenster wird von einigen Funktion als Platzhalter für das System angesehen.
14.24.1
Deklaration und Signatur
Declare Function GetDesktopWindow Lib "user32" () As Long
Rückgabe: Handle des Desktop-Fensters
Die folgende Anweisung erzeugt ein Handle des Desktop-Fensters: ... = GetDesktopWindow
14.25 GetDefaultPrinter Die nur unter Windows 2000 verfügbare Funktion GetDefaultPrinter gibt den DefaultDrucker des aktuellen Users zurück.
14.25.1
Deklaration und Signatur
Declare Function GetDefaultPrinter Lib "winspool.drv" Alias "GetDefaultPrinterA" (ByVal pszBuffer As String, pcchBuffer As Long) As Long
GetDiskFreeSpace
277
Argumente: pszBuffer
Druckername
pcchBuffer
Bruttogröße von pszBuffer
Rückgabe: 0 im Fehlerfalle
Die folgenden Zeilen ermitteln den Default-Drucker unter Windows 2000. Hierbei wird GetDefaultPrinter zuerst mit nSize = 0 aufgerufen, das sie ihrerseits mit der erforderlichen Größe belegt. Die Zeichenkette wird anschließend vorbelegt und GetDefaultPrinter erneut aufgerufen: lngResult = GetDefaultPrinter(strValue, nSize) strValue = Space(nSize) lngResult = GetDefaultPrinter(strValue, nSize) DefaultPrinter = Left(strValue, nSize – 1)
14.26 GetDiskFreeSpace GetDiskFreeSpace gibt entsprechende Werte zurück, um die Kapazität und den freien Speicherplatz eines Laufwerks zu errechnen.
14.26.1
Deklaration und Signatur
Declare Function GetDiskFreeSpace Lib "kernel32" Alias "GetDiskFreeSpaceA" (ByVal lpRootPathName As String, lpSectorsPerCluster As Long, lpBytesPerSector As Long, lpNumberOfFreeClusters As Long, lpTotalNumberOfClusters As Long) As Long
Argumente: lpRootPathName
Root-Bezeichnung des Laufwerks
lpSectorsPerCluster
Anzahl der Sektoren je Cluster
lpBytesPerSector
Anzahl der Bytes je Sektor
lpNumberOfFreeClusters
Anzahl der freien Clusters
lpTotalNumberOfClusters Gesamtzahl der Clusters Rückgabe: 0 im Fehlerfalle
278
14
Funktionsreferenz
Die folgenden beiden Zeilen sind dem Kapitel 6 entnommen: lngResult = GetDiskFreeSpace(strDrives(Index – 1), _ nSectorsPerCluster, nBytesPerSector, nFreeClusters, _ nTotalClusters) DriveTotalSpace = CDec(nTotalClusters * nSectorsPerCluster * _ nBytesPerSector)
14.26.2
Bemerkungen
GetDiskFreeSpace wurde ab Win 95 SR 2 durch GetDiskFreeSpaceEx abgelöst.
14.27 GetDiskFreeSpaceEx GetDiskFreeSpaceEx wird ab Win 95 SR 2 zur Ermittlung der Laufwerkskapazität und des freien Speicherbereichs eingesetzt.
14.27.1
Deklaration und Signatur
Declare Function GetDiskFreeSpaceEx Lib "kernel32" Alias "GetDiskFreeSpaceExA" (ByVal lpDirectoryName As String, lpFreeBytesAvailable As Any, lpTotalNumberOfBytes As Any, lpTotalNumberOfFreeBytes As Any) As Long
Argumente: lpDirectoryName
(Irgend)ein Verzeichnis auf dem zu untersuchenden Laufwerk
lpFreeBytesAvailable
Vorzeichenloser 64-Bit-Wert mit der Zahl der dem Anwender zur Verfügung stehenden freien Bytes
lpTotalNumberOfBytes
Vorzeichenloser 64-Bit-Wert mit der Kapazität in Bytes
lpTotalNumberOfFreeBytes
Vorzeichenloser 64-Bit-Wert mit der Zahl der freien Bytes
Rückgabe: 0 im Fehlerfalle
Im folgenden Beispiel wird die Kapazität eines Laufwerks ermittelt: lngResult = GetDiskFreeSpaceEx(strDrives(Index – 1), byteAvail(0), _ byteTotal(0), byteFree(0)) For iByte = 0 To 7 DriveTotalSpace = CDec(DriveTotalSpace + _ byteTotal(iByte) * 2 ^ (8 * iByte)) Next
Diese Zeilen sind der DriveTotalSpace-Eigenschaft aus Kapitel 6 entnommen.
GetDriveType
14.27.2
279
Bemerkungen
In der MSDN werden die drei numerischen Argumente als ULARGE_INTEGER deklariert. Dem liegt ein Datentyp zugrunde, in dem 2 VB-Longs in Reihe geschaltet werden: Private Type ULARGE_INTEGER 'Unsigned Large Integer LowPart As Long HighPart As Long End Type
Die Verwendung eines Byte-Array ist aber einfacher und deshalb vorzuziehen.
14.28 GetDriveType GetDriveType gibt die Zahl zurück, die den Typ des übergebenen Laufwerks repräsentiert.
14.28.1
Deklaration und Signatur
Declare Function GetDriveType Lib "kernel32" Alias "GetDriveTypeA" (ByVal nDrive As String) As Long
Argumente: nDrive
Laufwerksbezeichner
Rückgabe: einer der Werte der folgenden Tabelle
Konstante
Wert Laufwerksart
DRIVE_UNKNOWN
0
Laufwerkstyp kann nicht bestimmt werden
DRIVE_NO_ROOT_DIR
1
Kein gültiger Laufwerksbezeichner
DRIVE_REMOVABLE
2
Wechseldatenträger (Diskette, Zip, Wechselplatte)
DRIVE_FIXED
3
Fest installierte lokale Laufwerke (Festplatten)
DRIVE_REMOTE
4
Netzlaufwerke
DRIVE_CDROM
5
CD-Laufwerke
DRIVE_RAMDISK
6
RAM-Disks
Im folgenden Beispiel wird ein Laufwerks-Array durchlaufen und hierbei die CDLaufwerke gezählt:
280
14
Funktionsreferenz
For iDrive = 0 To UBound(strDrives) If GetDriveType(strDrives(iDrive)) = DRIVE_CDROM Then nDrives = nDrives + 1 End If Next
Diese Übersicht zeigt exemplarisch ein paar korrekte und fehlerhafte Laufwerksbezeichner und ihre Bewertung durch GetDriveType: Argument
Rückgabe
Bemerkung
"C"
1
Laufwerk nicht erkannt
"C:"
3
Laufwerk erkannt
"C:\"
3
Laufwerk erkannt
"C:\WINNT"
1
Backslash fehlt
"C:\WINNT\"
3
Laufwerk erkannt
"C:\Trullala\"
1
Verzeichnis existiert nicht
14.29 GetHostByName Diese Funktion gibt einen Zeiger auf eine HOSTENT-Struktur mit Informationen über einen Netzwerkrechner zurück.
14.29.1
Deklaration und Signatur
Declare Function GetHostByName Lib "WSOCK32.DLL" Alias "gethostbyname" (ByVal strHostname As String) As Long
Argumente: strHostName
DNS-Name des Rechners
Rückgabe: Zeiger auf die(!) HOSTENT-Struktur des Prozesses
Hier nun ein Beispielaufruf: lngHostAddress = GetHostByName(strDNSName)
14.29.2
Bemerkungen
Die MSDN warnt ausdrücklich davor, in der HOSTENT-Struktur zu arbeiten, deren Zeiger diese Funktion zurückgibt. Es muss also eine Kopie davon erstellt werden. Ein vollständiges Beispiel finden Sie in 5.4.3 bis 5.4.6.
GetHostName
281
14.30 GetHostName GetHostName gibt den DNS-Namen des aktuellen Rechners zurück.
14.30.1
Deklaration und Signatur
Declare Function GetHostName Lib "WSOCK32.DLL" Alias "gethostname" (ByVal HostName As String, ByVal HostLen As Integer) As Long
Argumente: HostName
DNS-Name
HostLen
[in] die Größe der Vorbelegung
Rückgabe: 0 im Erfolgsfall
Die folgenden Zeilen ermitteln den DNS-Namen: lngResult = GetHostName(strBuffer, 255) If lngResult = SOCKET_ERROR Then Err.Raise 9999, , "Windows Sockets Error: " & _ CStr(WSAGetLastError()) Else DNSName = Left(strBuffer, InStr(1, strBuffer, Chr(0)) – 1) End If
14.30.2
Bemerkungen
Die Windows Sockets Bibliothek muss initialisiert sein, bevor GetHostName verwendet werden kann.
14.31 GetLocaleInfo Diese Funktion ermittelt rund 100 verschiedene Einstellungen, die in irgendeiner Weise länderabhängig sind.
14.31.1
Deklaration und Signatur
Declare Function GetLocaleInfo Lib "kernel32" Alias "GetLocaleInfoA" (ByVal Locale As Long, ByVal LCType As Long, ByVal lpLCData As String, ByVal cchData As Long) As Long
282
14
Funktionsreferenz
Argumente: Locale
Legt fest, ob auf SystemDefault- oder UserDefault-Werte zugegriffen werden soll
LCType
Steuert die Art der angeforderten Einstellung
lpLCData
Von der Funktion gefüllte Zeichenkette
cchData
Bruttolänge von lpLCData (also inklusive abschließendem Chr(0))
Rückgabe: 0 im Fehlerfalle, wofür aber nur eine ungültige Locale oder LCType verantwortlich sein kann.
Im folgenden Beispiel wird der lokale Name der aktuellen Sprache ermittelt. hResult = GetLocaleInfo(lngLocale, lngLCType, strValue, nSize)
14.31.2
Bemerkungen
Locale GetLocaleInfo ist in der Lage, auf unterschiedliche Gebietsschemata zuzugreifen. Hierbei wird generell in das installierte Systemschema und das aktuell eingestellte unterschieden. Unterschiede ergeben sich zwangsläufig dann, wenn das aktuell gewählte Gebietsschema von dem installierten abweicht. Uns interessieren in aller Regel die Einstellungen, die dem aktuellen Benutzer zugewiesen sind. Aus diesem Grund übergeben wir hier die UserDefaultLCID, die uns die Funktion GetUserDefaultLCID zurückgibt. Für den Zugriff auf die Werte des installierten Systemschemas können Sie die Funktion GetSystemDefaultLCID verwenden, um die LCID zu ermitteln: lngLocale = GetUserDefaultLCID
oder lngLocale = GetSystemDefaultLCID
LCType Dieser Long-Datentyp steuert die Art der gewünschten Information, wofür rund 100 verschiedene Konstanten existieren.
lpLCData In diese, in ausreichender Größe zu übergebende Zeichenkette, schreibt die Funktion den angeforderten Wert. Ist cchData = 0, so wird dieses Argument ignoriert.
GetLogicalDrives
283
cchData Dieser Long-Wert signalisiert der Funktion die zur Verfügung stehende Größe des Arguments lpLCData. Sinnvollerweise kann die GetLocaleInfo diese Größe für uns ermitteln, indem wir sie mit dem Wert 0 in diesem Argument aufrufen. GetLocaleInfo gibt dann die erforderliche Bruttogröße von lpLCData zurück, wie folgendes Beispiel zeigt: nSize = GetLocaleInfo(lngLocale, lngLCType, strValue, 0) strValue = Space(nSize) hResult = GetLocaleInfo(lngLocale, lngLCType, strValue, nSize)
14.32 GetLogicalDrives GetLogicalDrives gibt ein Bitmuster zurück, das den verfügbaren Laufwerken entspricht.
14.32.1
Deklaration und Signatur
Declare Function GetLogicalDrives Lib "kernel32" () As Long
Rückgabe: Bitmuster von 20 für Laufwerk A bis 225 für Laufwerk Z
Im folgenden Beispiel wird die Verfügbarkeit des Laufwerks B (21) geprüft: bReady = getlogicaldrives And 2
14.33 GetLogicalDriveStrings GetLogicalDriveStrings gibt eine NULL-separierte Liste der verfügbaren Laufwerke zurück.
14.33.1
Deklaration und Signatur
Declare Function GetLogicalDriveStrings Lib "kernel32" Alias "GetLogicalDriveStringsA" (ByVal nBufferLength As Long, ByVal lpBuffer As String) As Long
284
14
Funktionsreferenz
Argumente: nBufferLength
Größe von lpBuffer
lpBuffer
String zur Aufnahme der NULL-separierten Laufwerksbezeichner. lpBuffer wird am Ende durch ein weiteres NULL abgeschlossen.
Rückgabe: Größe des Datenbereichs in lpBuffer ohne abschließenden NULL
In diesem Beispiel wird das Array strDrives mit den verfügbaren Laufwerksbezeichnern gefüllt: strBuffer = Space(200) nSize = GetLogicalDriveStrings(200, strBuffer) strDrives = Split(Left(strBuffer, nSize – 1), vbNullChar)
Siehe hierzu auch Kapitel 6.1.
14.34 GetModuleHandle GetModuleHandle gibt ein Handle auf einen EXE- oder DLL-Server zurück. Der Referenzzähler des Servers wird hierbei nicht inkrementiert.
14.34.1
Deklaration und Signatur
Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long
Argumente: lpModuleName
Name des Servers
Rückgabe: Handle auf die Bibliothek oder 0 bei Misserfolg
Siehe auch GetProcAddress
14.35 GetOpenFileName (APIGetOpenFileName) GetOpenFileName steuert den Datei-Öffnen-Dialog. Um den gleichnamigen Methodennamen zu ermöglichen, wurde die Funktion in APIGetOpenFileName umgetauft.
GetPrinter
14.35.1
285
Deklaration und Signatur
Declare Function APIGetOpenFileName Lib "comdlg32.dll" Alias "GetOpenFileNameA" (pOpenfilename As OPENFILENAME) As Long
Argumente: pOpenfilename
Zeiger auf OPENFILENAME-Struktur
Rückgabe: 0, wenn der Dialog abgebrochen wurde, und 1 bei OK
Weitere Informationen finden Sie im Kapitel 4.1 Dateidialoge.
14.36 GetPrinter Die GetPrinter-Funktion veranlasst Windows, eine PRINTER_INFO zurückzugeben.
14.36.1
Deklaration und Signatur
Declare Function GetPrinter Lib "winspool.drv" Alias "GetPrinterA" (ByVal hPrinter As Long, ByVal Level As Long, pPrinter As Any, ByVal cbBuf As Long, pcbNeeded As Long) As Long
Argumente: hPrinter
Handle des Druckers
Level
Wert zwischen 1 und 9, der die gewünschte PRINTER_INFO-Struktur angibt
pPrinter
Zeiger auf die PRINTER_INFO-Struktur
cbBuf
Größe der Struktur pPrinter
pcbNeeded
Anzahl der von der Funktion in die PRINTER_INFO-Struktur transportierten Bytes. Ist pcbNeeded größer als cbBuf, so schlägt der Funktionsaufruf fehl.
Rückgabe: 0 im Fehlerfalle
Im folgenden Auszug wird ein Drucker geöffnet und eine PRINTER_INFO_2-Struktur angefordert: Dim aDevMode(3046) As Byte 'Daten aus GetPrinter-Funktion pd.DesiredAccess = PRINTER_ALL_ACCESS Or pd.DesiredAccess lngResult = OpenPrinter(strPrinter, hPrinter, pd) lngResult = GetPrinter(hPrinter, 2, aDevMode(0), UBound(aDevMode), _ nSize)
286
14
Funktionsreferenz
Die Verwertung einer solchen PRINTER_INFO-Struktur sieht auf den ersten Blick einfacher aus, als sie wirklich ist. Wenn jedoch in der Struktur weitere Strukturen enthalten sind, sind die Standardmittel schnell am Ende; da muss kopiert werden. Weitere Informationen hierzu finden Sie in Kapitel 9.4
14.37 GetProcAddress GetProcAddress gibt die Adresse einer exportierten Funktion innerhalb einer Bibliothek zurück.
14.37.1
Deklaration und Signatur
Declare Function GetProcAddress Lib "kernel32" (ByVal hModule As Long, ByVal lpProcName As String) As Long
Argumente: hModule
Handle der Bibliothek
lpProcName
Name der Prozedur
Rückgabe: Adresse der Prozedur oder 0, wenn diese nicht gefunden wurde.
Im folgenden Beispiel wird GetProcAddress zusammen mit GetModuleHandle eingesetzt, um zu prüfen, ob die Prozedur GetDiskFreeSpaceExA im Kernel vorhanden ist. lngResult = GetProcAddress(GetModuleHandle("kernel32.dll"), _ "GetDiskFreeSpaceExA") If lngResult = 0 Then 'Win 95 vor SR 2 ... Else 'der Rest ... End IF
Das Beispiel ist Kapitel 6 entnommen.
14.38 GetSaveFileName (APIGetSaveFileName) GetSaveFileName steuert den Datei-Speichern-Dialog. Um den gleichnamigen Methodennamen zu ermöglichen, wurde die Funktion in APIGetSaveFileName umgetauft.
GetScrollBarInfo
14.38.1
287
Deklaration und Signatur
Declare Function APIGetSaveFileName Lib "comdlg32.dll" Alias "GetSaveFileNameA" (pOpenfilename As OPENFILENAME) As Long
Argumente: pOpenfilename
Zeiger auf OPENFILENAME-Struktur
Rückgabe: 0, wenn der Dialog abgebrochen wurde, und 1 bei OK
Weitere Informationen finden Sie im Kapitel 4.1 Dateidialoge.
14.39 GetScrollBarInfo GetScrollBarInfo füllt eine SCROLLBARINFO-Struktur und gibt einen Verweis darauf zurück. Die SCROLLBARINFO-Struktur enthält im Wesentlichen geometrische Informationen zu der ScrollBar.
14.39.1
Deklaration und Signatur
Declare Function GetScrollBarInfo Lib "user32.dll" (ByVal hWnd As Long, ByVal idObject As Long, psbi As SCROLLBARINFO) As Long
Argumente: hWnd
Handle des Fenster
idObject
Einer der Werte aus der Tabelle der Object-ID-Konstanten
psbi
SCROLLBARINFO-Struktur
Rückgabe: 0 im Fehlerfalle
Konstante
Wert
Bedeutung
OBJID_CLIENT
&HFFFFFFFC
hWnd ist ein Handle eines ScrollBar-Steuerelements
OBJID_HSCROLL
&HFFFFFFFA
Horizontale ScrollBar des durch hWnd referenzierten Fensters
OBJID_VSCROLL
&HFFFFFFFB
Vertikale ScrollBar des durch hWnd referenzierten Fensters
Tabelle 14.2: Object-ID-Konstanten
288
14
Funktionsreferenz
Die folgende Zeile fordert eine SCROLLBARINFO-Struktur der vertikalen ScrollBar des Fensters hWnd an: lngResult = GetScrollBarInfo(hWnd, OBJID_VSCROLL, ScrBarInfo)
14.39.2
Bemerkungen
Die GetScrollBarInfo-Funktion ist in Windows 98, NT 4 ab Service Pack 6 und in Windows 2000 verfügbar. Dort, wo sie nicht verfügbar ist, muss man sich mit der GetScrollInfo-Funktion behelfen.
14.40 GetScrollInfo GetScrollInfo gibt eine SCROLLINFO-Struktur mit allen Statusinformationen der ScrollBar zurück.
14.40.1
Deklaration und Signatur
Declare Function GetScrollInfo Lib "user32.dll" (ByVal hWnd As Long, ByVal fnBar As Long, lpsi As SCROLLINFO) As Long
Argumente: hWnd
Handle des Fenster
fnBar
Einer der Werte aus der Tabelle ScrollBar-Konstanten, der die Art der ScrollBar bezeichnet
lpsi
Verweis auf eine SCROLLINFO-Struktur
Rückgabe: 0 im Fehlerfalle
Konstante
Wert
Bedeutung
SB_CTL
2
Signalisiert der Funktion, dass hWnd sich auf ein ScrollBar-Steuerelement selbst bezieht.
SB_HORZ
0
Fordert die Werte der horizontalen ScrollBar an.
SB_VERT
1
Fordert die Werte der vertikalen ScrollBar an.
Tabelle 14.3: ScrollBar-Konstanten
Die folgende Zeile fordert eine SCROLLINFO-Struktur der vertikalen ScrollBar des Fensters hWnd an: lngResult = GetScrollInfo(hWnd, SB_VERT, ScrInfo)
GetScrollPos
289
14.41 GetScrollPos GetScrollPos gibt die Position des Thumbs einer ScrollBar zurück. Dieser Wert liegt zwischen dem unteren und dem oberen Grenzwert der ScrollBar.
14.41.1
Deklaration und Signatur
Declare Function GetScrollPos Lib "user32.dll" (ByVal hWnd As Long, ByVal nBar As Long) As Long
Argumente: hWnd
Handle des Fenster
nBar
Einer der Werte aus der Tabelle ScrollBar-Konstanten, der die Art der ScrollBar bezeichnet
Rückgabe: Position des Thumbs oder 0 im Fehlerfalle
Die nachfolgende Zeile ermittelt die Position des Thumbs der vertikalen ScrollBar des Fensters hWnd: lngPos = GetScrollPos(hWnd, SB_VERT)
14.42 GetScrollRange Diese Funktion belegt zwei übergebene Long-Variablen mit der oberen und unteren Grenze einer Scrollbar.
14.42.1
Deklaration und Signatur
Declare Function GetScrollRange Lib "user32.dll" (ByVal hWnd As Long, ByVal nBar As Long, lpMinPos As Long, lpMaxPos As Long) As Long
Argumente: hWnd
Handle des Fenster
nBar
Einer der Werte aus der Tabelle ScrollBar-Konstanten, der die Art der ScrollBar bezeichnet
lpMinPos
Untere Grenze der ScrollBar
lpMaxPos
Obere Grenze der ScrollBar
Rückgabe: 0 im Fehlerfalle
290
14
Funktionsreferenz
Die folgende Zeile fordert die beiden Werte in lngMin und lngMax an: hResult = GetScrollRange(hWnd, SB_VERT, lngMin, lngMax)
14.43 GetStockObject GetStockObject gibt ein Handle auf eines der vordefinierten Stock Objects der Objekttypen Pen, Brush Palette oder Font zurück.
14.43.1
Deklaration und Signatur
Declare Function GetStockObject Lib "gdi32" (ByVal nIndex As Long) As Long
Argumente: nIndex
Konstante für eines der vordefinierten logischen Objekte, die in der folgenden Tabelle dargestellt sind.
Rückgabe: Im Erfolgsfall ein Handle auf das angeforderte Stock Object und 0 im Fehlerfall
In der folgenden Tabelle sind die Stock-Object-Konstanten dargestellt: Konstante
Wert
Bedeutung
ANSI_FIXED_FONT
11
nicht-proportionale Systemschriftart
ANSI_VAR_FONT
12
proportionale Systemschriftart
BLACK_BRUSH
4
schwarzer Brush-Stil
BLACK_PEN
7
schwarzer Pen-Stil
DC_BRUSH
18
Windows 98, Windows 2000: einfarbiger Brush-Stil. Die Defaultfarbe ist weiß, kann aber über die SetDCBrushColor-Function verändert werden.
DC_PEN
19
Windows 98, Windows 2000: einfarbiger Pen-Stil. Die Defaultfarbe ist weiß, kann aber über die SetDCPenColorFunction verändert werden.
DEFAULT_GUI_FONT
17
MS Sans Serif
DEFAULT_PALETTE
15
Default-Palette, die aus den Farben der Systempalette besteht.
DEVICE_DEFAULT_FONT
14
Windows NT/2000: geräteabhängige Defaultschrift
DKGRAY_BRUSH
3
dunkelgrauer Brush-Stil
GRAY_BRUSH
2
grauer Brush-Stil
Tabelle 14.4: Stock-Object-Konstanten
GetSystemDefaultLCID
291
Konstante
Wert
Bedeutung
HOLLOW_BRUSH
5
unsichtbarer Brush-Stil
LTGRAY_BRUSH
1
hellgrauer Brush-Stil
NULL_BRUSH
5
unsichtbarer Brush-Stil
NULL_PEN
8
unsichtbarer Pen-Stil
OEM_FIXED_FONT
10
nicht-proportionale OEM-Schriftart
SYSTEM_FIXED_FONT
16
nicht-proportionale Systemschriftart, die nur aus Kompatibilitätsgründen zu älteren Windows-Versionen existiert
SYSTEM_FONT
13
MS Sans Serif bei Windows 95/98 und NT und Tahoma unter Windows 2000
WHITE_BRUSH
0
weißer Brush-Stil
WHITE_PEN
6
weißer Pen-Stil
Tabelle 14.4: Stock-Object-Konstanten (Fortsetzung)
Im folgenden Codebeispiel wird die Rückgabe der GetStockObject-Funktion an die Funktion SelectObject weitergereicht: lngResult = SelectObject(hdc, GetStockObject(Index))
Index ist hierbei einer der Werte aus obiger Tabelle.
14.44 GetSystemDefaultLCID Diese Funktion gibt die Locale-ID des installierten Systemgebietsschemas zurück. Diese Locale-ID kann beispielsweise für die GetLocaleInfo verwendet werden.
14.44.1
Deklaration und Signatur
Declare Function GetSystemDefaultLCID Lib "kernel32" () As Long
Rückgabe: Locale-ID des installierten Systemgebietsschemas
14.45 GetSystemMetrics GetSystemMetrics gibt Auskunft über diverse periphere Geräte und Einstellungen.
14.45.1
Deklaration und Signatur
Declare Function GetSystemMetrics Lib "user32" (ByVal nIndex As Long) As Long
292
14
Funktionsreferenz
Argumente: nIndex
Dieses Argument steuert, welchen der rund 80 Werte die Funktion zurückgeben soll.
Rückgabe: 0 im Fehlerfalle und andernfalls der angeforderte Wert. Bei logischen Werten bedeutet 0 False und alles andere True.
Die folgende Eigenschaft prüft, ob die Maustasten auf linkshändig konfiguriert wurden: Public Property Get MouseButtonsSwapped() As Boolean MouseButtonsSwapped = (GetSystemMetrics(SM_SWAPBUTTON) <> 0) End Property
14.46 GetTextFace GetTextFace gibt den Namen der Schriftart zurück, die im übergebenen Gerätekontext gewählt ist.
14.46.1
Deklaration und Signatur
Declare Function GetTextFace Lib "gdi32" Alias "GetTextFaceA" (ByVal hdc As Long, ByVal nCount As Long, ByVal lpFaceName As String) As Long
Argumente: hdc
Handle auf den Gerätekontext
nCount
Größe des Arguments lpFaceName
lpFaceName
Mit nCount Zeichen vorbelegter String zur Aufnahme des Schriftnamens
Rückgabe: Nettobreite des Schriftnamens in lpFaceName oder 0 im Fehlerfalle
Hier wird GetTextFace aufgerufen und anschließend der Nettoanteil des übergebenen Strings extrahiert: nSize = GetTextFace(hDC, 100, lpFaceName) ... = Left(lpFaceName, nSize)
GetTextMetrics
293
14.47 GetTextMetrics Diese Funktion veranlasst Windows, eine TEXTMETRIC-Struktur mit den Parametern des aktuell im Gerätekontext gewählten Fonts zurückzugeben.
14.47.1
Deklaration und Signatur
Declare Function GetTextMetrics Lib "gdi32" Alias "GetTextMetricsA" (ByVal hdc As Long, lpMetrics As TEXTMETRIC) As Long
Argumente: hdc
Handle auf den Gerätekontext
lpMetrics
Zeiger auf TEXTMETRIC-Struktur
Rückgabe: 0 im Fehlerfalle
Im folgenden Beispielcode wird eine TEXTMETRIC-Struktur gefüllt, nachdem der Map Mode vorher auf Twips eingestellt wurde: Dim udtTM As TEXTMETRIC 'TextMetric-Type lngPrevMapMode = SetMapMode(hDC, MM_TWIPS) GetTextMetrics hDC, udtTM lngPrevMapMode = SetMapMode(hDC, lngPrevMapMode)
Danach wird der ursprüngliche Map Mode wieder hergestellt.
14.48 GetTickCount Diese Funktion gibt die Zeit in Millisekunden zurück, die seit dem Systemstart vergangen ist. Alle 49,7 Tage [(231 -1) / (24 x 60 x 60 x 1000)] beginnt die Zählung von vorne, da der Wertebereich des Longs (auf Systemseite ein DWORD-Datentyp) zu Ende ist.
14.48.1
Deklaration und Signatur
Private Declare Function GetTickCount Lib "kernel32" () As Long
Rückgabe: Anzahl der Millisekunden seit Systemstart
294
14
Funktionsreferenz
14.49 GetUserDefaultLCID Diese Funktion gibt die Locale-ID des aktuellen Gebietsschemas des Anwenders zurück. Diese Locale-ID kann beispielsweise für die GetLocaleInfo verwendet werden.
14.49.1
Deklaration und Signatur
Declare Function GetUserDefaultLCID Lib "kernel32" () As Long
Rückgabe: Locale-ID des aktuellen Gebietsschemas des Anwenders
14.50 GetUserName GetUserName gibt den Windows-Anmeldenamen zurück.
14.50.1
Deklaration und Signatur
Declare Function GetUserName Lib "advapi32.dll" Alias "GetUserNameA" (ByVal lpBuffer As String, nSize As Long) As Long
Argumente: lpBuffer
Windows-Anmeldename
nSize
[in] die Größe der Vorbelegung und [out] Bruttogröße des Anmeldenamens
Rückgabe: 0 im Fehlerfalle
Die folgende Zeile ermittelt den Anmeldenamen: lngResult = GetUserName(strBuffer, nSize) LoginName = Left(strBuffer, nSize – 1)
14.51 GetVersionEx GetVersionEx füllt eine OSVERSIONINFO-Struktur mit Informationen zum verwendeten Betriebssystem.
GetWindowDC
14.51.1
295
Deklaration und Signatur
Declare Function GetVersionEx Lib "kernel32" Alias "GetVersionExA" (lpVersionInformation As OSVERSIONINFO) As Long
Argumente: lpVersionInformation
OSVERSIONINFO-Struktur
Rückgabe: 0 im Fehlerfalle
Im Beispiel wird die OSVERSIONINFO-Struktur parametriert, gefüllt und ein Systemfehler erzeugt, wenn GetVersionEx versagt: udtVersionInfo.dwOSVersionInfoSize = Len(udtVersionInfo) lngResult = GetVersionEx(udtVersionInfo) If lngResult = 0 Then Err.Raise 440, "clsSysInfo:LoadVersionStructure", _ "GetVersionEx failed to load OSVERSIONINFO-type." End If
14.52 GetWindowDC GetWindowDC gibt ein Handle auf den Gerätekontext des übergebenen Fensters zurück.
14.52.1
Deklaration und Signatur
Declare Function GetWindowDC Lib "user32" (ByVal hWnd As Long) As Long
Argumente: hWnd
Handle des Fensters, dessen Gerätekontext benötigt wird
Rückgabe: Handle auf den Gerätekontext des Fensters
Die folgende Zeile gibt ein Handle auf den Gerätekontext des übergebenen Fensters zurück: ... = GetWindowDC(hWnd)
296
14
Funktionsreferenz
14.53 GetWindowLong GetWindowLong gibt den aktuellen Wert eines Window-Attributes zurück.
14.53.1
Deklaration und Signatur
Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
Argumente: hwnd
Fenster-Handle
nIndex
Eine GWL-Konstante, die das betreffende Attribut bezeichnet
Rückgabe: aktueller Wert des Attributs
Konstante
Wert
Bedeutung
GWL_WNDPROC
-4
Ändert die Adresse der Window Procedure
GWL_STYLE
-16
Ändert Attribute des Window Styles
Tabelle 14.5: Auswahl an GWL-Konstanten
Hier wird der Window Style der aktuellen Form ermittelt. lngWindowStyle = SetWindowLong(Me.hwnd, GWL_STYLE)
14.54 lstrcat (GetANSIStringPointer) Bei der Übergabe eines Strings als Wert (ByVal) erzeugt VB automatisch eine ANSIVariante des Strings, der ja intern als BSTR des OLE-Subsystems dargestellt ist. Wenn jedoch ein Zeiger auf eine String-Variable von einem User Defined Type angefordert wird, steht uns dieser von ByVal bereitgestellte Mechanismus nicht zur Verfügung. Die VB-Funktion StrPtr scheidet ebenfalls aus, da diese Funktion einen Zeiger auf den Unicode-BSTR zurückgibt, aber nicht auf den angeforderten ANSI-String. Hier hilft die lstrcat-Funktion des Kernels, die dazu gedacht ist, zwei ANSI-Strings zusammenzusetzen und einen Zeiger auf das Ergebnis der Operation zurückzugeben. Da in der Regel nur ein String zur Verfügung steht, übergeben wir »« als zweiten String. Da der einzige Verwendungszweck die Rückgabe eines Zeigers auf einen String darstellt, wurde ihr mit GetANSIStringPointer ein entsprechender Name zugeordnet.
lstrcpy
14.54.1
297
Deklaration und Signatur
Declare Function GetANSIStringPointer Lib "kernel32" Alias "lstrcatA" (ByVal lpString1 As String, ByVal lpString2 As String) As Long
Argumente: lpString1
Zeiger auf den ersten ANSI-String
lpString2
Zeiger auf den zweiten ANSI-String
Rückgabe: Zeiger auf den zusammengesetzten ANSI-String
Im folgenden Beispiel wird die Funktion verwendet, um einen Zeiger auf den Titel des Verzeichnisdialogs an den User Defined Type BROWSEINFO zu übergeben: strTitle = "Bitte wählen Sie ein Verzeichnis aus" tBrowseInfo.lpszTitle = GetANSIStringPointer(strTitle, "")
14.55 lstrcpy Diese Funktion kopiert entweder einen Long-Wert oder einen ganzen String an eine andere Stelle im Speicher, und das hängt davon ab, wie die Funktion deklariert wird.
14.55.1
Deklaration und Signatur
Declare Function PtrToStr Lib "kernel32" Alias "lstrcpyA" (ByVal RetVal As String, ByVal Ptr As Long) As Long
oder ganz allgemein Declare Function PtrToPtr Lib "kernel32" Alias "lstrcpyA" (RetVal As Any, Ptr As Any) As Long
Argumente: RetVal
Ziel
Ptr
Quelle
Rückgabe: 0 im Fehlerfalle, sonst Adresse von RetVal
298
14.55.2
14
Funktionsreferenz
PtrToStr
Das ist die konventionelle Verwendung von lstrcpy: An der Speicheradresse Ptr steht ein String, der nach RetVal kopiert werden soll. Zur Ermittlung der erforderlichen Größe der Variablen strTemp(iPrinter) kommt die strlen-Funktion zum Einsatz. strDestination = Space(strlen(lngAddress)) lngResult = PtrToStr(strDestination, lngAddress)
14.55.3
PtrToPtr
Die Deklaration beider Argumente erfolgt hier ByRef und As Any. Durch den gezielten Einsatz von ByVal können wir nun beeinflussen, ob wir den Inhalt von Ptr, also den Zeiger, oder den Inhalt der Adresse kopieren wollen, auf den das Argument Ptr verweist. Die folgende Zeile kopiert den Inhalt (!) der durch (lngAddr(7) + lngOffset) bezeichneten Speicheradresse in die Variable intValue: PtrToPtr intValue, ByVal (lngAddr(7) + lngOffset)
Die nächste Zeile hingegen ist für das Gegenteil verantwortlich, den Wert intValue an die Adresse zu kopieren, die durch (lngAddr(7) + lngOffset) festgelegt wird. PtrToPtr ByVal (lngAddr(7) + lngOffset), intValue
In den Prozeduren SetDevModeValue und SetDevModeValue der Druckerkomponente kommen diese Techniken zum Einsatz (siehe 9.4.2).
14.56 OpenPrinter Die OpenPrinter-Funktion gibt ein Handle auf einen Drucker zurück.
14.56.1
Deklaration und Signatur
Declare Function OpenPrinter Lib "winspool.drv" Alias "OpenPrinterA" (ByVal pPrinterName As String, phPrinter As Long, pDefault As Any) As Long
Argumente: pPrinterName Name des zu öffnenden Druckers phPrinter
Von der Funktion ermitteltes Handle
pDefault
Eine PRINTER_DEFAULTS-Struktur oder 0
Rückgabe: 0 im Fehlerfalle
PostMessage
299
Der folgende Codeauszug öffnet einen Drucker mit allen Rechten: pd.DesiredAccess = PRINTER_ALL_ACCESS Or pd.DesiredAccess lngResult = OpenPrinter(strPrinter, hPrinter, pd)
14.57 PostMessage Die PostMessage-Funktion hinterlegt ein Kommando an ein Fenster in der Nachrichtenwarteschlange (Message Queue) des Threads, der das betreffende Fenster angelegt hat. PostMessage wartet hierbei nicht auf eine Rückgabe des Zielfensters, sondern produziert eine eigene Rückmeldung.
14.57.1
Deklaration und Signatur
Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long,ByVal wParam As Long, lParam As Long) As Long
Argumente: hWnd
Handle des Ziel-Fensters
wMsg
Die abzuliefernde Nachricht
wParam
Zusätzlicher, nachrichtenspezifischer Parameter
lParam
Zusätzlicher, nachrichtenspezifischer Parameter
Rückgabe: <> 0 bei Erfolg und 0 bei Fehler
Die folgende Zeile veranlasst das mit hWnd referenzierte Fenster, sich zu beenden: lngResult = PostMessage(hWnd, WM_CLOSE, 0&, 0&)
Siehe auch die Funktionen SendMessage und WaitForSingleObject.
14.57.2
Bemerkungen
hWnd Hier können wir ein Handle übergeben, das einem speziellen Fenster zugeordnet ist. Über die Konstante HWND_BROADCAST (&HFFFF&) können aber auch alle Top-Level-Fenster gleichzeitig angesprochen werden.
300
14
Funktionsreferenz
wMsg Bei diesem Argument handelt es sich um ein Konstante, die von einer speziellen Fensterklasse (z.B. ComboBox), einer Gruppe von Fensterklassen (z.B. Controls) oder allen Fenstern verarbeitet werden kann. Meist kann man am Präfix erkennen, um welche dieser Arten es sich handelt. Der Präfix WM_ beispielsweise richtet sich an alle Fensterklassen, ICC_ nur an Steuerelemente der Kategorie CommonControls oder EM_ an editierbare oder BM_ an klickbare Steuerelemente. Mit MCM_ beginnen Nachrichten, die das Month Calendar Control betreffen.
wParam und lParam Es gibt Nachrichten, die sind eindeutig und bedürfen keiner weiteren Parameter. Hierzu gehört etwa WM_QUIT, was das Zielfenster veranlasst, sich ordnungsgemäß zu schließen.
Rückgabe Im Gegensatz zur SendMessage-Funktion erhalten wir ein Rückgabe, sobald die Nachricht abgelegt wurde. Eventuelle kommandoabhängige Rückmeldungen des Zielfensters bleiben uns somit verborgen. Eine Rückgabe = 0 signalisiert einen Fehler, ungleich 0 bedeutet hingegen, dass das Kommando mit Erfolg in der Message Queue abgelegt wurde.
14.58 RedrawWindow RedrawWindow erzwingt ein Update eines Bereichs oder einer Region innerhalb eines Fensters.
14.58.1
Deklaration und Signatur
Declare Function RedrawWindow Lib "user32" (ByVal hWnd As Long, lprcUpdate As RECT, ByVal hrgnUpdate As Long, ByVal fuRedraw As Long) As Long
Argumente: hWnd
Handle des Fensters
lprcUpdate
RECT-Struktur mit Koordinaten des Bereichs oder 0, falls hrgnUpdate ein gültiges Handle auf eine Region beinhaltet.
hrgnUpdate
Handle auf eine Region innerhalb der Client-Area des Fensters
fuRedraw
Dieses Argument steuert das Verhalten des Fensters. Die Tabelle »Verwendete RedrawKonstanten« enthält die beiden verwendeten Konstanten.
RegCloseKey
301
Argumente: Rückgabe: 0 im Fehlerfalle
Konstante
Wert
Bedeutung
RDW_UPDATENOW
&H100 Sendet WM_NCPAINT, WM_ERASEBKGND und WM_PAINT zu allen spezifizierten Fenstern (RDW_ALLCHILDREN oder RDW_NOCHILDREN).
RDW_ALLCHILDREN
&H80
Sorgt dafür, dass die Operation zu allen untergeordneten Fenstern gesendet wird.
Tabelle 14.6: Verwendete Redraw-Konstanten
Das folgende Beispiel erzwingt eine Aktualisierung aller durch die RECT-Struktur berührten Fenster: lngResult = RedrawWindow(hWnd, rectWnd, 0&, _ RDW_UPDATENOW Or RDW_ALLCHILDREN)
14.59 RegCloseKey RegCloseKey schließt einen mit RegOpenKeyEx geöffneten Registry-Schlüssel.
14.59.1
Deklaration und Signatur
Declare Function RegCloseKey Lib "advapi32.dll" (ByVal hKey As Long) As Long
Argumente: hKey
Handle des zu schließenden Keys
Rückgabe: 0 bei Erfolg
Im folgenden Beispiel wird ein Registry-Key geschlossen: RegCloseKey hKey
14.60 RegCreateKeyEx Diese Funktion ermöglicht das Anlegen eines Registry-Schlüssels.
302
14.60.1
14
Funktionsreferenz
Deklaration und Signatur
Declare Function RegCreateKeyEx Lib "advapi32.dll" Alias "RegCreateKeyExA" (ByVal hKey As Long, ByVal lpSubKey As String, ByVal Reserved As Long, ByVal lpClass As String, ByVal dwOptions As Long, ByVal samDesired As Long, ByVal lpSecurityAttributes As Long, phkResult As Long, ByVal lpdwDisposition As Long) As Long
Argumente: hKey
Dem Root-Key zugeordnete Konstante, meist HKEY_CURRENT_USER
lpSubKey
Vollständiger anzulegende Schlüsselpfad, zum Beispiel »Software\Kunde\Programm\Settings
Reserved
Reserviert, wir übergeben 0
lpClass
Nullstring
dwOptions
Interessante Option zum Anlegen temporärer Schlüssel, die allerdings in Win 9x nicht zur Verfügung stehen, also 0
samDesired
Erforderliche Rechte
lpSecurityAttributes Handle zu Security Descriptor oder 0 phkResult
Handle auf den frisch angelegten Schlüssel
lpdwDisposition
0, womit dieses Argument ignoriert wird
Rückgabe: 0 bei Erfolg
Das folgende Codefragment versucht einen Schlüssel zu öffnen und legt ihn gleich an, wenn er nicht existiert: lngResult = RegOpenKeyEx(lngHKEY, strKey, 0, KEY_WRITE, hKey) If lngResult = ERROR_FILE_NOT_FOUND Then lngResult = RegCreateKeyEx(lngHKEY, strKey, 0&, vbNullString, 0&, _ KEY_WRITE, 0&, hKey, 0&) End If
14.61 RegDeleteKey RegDeleteKey löscht einen Registry-Schlüssel inklusive aller Werte.
14.61.1
Deklaration und Signatur
Declare Function RegDeleteKey Lib "advapi32.dll" Alias "RegDeleteKeyA" (ByVal hKey As Long, ByVal lpSubKey As String) As Long
RegDeleteValue
303
Argumente: hKey
Handle eines geöffneten Schlüssels
lpSubKey
Der Name des zu löschenden Schlüssels
Rückgabe: 0 bei Erfolg
Die folgenden Zeilen löschen einen Schlüssel: strKey = "Software\Kunde\Programm" strSubKey = "Settings" hResult = RegOpenKeyEx(HKEY_CURRENT_USER, strKey, 0&, _ KEY_ALL_ACCESS, hKey) hResult = RegDeleteKey(hKey, strSubKey)
Achten Sie auf ausreichende Rechte beim Öffnen des Schlüssels.
14.61.2
Bemerkungen
In Windows 95 und 98 löscht diese Funktion einen Schlüssel inklusive seiner Unterschlüssel. In Windows NT oder 2000 müssen solche Strukturen enumeriert werden, da RegDeleteKey nur Schlüssel am Ende der Kette löscht; es dürfen Werte vorhanden sein, aber keine Unterschlüssel.
14.62 RegDeleteValue Diese Funktion entfernt einen Wert eines Registry-Schlüssels.
14.62.1
Deklaration und Signatur
Declare Function RegDeleteValue Lib "advapi32.dll" Alias "RegDeleteValueA" (ByVal hKey As Long, ByVal lpValueName As String) As Long
Argumente: hKey
Handle eines geöffneten Schlüssels
lpValueName
Name des zu löschenden Wertes
Rückgabe: 0 bei Erfolg
304
14
Funktionsreferenz
Die folgenden Zeilen löschen einen Wert: strKey = "Software\Kunde\Programm\Settings" strValue = "ImportPath" hResult = RegOpenKeyEx(HKEY_CURRENT_USER, strKey, 0&, _ KEY_ALL_ACCESS, hKey) hResult = RegDeleteValue(hKey, strValue)
Achten Sie auch hier auf ausreichende Rechte beim Öffnen des Schlüssels.
14.63 RegEnumKeyEx RegEnumKeyEx dient dazu, über einen Index den Namen eines Unterschlüssels zu ermitteln:
14.63.1
Deklaration und Signatur
Declare Function RegEnumKeyEx Lib "advapi32.dll" Alias "RegEnumKeyExA" (ByVal hKey As Long, ByVal dwIndex As Long, ByVal lpName As String, lpcbName As Long, ByVal lpReserved As Long, ByVal lpClass As Long, lpcbClass As Long, lpftLastWriteTime As FILETIME) As Long
Argumente: hKey
Handle eines geöffneten Schlüssels
dwIndex
Null-basierter Index des betreffenden Unterschlüssels
lpName
Mit dem Namen des Unterschlüssels gefüllter String
lpcbName
Bruttolänge von lpName
lpReserved
0
lpClass
Zurzeit nicht unterstützt, also 0
lpcbClass
Länge von lpClass, also ebenfalls 0
lpftLastWriteTime Zeiger auf FILETIME-Struktur Rückgabe: 0 bei Erfolg
Zusammen mit RegQueryInfoKey kann diese Funktion zur Enumeration von Schlüsseln verwendet werden: lngResult = RegOpenKeyEx(lngHKEY, strKey, 0, KEY_ALL_ACCESS, hKey) lngResult = RegQueryInfoKey(hKey, 0&, 0&, 0&, nSubKeys, lngMaxSize, _ lngDummy, lngDummy, lngDummy, lngDummy, lngDummy, udtTime) For iSubKey = 0 To nSubKeys – 1 strSubKey = Space(lngMaxSize + 1) nSize = lngMaxSize + 1
RegEnumValue
305
lngResult = RegEnumKeyEx(hKey, iSubKey, strSubKey, nSize, 0&, 0&, _ 0&, udtTime) Next
14.64 RegEnumValue RegEnumKeyEx dient dazu, über einen Index den Namen eines Wertes zu ermitteln:
14.64.1
Deklaration und Signatur
Declare Function RegEnumValue Lib "advapi32.dll" Alias "RegEnumValueA" (ByVal hKey As Long, ByVal dwIndex As Long, ByVal lpValueName As String, lpcbValueName As Long, ByVal lpReserved As Long, lpType As Long, ByVal lpData As String, lpcbData As Long) As Long
Argumente: hKey
Handle eines geöffneten Schlüssels
dwIndex
Null-basierter Index des betreffenden Unterschlüssels
lpValueName
Mit dem Namen des Wertes gefüllter String
lpcbValueName
Bruttolänge von lpValueName
lpReserved
0
lpType
Registry-Datentyp des Wertes
lpData
String zur Entgegennahme des Wertes
lpcbData
Bruttolänge von lpData
Rückgabe: 0 bei Erfolg
Zusammen mit RegQueryInfoKey kann diese Funktion zur Enumeration von Werten verwendet werden: lngResult = RegOpenKeyEx(lngHKEY, strKey, 0, KEY_ALL_ACCESS, hKey) 'Parameter abfragen lngResult = RegQueryInfoKey(hKey, 0&, 0&, 0&, lngDummy, lngDummy, _ lngDummy, nValues, lngMaxNameSize, lngMaxValueSize, _ lngDummy, udtTime) For iValue = 0 To nValues – 1 'Variablen vorbelegen strValueName = Space(lngMaxNameSize + 1) nValueNameSize = lngMaxNameSize + 1 strValue = Space(lngMaxValueSize + 1) nValueSize = lngMaxValueSize + 1 'Funktion aufrufen
306
14
Funktionsreferenz
lngResult = RegEnumValue(hKey, iValue, strValueName, _ nValueNameSize, 0&, lngType, strValue, nValueSize) Next
14.65 RegOpenKeyEx Bevor ein Registry-Schlüssel ausgelesen, verändert, enumeriert oder gar gelöscht werden kann, muss er mit RegOpenKeyEx geöffnet werden.
14.65.1
Deklaration und Signatur
Declare Function RegOpenKeyEx Lib "advapi32.dll" Alias "RegOpenKeyExA" (ByVal hKey As Long, ByVal lpSubKey As String, ByVal ulOptions As Long, ByVal samDesired As Long, ByRef phkResult As Long) As Long
Argumente: hKey
Root-Schlüssel des zu öffnenden Schlüssels, z.B. HKEY_CURRENT_USER
lpSubKey
Der Name des zu öffnenden Schlüssels
ulOptions
Reserviert und 0
samDesired
Erforderliche Rechte für die Operation
phkResult
Handle auf geöffneten Schlüssel im Erfolgsfall
Rückgabe: 0 bei Erfolg
Diese Beispielzeile öffnet den Schlüssel »Software\Microsoft\Visual Basic\6.0«: strKey = "Software\Microsoft\Visual Basic\6.0" hResult = RegOpenKeyEx(HKEY_CURRENT_USER, strKey, 0, KEY_READ, hKey)
14.66 RegQueryInfoKey Diese Funktion ermittelt eine Reihe von Informationen über einen Registry-Schlüssel.
14.66.1
Deklaration und Signatur
Declare Function RegQueryInfoKey Lib "advapi32.dll" Alias "RegQueryInfoKeyA" (ByVal hKey As Long, ByVal lpClass As Long, lpcbClass As Long, ByVal lpReserved As Long, lpcSubKeys As Long, lpcbMaxSubKeyLen As Long, lpcbMaxClassLen As Long, lpcValues As Long, lpcbMaxValueNameLen As Long, lpcbMaxValueLen As Long, lpcbSecurityDescriptor As Long, lpftLastWriteTime As FILETIME) As Long
RegQueryValueEx
307
Argumente: hKey
Handle des Keys
lpClass
Wird zurzeit noch nicht unterstützt, also 0 übergeben
lpcbClass
Länge von lpClass, also auch 0
lpReserved
0
lpcSubKeys
Anzahl der Unterschlüssel
lpcbMaxSubKeyLen
Nettogröße des längsten Unterschlüsselnamens in Byte
lpcbMaxClassLen
Wird zurzeit noch nicht unterstützt, also 0 übergeben
lpcValues
Anzahl der Werte des Schlüssels ohne (Standard)
lpcbMaxValueNameLen
Nettogröße des längsten Wertnamens in Byte
lpcbMaxValueLen
Größe des längsten Wertes in Byte
lpcbSecurityDescriptor
0
lpftLastWriteTime
Zeiger auf eine FILETIME-Struktur
Rückgabe: 0 bei Erfolg
Im folgenden Beispiel wird die Anzahl der Unterschlüssel ermittelt: lngResult = RegOpenKeyEx(lngHKEY, strKey, 0, KEY_ALL_ACCESS, hKey) lngResult = RegQueryInfoKey(hKey, 0&, 0&, 0&, nSubKeys, lngDummy, _ lngDummy, lngDummy, lngDummy, lngDummy, lngDummy, udtTime) For iSubKey = 0 To nSubKeys – 1 ... Next
Man kann also eine Dummy-Variable für alle Werte einsetzen, die nicht interessieren. Weitere Informationen finden Sie in Kapitel 8.
14.67 RegQueryValueEx Diese Funktion ermöglicht das Auslesen eines Registry-Wertes. Zuvor muss der übergeordnete Schlüssel per RegOpenKeyEx geöffnet werden.
14.67.1
Deklaration und Signatur
Declare Function RegQueryValueEx Lib "advapi32.dll" Alias "RegQueryValueExA" (ByVal hKey As Long, ByVal lpValueName As String, ByVal lpReserved As Long, ByRef lpType As Long, lpData As Any, ByRef lpcbData As Long) As Long
308
14
Funktionsreferenz
Argumente: hKey
Handle des Keys
lpValueName
Name des Schlüssels und »« für (Standard)-Wert
lpReserved
Reserviert
lpType
Datentyp, in aller Regel einer der Werte REG_SZ, REG_EXPAND_SZ, REG_MULTI_SZ, REG_BINARY oder REG_DWORD
lpData
Inhalt des Wertes
lpcbData
Nettobreite des Arguments lpData. Die Übergabe ist in Tabelle 8.3 zu sehen. Rufen wir die Funktion mit lpcbData mit dem Wert 0 auf, so gibt die Funktion in lpcbData die Bruttobreite des angefragten Wertes zurück.
Rückgabe: 0 bei Erfolg
Im folgenden Beispiel wird das Importverzeichnis ausgelesen: strKey = "Software\Kunde\Programm\Settings" strValue = "ImportPath" 'Schlüssel öffnen hResult = RegOpenKeyEx(HKEY_CURRENT_USER, strKey, 0&, KEY_READ, hKey) 'Funktion mit leerem strData und nSize = 0 aufrufen hResult = RegQueryValueEx(hKey, strValue, 0&, REG_SZ, _ ByVal strData, nSize) 'nSize enthält nun die erforderliche Größe von strData strData = Space(nSize) 'Funktion erneut aufrufen ... hResult = RegQueryValueEx(hKey, strValue, 0&, REG_SZ, _ ByVal strData, nSize) '... und Rückgabe ausschneiden strImportPath = Left(strData, nSize – 1) 'Schlüssel wieder schließen RegCloseKey hKey
Dieses Beispiel ist vereinfacht, aber funktionsfähig. Weitere Informationen finden Sie in Kapitel 8.
14.68 RegSetValueEx Mit RegSetValueEx kann ein Registry-Wert beschrieben und zugleich der Datentyp bestimmt werden.
ReleaseDC
14.68.1
309
Deklaration und Signatur
Declare Function RegSetValueEx Lib "advapi32.dll" Alias "RegSetValueExA" (ByVal hKey As Long, ByVal lpValueName As String, ByVal Reserved As Long, ByVal dwType As Long, lpValue As Any, ByVal cbData As Long) As Long
Argumente: hKey
Handle des Keys
lpValueName
Name des Schlüssels und »« für (Standard)-Wert
Reserved
Reserviert, also 0
Type
Datentyp, in aller Regel einer der Werte REG_SZ, REG_EXPAND_SZ, REG_MULTI_SZ, REG_BINARY oder REG_DWORD
lpValue
Zeiger auf den zu schreibenden Inhalt
cbData
Bruttolänge von lpValue
Rückgabe: 0 bei Erfolg
Das folgende Codefragment beschreibt einen Wert: strKey = "Software\Kunde\Programm\Settings" strValue = "ImportPath" lngResult = RegOpenKeyEx(lngHKEY, strKey, 0, KEY_WRITE, hKey) lngResult = RegSetValueEx(hKey, strValue, 0&, REG_SZ, _ ByVal strValue, Len(strValue))
Weitere Informationen finden Sie in Kapitel 8.
14.69 ReleaseDC ReleaseDC gibt einen Verweis auf einen Gerätekontext wieder frei, der beispielsweise mit GetWindowDC angefordert wurde.
14.69.1
Deklaration und Signatur
Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As Long
Argumente: hwnd
Handle des Fensters
hdc
Handle des Gerätekontexts
Rückgabe: 1 bei Erfolg und 0 im Fehlerfalle
310
14
Funktionsreferenz
Im folgenden Beispiel werden die in zwei Arrays gespeicherten Handles auf Fenster und Gerätekontext wieder freigegeben: For iHandle = UBound(lngHDCs) – 1 To 0 Step -1 lngResult = ReleaseDC(lngHWnds(iHandle), lngHDCs(iHandle)) Next
14.70 SelectObject Diese Funktion stellt ein neues Objekt des Typs Bitmap, Brush, Pen, Font oder Region im übergebenen Gerätekontext ein.
14.70.1
Deklaration und Signatur
Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Argumente: hdc
Handle des Gerätekontexts
hObject
Handle auf das einzustellende Objekt
Rückgabe: Ein Handle auf das vorherige Objekt oder 0 bzw. -1 im Fehlerfalle
Die folgenden Zeilen aktivieren den per hFont referenzierten Font in dem Picture-Steuerelement actPic. lngPrevFont = SelectObject(actPic.hDC, hFont)
14.71 SendMessage Das dürfte die wohl meistbeschäftigte Funktion in Windows sein, denn mit ihr wird der Nachrichtenaustausch zwischen einzelnen Fenstern durchgeführt. Dass sie recht einfach konstruiert ist, sollte uns allerdings verdächtig erscheinen. Mit SendMessage können wir eine Nachricht an ein Fenster senden. Wenn Sie hierbei an Fenster im herkömmlichen Sinne denken, so ist das jedoch nur ein Teil der Wirklichkeit. Denn auch eine TextBox oder eine ComboBox stellt in Windows ein Fenster dar. Im Übrigen gibt es auch eine Reihe unsichtbarer Objekte, die über ein Handle verfügen und somit Nachrichten empfangen können.
SendMessage
14.71.1
311
Deklaration und Signatur
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long,ByVal wParam As Long, lParam As Long) As Long
Argumente: hWnd
Handle des Ziel-Fensters
wMsg
Die zu sendende Nachricht
wParam
Zusätzlicher, nachrichtenspezifischer Parameter
lParam
Zusätzlicher, nachrichtenspezifischer Parameter
Rückgabe: nachrichtenabhängiger Wert
14.71.2
Bemerkungen
hWnd Hier können wir ein Handle übergeben, das einem speziellen Fenster zugeordnet ist. Über die Konstante HWND_BROADCAST (&HFFFF&) können aber auch alle Top-Level-Fenster gleichzeitig angesprochen werden.
wMsg Bei diesem Argument handelt es sich um eine Konstante, die von einer speziellen Fensterklasse (z.B. ComboBox), einer Gruppe von Fensterklassen (z.B. Controls) oder allen Fenstern verarbeitet werden kann. Meist kann man am Präfix erkennen, um welche dieser Arten es sich handelt. Der Präfix WM_ beispielsweise richtet sich an alle Fensterklassen, ICC_ nur an Steuerelemente der Kategorie CommonControls oder EM_ an editierbare oder BM_ an klickbare Steuerelemente. Mit MCM_ beginnen Nachrichten, die das Month Calendar Control betreffen.
wParam und lParam Es gibt Nachrichten, die sind eindeutig und bedürfen keiner weiteren Parameter. Hierzu gehört etwa WM_UNDO, mit dem eine Änderung in einem Steuerelement rückgängig gemacht werden kann. Das Kommando WM_NCHITTEST wiederum übergibt die X- und Y-Koordinaten des Mauszeigers, benötigt also zwei Argumente.
312
14
Funktionsreferenz
Rückgabe Die SendMessage-Funktion wartet ab, bis das Zielfenster das Kommando ausgeführt hat, und übermittelt dessen kommandoabhängige Rückgabe. Siehe hierzu auch PostMessage-Funktion.
14.72 SetDefaultPrinter Die nur unter Windows 2000 verfügbare Funktion SetDefaultPrinter stellt einen neuen Default-Drucker des aktuellen Users ein.
14.72.1
Deklaration und Signatur
Declare Function SetDefaultPrinter Lib "winspool.drv" Alias "SetDefaultPrinterA" (ByVal pszPrinter As String) As Long
Argumente: pszBuffer
Mit NULL terminierter Druckername
Rückgabe: 0 im Fehlerfalle
Dieser Code stellt den neuen Default-Drucker ein: lngResult = SetDefaultPrinter(strNewValue & vbNullChar)
14.73 SetLocaleInfo SetLocaleInfo ermöglicht die Veränderung von rund 30 der insgesamt über 100 Werte der Ländereinstellungen.
14.73.1
Deklaration und Signatur
Declare Function SetLocaleInfo Lib "kernel32" Alias "GetLocaleInfoA" (ByVal Locale As Long, ByVal LCType As Long, ByVal lpLCData As String) As Long
Argumente: Locale
Legt fest, auf welche Einstellungen zugegriffen werden soll.
LCType
Steuert die Art der angeforderten Einstellung.
lpLCData
NULL-terminierter String mit dem neuen Wert
Rückgabe: 0 im Fehlerfalle, wofür jedes der drei Argumente verantwortlich sein kann.
SetMapMode
313
Im folgenden Beispiel wird der lokale Name der aktuellen Sprache ermittelt. hResult = SetLocaleInfo(lngLocale, lngLCType, strValue)
14.73.2
Bemerkungen
Locale GetLocaleInfo ist in der Lage, auf unterschiedliche Gebietsschemata zuzugreifen. Hierbei wird generell in das installierte Systemschema und das aktuell eingestellte unterschieden. Unterschiede ergeben sich zwangsläufig dann, wenn das aktuell gewählte Gebietsschema von dem installierten abweicht. Uns interessieren in aller Regel die Einstellungen, die dem aktuellen Benutzer zugewiesen sind. Aus diesem Grund übergeben wir hier die UserDefaultLCID, die uns die Funktion GetUserDefaultLCID zurückgibt: lngLocale = GetUserDefaultLCID
LCType Dieser Long-Datentyp steuert die Art der gewünschten Information, wofür 30 verschiedene Konstanten erlaubt sind.
lpLCData Dieses Argument erwartet den neuen Wert, den die Einstellung fortan annehmen soll. Bei den Datumsformaten ShortDate und LongDate sind nur die Zeichen »d« für Tag, »M« für Monat und »y« für Jahr erlaubt. Es findet auch eine Art rudimentäre syntaktische Prüfung des übergebenen Arguments statt, die sich jedoch darauf beschränkt, das erste Zeichen zu überprüfen und alles abzulehnen, was nicht mit einem dieser drei Zeichen »d«, »M«, und »y« beginnt. Nach diesen Zeichen kann jeder Blödsinn stehen; es interessiert die Funktion einfach nicht. In VB oder VBA verarbeiten wir diese Information üblicherweise mit der FormatFunktion, die recht tolerant, wie sie nun mal ist, auch mit »DD.MM.YYYY« zufrieden ist. Da die SetLocaleInfo jedoch auf Groß- und Kleinschreibung Wert legt, sollten wir uns sicherheitshalber daran halten.
14.74 SetMapMode Mit dieser Funktion wird eingestellt, in welchem Maß die Parameter eines Objekts in einem Gerätekontext interpretiert und somit umgesetzt werden sollen. Außerdem wird die Richtung der Y-Achse festgelegt.
314
14.74.1
14
Funktionsreferenz
Deklaration und Signatur
Declare Function SetMapMode Lib "gdi32" (ByVal hdc As Long, ByVal nMapMode As Long) As Long
Argumente: hdc
Handle des Gerätekontexts
nMapMode
Eine der Map-Mode-Konstanten aus der folgenden Tabelle
Rückgabe: 0 im Fehlerfalle
Konstante
Wert
Bedeutung
MM_ANISOTROPIC
8
Logical Units werden in ungleichmäßig skalierte Achsen übersetzt, deren Maße per SetWindowExtEx and SetViewportExtEx eingestellt werden können.
MM_HIENGLISH
5
Jede Logical Unit korrespondiert mit 0,001 Zoll. Positive X-Werte liegen rechts und positive Y-Werte oben.
MM_HIMETRIC
3
Jede Logical Unit korrespondiert mit 0,01 Millimeter. Positive X-Werte liegen rechts und positive Y-Werte oben.
MM_ISOTROPIC
7
Logical Units werden in gleichmäßig skalierte Achsen übersetzt, deren Maß per SetWindowExtEx and SetViewportExtEx eingestellt werden kann.
MM_LOENGLISH
4
Jede Logical Unit korrespondiert mit 0,01 Zoll. Positive X-Werte liegen rechts und positive Y-Werte oben.
MM_LOMETRIC
2
Jede Logical Unit korrespondiert mit 0,1 Millimeter. Positive X-Werte liegen rechts und positive Y-Werte oben.
MM_TEXT
1
Jede Logical Unit korrespondiert mit einem Pixel. Positive X-Werte liegen rechts und positive Y-Werte unten.
MM_TWIPS
6
Jede Logical Unit korrespondiert mit einem Zwanzigstel Punkt (1/1440 Zoll). Positive X-Werte liegen rechts und positive Y-Werte oben.
Tabelle 14.7: Map-Mode-Konstanten
Im folgenden Beispiel wird der Map Mode auf Twips und nach der entsprechenden Operation (...) wieder auf den vorherigen Wert eingestellt: lngPrevMapMode = SetMapMode(hDC, MM_TWIPS) ... lngPrevMapMode = SetMapMode(hDC, lngPrevMapMode)
SetPrinter
315
14.75 SetPrinter Die SetPrinter-Funktion übergibt eine PRINTER_INFO-Struktur an einen Drucker und verändert somit eine Einstellung des Druckers.
14.75.1
Deklaration und Signatur
Declare Function SetPrinter Lib "winspool.drv" Alias "SetPrinterA" (ByVal hPrinter As Long, ByVal Level As Long, pPrinter As Any, ByVal Command As Long) As Long
Argumente: hPrinter
Handle des Druckers
Level
Wert zwischen 1 und 9, der die gewünschte PRINTER_INFO-Struktur angibt
pPrinter
Zeiger auf die PRINTER_INFO-Struktur oder 0
Command
0 bei Verwendung pPrinter ungleich 0 und eine der Konstanten aus Tabelle 10.3, wenn pPrinter gleich 0
Rückgabe: 0 im Fehlerfalle
Konstante
Wert Bedeutung
PRINTER_CONTROL_PAUSE
1
Unterbricht den Druck.
PRINTER_CONTROL_PURGE
3
Löscht alle Druckaufträge.
PRINTER_CONTROL_RESUME
2
Führt einen unterbrochenen Druck fort.
PRINTER_CONTROL_SET_STATUS
4
Verändert den Druckerstatus, der wiederum im pPrinter-Argument angegeben werden muss.
Tabelle 14.8: Drucker-Kommando-Konstanten
Der folgende Code ermittelt eine PRINTER_INFO_2-Struktur und ersetzt innerhalb der dortigen DEVMODE-Struktur (lngAddr(7)) den durch den Offset lngOffset bezeichneten Wert durch intValue. lngResult = GetPrinter(hPrinter, 2, aDevMode(0), UBound(aDevMode), _ nSize) CopyMemory lngAddr(0), aDevMode(0), 32 lngResult = PtrToPtr(ByVal (lngAddr(7) + lngOffset), intValue) lngResult = SetPrinter(hPrinter, 2, aDevMode(0), 0)
14.76 SetWindowLong SetWindowLong ändert ein Attribut des angegebenen Fensters.
316
14.76.1
14
Funktionsreferenz
Deklaration und Signatur
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Argumente: hWnd
Fenster-Handle
nIndex
Legt fest, welches Attribut geändert werden soll
dwNewLong
Der neue Wert des durch nIndex spezifizierten Attributs
Rückgabe: vorheriger Wert oder 0 im Fehlerfalle
Hier nun die unter VB sinnvollen nIndex-Werte: Konstante
Wert
Bedeutung
GWL_WNDPROC
-4
Ändert die Adresse der Window Procedure
GWL_STYLE
-16
Ändert Attribute des Window Styles
Tabelle 14.9: Auswahl an GWL-Konstanten
Im ersten Beispiel wird ein Hook eingeleitet: lpPrevWndProc = SetWindowLong(hWnd, GWL_WNDPROC, _ AddressOf RTFWindowProc)
Der hier wieder aufgehoben wird: SetWindowLong hWnd, GWL_WNDPROC, lpPrevWndProc
14.77 SetWindowPos Mit dieser Funktion können Größe, Position, Anordnung in Z-Richtung und Verhalten in dieser Z-Richtung beeinflusst werden.
14.77.1
Deklaration und Signatur
Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
SHBrowseForFolder
317
Argumente: hwnd
Fenster-Handle
hWndInsertAfter
Handle des Fensters, vor dem das Fenster positioniert werden soll, oder eine der Konstanten aus der folgenden Tabelle.
x
Linker Rand des Fensters in client coordinates
y
Oberer Rand des Fensters in client coordinates
cx
Breite des Fensters in Pixel
cy
Höhe des Fensters in Pixel
wFlags
Steuert die Größe des Fensters sowie seine Anordnung mit einer Kombination von insgesamt 15 SetWindowPos-Konstanten (SWP), von denen in der Tabelle SetWindowPos-Konstanten zwei wiedergegeben sind.
Rückgabe: 0 im Fehlerfalle
Konstante
Wert
Bedeutung
HWND_BOTTOM
1
Ordnet das Fenster an hinterster Stelle an.
HWND_NOTOPMOST
-2
Ordnet das Fenster an erster Stelle hinter dem TopMostFenstern an. Die Operation hat keine Auswirkung, wenn das Fenster nicht zu den TopMost-Fenstern gehört.
HWND_TOP
0
Ordnet das Fenster an vorderster Stelle an.
HWND_TOPMOST
-1
Ordnet das Fenster an vorderster Stelle an. Diese Position wird beibehalten, auch wenn das Fenster deaktiviert wird.
Tabelle 14.10: Konstanten zur Positionierung eines Fensters
Konstante
Wert
Bedeutung
SWP_NOSIZE
1
Argumente cx und cy ignorieren
SWP_NOMOVE
2
Argumente x und y ignorieren
Tabelle 14.11: SetWindowPos-Konstanten
Die folgende Zeile aktiviert das Fenster an oberster Stelle der Z-Richtung, ohne Position und Größe zu verändern: lngResult = SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, _ SWP_NOMOVE Or SWP_NOSIZE)
14.78 SHBrowseForFolder SHBrowseForFolder steuert den Verzeichnisdialog.
318
14.78.1
14
Funktionsreferenz
Deklaration und Signatur
Declare Function SHBrowseForFolder Lib "shell32" (lpbi As BROWSEINFO) As Long
Argumente: lpbi
Zeiger auf BROWSEINFO-Struktur
Rückgabe: Ein Zeiger auf eine ItemIDList oder 0, wenn der Dialog abgebrochen wurde
Weitere Informationen finden Sie unter SHGetPathFromIDList oder im Kapitel 4.3 Verzeichnisdialog.
14.79 SHGetFileInfo Die SHGetFileInfo-Funktion bietet Zugang zu einer ganzen Reihe von Informationen zu einzelnen Dateien.
14.79.1
Deklaration und Signatur
Declare Function SHGetFileInfo Lib "shell32.dll" Alias "SHGetFileInfoA" (ByVal pszPath As String, ByVal dwFileAttributes As Long, psfi As SHFILEINFO, ByVal cbFileInfo As Long, ByVal uFlags As Long) As Long
Argumente: pszPath
Namen der Datei, der absolut angegeben werden kann, aber auch relativ zum Verzeichnis des aufrufenden Clients
dwFileAttributes
Ein Wert oder eine Kombination von Werten aus der Reihe FILE_ATTRIBUTEKonstanten. Dieses Argument wird aber ignoriert, wenn SHGFI_USEFILEATTRIBUTES nicht in uFlags enthalten ist.
psfi
Eine SHFILEINFO-Struktur, die von der Funktion gemäß den Flags gefüllt wird
cbFileInfo
Länge der SHFILEINFO-Struktur, die als psfi übergeben wird.
uFlags
Eine oder mehrere dieser Konstanten SHGFI_DISPLAYNAME, SHGFI_ICON oder SHGFI_TYPENAME (Auszug)
Rückgabe: 0 im Fehlerfalle, je nach uFlags aber auch ein Wert.
Die folgende Zeile ist der TypeName-Eigenschaft der Komponente clsSysFiles entnommen.
SHGetPathFromIDList
319
lngReturn = SHGetFileInfo(strFile, 0, udtSHFILEINFO, _ LenB(udtSHFILEINFO), SHGFI_TYPENAME)
Siehe hierzu auch Abschnitte 7.2 bis 7.4.
14.80 SHGetPathFromIDList SHGetPathFromIDList gibt den Text zurück, der dem von SHBrowseForFolder zurückgegebenen Zeiger auf die ItemIDList zugeordnet ist.
14.80.1
Deklaration und Signatur
Declare Function SHGetPathFromIDList Lib "shell32" (ByVal pidList As Long, ByVal lpBuffer As String) As Long
Argumente: pidList
Zeiger auf ItemIDList
lpBuffer
Name des Eintrags
Rückgabe: 0 bei Fehler und 1 bei Erfolg
Weitere Informationen finden Sie unter SHBrowseForFolder und im Kapitel 4.3 Verzeichnisdialog.
14.81 ShellExecute ShellExecute ist eine sehr vielseitige Funktion, die immer dann erforderlich wird, wenn die Shell-Anweisung von Visual Basic am Ende ist, und das ist ja relativ schnell der Fall. Sie kann zum Beispiel verwendet werden, um eine Applikation mit einem weiteren Argument zu starten, etwa dem Namen einer zu öffnenden Datei. Sie kann aber auch verwendet werden, um eine Anwendung zu starten, der ein gewisser Dateityp zugeordnet ist. Zumeist ist es klar, welche Applikation einem Dateityp zugeordnet ist. Aber denken Sie etwa an einen Browser, bei dem Sie nicht wissen, ob der Internet Explorer von Microsoft eingestellt ist oder beispielsweise der Netscape Navigator. Aber auch den Windows-Explorer können Sie mit einem Startverzeichnis aufrufen oder aber eine Datei einfach drucken, ohne die dazugehörige Anwendung starten zu müssen.
320
14.81.1
14
Funktionsreferenz
Deklaration und Signatur
Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" (ByVal hWnd As Long, ByVal lpOperation As String, ByVal lpFile As String, ByVal lpParameters As String, ByVal lpDirectory As String, ByVal nShowCmd As Long) As Long
Argumente: hWnd
Handle des Owner-Fensters, kann 0 sein
lpOperation
Ein String, der angibt, welche Operation durchgeführt werden soll (siehe Bemerkungen)
lpFile
Name der zu startenden Applikation oder Name der Datei, mit der die unter lpOperation angegebene Aktion ausgeführt werden soll (siehe Bemerkungen)
lpParameter
Optionale Argumente, wie etwa Name der zu öffnenden Datei, sofern es sich bei lpFile um eine Anwendung handelt
lpDirectory
Standardarbeitsverzeichnis der Applikation, mit dem zum Beispiel bei Datei Öffnen initialisiert wird
nShowCmd
Steuert Modus (sichtbar oder als Prozess) sowie Position und Größe des Fensters (siehe Bemerkungen)
Rückgabe: Bei Erfolg ein Wert > 32, andernfalls 0 bis 32 (siehe Bemerkungen)
14.81.2
Bemerkungen:
lpOperation Die folgende Übersicht enthält die definierten Verben, die als lpOperation übergeben werden können: Argument
Bedeutung
"edit"
Ermittelt die zu startende Applikation und lädt die Datei
"explore"
Startet den Explorer mit dem unter lpFile angegebenen Verzeichnis
"find"
Startet den Suchen-Dialog mit dem unter lpFile angegebenen Verzeichnis
"open"
Ermittelt die dem unter lpFile zugeordnete Anwendung, startet diese und übergibt lpFile
"print"
Druckt die unter lpFile angegebene Datei unter Verwendung der diesem Dateityp zugeordneten Anwendung
"properties"
Sollte eigentlich die Eigenschaften anzeigen, die dem Inhalt von lpFile zugeordnet sind, tut’s aber nicht.
ShellExecute
321
Wird das Argument weggelassen (vbNullString), so wird das Default-Verb des Class Names verwendet. Existiert kein Default-Eintrag, so wird »open« angenommen. Windows 2000 geht noch einen Schritt weiter und verwendet den ersten »verb«-Eintrag der Registry, falls kein Default-Eintrag definiert ist. Es sind zum einen nicht alle Verben erlaubt, die dem betreffenden Class Name in der Registry zugeordnet sind. Zum anderen unterstützen auch nicht alle Objekte alle Verben, die ShellExecute wiederum anbietet, so zum Beispiel »print«.
lpFile Dieses Argument enthält entweder den vollständigen Namen der zu startenden Anwendung oder aber den vollständigen Namen der Datei, mit der die Operation auszuführen ist, die als lpOperation verwendet wird.
nShowCmd Die folgende Tabelle zeigt die Konstanten, die als nShowCmd zur Anwendung kommen können. In der Spalte Bedeutung ist auch jeweils die VB-Konstante angegeben, die in der Enumeration VbAppWinStyle enthalten ist: Konstante
Wert
Bedeutung
SW_HIDE
0
vbHide Startet die Anwendung als Prozess.
SW_SHOWNORMAL
1
vbNormalFocus Startet die Anwendung und zeigt das Applikationsfenster in zuletzt verwendeter Größe und Position. Die gestartete Anwendung wird aktiviert.
SW_SHOWMINIMIZED
2
vbMinimizedFocus Startet die Anwendung und zeigt das Applikationsfenster minimiert. Die gestartete Anwendung wird aktiviert.
SW_SHOWMAXIMIZED
3
vbMaximizedFocus Startet die Anwendung und zeigt das Applikationsfenster maximiert. Die gestartete Anwendung wird aktiviert.
SW_SHOWNOACTIVATE
4
vbNormalNoFocus Startet die Anwendung und zeigt das Applikationsfenster in zuletzt verwendeter Größe und Position. Die startende Anwendung bleibt aktiv.
SW_MINIMIZE
6
vbMinimizedNoFocus Startet die Anwendung und zeigt das Applikationsfenster minimiert. Die startende Anwendung bleibt aktiv.
322
14
Funktionsreferenz
In der MSDN werden Sie neben den hier vorgestellten Konstanten noch eine Reihe anderer finden, die ebenfalls mit SW_ beginnen. Diese beziehen sich jedoch auf bereits geöffnete Fenster und machen zusammen mit der ShellExecute keinen Sinn.
Rückgabe ShellExecute unterscheidet sich auch in der Interpretation der Rückgabe von den meisten anderen API-Funktionen. Die folgende Tabelle zeigt die Konstanten und deren Bedeutungen: Konstante
Wert
Bedeutung
0
Das Betriebssystem verfügt nicht über genügend Speicher oder Ressourcen.
SE_ERR_FNF
2
Die als lpFile übergebene Datei wurde nicht gefunden.
SE_ERR_PNF
3
Das in lpFile übergebene Verzeichnis wurde nicht gefunden.
SE_ERR_ACCESSDENIED
5
Der Zugriff auf die in lpFile übergebene Datei wurde verweigert.
SE_ERR_OOM
8
Nicht genügend Speicher vorhanden, um die Operation abzuschließen.
ERROR_BAD_FORMAT
11
Die zu startende Anwendung (exe) ist nicht in Ordnung.
SE_ERR_SHARE
26
Fehler im gemeinsamen Dateizugriff
SE_ERR_ASSOCINCOMPLETE
27
Unvollständige oder ungültige Zuordnung der Dateierweiterung. Dieser Fehler konnte nicht reproduziert werden, da fehlerhafte Dateierweiterungen durch SE_ERR_NOASSOC quittiert werden.
SE_ERR_DDETIMEOUT
28
Bei der zu startenden Anwendung trat ein Timeout auf.
SE_ERR_DDEFAIL
29
Die DDE-Operation schlug fehl.
SE_ERR_DDEBUSY
30
Andere DDE-Operationen verursachten ein Scheitern der angeforderten Operation.
SE_ERR_NOASSOC
31
Der in lpFile übergebenen Dateierweiterung ist keine Anwendung zugeordnet.
SE_ERR_DLLNOTFOUND
32
Dieser Fehler kann eigentlich nicht auftreten, da wir bereits vorher einen Kompilierfehler ernten, wenn die DLL nicht gefunden werden kann.
Diese Errorcodes können wir in dieser Form natürlich den Anwendern nicht zumuten. Es empfiehlt sich also, die Rückgabe an eine Prozedur zu übergeben, die eine entsprechende Fehlermeldung generiert, falls die Rückgabe größer als 32 sein sollte.
Sleep
14.81.3
323
Beispiele
14.82 Sleep Sleep übergibt die Steuerung für die übergebene Zahl von Millisekunden an Windows.
14.82.1
Deklaration und Signatur
Declare Sub Sleep Lib "kernel32" (ByVal lngMilliseconds As Long)
Argumente: lngMilliseconds
Dauer der Unterbrechung in Millisekunden
14.83 StringFromGUID2 StringFromGUID2 wandelt eine in einer GUID-Struktur enthaltene GUID in einen entsprechenden String um.
14.83.1
Deklaration und Signatur
Declare Function StringFromGUID2 Lib "OLE32.DLL" (pGUID As GUID, ByVal lpszString As String, ByVal lMax As Long) As Long
Argumente: pGUID
GUID-Struktur
lpszString
In Unicode formatiertes Ergebnis
lMax
Größe des Arguments lpszString in Byte
Rückgabe: Anzahl der benötigten Unicode-Zeichen
Die folgenden Zeilen erzeugen eine GUID und übergeben diese an StringFromGUID2. Die Rückgabe wird entsprechend nachbehandelt. strGUID = Space(78) 'GUID erzeugen CoCreateGuid udtGUID 'nach ANSI konvertieren nSize = StringFromGUID2(udtGUID, strGUID, Len(strGUID)) strGUID = Left(StrConv(strGUID, vbFromUnicode), nSize – 1)
324
14
Funktionsreferenz
Die Größe der Vorbelegung 78 ergibt sich aus 38 (so groß ist das Ergebnis) + den abschließenden NULL x 2 für Unicode. Siehe hierzu auch das Beispiel in Kapitel 5.5.
14.84 StrLen StrLen gibt die Länge eines Strings zurück.
14.84.1
Deklaration und Signatur
Declare Function StrLen Lib "kernel32" Alias "lstrlenA" (ByVal Ptr As String) As Long
Argumente: Ptr
Zu bestimmender String
Rückgabe: Länge des Strings in Byte
Siehe hierzu das Beispiel zur ExpandEnvironmentStrings-Funktion.
14.85 WaitForSingleObject WaitForSingleObject wartet für einen anzugebenden Zeitraum, bis das Objekt hinter dem Handle seinen Zustand gewechselt hat.
14.85.1
Deklaration und Signatur
Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long
Argumente: hHandle
Handle des zu überwachenden Objekts (Fenster oder Prozess)
dwMilliseconds
Anzahl der Millisekunden oder INFINITE (&HFFFFFFFF)
Rückgabe: 0 im Fehlerfalle
Im folgenden Beispiel wird eine Applikation über ihr Top-Level-Fenster geschlossen: lngResult = PostMessage(hWnd, WM_CLOSE, 0&, 0&) Sleep 100 lngResult = WaitForSingleObject(hWnd, INFINITE)
WSAGetLastError
325
14.86 WSAGetLastError Diese Funktion gibt die Nummer des letzten Fehlers im Windows Sockets API (WSA) zurück.
14.86.1
Deklaration und Signatur
Declare Function WSAGetLastError Lib "WSOCK32.DLL" () As Long
Rückgabe: Fehlernummer
Das Beispiel zeigt das Erzeugen eines Fehlers, falls die Rückgabe einer Funktion einen Fehler signalisiert: If lngResult = SOCKET_ERROR Then Err.Raise 9999, , "Windows Sockets Error: " & _ CStr(WSAGetLastError())
14.87 WSAStartup WSAStartup initialisiert die Windows Sockets Bibliothek.
14.87.1
Deklaration und Signatur
Declare Function WSAStartup Lib "WSOCK32.DLL" (ByVal wVersionRequired As Integer, lpWSAData As WSADATA) As Long
Argumente: wVersionRequired
Mindestversion der Bibliothek: 1.1 = &H101
lpWSAData
WSADATA-Struktur
Rückgabe: 0 im Erfolgsfall
Die folgenden Zeilen initialisieren die Windows Sockets Bibliothek: Const WSARequVersion As Integer = &H101 Dim udtWSADATA As WSADATA 'WSADATA-Struktur lngReturn = WSAStartup(WSARequVersion, udtWSADATA) If lngReturn <> 0 Then Err.Raise 9999, , "Winsock.dll is not responding."
326
14
Funktionsreferenz
14.88 WSACleanup WSACleanup beendet eine per WSAStartup gestartete Sitzung.
14.88.1
Deklaration und Signatur
Declare Function WSACleanup Lib "WSOCK32.DLL" () As Long
Rückgabe: 0 im Erfolgsfall und -1 im Fehlerfall
Im Beispiel werden WSA-Sitzungen so lange beendet, bis die Rückgabe Misserfolg signalisiert oder eine festgesetzte Obergrenze von 10 Durchläufen erreicht hat: Do lngCounter = lngCounter + 1 lngReturn = WSACleanup() Loop Until lngReturn <> 0 Or lngCounter > 10
15 Konstanten Name
Wert
Bedeutung
ACL_REVISION
2
Revisionsstand der Access Control List
ANTIALIASED_QUALITY
4
Antialiasing wird vorgenommen, wenn der Font es unterstützt und die Größe nicht zu extrem ist
BIF_DONTGOBELOWDOMAIN
2
Keine Domainenmitglieder zeigen.
BIF_RETURNONLYFSDIRS
1
Nur Auswahl von Verzeichnissen ermöglichen.
CC_ANYCOLOR
&H100
Erlaubt auch die Auswahl von Nicht-VGA-Farben.
CC_FULLOPEN
2
Startet den Dialog mit eingeblendeter Farbdefinition.
CC_PREVENTFULLOPEN
4
Startet den Dialog ohne Farbdefinition und disabled die Schaltfläche zum Einblenden der Farbdefinition.
CC_RGBINIT
1
Ermöglicht die Übergabe einer Initialisierungsfarbe.
CC_SOLIDCOLOR
&H80
Bewirkt, dass statt einer NichtVGA-Farbe eine passende VGAFarbe verwendet wird.
CREATE_ALWAYS
2
Datei erzeugen. Existiert sie bereits, wird sie überschrieben.
CREATE_NEW
1
Datei erzeugen.
DC_BINNAMES
12
Veranlasst DeviceCapabilities, die Namen in einem NULL-separierten String-Array zurückzugeben. Diese Zeichenkette beinhaltet 24 Bytes Brutto je Eintrag, wobei nicht genutzte Zeichen durch NULLs ersetzt werden.
328
15
Konstanten
Name
Wert
Bedeutung
DC_BINS
6
Veranlasst DeviceCapabilities, ein Integer-Arrays mit Papierzufuhrkonstanten (DMBIN_*) zurückzugeben.
DC_COPIES
18
Veranlasst DeviceCapabilities, die maximale Zahl von Kopien zurückzugeben, die der Drucker erzeugen kann.
DC_DUPLEX
7
Wenn der Drucker beidseitigen Druck beherrscht, so gibt DeviceCapabilities 1 zurück, anderenfalls 0.
DEFAULT_CHARSET
1
Unspezifischer Verweis auf lokales Character Set
DEFAULT_PITCH
0
Keine Angabe zu Zeichenabstand
DEFAULT_QUALITY
0
Erscheinungsform des Fonts ohne Bedeutung
DELETE
&H10000
Löschen
DEVICE_FONTTYPE
2
Gerätespezifischer Font
DM_COLLATE
&H8000&
Download bei Mehrfachkopien
DM_COLOR
&H800&
DMCOLOR_MONOCHROME oder DMCOLOR_COLOR
DM_COPIES
&H100&
Anzahl der Kopien
DM_DEFAULTSOURCE
&H200&
Papierzufuhr
DM_DITHERTYPE
&H10000000
Art des Dithering (Win 9x und 2000)
DM_DUPLEX
&H1000&
Beidseitiges Drucken
DM_FORMNAME
&H10000000
Bei NT Formularname
DM_ICMINTENT
&H40000000
Art der ICM-Unterstützung (Win 9x und 2000)
DM_ICMMETHOD
&H20000000
Image-Color-Management Unterstützung (Win 9x und 2000)
DM_MEDIATYPE
&H80000000
Art des Druckmediums (Win 9x und 2000)
DM_ORIENTATION
&H1&
Papierausrichtung
DM_PAPERLENGTH
&H4&
Papierlänge in mm -1
DM_PAPERSIZE
&H2&
Papiergröße (siehe DMPAPERKonstanten)
DM_PAPERWIDTH
&H8&
Papierbreite in mm -1
329
Name
Wert
Bedeutung
DM_PRINTQUALITY
&H400&
Anzahl der DPI oder eine der DMRES-Konstanten
DM_SCALE
&H10&
Skalierung bezogen auf 100
DM_TTOPTION
&H4000&
Handling von TrueType-Schriften
DM_YRESOLUTION
&H2000&
Anzahl der DPI in Y-Richtung
DMBIN_AUTO
7
Automatische Blattzufuhr
DMBIN_CASSETTE
14
Papierkassette
DMBIN_ENVELOPE
5
Behälter für Umschläge
DMBIN_ENVMANUAL
6
Manuelle Umschlagzufuhr
DMBIN_LARGECAPACITY
11
Behälter für große Kapazitäten
DMBIN_LARGEFMT
10
Behälter für große Formate
DMBIN_LOWER
2
Unterer Behälter
DMBIN_MANUAL
4
Manuelle Blattzufuhr
DMBIN_MIDDLE
3
Mittlerer Behälter
DMBIN_ONLYONE
1
Nur ein Behälter vorhanden
DMBIN_SMALLFMT
9
Behälter für kleine Formate
DMBIN_TRACTOR
8
Traktoreinzug
DMBIN_UPPER
1
Oberer Behälter
DMBIN_USER
256
Grenze für treiberspezifische Werte
DMCOLLATE_FALSE
0
Daten bei Kopien für jede Kopie senden
DMCOLLATE_TRUE
1
Daten bei Kopien nur einmal senden
DMCOLOR_COLOR
2
Farbdrucker
DMCOLOR_MONOCHROME
1
Monochrom-Drucker
DMDUP_HORIZONTAL
3
Beidseitig, Bindung an Breitseite
DMDUP_SIMPLEX
1
Nur einseitiger Druck
DMDUP_VERTICAL
2
Beidseitig, Bindung an Schmalseite
DMORIENT_LANDSCAPE
2
Querformat
DMORIENT_PORTRAIT
1
Hochformat
DMPAPER_A3
8
A3 (297 x 420 mm)
DMPAPER_A4
9
A4 (210 x 297 mm)
DMPAPER_A5
11
A5 (148 x 210 mm)
DMPAPER_B4
12
B4 (250 x 354 mm)
DMPAPER_B5
13
B5 (182 x 257 mm)
330
15
Konstanten
Name
Wert
Bedeutung
DMPAPER_ENV_B4
33
Umschlag B4 (250 x 353 mm)
DMPAPER_ENV_B5
34
Umschlag B5 (176 x 250 mm)
DMPAPER_ENV_B6
35
Umschlag B6 (176 x 125 mm)
DMPAPER_ENV_C3
29
Umschlag C3 (324 x 458 mm)
DMPAPER_ENV_C4
30
Umschlag C4 (229 x 324 mm)
DMPAPER_ENV_C5
28
Umschlag C5 (162 x 229 mm)
DMPAPER_ENV_C6
31
Umschlag C6 (114 x 162 mm)
DMPAPER_ENV_C65
32
Umschlag C65 (114 x 229 mm)
DMPAPER_ENV_DL
27
Umschlag DL (110 x 220 mm)
DMPAPER_ENV_ITALY
36
Umschlag (110 x 230 mm)
DMPAPER_QUARTO
15
Quarto (215 x 275 mm)
DMRES_DRAFT
-1
Entwurfsqualität
DMRES_HIGH
-4
Hohe Auflösung
DMRES_LOW
-2
Niedrige Auflösung
DMRES_MEDIUM
-3
Mittlere Auflösung
DMTT_BITMAP
1
TrueType-Fonts als Grafik drucken. Voreinstellung für Matrixdrucker
DMTT_DOWNLOAD
2
TrueType-Fonts als Soft Fonts in Drucker laden. Voreinstellung für HP-Drucker, die Printer Control Language (PCL) unterstützen.
DMTT_DOWNLOAD_OUTLINE
4
TrueType-Fonts als Outline Soft Fonts laden.
DMTT_SUBDEV
3
Geräte-Fonts für TrueType-Fonts ersetzen. Voreinstellung für PostScriptDrucker
DRAFT_QUALITY
1
Erscheinungsform des Fonts weniger wichtig als bei PROOF_QUALITY
DRIVE_CDROM
5
CD-Laufwerke
DRIVE_FIXED
3
Fest installierte lokale Laufwerke (Festplatten)
DRIVE_NO_ROOT_DIR
1
Kein gültiger Laufwerksbezeichner
DRIVE_RAMDISK
6
RAM-Disks
DRIVE_REMOTE
4
Netzlaufwerke
331
Name
Wert
Bedeutung
DRIVE_REMOVABLE
2
Wechseldatenträger (Diskette, Zip, Wechselplatte)
DRIVE_UNKNOWN
0
Laufwerkstyp kann nicht bestimmt werden.
ERROR_BAD_FORMAT
11
Die Exe ist keine gültige Win32-Exe oder es ist ein Fehler im Exe-Image aufgetreten.
ERROR_FILE_NOT_FOUND
2
Datei oder Registry-Schlüssel nicht vorhanden.
ERROR_PATH_NOT_FOUND
3
Das angegebene Verzeichnis wurde nicht gefunden.
ERROR_SHARING_VIOLATION
&H20
Objekt bereits exklusiv geöffnet.
ERROR_SUCCESS
0
Rückgabecode bei Erfolg
FF_DECORATIVE
80
Schriftfamilie Romanschrift z.B. Old English
FF_DONTCARE
0
Schriftfamilie egal
FF_MODERN
&H30
Schriftfamilie der nicht-proportionalen Schriften, z.B. Pica, Elite und CourierNew
FF_ROMAN
&H10
Schriftfamilie der proportionalen Serifenschriften, z.B. MS Serif
FF_SCRIPT
&H40
Schriftfamilie im Stile von Handschriften, z.B. Script
FF_SWISS
&H20
Schriftfamilie der proportionalen serifenlosen Schriften, z.B. MS Sans Serif
FILE_ATTRIBUTE_ARCHIVE
&H20
Archive-Flag gesetzt
FILE_ATTRIBUTE_ENCRYPTED
&H40
Die Datei ist verschlüsselt
FILE_ATTRIBUTE_HIDDEN
2
Hidden-Flag gesetzt.
FILE_ATTRIBUTE_NORMAL
&H80
Normale Datei ohne Mitwirkung einer der anderen FILEATTRIBUTE-Konstanten
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
&H2000
Datei wird nicht vom Content Indexing Service berücksichtigt.
FILE_ATTRIBUTE_OFFLINE
&H1000
Signalisiert, dass die Datei physikalisch auf einen Offline-Speicherplatz verschoben wurde. Dies ist ein Windows-2000-Feature.
FILE_ATTRIBUTE_READONLY
1
Read-Only-Flag ist gesetzt.
332
15
Konstanten
Name
Wert
Bedeutung
FILE_ATTRIBUTE_SYSTEM
4
System-Flag ist gesetzt.
FILE_ATTRIBUTE_TEMPORARY
&H100
Die Datei wird derzeit vom System genutzt und befindet sich im temporären Speicher.
FILE_SHARE_DELETE
4
Weitere Dateizugriffe müssen mit Löschrechten ausgestattet sein.
FILE_SHARE_READ
1
Weitere Dateizugriffe müssen mit Leserechten ausgestattet sein.
FILE_SHARE_WRITE
2
Weitere Dateizugriffe müssen mit Schreibrechten ausgestattet sein.
FIXED_PITCH
1
Konstanter Zeichenabstand
FORMAT_MESSAGE_FROM_SYSTEM
&H1000
Kennzeichen für System Message Table
FW_BLACK
900
Maximale Schriftstärke
FW_BOLD
700
Fette Schriftstärke
FW_DEMIBOLD
600
Halbfette Schriftstärke
FW_DONTCARE
0
Keine Schriftstärke
FW_EXTRABOLD
800
Extrafette Schriftstärke
FW_EXTRALIGHT
200
Schriftstärke extra light
FW_HEAVY
900
Maximale Schriftstärke
FW_LIGHT
300
Schriftstärke light
FW_MEDIUM
500
Mittlere Schriftstärke
FW_NORMAL
400
Normale Schriftstärke
FW_SEMIBOLD
600
Halbfette Schriftstärke
FW_THIN
100
Dünne Schriftstärke
FW_ULTRABOLD
800
Extrafette Schriftstärke
FW_ULTRALIGHT
200
Schriftstärke extra light
GENERIC_READ
&H80000000
Lesezugriff
GENERIC_WRITE
&H40000000
Schreibzugriff
GWL_STYLE
-16
Ändert Attribute des Window Styles.
GWL_WNDPROC
-4
Ändert die Adresse der Window Procedure.
HKEY_CLASSES_ROOT
&H80000000
HKEY_CLASSES_ROOT-RegistrySchlüssel
HKEY_CURRENT_CONFIG
&H80000005
HKEY_CURRENT_CONFIGRegistry-Schlüssel
333
Name
Wert
Bedeutung
HKEY_CURRENT_USER
&H80000001
HKEY_CURRENT_USER-RegistrySchlüssel
HKEY_DYN_DATA
&H80000006
HKEY_DYN_DATA-RegistrySchlüssel
HKEY_LOCAL_MACHINE
&H80000002
HKEY_LOCAL_MACHINERegistry-Schlüssel
HKEY_USERS
&H80000003
HKEY_USERS-Registry-Schlüssel
HWND_BOTTOM
1
Ordnet das Fenster an hinterster Stelle an.
HWND_BROADCAST
&HFFFF
Message-Konstante, die Nachricht an alle Top-Level-Fenster sendet
HWND_NOTOPMOST
-2
Ordnet das Fenster an erster Stelle hinter den TopMost-Fenstern an. Die Operation hat Keine Auswirkung, wenn das Fenster nicht zu den TopMost-Fenstern gehört.
HWND_TOP
0
Ordnet das Fenster an vorderster Stelle an.
HWND_TOPMOST
-1
Ordnet das Fenster an vorderster Stelle an. Diese Position wird beibehalten, auch wenn das Fenster deaktiviert wird.
INVALID_HANDLE_VALUE
-1
Kennzeichnet ein ungültiges Handle.
KEY_ALL_ACCESS
&HF003F
Generalschlüssel ((STANDARD_RIGHTS_ALL Or KEY_QUERY_VALUE Or KEY_SET_VALUE Or KEY_CREATE_SUB_KEY Or KEY_ENUMERATE_SUB_KEYS Or KEY_NOTIFY Or KEY_CREATE_LINK) And (Not SYNCHRONIZE)
KEY_CREATE_LINK
&H20
Recht zur Erzeugung eines symbolischen Links
KEY_CREATE_SUB_KEY
4
Recht zur Erzeugung von Unterschlüsseln
KEY_ENUMERATE_SUB_KEYS
8
Enumerationsrecht
KEY_NOTIFY
&H10
Benachrichtigungsrecht bei Änderung
KEY_QUERY_VALUE
1
Leserecht
334
15
Konstanten
Name
Wert
Bedeutung
KEY_READ
&H20001
Vollständiges Leserecht STANDARD_RIGHTS_READ Or KEY_QUERY_VALUE
KEY_SET_VALUE
2
Schreibrecht in Wert
KEY_WRITE
&HF0002
Vollständiges Schreibrecht ((STANDARD_RIGHTS_ALL Or KEY_SET_VALUE) And (Not SYNCHRONIZE))
LF_FACESIZE
&H20
Maximale Größe von Fontattributen wie Stil oder Herkunft
LF_FULLFACESIZE
&H40
Maximale Größe von Fontnamen
LOCALE_ICOUNTRY
&H5
Country Code (z.B. 49 für Deutschland)
LOCALE_SCOUNTRY
&H6
Lokaler Landesname (z.B. Deutschland für Deutschland)
LOCALE_SCURRENCY
&H14
Lokales Währungssymbol (DM für Deutschland)
LOCALE_SDECIMAL
&HE
Dezimaltrennzeichen (üblicherweise »,« im deutschsprachigen Raum)
LOCALE_SENGCOUNTRY
&H1002
Englischer Landesname (z.B. Germany für Deutschland)
LOCALE_SENGLANGUAGE
&H1001
Englischer Name der Landessprache (German im deutschsprachigen Raum)
LOCALE_SINTLSYMBOL
&H15
Internationales Währungssymbol (DEM für Deutschland)
LOCALE_SLIST
&HC
Listentrennzeichen (üblicherweise »;« im deutschsprachigen Raum)
LOCALE_SLONGDATE
&H20
Lange Datumsformat, beispielsweise d. MMMM yyyy für 8. Juni 2000
LOCALE_SMONTHNAME1
&H38
Lokaler Name des ersten Monats (Januar bzw. Jänner im deutschsprachigen Raum)
LOCALE_SNATIVELANGNAME
&H4
Lokaler Name der Landessprache (Deutsch im deutschsprachigen Raum)
LOCALE_SSHORTDATE
&H1F
Kurzes Datumsformat, beispielsweise dd.MM.yyyy für 08.06.2000
335
Name
Wert
Bedeutung
LOCALE_USER_DEFAULT
&H400
Gebietsschema des angemeldeten Anwenders
MAX_PATH
260
Größtmöglicher Dateinamen und Registry Key
MM_ANISOTROPIC
8
Logical Units werden in ungleichmäßig skalierte Achsen übersetzt, deren Maße per SetWindowExtEx und SetViewportExtEx eingestellt werden können.
MM_HIENGLISH
5
Jede Logical Unit korrespondiert mit 0,001 Zoll. Positive X-Werte liegen rechts und positive Y-Werte oben.
MM_HIMETRIC
3
Jede Logical Unit korrespondiert mit 0,01 Millimeter. Positive X-Werte liegen rechts und positive Y-Werte oben.
MM_ISOTROPIC
7
Logical Units werden in gleichmäßig skalierte Achsen übersetzt, deren Maß per SetWindowExtEx and SetViewportExtEx eingestellt werden können.
MM_LOENGLISH
4
Jede Logical Unit korrespondiert mit 0,01 Zoll. Positive X-Werte liegen rechts und positive Y-Werte oben.
MM_LOMETRIC
2
Jede Logical Unit korrespondiert mit 0,1 Millimeter. Positive X-Werte liegen rechts und positive Y-Werte oben.
MM_TEXT
1
Jede Logical Unit korrespondiert mit einem Pixel. Positive X-Werte liegen rechts und positive Y-Werte unten.
MM_TWIPS
6
Jede Logical Unit korrespondiert mit einem Zwanzigstel Punkt (1/1440 Zoll). Positive X-Werte liegen rechts und positive Y-Werte oben.
NONANTIALIASED_QUALITY
3
Kein Antialiasing
336
15
Konstanten
Name
Wert
Bedeutung
OBJID_CLIENT
&HFFFFFFFC
Unterschiedliche Bedeutung. GetScrollBarInfo-Funktion: kennzeichnet ein ScrollBarSteuerelement selbst.
OBJID_HSCROLL
&HFFFFFFFA
Horizontale ScrollBar eines Fensters
OBJID_VSCROLL
&HFFFFFFFB
Vertikale ScrollBar eines Fensters
OPEN_ALWAYS
4
Datei öffnen und anlegen, wenn sie nicht existiert
OPEN_EXISTING
3
Datei öffnen
PRINTER_ACCESS_ADMINISTER
&H4
Administrationsrechte für SetPrinter
PRINTER_ACCESS_USE
&H8
Basisrechte
PRINTER_ALL_ACCESS
&HF000C000
(STANDARD_RIGHTS_ REQUIRED Or PRINTER_ACCESS_ADMINISTER Or PRINTER_ACCESS_USE)
PRINTER_ATTRIBUTE_LOCAL
&H40
Lokaler Drucker
PRINTER_ATTRIBUTE_NETWORK
&H10
Netzwerkdrucker
PRINTER_CONTROL_PAUSE
1
Unterbricht den Druck.
PRINTER_CONTROL_PURGE
3
Löscht alle Druckaufträge.
PRINTER_CONTROL_RESUME
2
Führt einen unterbrochenen Druck fort.
PRINTER_CONTROL_SET_STATUS
4
Verändert den Druckerstatus.
PRINTER_STATUS_BUSY
&H200
Drucker ist beschäftigt.
PRINTER_STATUS_DOOR_OPEN
&H400000
Eine Druckertür ist geöffnet.
PRINTER_STATUS_ERROR
&H2
Drucker hat Fehler.
PRINTER_STATUS_INITIALIZING
&H8000
Drucker wird initialisiert.
PRINTER_STATUS_IO_ACTIVE
&H100
Drucker ist in einem aktiven Einoder Ausgabe-Status.
PRINTER_STATUS_MANUAL_FEED
&H20
Die manuelle Papierzufuhr des Druckers ist aktiviert.
PRINTER_STATUS_NO_TONER
&H40000
Drucker benötigt Toner.
PRINTER_STATUS_NOT_AVAILABLE
&H1000
Drucker derzeit nicht verfügbar.
PRINTER_STATUS_OFFLINE
&H80
Drucker ist offline.
PRINTER_STATUS_OUT_OF_MEMORY
&H200000
Drucker hat Speicherproblem.
PRINTER_STATUS_OUTPUT_BIN_FULL
&H800
Papierausgabebehälter des Druckers ist voll.
337
Name
Wert
Bedeutung
PRINTER_STATUS_PAGE_PUNT
&H80000
Drucker kann die aktuelle Seite nicht zu Ende drucken, weil sie möglicherweise zu komplex ist.
PRINTER_STATUS_PAPER_JAM
&H8
Drucker hat Papierstau.
PRINTER_STATUS_PAPER_OUT
&H10
Drucker benötigt Papier.
PRINTER_STATUS_PAPER_PROBLEM
&H40
Drucker hat ein Papierproblem.
PRINTER_STATUS_PAUSED
&H1
Drucker wurde unterbrochen (kann nur gelesen werden).
PRINTER_STATUS_PENDING_DELETION
&H4
Drucker löscht einen Auftrag (kann nur gelesen werden).
PRINTER_STATUS_POWER_SAVE
&H1000000
Drucker im Stromsparmodus
PRINTER_STATUS_PRINTING
&H400
Drucker druckt (wie schön) ;-).
PRINTER_STATUS_PROCESSING
&H4000
Drucker führt einen Druckauftrag aus.
PRINTER_STATUS_SERVER_UNKNOWN
&H800000
Druckerstatus unbekannt
PRINTER_STATUS_TONER_LOW
&H2000
Toner geht aus.
PRINTER_STATUS_USER_INTERVENTION
&H100000
Eingriff des Benutzers am Drucker erforderlich.
PRINTER_STATUS_WAITING
&H2000
Drucker wartet.
PRINTER_STATUS_WARMING_UP
&H10000
Drucker in Aufwärmphase
PROOF_QUALITY
2
Erscheinungsform des Fonts von hoher Bedeutung
RASTER_FONTTYPE
1
Raster-Font
RDW_ALLCHILDREN
&H80
Redraw-Konstante, die dafür sorgt, dass die Operation zu allen untergeordneten Fenstern gesendet wird.
RDW_UPDATENOW
&H100
Redraw-Konstante, die WM_NCPAINT, WM_ERASEBKGND und WM_PAINT zu allen spezifizierten Fenstern sendet (RDW_ALLCHILDREN oder RDW_NOCHILDREN).
REG_BINARY
3
Binäre Daten
REG_DWORD
4
C-Long
REG_EXPAND_SZ
2
Unicode Null-terminated String
REG_MULTI_SZ
7
Multiple Unicode Strings
338
15
Konstanten
Name
Wert
Bedeutung
REG_OPTION_BACKUP_RESTORE
4
Ermöglicht ein Importieren und Exportieren des Schlüssels nebst aller Unterelemente von bzw. in Eine separate Datei. In diesem Falle wird samDesired ignoriert und der Schlüssel mit allen erforderlichen Rechten angelegt und geöffnet. (NT/2000)
REG_OPTION_NON_VOLATILE
0
Anzulegender Registry-Wert wird gespeichert und steht somit nach dem nächsten Systemstart zur Verfügung.
REG_OPTION_VOLATILE
1
Anzulegender Registry-Wert ist flüchtig und steht nach dem nächsten Systemstart nicht zur Verfügung (NT/2000).
REG_SZ
1
Unicode String
SB_CTL
2
Spezifiziert ein ScrollBar-Steuerelement.
SB_ENDSCROLL
8
ScrollBar-Nachricht, Ende einer Scroll-Bewegung
SB_HORZ
0
Spezifiziert die horizontale ScrollBar eines Fensters.
SB_THUMBTRACK
5
ScrollBar-Nachricht, Thumb wurde bewegt
SB_VERT
1
Spezifiziert die vertikale ScrollBar eines Fensters.
SE_ERR_ACCESSDENIED
5
Der Zugriff auf die Datei wurde verweigert.
SE_ERR_ASSOCINCOMPLETE
27
Die Zuordnung der Dateierweiterung ist fehlerhaft oder unvollständig.
SE_ERR_DDEBUSY
30
Die DDE-Transaktion konnte nicht abgeschlossen werden, da andere DDE-Transaktionen durchgeführt wurden.
SE_ERR_DDEFAIL
29
Die DDE-Transaktion schlug fehl.
SE_ERR_DDETIMEOUT
28
Die DDE-Transaktion konnte wegen eines Timeout nicht abgeschlossen werden.
339
Name
Wert
Bedeutung
SE_ERR_DLLNOTFOUND
32
Die angegebene DLL wurde nicht gefunden.
SE_ERR_FNF
2
Siehe ERROR_FILE_NOT_FOUND
SE_ERR_NOASSOC
31
Der Dateierweiterung ist keine Anwendung zugeordnet. Dieser Fehler wird auch erzeugt, wenn versucht wird, eine nicht druckbare Datei zu drucken.
SE_ERR_OOM
8
Es besteht nicht genügend Speicher, um die Operation zu Ende zu führen.
SE_ERR_PNF
3
siehe ERROR_PATH_NOT_FOUND
SE_ERR_SHARE
26
Ein Dateizugriffsfehler ist aufgetreten.
SHGFI_DISPLAYNAME
&H200
Icon-Handle
SHGFI_ICON
&H100
Anzeigename (Dateiname ohne Pfad)
SHGFI_TYPENAME
&H400
Typname
SIF_ALL
&H17
ScrollInfo, SIF_RANGE bis SIF_TRACKPOS
SIF_DISABLENOSCROLL
8
ScrollInfo, ScrollBar disabled beibehalten, wenn sie nicht mehr erforderlich ist.
SIF_PAGE
2
ScrollInfo, Seitengröße beim Scrollen
SIF_POS
4
ScrollInfo, aktueller Wert
SIF_RANGE
1
ScrollInfo, Min- und Max-Werte
SIF_TRACKPOS
&H10
ScrollInfo, aktueller Wert vor Anwender-Aktion
SM_SWAPBUTTON
23
SystemMetrics-Konstante zur Konfiguration der Maustasten für Linkshänder
SOCKET_ERROR
-1
Fehler in Windows Sockets API
STANDARD_RIGHTS_ALL
&H1F0000
Alle Standard-Zugriffsrechte
STANDARD_RIGHTS_READ
&H20000
Leserecht
STANDARD_RIGHTS_REQUIRED
&HF0000000
Basisrecht bestehend aus DELETE, READ_CONTROL, WRITE_DAC und WRITE_OWNER
340
15
Konstanten
Name
Wert
Bedeutung
SW_HIDE
0
Versteckt das Fenster und aktiviert ein anderes.
SW_MAXIMIZE
3
Maximiert das Fenster.
SW_MINIMIZE
6
Minimiert das Fenster und aktiviert das nächste in Z-Richtung.
SW_RESTORE
9
Aktiviert das Fenster und zeigt es somit an. Ist das Fenster minimiert oder maximiert, so wird die Originalgröße und -position wieder hergestellt. Dieses Flag sollte gesetzt werden, wenn ein minimiertes Fenster wieder hergestellt werden soll.
SW_SHOW
5
Aktiviert das Fenster und zeigt es in der aktuellen Größe und Position.
SW_SHOWDEFAULT
10
Startet die Anwendung mit dem Flag, das in der STARTUPINFOStruktur an die CreateProcessFunktion übergeben wird.
SW_SHOWMAXIMIZED
3
Aktiviert das Fenster und zeigt es maximiert an.
SW_SHOWMINIMIZED
2
Aktiviert das Fenster und zeigt es minimiert an.
SW_SHOWMINNOACTIVE
7
Zeigt das Fenster minimiert an. Das aktive Fenster bleibt hierbei aktiv.
SW_SHOWNA
8
Zeigt das Fenster in seiner aktuellen Größe und Position an. Das aktive Fenster bleibt hierbei aktiv.
SW_SHOWNOACTIVATE
4
Zeigt das Fenster in seiner letzten Größe und Position an. Das aktive Fenster bleibt hierbei aktiv.
SW_SHOWNORMAL
1
Aktiviert das Fenster und zeigt es an. Ist das Fenster minimiert oder maximiert, wird es in Originalgröße und -position angezeigt.
SWP_NOMOVE
2
Argumente x und y ignorieren.
SWP_NOSIZE
1
Argumente cx und cy ignorieren.
SYNCHRONIZE
&H100000
TMPF_DEVICE
8
Geräteabhängige Schrift
341
Name
Wert
Bedeutung
TMPF_FIXED_PITCH
1
Ist dieses Bit gesetzt, so handelt es sich um einen proportionalen (!) Font.
TMPF_TRUETYPE
4
TrueType-Schrift
TMPF_VECTOR
2
Vektorschrift
TRUETYPE_FONTTYPE
4
TrueType-Font
TRUNCATE_EXISTING
Datei öffnen und leeren.
VARIABLE_PITCH
2
Variabler Zeichenabstand
VER_PLATFORM_WIN32_NT
2
Windows NT oder 2000
VER_PLATFORM_WIN32_WINDOWS
1
Windows 95 oder 98
VER_PLATFORM_WIN32s
0
Win32s auf Windows 3.1
VK_CONTROL
&H11
Virtual-Key-Konstante für eine der beiden Steuerungstasten
VK_LBUTTON
1
Virtual-Key-Konstante für linke Maustaste (physikalisch)
VK_LCONTROL
&HA2
Virtual-Key-Konstante für linke Steuerungstaste
VK_LMENU
&HA4
Virtual-Key-Konstante für linke Alt-Taste
VK_LSHIFT
&HA0
Virtual-Key-Konstante für linke Shift-Taste
VK_MBUTTON
4
Virtual-Key-Konstante für mittlere Maustaste
VK_MENU
&H12
Virtual-Key-Konstante für eine der beiden Alt-Tasten
VK_RBUTTON
2
Virtual-Key-Konstante für rechte Maustaste (physikalisch)
VK_RCONTROL
&HA3
Virtual-Key-Konstante für rechte Steuerungstaste
VK_RMENU
&HA5
Virtual-Key-Konstante für rechte Alt-Taste
VK_RSHIFT
&HA1
Virtual-Key-Konstante für rechte Shift-Taste
VK_SHIFT
&H10
Virtual-Key-Konstante für eine der beiden Shift-Tasten
WM_NCHITTEST
&H84
Windows Messaging, kennzeichnet eine Cursor-Bewegung oder eine Veränderung einer Maustaste.
342
15
Konstanten
Name
Wert
Bedeutung
WM_PAINT
&HF
Windows Messaging, veranlasst ein Neuzeichnen eines Fensters.
WM_UNDO
&H304
Windows Messaging, macht die Änderung in einer TextBox rückgängig.
WM_VSCROLL
&H115
Windows Messaging, kennzeichnet eine vertikale Scroll-Bewegung oder löst sie aus.
WM_WININICHANGE
&H1A
Message-Konstante, die die Empfänger darüber informiert, dass beispielsweise ein neuer DefaultDrucker gewählt wurde.
16 Strukturen 16.1 ACL Eine ACL-Struktur ist der Header einer Access Control List. Eine komplette Access Control List besteht aus einer ACL-Struktur gefolgt von 0 bis mehreren Access Control Entries ACE). Name
Typ
Bedeutung
AclRevision
Byte
Revisionsstufe der ACL, sollte ACL_REVISION sein
Sbz1
Byte
0 (ein zero byte)
AclSize
Integer Größe von ACL plus ACEs
AceCount
Integer Anzahl der ACEs
Sbz2
Integer 0 (zwei zero bytes)
16.2 BROWSEINFO Diese Struktur dient der Kommunikation mit der SHBrowseForFolder-Funktion aus »Shell32.dll«. Name
Typ
Bedeutung
hWndOwner
Long
Handle des Besitzers dieses Dialoges oder 0
pIDLRoot
Long
Eine Struktur eines Namespace-Objekts für das Rootverzeichnis oder 0 als Long für Desktop-Ordner
pszDisplayName
Long
Der reine Name des ausgewählten Verzeichnisses
lpszTitle
String Titel des Dialogs
ulFlags
Long
Kombination von bis zu 13 Konstanten
lpfnCallback
Long
Adresse der Callback-Funktion
lParam
Long
Parameter, der zur Callback-Prozedur gesendet werden kann
iImage
Long
Index des Icons des ausgewählten Eintrags aus der System Image List
344
16
Strukturen
16.3 CHOOSECOLOR Diese Struktur dient der Kommunikation mit der ChooseColor-Funktion aus »Comdlg32.dll«. Name
Typ
Bedeutung
lStructSize
Long
Länge der kompletten Struktur in Bytes
hWndOwner
Long
Handle des Besitzers dieses Dialoges oder 0
hInstance
Long
Handle der aufrufenden Instanzen
rgbResult
Long
Der RGB-Wert, wenn eine Farbe gewählt wurde. Dieser Wert kann auch mit einer Farbe vorbelegt werden, sofern CC_RGBINIT in flags enthalten ist.
lpCustColors
Long
Zeiger auf ein Array von 16 Long-Werten, in denen die benutzerdefinierten Farben übergeben und wieder entgegengenommen werden können.
flags
Long
Kombination der Konstanten CC_ANYCOLOR, CC_FULLOPEN, CC_PREVENTFULLOPEN, CC_RGBINIT oder CC_SOLIDCOLOR
lCustData
Long
Parameter, der zur Hook-Prozedur gesendet wird
lpfnHook
Long
Zeiger auf Hook-Prozedur
lpTemplateName
String
Name der Vorlagenressource
16.4 DEVMODE Diese Struktur beinhaltet unfangreiche Informationen über die Fähigkeiten und Einstellungen eines Druckers. Name
Typ
Bedeutung
dmDeviceName
String
(32 Byte), der friendly-Name des Druckers, der unter den Gerätetreibern eindeutig ist.
dmSpecVersion
Integer
Laut MSDN sollen wir hier die Konstante DM_SPECVERSION = &H320 eintragen, die anzeigt, auf welcher Spezifikationsnummer die DEVMODE_Struktur basiert, wenn wir eine erzeugen. Lässt man sich die Nummer hingegen ausgeben, so kommt allerdings &H401 heraus. Zum Glück kann man dieses Element einfach so lassen, wie es ist.
dmDriverVersion
Integer
Versionsnummer des Druckertreibers
dmSize
Integer
Größe der DEVMODE-Struktur ohne dmDriverExtra
dmDriverExtra
Integer
Zahl der treiberspezifischen Bytes, die hinter der offiziellen DEVMODEStruktur anzutreffen sind.
ENUMLOGFONTEX
345
Name
Typ
Bedeutung
dmFields
Long
Eine Kombination aus DM-Konstanten, wobei ein enthaltener Wert bedeutet, dass der betreffende Drucker über das Merkmal verfügt.
dmOrientation
Integer
DMORIENT_PORTRAIT oder DMORIENT_LANDSCAPE
dmPaperSize
Integer
Papiergröße als eine der DMPAPER_-Konstanten
dmPaperLength
Integer
Papierhöhe in mm -1. Dieser Wert überschreibt dmPaperSize.
dmPaperWidth
Integer
Papierbreite in mm -1. Dieser Wert überschreibt dmPaperSize.
dmScale
Integer
Skalierungsfaktor des Ausdrucks bezogen auf 100. Ein Wert von 50 resultiert in einem Druck halber Höhe und Breite, also einem Viertel der Originalfläche.
dmCopies
Integer
Anzahl der Kopien je Blatt. Siehe hierzu auch dmCollate.
dmDefaultSource
Integer
Eine der DMBIN_-Konstante für Papierzufuhr
dmPrintQuality
Integer
Eine der (negativen) DMRES_-Druckauflösungskonstanten oder die Zahl der dots per inch (DPI), wenn die Zahl positiv ist
dmColor
Integer
DMCOLOR_MONOCHROME oder DMCOLOR_COLOR
dmDuplex
Integer
Steuert ein- oder beidseitigen Druck mit einer der DMDUP_Konstanten
dmYResolution
Integer
Vertikale Druckauflösung in dots per inch (DPI). Ist dieser Wert in dmFields initialisiert worden, so enthält dmPrintQuality die Auflösung in horizontaler Richtung.
dmTTOption
Integer
Steuert die Handhabung der TrueType-Schriften mit einer der DMTT_-Konstanten.
dmCollate
Integer
Steuert den Datenversand bei Mehrfachkopien mit einer der DMCOLLATE_-Konstanten.
dmFormName
String
(32 Byte), Formularname. Die vom Drucker unterstützten Formulare können über die EnumForms-Funktion ermittelt werden.
dmUnusedPadding
Integer
Wird derzeit nicht verwendet.
dmBitsPerPel
Long
Darf bei Druckern nicht verwendet werden.
dmPelsWidth
Long
Darf bei Druckern nicht verwendet werden.
dmPelsHeight
Long
Darf bei Druckern nicht verwendet werden.
dmDisplayFlags
Long
Darf bei Druckern nicht verwendet werden.
dmDisplayFrequency
Long
Darf bei Druckern nicht verwendet werden.
16.5 ENUMLOGFONTEX Diese Struktur, die in der Callback-Funktion ankommt, beinhaltet alle Informationen über einen Font.
346
16
Name
Strukturen
Typ
Bedeutung
elfLogFont
LOGFONT
Eine LOGFONT-Struktur
elfFullName
Byte-Array
64 Elemente, Schriftname: z.B. Arial, Courier New, Century Gothic
elfStyle
Byte-Array
32 Elemente, Stil: z.B. Standard, Regular, Fett, Kursiv, Fett Kursiv, Bold, Antiqua, Roman, Medium
elfScript
Byte-Array
32 Elemente, Schrifttyp: z.B. OEM/DOS, Symbol, Mitteleuropäisch, Westlich, Griechisch, Baltisch, Kyrillisch, Türkisch, Hebräisch.
16.6 FILETIME Dahinter steckt ein vorzeichenloser 64-Bit-Wert mit den seit dem 1. Januar 1601 vergangenen 100-ns-Intervallen. Das macht also 10 Millionen Einheiten je Sekunde. Die MSDN legt folgende Struktur nahe: Name
Typ
Bedeutung
dwLowDateTime
Long die unteren 32 Bit
dwHighDateTime
Long die oberen 32 Bit
Mit einem aus acht Elementen bestehenden Byte-Array sind wir jedoch besser bedient.
16.7 GUID Diese Struktur nimmt eine Globally Unique ID von der Funktion CoCreateGuid entgegen. Name
Typ Bedeutung
bytes(15) Byte Array zur Aufnahme der 16 Byte großen GUID
16.8 HOSTENT Die Struktur HOSTENT enthält Informationen über einen Netzwerkrechner. Name
Typ
Bedeutung
hName
Long
Zeiger auf DNS-Namen des Rechners
hAliases
Long
Zeiger auf Array von Alias-Namen des Rechners
LOGFONT
Name
347
Typ
Bedeutung
hAddrType Integer Adresstyp hLength
Integer Länge der hAddrList in Bytes
hAddrList
Long
Zeiger auf NULL-terminierte Liste von IP-Adressen, wobei uns Element 0 interessiert
16.9 LOGFONT Die LOGFONT-Struktur beschreibt einen logischen Font (logical font). Name
Typ
Bedeutung
lfHeight
Long
Höhe des Fonts in logical units
lfWidthin
Long
Breite des Fonts in logical units
lfEscapement
Long
Winkel der Fluchtlinie eines Zeichens zur X-Achse in Zehntel Grad
lfOrientation
Long
Winkel der Grundlinie eines Zeichens zur X-Achse in Zehntel Grad
lfWeight
Long
Maß für die Strichstärke zwischen 0 und 1000
lfItalic
Byte
0 nicht kursiv und 1 kursiv
lfUnderline
Byte
0 nicht unterstrichen und 1 unterstrichen
lfStrikeOut
Byte
0 nicht durchgestrichen und 1 durchgestrichen
lfCharSet
Byte
Konstante, mit der die Zeichensatzherkunft bezeichnet wird. Es gibt beispielsweise Konstanten für kyrillische, griechische oder gar chinesische Zeichensätze. Die MSDN empfiehlt allerdings, nur DEFAULT_CHARSET (1) zu verwenden, um unerwartete Ergebnisse zu vermeiden.
lfOutPrecision
Byte
Legt fest, ob beispielsweise ein Rasterfont, ein TrueType- oder geräteabhängiger Font gewählt werden soll, wenn mehrere Fonts des angegebenen Namens existieren.
lfClipPrecision
Byte
Steuert die Clipping-Präzision.
lfQuality
Byte
Steuert die Genauigkeit, mit der die anderen Parameter von dem ausgewählten Font eingehalten werden müssen.
lfPitchAndFamily
Byte
Beeinflusst die Schriftfamilie und den Pitch.
lfFaceName
String 32 Elemente, Name der Schriftart inkl. des abschließenden NULL
16.10 OPENFILENAME Diese Struktur dient der Kommunikation mit der GetOpenFileName- und der GetSaveFileName-Funktion aus »Comdlg32.dll«.
348
Name
16
Typ
Strukturen
Bedeutung
lStructSize
Long
Länge der kompletten Struktur in Bytes
hWndOwner
Long
Handle des Besitzers dieses Dialoges oder 0
hInstance
Long
Handle der aufrufenden Instanz oder 0
lpstrFilter
String
NULL-terminierter String mit Dateifilter
lpstrCustomFilter String
Filter ähnlich lpstrFilter, für uns jedoch ohne Bedeutung
nMaxCustFilter
Länge von lpstrCustomFilter
Long
nFilterIndex
Long
lfd. Nummer des vorselektierten Filters in lpstrFilter
lpstrFile
String
NULL-terminierter, vollständiger Name der ausgewählten Datei
nMaxFile
Long
Nettogröße von lpstrFile
lpstrFileTitle
String
NULL-terminierter Name (ohne Pfad) der ausgewählten Datei
nMaxFileTitle
Long
Nettolänge von lpstrFileTitle
lpstrInitialDir
String
anzuzeigendes Startverzeichnis
lpstrTitle
String
anzuzeigender Titel des Dialogs
flags
Long
26 teils miteinander kombinierbare Flags zur Steuerung des Dialogs
nFileOffset
Integer Zeiger auf erste Stelle des Dateinamens in lpstrFile
nFileExtension
Integer Zeiger auf erste Stelle der Dateierweiterung in lpstrFile
lpstrDefExt
String
Default-Dateierweiterung für nicht existierende Dateien
lCustData
Long
Parameter, der zur Hook-Prozedur gesendet wird
lpfnHook
Long
Zeiger auf Hook-Prozedur
lpTemplateName
String
Name der Vorlagenressource
16.11 OSVERSIONINFO Diese Struktur beinhaltet Informationen über das zugrunde liegende Betriebssystem. Name
Typ
Bedeutung
dwOSVersionInfoSize
Long Die Größe OSVERSIONINFO-Struktur in Bytes. Diesen Wert müssen wir übergeben.
dwMajorVersion
Long Das ist die Nummer der Hauptversion bei Windows NT.
dwMinorVersion
Long Die Nummer der Unterversion des Betriebssystems
dwBuildNumber
Long Mit einem Build wird ein Kompilat identifiziert.
dwPlatformId
Long Dieser Wert bildet die Gruppen Win32S auf Windows 3.1, Win 95 oder 98 sowie Win NT. Als Platform-IDs sind folgende Konstanten vorhanden: VER_PLATFORM_WIN32s, VER_PLATFORM_WIN32_WINDOWS oder VER_PLATFORM_WIN32_NT.
PRINTER_DEFAULTS
349
Name
Typ
Bedeutung
szCSDVersion
Long Bei Windows NT verbirgt sich dahinter das installierte Service Pack im Klartext.
16.12 PRINTER_DEFAULTS Die PRINTER_DEFAULTS enthält u.a. Initialisierungswerte und Zugriffsrechte für einen Drucker. Name
Typ
Bedeutung
pDatatype
String Dahinter verbirgt sich der Standard-Datentyp des Druckers, also »EMF« für PCL-, »RAW« für PostScript-Drucker oder TEXT für Textmodus.
pDevMode
Long
Ein Zeiger auf eine entsprechend parametrierte DEVMODE-Struktur oder 0
DesiredAccess
Long
Eine Kombination aus den DesiredAccess-Konstanten STANDARD_RIGHTS_REQUIRED, PRINTER_ACCESS_ADMINISTER, PRINTER_ACCESS_USE und PRINTER_ALL_ACCESS.
16.13 PRINTER_INFO_2 Die PRINTER_INFO_2-Struktur enthält allgemeine Informationen zu einem Drucker. Hier werden die String-Elemente als Long geführt, was ihrer Natur näher kommt. Bitte beachten Sie die Ausführung im Druckerkapitel über den Zugriff auf die DEVMODEStruktur. Name
Typ
Bedeutung
pServerName
Long Verweis auf den Namen des Servers, an dem der Drucker hängt
pPrinterName
Long Verweis auf den Druckernamen
pShareName
Long Verweis auf Freigabenamen des Druckers
pPortName
Long Verweis auf den Portnamen
pDriverName
Long Verweis auf den Namen des Druckertreibers
pComment
Long Verweis auf einen beschreibenden Text zum Drucker
pLocation
Long Verweis auf den Ort des Druckers
pDevMode
Long Verweis auf eine DEVMODE-Struktur
pSepFile
Long Verweis auf die Datei, die als Trennseite Jobs voneinander trennt
pPrintProcessor
Long Verweis auf den verwendeten Druckprozessor, z.B. winprint
pDatatype
Long Verweis auf Datentyp (RAW, EMF oder TEXT)
350
16
Name
Typ
pParameters
Long Verweis auf Parameter
Strukturen
Bedeutung
pSecurityDescriptor Long Verweis auf eine SECURITY_DESCRIPTOR-Struktur Attributes
Long Eine Kombination aus PRINTER_ATTRIBUTE_-Konstanten
Priority
Long Vom Spooler verwendete Priorität
DefaultPriority
Long Default-Priorität eines neuen Druckjobs
StartTime
Long Frühester Zeitpunkt in Minuten seit 12h00 GMT, zu dem der Job gedruckt werden kann
UntilTime
Long Spätester Zeitpunkt in Minuten seit 12h00 GMT, zu dem der Job gedruckt werden kann
Status
Long Eine Kombination aus PRINTER_STATUS_-Konstanten
cJobs
Long Anzahl der Druckjobs, die sich in der Warteschlange befinden
AveragePPM
Long Durchschnittliche Zahl der Seiten je Minute
16.14 PRINTER_INFO_4 Die PRINTER_INFO_4-Struktur enthält allgemeine Informationen zu einem Drucker. Diese Struktur wird nur in Windows NT und 2000 unterstützt. Name
Typ
Bedeutung
pPrinterName
String NULL-terminierter String mit dem Druckernamen
pServerName
String NULL-terminierter String mit dem Namen des Servers
Attributes
Long
PRINTER_ATTRIBUTE_LOCAL oder PRINTER_ATTRIBUTE_NETWORK
16.15 PRINTER_INFO_5 Die PRINTER_INFO_5-Struktur enthält allgemeine Informationen zu einem Drucker. Diese Struktur wird nur von Win 95 und 98 unterstützt. Name
Typ
Bedeutung
pPrinterName
String NULL-terminierter String mit dem Druckernamen
pPortName
String NULL-terminierter String mit dem Namen des Ports, z.B. LPT1:
Attributes
Long
Kombination aus PRINTER_ATTRIBUTE_-Konstanten
DeviceNotSelectedTimeout
Long
Maximale Dauer in Millisekunden zwischen zwei Versuchen, einen Drucker auszuwählen
TransmissionRetryTimeout
Long
Maximale Dauer in Millisekunden zwischen zwei Übertragungsversuchen
RECT
351
16.16 RECT Eine RECT-Struktur enthält zwei Koordinaten zur Beschreibung eines Rechtecks. Die Werte sind in Pixel definiert. Name
Typ
Bedeutung
Left
Long
X-Komponente der oberen linken Ecke
Top
Long
Y-Komponente der oberen linken Ecke
Right
Long
X-Komponente der unteren rechten Ecke
Bottom
Long
Y-Komponente der unteren rechten Ecke
16.17 SCROLLBARINFO Die SCROLLBARINFO-Struktur enthält detaillierte Informationen zu einer ScrollBar. Name
Typ
Bedeutung
cbSize
Long
Größe der Struktur
rcScrollBar
Long
Verweis auf eine RECT-Struktur mit den Koordinaten der ScrollBar
dxyLineButton
Long
Höhe oder Breite des Thumb (je nach Lage)
xyThumbTop
Long
Linke oder obere Kante des Thumb
xyThumbBottom
Long
Untere oder rechte Kante des Thumb
reserved
Long
Reserviert
rgstate
Long
Array aus 6 Long-Werten mit Statusinformationen zu den Elementen der ScrollBar
16.18 SCROLLINFO Diese Struktur beinhaltet die Parameter einer ScrollBar Name
Typ
Bedeutung
cbSize
Long Größe der Struktur
fMask
Long Eine Kombination aus SIF_-Konstanten die festlegen, welche Parameter ausgelesen oder verändert werden sollen
nMin
Long Min-Wert der ScrollBar
nMax
Long Max-Wert der ScrollBar
nPage
Long Seitengröße der ScrollBar
352
16
Name
Typ
nPos
Long Aktueller Wert der Scrollbar
nTrackPos
Long Aktueller Wert bei laufender Anwenderaktion (Dragging)
Strukturen
Bedeutung
16.19 SECURITY_DESCRIPTOR Diese Struktur enthält Sicherheitsinformationen des entsprechenden Objekts. Name
Typ
Revision Byte
Bedeutung Revisionsstand der Struktur
Sbz1
Byte
0 (ein so genanntes zero byte)
Control
Long
Steuerungsinformation aus SE_-Konstanten. Dieser Wert firmiert auch als SECURITY_DESCRIPTOR_CONTROL-Datentyp, aber dahinter verbirgt sich ein reiner Long-Wert.
Owner
Long
Verweis auf eine Security-ID mit Informationen über den Besitzer
Group
Long
Verweis auf eine Security-ID mit Informationen über die Besitzergruppe
Sacl
Long
Verweis auf eine System ACL-Struktur
Dacl
Long
Verweis auf eine Discretionary ACL-Struktur
16.20 SHFILEINFO SHFILEINFO enthält diverse Informationen zu einer Datei. Sie wird von der Funktion SHGetFileInfo verarbeitet. Name
Typ
Bedeutung
hIcon
Long
Handle des Icons, sofern SHGFI_ICON oder eine andere Icon-Konstante an SHGetFileInfo übergeben wurde
iIcon
Long
Index des Icons in der System Image List, sofern ein Handle angefordert wurde
dwAttributes
Long
Bit-Array mit Attributen der Datei
szDisplayName
String Auf MAX_PATH begrenzter Anzeigename der Datei, sofern SHGFI_DISPLAYNAME an SHGetFileInfo übergeben wurde
szTypeName
String Auf 80 Zeichen begrenzter Typename der Datei, sofern SHGFI_TYPENAME an SHGetFileInfo übergeben wurde
TEXTMETRIC
353
16.21 TEXTMETRIC Die TEXTMETRIC-Struktur beinhaltet Informationen über den Aufbau einer Schrift. Mit Ausnahme von tmWeight hängen die Maße vom aktuellen map mode ab. Name
Typ
Bedeutung
tmHeight
Long
Gesamthöhe der Schrift inklusive Oberlänge und Unterlänge
tmAscent
Long
Anteil der Höhe oberhalb der Grundlinie, also Körper und Oberlänge
tmDescent
Long
Anteil der Höhe unterhalb der Grundlinie, also Unterlänge
tmInternalLeading
Long
Höhe innerhalb des Zeichens für Akzente (kann 0 sein)
tmExternalLeading
Long
Abstand zwischen zwei Zeilen (kann 0 sein)
tmAveCharWidth
Long
Mittlere Breite eines Zeichens (üblicherweise identisch mit dem Zeichen »x«) in normalem Schriftschnitt, also ohne kursive oder fette Eigenheiten
tmMaxCharWidth
Long
Breite des breitesten Zeichens
tmWeight
Long
Wert zwischen 0 und 1000, der die Strichstärke beschreibt (siehe auch das lfWeight-Element der LOGFONT-Struktur)
tmOverhang
Long
Maß für die Verdickung eines fetten zu einem normalen Zeichen oder den Überhang, den ein kursives Zeichen gegenüber einem normalen Zeichen aufweist.
tmDigitizedAspectX
Long
Horizontales Lagemaß des Gerätes, für den der Font entworfen wurde
tmDigitizedAspectY
Long
Vertikales Lagemaß des Gerätes, für den der Font entworfen wurde
tmFirstChar
Byte
ANSI-Code des ersten Zeichens des Fonts (i. d. R. 32 für Leerzeichen)
tmLastChar
Byte
ANSI-Code des letzten Zeichens des Fonts (i. d. R. 255)
tmDefaultChar
Byte
ANSI-Code des Zeichens, das dargestellt werden soll, wenn ein Zeichen angefordert wurde, das außerhalb der Grenzen von tmFirstChar und tmLastChar liegt
tmBreakChar
Byte
ANSI-Code des Zeichens, das Wörter voneinander trennt
tmItalic
Byte
Ungleich 0 bei kursiver Schrift
tmUnderlined
Byte
Ungleich 0 bei unterstrichener Schrift
tmStruckOut
Byte
Ungleich 0 bei durchgestrichener Schrift
tmPitchAndFamily
Byte
Bitmaske für Schriftstil und -familie
tmCharSet
Byte
Konstante, mit der die Zeichensatzherkunft bezeichnet wird. So gibt es zum Beispiel Konstanten für kyrillische oder griechische Zeichensätze. Wir erhalten 0 für ANSI_CHARSET.
354
16
Strukturen
16.22 WSADATA WSADATA enthält im Wesentlichen Informationen über die Version der eingesetzten Windows Sockets Bibliothek. Name
Typ
Bedeutung
wVersion
Integer Kleinste verfügbare Version gemäß Anforderung
wHighVersion
Integer Größte verfügbare Version
szDescription(256)
Byte
Maximal 256 (+ NULL) Zeichen langer Text des Herstellers
szSystemStatus(128)
Byte
Maximal 128 (+ NULL) Zeichen zusätzlicher Text
iMaxSockets
Integer Ursprüngliche Zahl der verfügbaren Sockets, soll aber nun ignoriert werden
iMaxUdpDg
Integer Maximale Nachrichtengröße, die auch ignoriert werden soll
lpszVendorInfo
Long
Zeiger auf Informationen, die auch ignoriert werden sollen
Index
A
Access Control Entries 343 Access Control List 327, 343 ACL_REVISION-Konstante 327 AddressOf-Operator 209, 239 ANTIALIASED_QUALITYKonstante 207, 327 B
BIF_DONTGOBELOWDOMAINKonstante 81, 83, 327 BIF_RETURNONLYFSDIRSKonstante 81, 83, 327 BROWSEINFO-Struktur 81, 297, 318, 343 C
Callback 204 CallWindowProc-Funktion 241, 242, 261 CC_ANYCOLOR-Konstante 79, 327, 344 CC_FULLOPEN-Konstante 79, 80, 327, 344 CC_PREVENTFULLOPENKonstante 79, 327, 344 CC_RGBINIT-Konstante 78, 79, 80, 327, 344 CC_SOLIDCOLOR-Konstante 79, 80, 327, 344 CHOOSECOLOR-Funktion 59, 78, 80, 344 CHOOSECOLOR-Struktur 262 CloseHandle-Funktion 116, 262, 263 ClosePrinter-Funktion 182, 183, 184, 186, 195, 263
CoCreateGUID-Funktion 263 CoFileTimeNow-Funktion 264 CopyMemory-Funktion 94, 95, 195, 264, 265, 315 CREATE_ALWAYS-Konstante 115, 327 CREATE_NEW-Konstante 115, 327 CreateDC-Funktion 200, 201, 265, 267 CreateFile-Funktion 113, 114, 115, 116, 117, 266 CreateFontIndirect-Funktion 219, 266, 268 D
DC_BINNAMES-Konstante 196, 197, 269, 327 DC_BINS-Konstante 196, 197, 269, 328 DC_COPIES-Konstante 269, 328 DC_DUPLEX-Konstante 269, 328 DEFAULT_CHARSET-Konstante 207, 209, 271, 328, 347 DEFAULT_PITCH-Konstante 208, 328 DEFAULT_QUALITY-Konstante 207, 328 DeleteDC-Funktion 179, 197, 201, 267 DELETE-Konstante 114, 127, 182, 328 DeleteObject-Funktion 218, 219, 267, 268 DELETE-Struktur 339 DestroyIcon-Funktion 35, 167, 170, 171, 268, 273 Device Context 35, 46, 169, 170, 205, 209, 222, 269, 270 DEVICE_FONTTYPE-Konstante 210, 328
356
DeviceCapabilities-Funktion 195, 196, 197, 268, 269, 328 DEVMODE-Struktur 187, 192, 194, 195, 198, 199, 200, 344 DM_COLLATE-Konstante 188, 328 DM_COLOR-Konstante 188, 328 DM_COPIES-Konstante 188, 328 DM_DEFAULTSOURCE-Konstante 188, 328 DM_DITHERTYPE-Konstante 188, 328 DM_DUPLEX-Konstante 188, 328 DM_FORMNAME-Konstante 188, 328 DM_ICMINTENT-Konstante 188, 328 DM_ICMMETHOD-Konstante 188, 328 DM_MEDIATYPE-Konstante 188, 328 DM_ORIENTATION-Konstante 187, 328 DM_PAPERLENGTH-Konstante 187, 328 DM_PAPERSIZE-Konstante 187, 328 DM_PAPERWIDTH-Konstante 188, 328 DM_PRINTQUALITY-Konstante 188, 329 DM_SCALE-Konstante 188, 329 DM_TTOPTION-Konstante 188, 329 DM_YRESOLUTION-Konstante 188, 329 DMBIN_AUTO-Konstante 190, 329 DMBIN_CASSETTE-Konstante 190, 329 DMBIN_ENVELOPE-Konstante 190, 329 DMBIN_ENVMANUAL-Konstante 190, 329 DMBIN_LARGECAPACITYKonstante 190, 329 DMBIN_LARGEFMT-Konstante 190, 329 DMBIN_LOWER-Konstante 190, 329 DMBIN_MANUAL-Konstante 190, 329 DMBIN_MIDDLE-Konstante 190, 329 DMBIN_ONLYONE-Konstante 190, 329 DMBIN_SMALLFMT-Konstante 190, 329 DMBIN_TRACTOR-Konstante 190, 329 DMBIN_UPPER-Konstante 190, 329 DMBIN_USER-Konstante 190, 329
Index
DMCOLLATE_FALSE-Konstante 192, 329 DMCOLLATE_TRUE-Konstante 192, 329 DMCOLOR_COLOR-Konstante 191, 329, 345 DMCOLOR_MONOCHROMEKonstante 191, 329, 345 DMDUP_HORIZONTALKonstante 191, 329 DMDUP_SIMPLEX-Konstante 191, 329 DMDUP_VERTICAL-Konstante 191, 329 DMORIENT_LANDSCAPEKonstante 188, 200, 329, 345 DMORIENT_PORTRAITKonstante 188, 200, 329, 345 DMPAPER_A3-Konstante 189, 329 DMPAPER_A4-Konstante 189, 329 DMPAPER_A5-Konstante 189, 329 DMPAPER_B4-Konstante 189, 329 DMPAPER_B5-Konstante 189, 329 DMPAPER_ENV_B4-Konstante 189, 330 DMPAPER_ENV_B5-Konstante 189, 330 DMPAPER_ENV_B6-Konstante 189, 330 DMPAPER_ENV_C3-Konstante 189, 330 DMPAPER_ENV_C4-Konstante 189, 330 DMPAPER_ENV_C5-Konstante 189, 330 DMPAPER_ENV_C65-Konstante 189, 330 DMPAPER_ENV_C6-Konstante 189, 330 DMPAPER_ENV_DL-Konstante 189, 330 DMPAPER_ENV_ITALYKonstante 189, 330 DMPAPER_QUARTO-Konstante 189, 330 DMRES_DRAFT-Konstante 190, 330 DMRES_HIGH-Konstante 190, 330 DMRES_LOW-Konstante 190, 330 DMRES_MEDIUM-Konstante 190, 330 DMTT_BITMAP-Konstante 191, 330
Index
DMTT_DOWNLOAD_OUTLINEKonstante 191, 330 DMTT_DOWNLOAD-Konstante 191, 330 DMTT_SUBDEV-Konstante 191, 330 DRAFT_QUALITY-Konstante 208, 330 DrawIcon-Funktion 34, 35, 170, 171, 269, 270 DRIVE_CDROM-Konstante 31, 100, 104, 105, 279, 280, 330 DRIVE_FIXED-Konstante 31, 100, 279, 330 DRIVE_NO_ROOT_DIR-Konstante 279, 330 DRIVE_RAMDISK-Konstante 31, 100, 279, 330 DRIVE_REMOTE-Konstante 31, 100, 279, 330 DRIVE_REMOVABLE-Konstante 31, 100, 279, 331 DRIVE_UNKNOWN-Konstante 279, 331 Duplexdruck 269 E
EnumFontFamiliesEx-Funktion 44, 205, 209, 270, 271 ENUMLOGFONTEX-Struktur 210, 345 EnumPrinters-Funktion 174, 175, 176, 178, 180, 184, 185, 272 Environment-Variable 133 ERROR_BAD_FORMAT-Konstante 248, 322, 331 ERROR_FILE_NOT_FOUNDKonstante 130, 138, 248, 302, 331, 339 ERROR_FILE_NOT_FOUNDKonstanteKonstante 140 ERROR_PATH_NOT_FOUNDKonstante 248, 331, 339 ERROR_SHARING_VIOLATIONKonstante 116, 117, 331 ERROR_SUCCESS-Konstante 130, 131, 137, 138, 139, 140, 149, 150, 331 ExpandEnvironmentStringsFunktion 132, 133, 272 Expandieren eines Strings 131 ExtractIcon-Funktion 166, 168, 273
357 F
Fenster-Handle 204, 223, 250, 254, 296, 316, 317 FF_DECORATIVE-Konstante 208, 331 FF_DONTCARE-Konstante 208, 331 FF_MODERN-Konstante 208, 331 FF_ROMAN-Konstante 208, 331 FF_SCRIPT-Konstante 208, 331 FF_SWISS-Konstante 208, 331 FILE_ATTRIBUTE_ARCHIVEKonstante 115, 331 FILE_ATTRIBUTE_ENCRYPTEDKonstante 115, 331 FILE_ATTRIBUTE_HIDDENKonstante 115, 331 FILE_ATTRIBUTE_NORMALKonstante 115, 116, 117, 266, 331 FILE_ATTRIBUTE_NOT_CONTENT_INDEXED-Konstante 115, 331 FILE_ATTRIBUTE_OFFLINEKonstante 115, 331 FILE_ATTRIBUTE_READONLYKonstante 115, 331 FILE_ATTRIBUTE_SYSTEMKonstante 116, 332 FILE_ATTRIBUTE_TEMPORARYKonstante 116, 332 FILE_SHARE_DELETE-Konstante 114, 332 FILE_SHARE_READ-Konstante 114, 332 FILE_SHARE_WRITE-Konstante 114, 332 FILETIME-Struktur 146, 148, 149, 153, 304, 306, 346 FindWindow-Funktion 249, 250, 273, 274 FIXED_PITCH-Konstante 208, 332 Font Mapping 212 FORMAT_MESSAGE_FROM_SYSTEMKonstante 141, 142, 274, 275, 332 FormatMessage-Funktion 127, 136, 141, 142, 143, 274, 275 FW_BLACK-Konstante 207, 332 FW_BOLD-Konstante 206, 332 FW_DEMIBOLD-Konstante 206, 332 FW_DONTCARE-Konstante 206, 332
358
FW_EXTRABOLD-Konstante 206, 332 FW_EXTRALIGHT-Konstante 206, 332 FW_HEAVY-Konstante 207, 332 FW_LIGHT-Konstante 206, 332 FW_MEDIUM-Konstante 206, 332 FW_NORMAL-Konstante 206, 332 FW_REGULAR-Konstante 206 FW_SEMIBOLD-Konstante 206, 332 FW_THIN-Konstante 206, 332 FW_ULTRABOLD-Konstante 206, 332 FW_ULTRALIGHT-Konstante 206, 332 G
GENERIC_READ-Konstante 114, 117, 266, 332 GENERIC_WRITE-Konstante 114, 332 Gerätekontext 44, 46, 169, 179, 200, 201, 204, 205, 209, 211, 212, 217, 219, 222, 223, 265, 267, 270, 292, 293, 295, 309, 310, 313 GetAsyncKeyState-Funktion 259, 260, 275 GetComputerName-Funktion 86, 275, 276 GetDefaultPrinter-Funktion 180, 181, 276, 277 GetDesktopWindow-Funktion 209, 221, 222 GetDiskFreeSpaceEx-Funktion 106, 107, 108, 109, 110, 111, 278 GetDiskFreeSpace-Funktion 106, 107, 110, 111, 277, 278 GetDriveType-Funktion 102, 103, 104, 105, 279, 280 GetHostByName-Funktion 86, 93, 265, 280 GetHostName-Funktion 87, 95, 281 GetLocaleInfo-Funktion 67, 68, 69, 70, 281, 282, 283, 294 GetLogicalDrives-Funktion 100, 283 GetLogicalDriveStrings-Funktion 100, 101, 103, 283, 284 GetModuleHandle-Funktion 106, 107, 110, 167, 168, 273, 284, 286 GetOpenFileName-Funktion 25, 71, 76, 77, 284, 347
Index
GetPrinter-Funktion 183, 185, 186, 193, 194, 195, 198, 285, 315 GetProcAddress-Funktion 106, 107, 110, 284, 286 GetSaveFileName-Funktion 25, 76, 286, 347 GetScrollBarInfo-Funktion 230, 242, 287, 288 GetScrollInfo-Funktion 230, 232, 233, 236, 242, 288 GetScrollPos-Funktion 230, 233, 236, 289 GetScrollRange-Funktion 230, 233, 234, 235, 289, 290 GetStockObject-Funktion 291 GetSystemDefaultLCID-Funktion 282, 291 GetSystemMetrics-Funktion 258, 291, 292 GetTextFace-Funktion 211, 292 GetTextMetrics-Funktion 214, 293 GetTickCount-Funktion 20, 293 GetUserDefaultLCID-Funktion 67, 70, 282, 294, 313 GetUserName-Funktion 60, 86, 294 GetVersionEx-Funktion 87, 88, 89, 90, 294, 295 GetWindowDC-Funktion 209, 221, 222, 223, 295, 309 GetWindowLong-Funktion 296 GUID 30, 96, 97, 263, 323 GUID-Struktur 96, 97, 263, 323, 346 GWL_STYLE-Konstante 296, 316, 332 GWL_WNDPROC-Konstante 240, 296, 316, 332 H
Handle 249, 251, 255, 261, 274 Header-Datei 63, 64 HKEY_CLASSES_ROOT-Konstante 36, 53, 113, 121, 122, 151, 155, 158, 162, 163, 164, 332 HKEY_CURRENT_CONFIGKonstante 36, 122, 158, 332 HKEY_CURRENT_USER-Konstante 36, 65, 122, 126, 129, 134, 137, 139, 143, 149, 152, 155, 158, 180, 302, 303, 304, 306, 308, 333
Index
HKEY_DYN_DATA-Konstante 36, 122, 333 HKEY_LOCAL_MACHINEKonstante 36, 122, 158, 333 HKEY_USERS-Konstante 36, 122, 155, 158, 333 Hook 230, 239, 240, 241, 316 HOSTENT-Struktur 95, 346 HWND_BOTTOM-Konstante 224, 251, 317, 333 HWND_BROADCAST-Konstante 184, 299, 311, 333 HWND_NOTOPMOST-Konstante 224, 251, 254, 317, 333 HWND_TOP-Konstante 224, 251, 317, 333 HWND_TOPMOST-Konstante 224, 226, 251, 254, 317, 333 I
INVALID_HANDLE_VALUEKonstante 116, 117, 266, 333 IP-Adresse 30, 85, 90, 91, 94, 95 K
KEY_ALL_ACCESS-Konstante 127, 135, 140, 149, 153, 303, 304, 305, 307, 333 KEY_CREATE_LINK-Konstante 127, 333 KEY_CREATE_SUB_KEYKonstante 127, 135, 333 KEY_ENUMERATE_SUB_KEYSKonstante 127, 333 KEY_NOTIFY-Konstante 127, 333 KEY_QUERY_VALUE-Konstante 126, 127, 333 KEY_READ-Konstante 126, 127, 130, 169, 306, 308, 334 KEY_SET_VALUE-Konstante 126, 127, 135, 334 KEY_WRITE-Konstante 127, 135, 138, 302, 309, 334 L
Language-ID 66, 141, 185, 274 LF_FACESIZE-Konstante 334 LF_FULLFACESIZE-Konstante 334
359
Locale 282 LOCALE_ICOUNTRY-Konstante 70, 334 LOCALE_SCOUNTRY-Konstante 334 LOCALE_SCURRENCY-Konstante 68, 334 LOCALE_SDECIMAL-Konstante 68, 334 LOCALE_SENGCOUNTRYKonstante 334 LOCALE_SENGLANGUAGEKonstante 68, 334 LOCALE_SINTLSYMBOLKonstante 334 LOCALE_SLIST-Konstante 68, 334 LOCALE_SLONGDATE-Konstante 68, 334 LOCALE_SMONTHNAME1Konstante 68, 334 LOCALE_SNATIVELANGNAMEKonstante 68, 334 LOCALE_SSHORTDATE-Konstante 68, 334 LOCALE_USER_DEFAULTKonstante 67, 335 Locale-ID 66, 67, 291, 294 LOGFONT-Struktur 205, 209, 217, 219, 266, 267, 270, 346, 347 Logical Font 206, 347 Logical Unit 206, 347 logische Fonts 204, 212 logische Maustaste 257 logische Objekte 204 lpBuffer 142 lstrcat-Funktion 296 lstrcpy-Funktion 265, 297, 298 M
Map-Modus 213, 214, 353 MAX_PATH-Konstante 73, 118, 335, 352 MM_ANISOTROPIC-Konstante 213, 314, 335 MM_HIENGLISH-Konstante 213, 314, 335 MM_HIMETRIC-Konstante 213, 314, 335
360
MM_ISOTROPIC-Konstante 213, 314, 335 MM_LOENGLISH-Konstante 213, 314, 335 MM_LOMETRIC-Konstante 213, 314, 335 MM_TEXT-Konstante 314, 335 MM_TWIPS-Konstante 293, 314, 335 Module Handle 106 MSDN 56 N
NONANTIALIASED_QUALITYKonstante 208, 335 O
OBJID_CLIENT-Konstante 287, 336 OBJID_HSCROLL-Konstante 287, 336 OBJID_VSCROLL-Konstante 242, 287, 288, 336 OPEN_ALWAYS-Konstante 115, 336 OPEN_EXISTING-Konstante 115, 117, 266, 336 Openfilename-Funktion 287 OPENFILENAME-Struktur 71, 73, 76 OpenPrinter-Funktion 181, 182, 183, 194, 195, 263, 285, 298, 299 OSVERSIONINFO-Struktur 88, 295, 348 P
Papierzufuhr 42, 173, 186, 188, 190, 197, 198, 199, 200, 328, 336, 345 physikalische Maustaste 257 PostMessage-Funktion 255, 261, 299, 324 PRINTER_ACCESS_ADMINISTERKonstante 182, 336, 349 PRINTER_ACCESS_USEKonstante 182, 336, 349 PRINTER_ALL_ACCESS-Funktion 299 PRINTER_ALL_ACCESSKonstante 182, 183, 194, 195, 285, 336, 349 PRINTER_ATTRIBUTE_LOCALKonstante 336
Index
PRINTER_ATTRIBUTE_NETWORKKonstante 336 PRINTER_CONTROL_PAUSEKonstante 315, 336 PRINTER_CONTROL_PURGEKonstante 315, 336 PRINTER_CONTROL_RESUMEKonstante 315, 336 PRINTER_CONTROL_SET_STATUSKonstante 315, 336 PRINTER_DEFAULTS-Struktur 182, 183, 194, 195, 298, 349 PRINTER_INFO_2-Struktur 63, 185, 186, 192, 193, 194, 195, 285, 315, 349 PRINTER_INFO_4-Struktur 175, 176, 178, 350 PRINTER_INFO_5-Struktur 176, 179, 183 PRINTER_STATUS_BUSYKonstante 336 PRINTER_STATUS_DOOR_OPENKonstante 336 PRINTER_STATUS_ERRORKonstante 336 PRINTER_STATUS_INITIALIZINGKonstante 336 PRINTER_STATUS_IO_ACTIVEKonstante 336 PRINTER_STATUS_MANUAL_FEEDKonstante 336 PRINTER_STATUS_NO_TONERKonstante 336 PRINTER_STATUS_NOT_AVAILABLEKonstante 336 PRINTER_STATUS_OFFLINEKonstante 336 PRINTER_STATUS_OUT_OF_ MEMORY-Konstante 336 PRINTER_STATUS_OUTPUT_BIN_ FULL-Konstante 336 PRINTER_STATUS_PAGE_PUNTKonstante 337 PRINTER_STATUS_PAPER_JAMKonstante 337
Index
PRINTER_STATUS_PAPER_OUTKonstante 337 PRINTER_STATUS_PAPER_PROBLEMKonstante 337 PRINTER_STATUS_PAUSEDKonstante 337 PRINTER_STATUS_PENDING_ DELETION-Konstante 337 PRINTER_STATUS_POWER_SAVEKonstante 64, 337 PRINTER_STATUS_PRINTINGKonstante 337 PRINTER_STATUS_PROCESSINGKonstante 337 PRINTER_STATUS_SERVER_ UNKNOWN-Konstante 337 PRINTER_STATUS_TONER_LOWKonstante 337 PRINTER_STATUS_USER_INTERVENTION-Konstante 337 PRINTER_STATUS_WAITINGKonstante 337 PRINTER_STATUS_WARMING_UPKonstante 337 PROOF_QUALITY-Konstante 208, 330, 337 R
RASTER_FONTTYPE-Konstante 210, 337 RDW_ALLCHILDREN-Konstante 253, 254, 301, 337 RDW_UPDATENOW-Konstante 253, 254, 301, 337 RECT-Struktur 249, 253, 300, 351 RedrawWindow-Funktion 253, 254, 300, 301 REG_BINARY-Konstante 123, 124, 129, 130, 131, 137, 138, 308, 309, 337 REG_DWORD-Konstante 37, 123, 124, 129, 130, 131, 137, 138, 308, 309, 337 REG_EXPAND_SZ-Konstante 123, 124, 129, 130, 131, 137, 138, 272, 308, 309, 337 REG_MULTI_SZ-Konstante 123, 124, 129, 130, 137, 138, 308, 309, 337
361
REG_OPTION_BACKUP_RESTOREKonstante 135, 338 REG_OPTION_NON_VOLATILEKonstante 134, 338 REG_OPTION_VOLATILEKonstante 134, 338 REG_SZ-Konstante 123, 124, 129, 130, 137, 138, 308, 309, 338 RegCloseKey-Funktion 127, 131, 139, 140, 150, 153, 169, 301 RegCreateKeyEx-Funktion 134, 135, 136, 138, 301, 302 RegDeleteKey-Funktion 302, 303 RegDeleteValue-Funktion 139, 140, 303, 304 RegEnumKeyEx-Funktion 146, 148, 150, 151, 304, 305 RegEnumValue-Funktion 151, 153, 305, 306 RegOpenKeyEx-Funktion 125, 126, 127, 128, 130, 135, 136, 138, 140, 149, 153, 169, 301, 302, 303, 304, 305, 306, 307, 308, 309 RegQueryInfoKey-Funktion 146, 148, 150, 151, 152, 153, 304, 305, 306, 307 RegQueryValueEx-Funktion 126, 128, 130, 131, 169, 307, 308 RegSetValueEx-Funktion 134, 136, 137, 138, 308, 309 ReleaseDC-Funktion 222, 309, 310 S
SB_CTL-Konstante 232, 288, 338 SB_ENDSCROLL-Konstante 242, 338 SB_HORZ-Konstante 232, 288, 338 SB_THUMBTRACK-Konstante 338 SB_VERT-Konstante 232, 233, 234, 235, 242, 288, 289, 290, 338 SCROLLBARINFO-Struktur 287, 288, 351 SCROLLINFO-Struktur 231, 232, 233 SE_ERR_ACCESSDENIEDKonstante 248, 322, 338 SE_ERR_ASSOCINCOMPLETEKonstante 248, 322, 338 SE_ERR_DDEBUSY-Konstante 248, 322, 338
362
SE_ERR_DDEFAIL-Konstante 248, 322, 338 SE_ERR_DDETIMEOUT-Konstante 248, 322, 338 SE_ERR_DLLNOTFOUNDKonstante 248, 322, 339 SE_ERR_FNF-Konstante 248, 322, 339 SE_ERR_NOASSOC-Konstante 248, 322, 339 SE_ERR_OOM-Konstante 248, 322, 339 SE_ERR_PNF-Konstante 248, 322, 339 SE_ERR_SHARE-Konstante 248, 322, 339 SECURITY_DESCRIPTOR-Struktur 193, 350, 352 SelectObject-Funktion 218, 219, 291, 310 SendMessage-Funktion 21, 157, 183, 184, 227, 228, 252, 254, 255, 261, 299, 310, 311 SetDefaultPrinter-Funktion 181, 183, 184, 312 SetLocaleInfo-Funktion 312, 313 SetMapMode-Funktion 213, 293, 313, 314 SetPrinter-Funktion 182, 183, 184, 194, 195, 315 SetWindowLong-Funktion 239, 240, 296, 315, 316 SetWindowPos-Funktion 223, 226, 250, 251, 254, 316, 317 SHBrowseForFolder-Funktion 81, 317, 318, 319 ShellExecute-Funktion 246, 249, 250, 319, 320, 321, 322 SHFILEINFO-Struktur 117, 119, 318, 352 SHGetFileInfo-Funktion 113, 117, 119, 318, 319, 352 SHGetPathFromIDList-Funktion 83, 318, 319 SHGFI_DISPLAYNAME-Konstante 118, 119, 318, 339, 352 SHGFI_ICON-Konstante 118, 318, 339, 352 SHGFI_TYPENAME-Konstante 118, 119, 318, 319, 339, 352 SIF_ALL-Konstante 231, 339
Index
SIF_DISABLENOSCROLLKonstante 231, 339 SIF_PAGE-Konstante 231, 339 SIF_POS-Konstante 231, 339 SIF_RANGE-Konstante 231, 233, 339 SIF_TRACKPOS-Konstante 231, 339 Sleep-Funktion 19, 20, 49, 225, 249, 250, 255, 323, 324 SM_SWAPBUTTON-Konstante 258, 292, 339 SOCKET_ERROR-Konstante 87, 281, 325, 339 STANDARD_RIGHTS_ALLKonstante 126, 127, 135, 334, 339 STANDARD_RIGHTS_READKonstante 126, 334, 339 STANDARD_RIGHTS_REQUIREDKonstante 182, 339, 349 Stock Object 290 StringFromGUID2-Funktion 96, 97, 263, 323 StrLen-Funktion 133, 324 SW_HIDE-Konstante 247, 321, 340 SW_MAXIMIZE-Konstante 247, 340 SW_MINIMIZE-Konstante 247, 321, 340 SW_RESTORE-Konstante 247, 340 SW_SHOWDEFAULT-Konstante 247, 340 SW_SHOW-Konstante 247, 340 SW_SHOWMAXIMIZEDKonstante 247, 321, 340 SW_SHOWMINIMIZED-Konstante 247, 321, 340 SW_SHOWMINNOACTIVEKonstante 247, 340 SW_SHOWNA-Konstante 247, 340 SW_SHOWNOACTIVATEKonstante 247, 321, 340 SW_SHOWNORMAL-Konstante 248, 321, 340 SWP_NOMOVE-Konstante 225, 226, 252, 254, 317, 340 SWP_NOSIZE-Konstante 225, 226, 252, 254, 317, 340 SYNCHRONIZE-Konstante 126, 135, 333, 340 System Image List 82, 118, 343
Index T
TEXTMETRIC-Struktur 213, 214, 353 TMPF_DEVICE-Konstante 216, 340 TMPF_FIXED_PITCH-Konstante 216, 341 TMPF_TRUETYPE-Konstante 216, 341 TMPF_VECTOR-Konstante 216, 341 Top-Level-Fenster 249, 254, 273, 299, 311, 324, 333 TRUETYPE_FONTTYPE-Konstante 210, 341 TRUNCATE_EXISTING-Konstante 115, 341 V
VARIABLE_PITCH-Konstante 208, 341 VER_PLATFORM_WIN32_NTKonstante 89, 341 VER_PLATFORM_WIN32_WINDOWSKonstante 89, 90, 341, 348 VER_PLATFORM_WIN32sKonstante 89, 341 VK_CONTROL-Konstante 259, 260, 275, 341 VK_LBUTTON-Konstante 258, 259, 341 VK_LCONTROL-Konstante 259, 341 VK_LMENU-Konstante 259, 341 VK_LSHIFT-Konstante 259, 341
363
VK_MBUTTON-Konstante 259, 341 VK_MENU-Konstante 259, 341 VK_RBUTTON-Konstante 258, 259, 341 VK_RCONTROL-Konstante 259, 341 VK_RMENU-Konstante 259, 341 VK_RSHIFT-Konstante 259, 341 VK_SHIFT-Konstante 259, 341 W
WaitForSingleObject-Funktion 254, 255, 299, 324 Window-Prozedur 239, 240 Windows Sockets 87, 90, 91, 92, 281, 325, 339, 354 WM_NCHITTEST-Konstante 311, 341 WM_PAINT-Konstante 21, 250, 252, 253, 254, 301, 337, 342 WM_QUIT-Konstante 300 WM_UNDO-Konstante 226, 227, 311, 342 WM_VSCROLL-Konstante 242, 342 WM_WININICHANGE-Konstante 184, 342 WSACleanup-Funktion 92, 326 WSADATA-Struktur 91, 92, 325, 354 WSAGetLastError-Funktion 87, 281 WSAStartup-Funktion 91, 92, 325, 326
T H E
S I G N
O F
E X C E L L E N C E
Go To VBA mit Excel 2000 Klaus Prinz
Als Anleitung und Nachschlagewerk für das Entwickeln in Excel-VBA wird Sie dieses GoToBuch sicher und kompetent von den Anfängen bis hin zum Stadium der professionellen Excel-VBA-Entwicklung begleiten. Themen wie Applikationsdesign und Fehlerbehandlung werden ausführlich erläutert. Dem ambitionierten VB-Entwickler wird das nötige Wissen über das Excel-Objektmodell vermittelt, um ActiveX-Zugriffe auf die Excel-Bibliotheken erfolgreich in Visual Basic-Programmen zu implementieren. Go To 672 Seiten, 1 CD-ROM DM 79,90/öS 583,00/sFr 73,00 ISBN 3-8273-1525-5
T H E
S I G N
O F
E X C E L L E N C E
Go to COM Peter Loos
Der Mythos, COM (Component Object Model) sei unverständlich, kann nach der Lektüre dieses Buches nicht mehr aufrechterhalten werden. Nach Aufbereitung des Grundlagenwissens (GUID, CLSID, COM-Schnittstelle, -Klasse und -Objekt) lernen Sie alle Themen des Objektmodells wie COM-InProc- und -OutProc-Server, Typbibliotheken, Marshaling etc. im Detail kennen. Dann wenden Sie sich fortgeschritteneren Themen wie Persistenz, Benachrichtigungen und Multithreading zu. Go To 672 Seiten, 1 CD-ROM DM 79,90/öS 583,00/sFr 73,00 ISBN 3-8273-1678-2
T H E
S I G N
O F
E X C E L L E N C E
Visual Basic 6 Programmiertechniken, Datenbanken, Internet Michael Kofler
Dieses Buch gibt eine ebenso kompetente wie tiefgehende Beschreibung von Visual Basic. Seit der ersten Auflage zu Version 1 bildet es das Fundament für zehntausende professionelle Visual-Basic-Programmierer. Dank seiner Grundlagenorientierung beschreibt es nicht nur, wie Visual Basic funktioniert, sondern auch warum. Unzählige Beispielprogramme und Programmiertechniken helfen dabei, Visual Basic rasch und effizient einzusetzen. Das Buch macht dabei nicht an den Grenzen Visual Basic’s halt, sondern beschreibt auch das Zusammenspiel mit anderen Komponenten (Office, Internet Explorer etc.). Kompakte Syntaxzusammenfassungen ergänzen die Online-Dokumentation Visual Basics und vermitteln den Überblick, der dort fehlt. Die vorliegende Neuauflage wurde vollständig überarbeitet; besondere Berücksichtigung finden die ADO-Datenbankbibliothek, die neuen Zusatzsteuerelemente und HTMLHelp. Professionelle Programmierung 1200 Seiten, 1 CD-ROM DM 99,90/öS 729,00/sFr 88,00 ISBN 3-8273-1428-3