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
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 User- Default-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 Locale Settings Konstante Wert 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.