Donnerstag, 27. März 2008

Arbeiten mit Verzeichnissen und Dateinamen (Teil 1) / Working with directories and filenames (Part 1)

Visual Foxpro ist eine Entwicklungssprache, bei der man sich nicht über einen Mangel an Befehlen und Funktionen beschweren kann. Ganz im Gegenteil. Problematisch ist es allerdings schon, in diesem Befehlsdschungel so halbwegs den Überblick zu bewahren.

Verständlich, dass der ein oder andere lieber auf die wenigen Befehle zurückgreift, die er oder sie bereits seit vielen Jahren immer wieder einsetzt. Schade eigentlich, denn gerade im Bereich der Manipulation von Strings hat der Fux einiges zu bieten.

Dieser und die nächsten Einträge befassen sich mit dem Umgang mit Verzeichnissen und Dateinamen. Hier stehen eine Vielzahl von Funktionen zur Verfügung, die uns viel Fummelei abnehmen.

Das Auswählen eines Verzeichnisses ist sicherlich allen hinlänglich bekannt.

cDir = GETDIR()

Optional stellt GETDIR() jedoch diverse zusätzliche Parameter bereit:

GETDIR([cDirectory [, cText [, cCaption [, nFlags [, lRootOnly]]]]])

Parameter 1 nimmt ein Startverzeichnis auf, in dem die Auswahl beginnen soll
Parameter 2 nimmt einen erklärenden Text auf
Parameter 3 nimmt eine individuelle Caption auf
Parameter 4 nimmt Flags auf, die von ihrer Wertigkeit additiv übergeben werden
Parameter 5 setzt fest, ob übergeordnete und/oder parallele Ordner aufgerufen werden können

cDir = GETDIR([C:\TEMP\],[Bitte wählen Sie einen Ordner aus], [Ordnerauswahl], 1+2+8+16+32+64+16384, .T.)

Nach erfolgreicher Auswahl steht in cDir nun u.U. folgendes:

c:\temp\unterordner1\

Wichtig hierbei ist, dass unserem Verzeichnis bereits ein Backslash (\) angehängt wurde.
Können wir uns nicht darauf verlassen, ob eine bestimmte Funktion einen Pfadnamen inklusive Backslash am Ende liefert, so stehen uns diverse Möglichkeiten offen, diesen ergänzen.

Variante 1 (Für alle mit einem gesunden Gottvertrauen ;-) )

cDir = cDir + [\]

Variante 2 (nicht falsch aber viel zu umständlich)

cDir = cDir + IIF(RIGHT(cDir,1) = [\],[],[\])

Variante 3 (Die Richtige!!)

cDir = ADDBS(cDir)

INFO: ADDBS() hängt nur im Bedarfsfall einen Backslash an den übergebenen String. Wir müssen uns also keine Gedanken darüber machen, ob bereits einer vorhanden ist oder nicht.

Mittwoch, 26. März 2008

Word über Windows Scripting Host fernsteuern / Remote controlling Word with windows scripting host

Wer intensiv mit Word arbeitet weiss, dass es für fast alle Funktionen drei Möglichkeiten gibt, diese aufzurufen (OK, manchmal gibt es auch vier ;-) ).

Die drei Basiswege hierbei stellen die Menüs und Popup-Menüs, die Maus und die Tastatur dar.

An dieser Stelle soll es sich um die eingebetteten Tastaturfunktionen drehen. Die meisten dieser Schnelltasten (Shortcuts) werden im übrigen in den diversen Menüs und Untermenüs an deren rechtem Rand angezeigt.

Wer sich nicht durch die Objekthierarchien von Word zwecks OLE Automation durcharbeiten möchte, kann mit Hilfe der Schnelltasten viele Funktionen von Word aufrufen. Das folgende Codebeispiel zeigt einige der Möglichkeiten auf. Letztlich passiert nicht wirklich viel, aber wir erhalten dadurch einen Eindruck der verfügbaren Möglichkeiten.

* Starte WinWord, übergebe einen String und sortiere ihn,
* dann starte Notepad und füge ihn ein


