Dienstag, 22. Januar 2008

Feststellen der aktuellen Windows Spracheinstellung

Mehrsprachigkeit in Applikationen ist schon lange ein Thema. Wenn die Anwendung mit eigenen Logins arbeitet, kommt meist eine Klassenbibliothek zum Einsatz, die auf Basis des Anwendungslogins, die korrekte Sprache für die jeweiligen Masken einblendet. Diese User-bezogene Reaktion können wir jedoch ganz einfach auf eine höhere Ebene tragen. Es wäre doch eigentlich ganz praktisch, wenn unsere Anwendung von sich aus merkt, dass der User bereits vom BS aus in einer anderen Sprache unterwegs ist.
Hilfreich ist uns hierbei wieder einmal die Windows API. Der folgende recht kurze Code zeigt, wie wir die entsprechenden Informationen aus Windows herauskitzeln.

* Konstantendeklaration
#DEFINE LOCAL_USER_DEFAULT BITLSHIFT(0x01, 10) && Benutzer Standard
#DEFINE LOCAL_SYSTEM_DEFAULT BITLSHIFT(0x02, 10) && System Standard
#DEFINE LOCAL_SENGLANGUAGE 0x00001001 && Englischer Name
#DEFINE LOCAL_SABBREVLANGNAME 0x00000003 && Abgekürzt
#DEFINE LOCAL_SNATIVELANGNAME 0x00000004 && Ausgeschrieben
* API Deklaration
DECLARE Integer GetLocaleInfo IN Win32API ;
Long Locale, Long LCType, String @ LCData, Integer size
* Lokale Arbeitsvariablen
LOCAL lcBuffer as String, liLength as Integer
lcBuffer = SPACE(256)
liLength = 0

CLEAR

* Benutzersprache (standard), englisch, ausgeschrieben
liLength = GetLocaleInfo(LOCAL_USER_DEFAULT, LOCAL_SENGLANGUAGE, @lcBuffer, 256)
? [(User) Standardsprache : ] + LEFT( lcBuffer , liLength - 1 )

* Systemsprache, abgekürzt, deutsch
liLength = GetLocaleInfo(LOCAL_SYSTEM_DEFAULT, LOCAL_SABBREVLANGNAME, @lcBuffer, 256)
? [(System) Abgekürzt : ] + LEFT( lcBuffer , liLength - 1 )

* Systemsprache, ausgeschrieben, deutsch
liLength = GetLocaleInfo(LOCAL_SYSTEM_DEFAULT, LOCAL_SNATIVELANGNAME, @lcBuffer, 256)
? [Systemsprache : ] + LEFT( lcBuffer , liLength - 1 )

Montag, 14. Januar 2008

Herausfinden der Pfadnamen von CSLID-Ordnern

Windows stellt eine Reihe von allgemein zugänglichen Standardverzeichnissen bereit. Diese über Common System Location IDentifiers (CSLIDs) referenzierten Ordner werden über das Shell.Application - Objekt zum Abruf bereitgestellt. Hierbei steht für jeden Ordner den das jeweilige Windows BS unterstützt ein eigenes Ordnerobjekt zur Verfügung.
Der unten aufgeführte Mustercode beruht auf WinXPSP2 als BasisBS. Ältere oder andere BS-Versionen stellen einige der aufgelisteten Pfade nicht bereit.

Das Shell.Application - Objekt ist Bestandteil der Active Desktop Komponenten. Um auf dieses Objekt unter Win95 und WinNT4 zugreifen zu können muss zwingend der IE4 oder IE4.01 installiert sein. Neuere Versionen der Browsers enthalten die entsprechenden Komponenten nicht mehr.