#DEFINE CRLF chr(13)+chr(10) && Wagenvorlauf und Zeilenvorschub (Carriage Return / Linefeed)
#DEFINE WSHIDE             0 && Versteckt das Fenster und aktiviert ein anderes
#DEFINE WSNORMAL           1 && Aktiviert und zeigt das Fenster
#DEFINE WSMIN              2 && Aktiviert und zeigt das Fenster im Minimieren-Modus
#DEFINE WSMAX              3 && Aktiviert und zeigt das Fenster im Maximieren-Modus
#DEFINE WSLAST             4 && Zeigt das Fenster im zuletzt benutzten Modus in Bezug auf Größe und Position an. Das aktive Fenster bleibt aktiv.
#DEFINE WSSAME             5 && Aktiviert das Fenster und zeigt es in aktuelle Größe und Position.
#DEFINE WSMINNEXT          6 && Minimiert das spezifizierte Fenster und aktiviert das nächte Top-Level-Fenster im Z-Order Stapel.
#DEFINE WSMINSAME          7 && Zeigt das Fenster im Minimiert-Modus. Das aktive Fenster bleibt aktiv.
#DEFINE WSSAMECUR          8 && Zeigt das Fenster im aktuellen Modus. Das aktive Fenster bleibt aktiv.
#DEFINE WSORIG             9 && Aktiviert und zeigt das Fenster
#DEFINE WSSETSHOW         10 && Setzt den Anzeigemodus basierend auf der aufrufenden Applikation.

DECLARE Sleep IN WIN32API INTEGER       && Millisekunden basierender Wartezyklus
                                        && dient zur besseren Veranschaulichung

_Cliptext = [Eins Zwei Drei Vier Fünf ]    && Basisstring definieren
_cliptext = STRTRAN(_Cliptext,[ ],CRLF)    && Leerzeichen durch CR/LF ersetzen

WshShell = CreateObject([wscript.Shell])


* Winword normal startenl
WshShell.Run([WinWord],WSNORMAL)
=Sleep(1000)

* STRG+v  - Zwischenablage einfügen
WshShell.SendKeys([^v])
=Sleep(1000)

* ALT+l+s - Sortiervorgang anstossen
WshShell.SendKeys([%ls])    && Word 2003
=Sleep(1000)

* ENTER   - Abfrage bestätigen
WshShell.SendKeys([{ENTER}])
=Sleep(1000)

* STRG+a  - alles markieren
WshShell.SendKeys([^a])
=Sleep(1000)

* STRG+c  - Markierung in die Zwischenablage kopieren
WshShell.SendKeys([^c])
=Sleep(1000)

* ALT+F4  - Winword Dokument schliessen
WshShell.SendKeys([%{F4}])
=Sleep(1000)

* n-(No)  - Speichern-Abfrage verneinen
WshShell.SendKeys([n])
=Sleep(1000)

* Notepad normal starten
WshShell.Run([notepad],WSNORMAL)
=Sleep(1000)

* Notepad aktivieren
WshShell.AppActivate([notepad])
=Sleep(1000)

* STRG+v  - Zwischenablage (jetzt sortiert) einfügen
WshShell.SendKeys([^v])


Mittwoch, 19. März 2008

Netzwerk Ping in VFP / Network ping with VFP

Wie eine Netzwerkadresse auf ihre Verfügbarkeit geprüft wird dürfte wohl den meisten Entwicklern bekannt sein...

    ping domaenenname.de

Hierbei erfolgt die Namensauflösung in eine IP-Adresse bei korrekt konfiguriertem DNS automatisch. Alternativ können wir natürlich auch direkt die IP-Adresse eingeben.

    ping 192.168.1.70

Im DOS-Befehlsfenster oder auch in Batchdateien kann es heute noch durchaus sinnvoll sein, diesen Befehl einzusetzen. Aber selbst dort stossen wir sofort an die Grenzen dieses transienten DOS-Befehls, er liefert nämlich nur eine visuelle Rückmeldung.

Aus diesem Grund kann ping.exe nicht für eine Ergebnisabfrage und eine davon abhängige weitere Programmausführung eingesetzt werden. In Batchdateien kommt bei mir deshalb schon lange das kleine Tool alive.exe zum Einsatz. Es liefert einen Fehlercode (im DOS-Jargon Errorlevel), der über das Ergebnis Auskunft gibt und so die weitere Verarbeitung steuern kann.

@echo off
cls

set TGT_HOST=myHost
set PRT_SCREEN="Y"
set PRT_FILE="C:\TEMP"

alive /repeat=30 %TGT_HOST% >>%PRT_FILE%
if %errorlevel% == 255 if %PRT_SCREEN% == "Y" echo Unbekannter Fehler
if %errorlevel% == 8   if %PRT_SCREEN% == "Y" echo Zieladresse unbekannt
if %errorlevel% == 7   if %PRT_SCREEN% == "Y" echo Verbindungsweg zu lang
if %errorlevel% == 6   if %PRT_SCREEN% == "Y" echo Hardwarefehler
if %errorlevel% == 5   if %PRT_SCREEN% == "Y" echo Zielport nicht erreichbar
if %errorlevel% == 4   if %PRT_SCREEN% == "Y" echo Zielprotokoll nicht erreichbar
if %errorlevel% == 3   if %PRT_SCREEN% == "Y" echo Zielnetzwerk nicht erreichbar
if %errorlevel% == 2   if %PRT_SCREEN% == "Y" echo Zielrechner nicht erreichbar
if %errorlevel% == 1   if %PRT_SCREEN% == "Y" echo Paketlaufzeit abgelaufen
if %errorlevel% == 0   goto :OEFFNE_VERBINDUNG

goto :ENDE_DER_VERARBEITUNG

:OEFFNE_VERBINDUNG
REM Hier findet der Connect und die sonstige Verarbeitung statt

:ENDE_DER_VERARBEITUNG
cls

So weit so gut. Aber wer möchte in VFP schon freiwillig eine hässliche DOS-Box öffnen? Also suchen wir uns einen anderen Weg.