Da nicht alle Ordnerobjekte in den verschiedenen BS zur Verfügung stehen und ebenfalls nicht alle BS-Varianten ein '.Self' Unterobjekt bereitstellen ist es unter Umständen notwendig, die Abfrage der Ordner über eine spezielle Schleife abzufragen (zu sehen im zweiten 'TRY...CATCH'-Block der Funktion 'ShowInfo()'.

#DEFINE NS_DESKTOP          0
#DEFINE NS_PROGRAMS 0x2
#DEFINE NS_CONTROLS 0x3
#DEFINE NS_PRINTERS 0x4
#DEFINE NS_PERSONAL 0x5
#DEFINE NS_FAVORITES 0x6
#DEFINE NS_STARTUP 0x7
#DEFINE NS_RECENT 0x8
#DEFINE NS_SENDTO 0x9
#DEFINE NS_BITBUCKET 0xa
#DEFINE NS_STARTMENU 0xb
#DEFINE NS_DESKTOPDIRECTORY 0x10
#DEFINE NS_DRIVES 0x11
#DEFINE NS_NETWORK 0x12
#DEFINE NS_NETHOOD 0x13
#DEFINE NS_FONTS 0x14
#DEFINE NS_TEMPLATES 0x15
#DEFINE NS_COMMONSTARTMENU 0x16
#DEFINE NS_COMMONPROGRAMS 0x17
#DEFINE NS_COMMONSTARTUP 0x18
#DEFINE NS_COMMONDESKTOPDIR 0x19
#DEFINE NS_APPDATA 0x1a
#DEFINE NS_PRINTHOOD 0x1b
#DEFINE NS_LOCALAPPDATA 0x1c
#DEFINE NS_ALTSTARTUP 0x1d
#DEFINE NS_COMMONALTSTARTUP 0x1e
#DEFINE NS_COMMONFAVORITES 0x1f
#DEFINE NS_INTERNETCACHE 0x20
#DEFINE NS_COOKIES 0x21
#DEFINE NS_HISTORY 0x22
#DEFINE NS_COMMONAPPDATA 0x23
#DEFINE NS_WINDOWS 0x24
#DEFINE NS_SYSTEM 0x25
#DEFINE NS_PROGRAMFILES 0x26
#DEFINE NS_MYPICTURES 0x27
#DEFINE NS_PROFILE 0x28

CLEAR

PUBLIC oShell as Object
LOCAL lcScreenFont as String

lcScreenFont = _screen.FontName
_screen.FontName = [Courier New]

oShell = CREATEOBJECT([Shell.Application])

IF VARTYPE(oShell) = [O]

ShowInfo(NS_DESKTOP) && Desktop
ShowInfo(NS_PROGRAMS) && Programme
ShowInfo(NS_CONTROLS) && Systemsteuerung (Registrykey)
ShowInfo(NS_PRINTERS) && Drucker und Faxgeräte (Registrykey)
ShowInfo(NS_PERSONAL) && Eigene Dateien
ShowInfo(NS_FAVORITES) && Favoriten
ShowInfo(NS_STARTUP) && Autostart
ShowInfo(NS_RECENT) && Zuletzt verwendete Dateien
ShowInfo(NS_SENDTO) && Senden an
ShowInfo(NS_BITBUCKET) && Papierkorb (Registry)
ShowInfo(NS_STARTMENU) && Startmenü
ShowInfo(NS_DESKTOPDIRECTORY) && Desktop
ShowInfo(NS_DRIVES) && Arbeitsplatz (Registrykey)
ShowInfo(NS_NETWORK) && Netzwerkumgebung (Registrykey)
ShowInfo(NS_FONTS) && Schriftarten
ShowInfo(NS_TEMPLATES) && Vorlagen
ShowInfo(NS_COMMONSTARTMENU) && Startmenü 'All Users'
ShowInfo(NS_COMMONPROGRAMS) && Programme 'All Users'
ShowInfo(NS_COMMONSTARTUP) && Autostart 'All Users'
ShowInfo(NS_COMMONDESKTOPDIR) && Desktop ' All Users'
ShowInfo(NS_APPDATA) && Anwendungsdaten
ShowInfo(NS_PRINTHOOD) && Druckumgebung
ShowInfo(NS_LOCALAPPDATA) && Lokale Einstellungen / Anwendungsdaten
ShowInfo(NS_ALTSTARTUP) && ?????
ShowInfo(NS_COMMONALTSTARTUP) && ?????
ShowInfo(NS_COMMONFAVORITES) && Favoriten 'All Users'
ShowInfo(NS_INTERNETCACHE) && Temporäre Internetdateien
ShowInfo(NS_COOKIES) && Cookies
ShowInfo(NS_HISTORY) && Verlauf
ShowInfo(NS_COMMONAPPDATA) && Anwendungsdaten 'All Users'
ShowInfo(NS_WINDOWS) && Windows
ShowInfo(NS_SYSTEM) && System32
ShowInfo(NS_PROGRAMFILES) && Programme
ShowInfo(NS_MYPICTURES) && Eigene Bilder
ShowInfo(NS_PROFILE) && Profile

ENDIF

_screen.FontName = lcScreenFont
oShell = [ ]
oFolder = [ ]
RELEASE lcScreenFont, oShell, oFolder


FUNCTION ShowInfo
LPARAMETERS vNameSpaceID
LOCAL loFolder as Object
loFolder = oShell.NameSpace(m.vNameSpaceID)

IF VARTYPE(loFolder) = [O]
* Unter XP-SP2 stehen fast alle Ordner zur Verfügung
TRY
? PADR(loFolder.Self.Name,30,[ ]), loFolder.Self.Path
CATCH
* Info ausgeben, Zugriff auf NameSpace fehlgeschlagen ist
? ['->] + PADR(TRANSFORM(m.vNameSpaceID,[@0]),27,[ ]), [nicht verfügbar]
ENDTRY

* Unter älteren WinBS oder nur basierend auf dieser 2. Routine
* stehen diverse Ordner nicht zur Verfügung
lcPath = NULL
TRY
FOR EACH item IN loFolder.ParentFolder.Items
IF item.name == loFolder.Title
? PADR(['-> ] + item.name,30,[ ]), Item.Path
EXIT
ENDIF
ENDFOR
CATCH
* Info ausgeben, Zugriff auf NameSpace fehlgeschlagen ist
? PADR(['-> ] + TRANSFORM(m.vNameSpaceID,[@0]),30,[ ]), [nicht verfügbar]
ENDTRY
ENDIF

loFolder = [ ]
RELEASE loFolder
ENDFUNC

Donnerstag, 3. Januar 2008

Beliebige Dateien über assoziierte Anwendung öffnen

Um externe Anwendungen zu starten stellt Visual FoxPro den Befehl RUN zur Verfügung. Wenn wir jedoch nicht wissen, wie die Anwendung genau heisst oder wo sie liegt, dann führt meist kein Weg an ShellExecute vorbei. Dieses von shell32.dll bereitgestellte Interface ermöglicht uns ein recht flexibles Handling mit Dateien und den damit assoziierten Anwendungen.
Bevor wir mit ShellExecute arbeiten können, ist die Deklaration der Schnittstelle notwendig. Bereitgestellt werden insgesamt sechs Parameter.

hwndParent  Fensterhandle (benötigt bei Fehlermeldungen)
cVerb Funktionsbezeichner (open,print,printto,edit,explore,find)
cFilename Dateiname oder URL
cParameters Zu übergebende Parameter
cDirectory Arbeitsverzeichnis
nCmdShow Anzeigevariante
Parameter #6 können wir wahlweise als numerischen Wert oder als deklarierte Konstante übergeben. Im u.a. Beispielcode findet sich die entsprechende Deklaration direkt zu Anfang.

Definieren wir die Funktion ShellExecute als Integer, so liefert sie einen numerischen Rückgabewerte. Liegt dieser über 32 dann wurde die Operation erfolgreich ausgeführt. Andernfalls sollten wir anhand der folgenden Liste eine entsprechende Info ausgeben:

Konstante               Beschreibung                                  Wert
SE_ERR_FNF Die Datei wurde nocht gefunden 2
SE_ERR_PNF Der Pfad wurde nicht gefunden 3
SE_ERR_ACCESSDENIED Das OS verweigerte den Zugriff auf die Datei 5
SE_ERR_OOM Nicht genügend Arbeitsspeicher 8
ERROR_BAD_FORMAT Die .exe Datei hat ein ungültiges Format 11
(keine Win32 .exe oder Fehler in Datei)
SE_ERR_SHARE Zugriffsverletzung bei Mehrfachnutzung 26
SE_ERR_ASSOCINCOMPLETE Das Suffix ist unvollständig oder ungültig 27
SE_ERR_DDETIMEOUT DDE Transaction wegen Timeout abgebrochen 28
SE_ERR_DDEFAIL Die DDE Transaction schlug fehl 29
SE_ERR_NOASSOC Keine assoziierte Applikation für das Suffix 31
(Erscheint auch bei nicht druckbaren Dateien)
SE_ERR_DLLNOTFOUND Die spezifizierte DLL wurde nicht gefunden 32
Der folgende Beispielcode zeigt vier verschiedene Möglichkeiten für den Umgang mit ShellExecute auf. Um den u.a. Code auszuprobieren genügt es, in VFP ein neues PRG anzulegen und den markierten Code über die Zwischenablage einzufügen.

#DEFINE SW_HIDE             0
#DEFINE SW_SHOWNORMAL 1
#DEFINE SW_NORMAL 1
#DEFINE SW_SHOWMINIMIZED 2
#DEFINE SW_SHOWMAXIMIZED 3
#DEFINE SW_MAXIMIZE 3
#DEFINE SW_SHOWNOACTIVATE 4
#DEFINE SW_SHOW 5
#DEFINE SW_MINIMIZE 6
#DEFINE SW_SHOWMINNOACTIVE 7
#DEFINE SW_SHOWNA 8
#DEFINE SW_RESTORE 9
#DEFINE SW_SHOWDEFAULT 10
#DEFINE SW_FORCEMINIMIZE 11

* // Deklaration der benötigten Arbeitsvariablen
LOCAL llExistsErrorCrs as Boolean, llDeclareStatus as Boolean, ;
lcTyp as String, lcFile as String, lcPrinter as String, ;
lcMailto as String, lcSubject as String, lcBody as String, ;
liHwndMain as Integer, liReturn as Integer

* // Variablen initialisieren
liHwndMain = _SCREEN.HWnd
llExistsErrorCrs = CreateErrorCursor()
llDeclareStatus = DeclareShellExec()
STORE [] TO lcTyp, lcFile, lcPrinter, lcMailto, lcSubject, lcBody
STORE 0 TO liReturn

* // Wenn sowohl die Cursorerstellung als auch die Deklaration
* // von ShellExec funktioniert hat, dann kann es jetzt losgehen
IF llDeclareStatus AND llExistsErrorCrs

* // Beispiel 1 ---------------------
* // Öffnet das Standard-Mailprogramm
IF MESSAGEBOX([EMail-Erstellung testen?],4+32+0,[Abfrage]) = 6
lcMailto = [mailto:mustermann@musterdomaene.de]
lcSubject = [?Subject=EMail Test]
lcBody = [&Body=Hallo Welt]
liReturn = ShellExecute(liHwndMain,[open],lcMailto + lcSubject + lcBody,[],[],SW_SHOWNORMAL)
IF liReturn <= 32
ShowErrorMessage(liReturn)
ENDIF
ENDIF

* // Beispiel 2 ---------------------
* // Öffnet das assoziierte Programm zum ausgewählten Dateityp
IF MESSAGEBOX([Starten einer assoziierten Applikation testen?],4+32+0,[Abfrage]) = 6
lcTyp = [Dokument:doc;Text:txt;Tabelle:xls;Grafik:jpg,bmp,tif,gif,png]
lcFile = GETFILE(lcTyp,[Auswählen],[Öffnen],1,[Datei auswählen])
IF FILE(lcFile)
liReturn = ShellExecute(liHwndMain,[open],lcFile,[],[],SW_SHOWNORMAL)
IF liReturn <= 32
ShowErrorMessage(liReturn)
ENDIF
ENDIF
ENDIF

* // Beispiel 3 ---------------------
* // Druckt die ausgewählte Datei über das assoziierte Programm zum ausgewählten Dateityp
IF MESSAGEBOX([Druckausgabe testen?],4+32+0,[Abfrage]) = 6
lcTyp = [Dokument:doc;Text:txt;Tabelle:xls;Grafik:jpg,bmp,tif,gif,png]
lcFile = GETFILE(lcTyp,[Auswählen],[Öffnen],1,[Datei auswählen])
lcPrinter = GETPRINTER()
IF FILE(lcFile)
liReturn = ShellExecute(liHwndMain,[printto],lcFile,["] + lcPrinter + ["],[],SW_HIDE)
IF liReturn <= 32
ShowErrorMessage(liReturn)
ENDIF
ENDIF
ENDIF

* // Beispiel 4 ---------------------
* // Öffnet den Standard-HTML-Editor um die ausgewählte Datei zu bearbeiten
IF MESSAGEBOX([Öffnen des HTML-Editors testen?],4+32+0,[Abfrage]) = 6
lcTyp = [HTML:htm,html;ASP:asp;PHP:php;PYTHON:py;STYLESHEET:css]
lcFile = GETFILE(lcTyp,[Auswählen],[Öffnen],1,[Datei auswählen])
IF FILE(lcFile)
liReturn = ShellExecute(liHwndMain,[edit],lcFile,[],[],SW_SHOWNORMAL)
IF liReturn <= 32
ShowErrorMessage(liReturn)
ENDIF
ENDIF
ENDIF

ENDIF

* // Freigeben der Ressourcen
llDeclareStatus = ClearShellExec()

USE IN SELECT([crsError])

RELEASE llExistsErrorCrs, llDeclareStatus, ;
lcTyp, lcFile, lcPrinter, ;
lcMailto, lcSubject, lcBody, ;
liHwndMain, liReturn

* //----------------------------//
FUNCTION CreateErrorCursor
LOCAL llReturn
* // Error-Cursor erzeugen und befüllen
TRY
CREATE CURSOR crsError (iValue I, cInfo c(50))
INSERT INTO crsError (iValue, cInfo) VALUES ( 2,[Die Datei wurde nocht gefunden])
INSERT INTO crsError (iValue, cInfo) VALUES ( 3,[Der Pfad wurde nicht gefunden])
INSERT INTO crsError (iValue, cInfo) VALUES ( 5,[Das OS verweigerte den Zugriff auf die Datei])
INSERT INTO crsError (iValue, cInfo) VALUES ( 8,[Nicht genügend Arbeitsspeicher])
INSERT INTO crsError (iValue, cInfo) VALUES (11,[Die .exe Datei hat ein ungültiges Format])
INSERT INTO crsError (iValue, cInfo) VALUES (26,[Zugriffsverletzung bei Mehrfachnutzung])
INSERT INTO crsError (iValue, cInfo) VALUES (27,[Das Suffix ist unvollständig oder ungültig])
INSERT INTO crsError (iValue, cInfo) VALUES (28,[DDE Transaction wegen Timeout abgebrochen])
INSERT INTO crsError (iValue, cInfo) VALUES (29,[Die DDE Transaction schlug fehl])
INSERT INTO crsError (iValue, cInfo) VALUES (31,[Keine assoziierte Applikation für das Suffix])
INSERT INTO crsError (iValue, cInfo) VALUES (32,[Die spezifizierte DLL wurde nicht gefunden])
llReturn = .T.
CATCH
llReturn = .F.
ENDTRY
RETURN llReturn
ENDFUNC

* //----------------------------//
FUNCTION ShowErrorMessage
LPARAMETERS vValue as Integer, vExistsErrorCrs as Boolean
* // Routine zur Ausgabe der SellExecute-Fehlernummer
SELECT crsError
GO TOP
LOCATE FOR iValue = m.vValue
IF FOUND()
TEXT TO cString NOSHOW ADDITIVE TEXTMERGE PRETEXT 2
Fehler #<<crsError.iValue>>
<<crsError.cInfo>>
ENDTEXT
MESSAGEBOX(cString,0+48,0)
ENDIF
ENDFUNC

* //----------------------------//
FUNCTION DeclareShellExec
* // Deklaration der API-Funktion 'ShellExecute'
DECLARE Integer ShellExecute ;
IN shell32.dll ;
Integer hwndParent, ;
String cVerb, ;
String cFilename, ;
String cParameters, ;
String cDirectory, ;
Integer nCmdShow
RETURN .T.
ENDFUNC

* //----------------------------//
FUNCTION ClearShellExec
* // Freigeben der zuvor genutzten API-Funktion
CLEAR DLLS [ShellExecute]
RETURN .F.
ENDFUNC