Hilfreich ist hierbei die IP-Hilfs-API (zu finden im WINDOWS\System32 Ordner (iphlpapi.dll).
Im folgenden Codebeispiel, das im übrigen auch als Methode in der Klassenbibliothek unserer Wahl implementiert werden kann, ist die maximale Anzahl an Hops auf 5 beschränkt.

FUNCTION isAlive
LPARAMETERS vIpNum, vSilent

    * Deklaration der API-Schnittstelle
    DECLARE INTEGER GetRTTAndHopCount IN Iphlpapi;
       INTEGER DestIpAddress, LONG @HopCount,;
       INTEGER MaxHops, LONG @RTT
    DECLARE INTEGER inet_addr IN ws2_32 STRING cp

    * Deklaration der benötigten Variablen
    LOCAL lnDst, lnHop, lnRTT, llReturn

    lnDst        = inet_addr(m.vIpnum)
    llReturn    = .F.
    STORE 0 TO lnHop, lnRTT

    * Zieladresse anpingen und wenn als Ergebnis eine
    * 0 geliefert wird, dann war der ping erfolglos
    IF GetRTTAndHopCount(lnDst, @lnHop, 5, @lnRTT) = 0

        TEXT TO lcMsg TEXTMERGE NOSHOW PRETEXT 3
            keine Antwort von IP-Adresse <<m.vIpnum>>
        ENDTEXT
    
    * Andernfalls konnte die Zieladresse kontaktiert werden
    ELSE

        TEXT TO lcMsg TEXTMERGE NOSHOW PRETEXT 3
            Antwort von IP-Adresse <<m.vIpnum>>
            (<<m.lnRTT>> MSek / <<m.lnHop>> Hops)
        ENDTEXT
        llReturn = .T.
      
    ENDIF

    * Wenn Meldung erlaubt, dann das Ergebnis anzeigen
    IF !vSilent
        MESSAGEBOX(lcMsg,0+64+0,[Programminformation])
    ENDIF

    * Rückmeldung .T. = erfolgreich, .F. = erfolglos
    RETURN llReturn

ENDFUNC

Montag, 17. März 2008

Editoreinstellungen / Editor settings

Am 13. März hatte ich nach mehreren Monaten Pause wieder die Zeit gefunden, am Kölner dFPUG Regionaltreffen teilzunehmen.

Neben informativen und aufschlussreichen Gesprächen fiel mir zum wiederholten Male auf, dass Jo Hilgers spezielle, individuelle Syntax-Farbeinstellungen für den VFP-Editor vorgenommen hatte.

An sich ist das ja nichts aussergewhöhnliches. Mein Editor leuchtet ebenfalls in individueller Farbenpracht, um Literale, Variablen, Strings usw. auf den ersten Blick kenntlich zu machen.

Was ich bisher jedoch noch nie eingesetzt hatte, war die Einstellung der Hintergrundfarbe und hier machte es beim Betrachten von Jo's Einstellungen plötzlich Klick. Bisher war es immer eine ziemlich üble Angelegenheit, wenn mal wieder irgendwo ein Anführungszeichen (o. Hochkomma o. eckige Klammer) fehlte oder überhaupt den Überblick zu bewahren, wenn komplexere Stringkonkatenierungen notwendig waren.

Die alleinige Schriftfarbeneinstellung für Strings war letztlich nie ausreichend. Nachdem ich nun meinen Editor für Strings und Kommentare auf ausgewählte Hintergrundfarben eingestellt habe, ist der Überblick um einiges besser.

Dienstag, 11. März 2008

Gleicher als Gleich - Die Tücken des Stringvergleichs / More same as same - the pitfalls of the string comparison

Angenommen, wir haben den folgenden Code in einer Prozedur/Funktion/Methode hinterlegt um den aktuellen Wert einer Listbox in Erfahrung zu bringen:

DO CASE
CASE "LKW" = listbox.value
    ....
CASE "Hänger" = listbox.value
    ...
CASE "LKW+Hänger" = listbox.value
    ...
ENDCASE

Wir werden u.U. nie in den dritten Zweig fallen. Ursache ist die Art und Weise, mit der VFP Strings miteinander vergleicht.

Wenn "LKW+Hänger" ausgewählt wird, dann erfolgt die Abarbeitung des ersten CASE Zweigs, denn nach VFP Logik ist "LKW" = "LKW+Hänger" wahr. Der Ausdruck links vom "=" wird nur mit seiner effektiven Länge von 3 Zeichen mit dem Ausdruck rechts vom "=" verglichen. Und das ist in diesem Fall immer "LKW" und somit für VFP gleich.

Um dieses Verhalten zu beeinflussen besitzt Visual Foxpro zwei relationale Operatoren, die Zeichenfolgen auf Gleichheit überprüfen.

Der =-Operator führt einen Vergleich zwischen zwei Werten desselben Datentyps durch. Dieser Operator eignet sich zum Vergleichen von Daten des Typs Zeichen, Numerisch, Datum und Logisch. Wenn Zeichenausdrücke mit dem =-Operator verglichen werden, kann es jedoch vorkommen, dass das Ergebnis nicht den Erwartungen entspricht. Zeichenausdrücke werden zeichenweise von links nach rechts verglichen, bis entweder die Ausdrücke nicht mehr übereinstimmen, das Ende des Ausdrucks auf der rechten Seite des =-Operators erreicht ist (SET EXACT OFF) oder das Ende beider Ausdrücke erreicht ist (SET EXACT ON).

Der ==-Operator kann verwendet werden, wenn ein exakter Vergleich der Zeichenausdrücke erfolgen soll. Wenn zwei Zeichenausdrücke mit dem ==-Operator verglichen werden, müssen beide Ausdrücke (unabhängig davon, auf welcher Seite des Operators sie sich befinden) genau dieselben Zeichen, einschließlich Leerzeichen, enthalten, um als gleichbedeutend zu gelten.

Die mit SET EXACT festgelegte Einstellung wird ignoriert, wenn Zeichenfolgen mit dem ==-Operator verglichen werden.

Die folgende Tabelle zeigt, wie die Wahl des Operators und die mit SET EXACT festgelegte Einstellung die Vergleichsergebnisse beeinflussen. (Ein Unterstrich steht hier für ein Leerzeichen.)

Vergleich .......... [= + EXACT OFF] . [= + EXACT ON] . [==]
"abc" = "abc" ...... TRUE ............ TRUE ........... TRUE
"ab"  = "abc" ...... FALSE ........... FALSE .......... FALSE
"abc" = "ab" ....... TRUE ............ FALSE .......... FALSE
"abc" = "ab_" ...... FALSE ........... FALSE .......... FALSE
"ab"  = "ab_" ...... FALSE ........... TRUE ........... FALSE
"ab_" = "ab" ....... TRUE ............ TRUE ........... FALSE
""    = "ab" ....... FALSE ........... FALSE .......... FALSE
"ab"  = "" ......... TRUE ............ FALSE .......... FALSE
"__"  = "" ......... TRUE ............ TRUE ........... FALSE
""    = "___" ...... FALSE ........... TRUE ........... FALSE
TRIM("__") = "" .... TRUE ............ TRUE ........... TRUE
""    = TRIM("__") . TRUE ............ TRUE ........... TRUE

TRUE = Übereinstimmung, FALSE = Keine Übereinstimmung Die bessere Codevariante ist demnach die Folgende:

DO CASE
CASE "LKW" == listbox.value
    ....
CASE "Hänger" == listbox.value
    ...
CASE "LKW+Hänger" == listbox.value
    ...
ENDCASE