Montag, 30. Juni 2008

Kopieren, verschieben und löschen von Verzeichnisstrukturen / Copy, move and delete directory structures

Das Kopieren, Verschieben/Umbenennen und Löschen von einzelnen Dateien stellt unter Visual Foxpro üblicherweise kein Problem dar. Die Befehle


COPY FILE Filename1 TO Filename2
RENAME Filename1 TO Filename2
DELETE FILE Filename [RECYCLE]

übernehmen diese Aufgaben. An dieser Stelle möchte ich jedoch nochmals auf meinen Blog-Eintrag zum Thema Namensausdrücke hinweisen, denn die dort beschriebenen Vorgehensweisen gelten auch für die o.a. Befehle. Der Sourcecode sollte somit in etwa wie folgt ausschauen:


COPY FILE (cFilename1) TO (cFilename2)
RENAME (cFilename1) TO (cFilename2)
DELETE FILE (cFilename) RECYCLE

Im aktuellen Posting soll es sich aber nicht um einzelne Dateien sondern um komplette Verzeichnisse und Verzeichnisstrukturen drehen.
Um solche Aufgaben zu erledigen, steht uns die Script-Laufzeitbibliothek zur Verfügung. Hierbei handelt es sich um eine einzelne DLL mit dem Namen scrrun.dll die von Microsoftseite üblicherweise von den folgenden Anwendungen installiert wird: Windows Scripting Host, VBScript, Internet Explorer, Office.

In einem früheren Posting habe ich das FileSystemObject (FSO) bereits genutzt um diverse Laufwerksinformationen abzurufen. Damit sind die verfügbaren Möglichkeiten dieses Objektes jedoch noch nicht erschöpft. Bspw. stehen dort auch Funktionen zur Verfügung, welche wie die bereits oben beschriebenen VFP Befehle, das Kopieren, Umbenennen und Löschen einzelner Dateien ermöglichen. Auch das Erstellen, Lesen und Schreiben von Textdateien ist möglich, doch solche Funktionen nutze ich dann doch lieber mit VFP-Bordmitteln.

Wer sich intensiver mit dem FSO befassen möchte, dem steht u.a. dieser zugegebenermassen schon etwas ältere Technet-Artikel zur Verfügung:
http://www.microsoft.com/germany/technet/datenbank/articles/600360.mspx

Hier gibts die Seite im PDF-Format:
http://download.microsoft.com/download/a/3/3/a3393bf9-cfb4-45d9-ab90-772c81884d9b/sas_adm_nize.pdf

Nun zum eigentlichen Thema. Das FSO stellt uns u.a. drei Methoden zur Verfügung, mit deren Hilfe wir Ordner bearbeiten können.

Mit oFSO.CopyFolder werden komplette Verzeichnisstrukturen kopiert. oFSO.MoveFolder verschiebt Verzeichnisse und oFSO.DeleteFolder löscht sie. Nun ja, die sprechenden Funktionsnamen lassen ohne weiteres den Rückschluss auf ihre jeweilige Funktion zu. Der Hinweis an dieser Stelle ist sozusagen rein obligatorisch... ;-)

Der Einsatz der drei Methoden ist recht unspektakulär, solange wir darauf achten, dass wir Verzeichnisnamen nicht mit einem Backslash (\) abschliessen. In einem solchen Fall reagiert die DLL nämlich leicht verschnupft. Dies ist auch der Grund, weswegen im u.a. Code die etwas unsinnig wirkende Kombination von JUSTPATH und ADDBS Verwendung findet. Sie stellt letztlich sicher, dass grundsätzlich ein Backslash vorhanden ist, damit dieser problemlos entsorgt werden kann.

Noch eine Anmerkung: Der Einsatz von Wildcards (*) ist zwar von FSO-Seite möglich, wird im u.a. Beispielcode jedoch nicht berücksichtigt.


FUNCTION CopyFolder as Boolean
LPARAMETERS vSrcDir as String, vTgtDir as String
   LOCAL oFSO as Object, llOverwrite as Boolean, llReturn as Boolean
   llReturn = .F.
   * // Prüfen ob es das Quellverzeichnis gibt
   IF DIRECTORY(m.vSrcDir)
       * // Prüfen ob es da Ziellaufwerk gibt.
       * // (Ordner kann das FSO erzeugen, aber mit Laufwerken
       * // gibt es da schon noch das ein oder andere Problem) ;-)
       IF DIRECTORY(JUSTDRIVE(m.vTgtDir))
           m.vSrcDir    = JUSTPATH(ADDBS(m.vSrcDir))
           m.vTgtDir    = JUSTPATH(ADDBS(m.vTgtDir))
           llOverWrite    = .T.
           oFSO        = CREATEOBJECT([Scripting.FileSystemObject])
           oFSO.CopyFolder(m.vSrcDir, m.vTgtDir, llOverwrite)
       ENDIF
   ENDIF
   oFSO = []
   RELEASE oFSO
   IF DIRECTORY(m.vTgtDir)
       llReturn = .T.
   ENDIF
   RETURN llReturn
ENDFUNC

FUNCTION MoveFolder as Boolean
LPARAMETERS vSrcDir as String, vTgtDir as String
   LOCAL oFSO as Object, llReturn as Boolean
   llReturn = .F.
   * // Prüfen ob es das Quellverzeichnis gibt
   IF DIRECTORY(m.vSrcDir)
       * // Zum Einen prüfen ob es da Ziellaufwerk gibt.
       * // (Ordner kann das FSO erzeugen, aber mit Laufwerken
       * // gibt es da schon noch das ein oder andere Problem) ;-)
       * // zum Anderen sicherstellen, dass es das Zielverzeichnis
       * // noch nicht gibt. Andernfalls würde dessen Inhalt
       * // überschrieben werden...
       IF DIRECTORY(JUSTDRIVE(m.vTgtDir)) ;
       AND !DIRECTORY(m.vTgtDir)
           m.vSrcDir    = JUSTPATH(ADDBS(m.vSrcDir))
           m.vTgtDir    = JUSTPATH(ADDBS(m.vTgtDir))
           oFSO        = CREATEOBJECT([Scripting.FileSystemObject])
           oFSO.MoveFolder(m.vSrcDir, m.vTgtDir)
           IF DIRECTORY(m.vTgtDir)
               llReturn = .T.
           ENDIF
       ENDIF
   ENDIF
   oFSO = []
   RELEASE oFSO
   RETURN llReturn
ENDFUNC

FUNCTION DeleteFolder as Boolean
LPARAMETERS vTgtDir as String
   LOCAL oFSO as Object, llReturn as Boolean, llForceDel as Boolean
   llReturn = .T.
   * // Prüfen ob es das Verzeichnis gibt
   IF DIRECTORY(m.vTgtDir)
       m.vTgtDir    = JUSTPATH(ADDBS(m.vTgtDir))
       llForceDel    = .T.
       oFSO        = CREATEOBJECT([Scripting.FileSystemObject])
       oFSO.DeleteFolder(m.vTgtDir, llForceDel)
   ENDIF
   oFSO = []
   RELEASE oFSO
   IF DIRECTORY(m.vTgtDir)
       llReturn = .F.
   ENDIF
   RETURN llReturn
ENDFUNC

Dienstag, 24. Juni 2008

Wie bewege ich eine Form ohne Titelleiste / How to move a form without titlebar

Ab und an besteht der Bedarf, ein Formobjekt ohne Titelleiste darzustellen. Mit Hilfe der Form-Eigenschaft 'TitleBar' läßt sich das soweit auch problemlos bewerkstelligen. Soll diese nun 'kopflose' Form jedoch trotzdem auf dem Bildschirm bewegt werden können, dann haben wir jetzt ein klitzekleines Problem, denn die Titelleiste einer Form dient nun einmal als Haltegriff für die Maus, wenn eine Form verschoben werden soll.

Um unsere Form aus ihrer Zwangsstarre zu befreien benötigen wir jedoch nur zwei Funktionen aus der win32api und schon klappts es wieder mit dem Bewegen. Um der Form eine maximale Beweglichkeit zu ermöglichen sollten wir zuvor noch einige Eigenschaften anpassen:


AlwaysOnTop = .T. && optional
Desktop     = .T. && optional
ShowWindow  = 2   && optional
TitleBar    = 0

Innerhalb des LOAD-Events der Form fügen wir den folgenden Code ein:


DECLARE integer ReleaseCapture IN WIN32API
DECLARE integer SendMessage IN WIN32API INTEGER, INTEGER, INTEGER, INTEGER
Dieser Code kommt in den MOUSEDOWN-Event:

LPARAMETERS nButton, nShift, nXCoord, nYCoord

IF nButton = 1
    ReleaseCapture()
    SendMessage(This.HWnd, 0x112, 0xF012,0)
ENDIF

Sobald wir nun mit der Maus auf einen objektfreien Bereich klicken und die Taste festhalten, können wir die Form frei bewegen.

Wenn nun noch eine kleine 'Einrastfunktion' wie in WinAMP dazukommen soll, dann packen wir einfach den folgenden Code in das MOVED-Ereignis:


liOffset = 20
WITH This
    IF .Height < SYSMETRIC(2) - liOffset
        DO CASE    
        CASE .Top < liOffset
            .Top = 0
        CASE .Top + .Height > SYSMETRIC(2) - liOffset
            .Top = SYSMETRIC(2) - .Height
        ENDCASE
    ENDIF
    IF .Width < SYSMETRIC(1) - liOffset
        DO CASE
        CASE .Left < liOffset
            .Left = 0
        CASE .Left + .Width > SYSMETRIC(1) - liOffset
            .Left = SYSMETRIC(1) - .Width
        ENDCASE 
    ENDIF
ENDWITH

Die o.a. lokale Variable liOffset kann natürlich auch als Form-Eigenschaft hinterlegt werden. Sie definiert letztlich nur den Randabstand, ab dem die Einrastfunktionalität greifen soll.
Übrigens wäre auf dieser Form ein Button zum Schliessen recht sinnvoll. Wegen der ausgeblendeten Titelleiste fehlt diese Funktion nämlich ;-).

Mittwoch, 18. Juni 2008

Zeichenketten auf ihren Aufbau prüfen / Checking strings on their structure

Im UT kam vor einigen Wochen die Frage auf, wie eine gültige Telefonnummer innerhalb einer Zeichenkette gefunden werden könne.

Der Aufbau der Zeichenketten war wie folgt:  
Suite 100 123-1234 -> 123-1234  
Room 4711 333-12345 -> ungültig  
Bldg J (910)222-2222 -> 222-2222

Die Lösung ist recht einfach. Im ersten Schritt wird die potentielle Telefonnummer (letzter Block) herausgelöst. Im zweiten Schritt erfolgt die Konvertierung dieses Strings in eine Maske, die im Anschluss direkt mit der gültigen Struktur (NNN-NNNN) verglichen wird.

LOCAL laZKette(3) as String, lcConvertToN as String, lcDelimiters as String, ;
  lcNumBlock as String, lcConverted as String

laZKette(1)     = [Bldg J (910)222-2222]
laZKette(2)     = [Room 4711 333-12345]
laZKette(3)     = [Suite 100 123-1234]
lcConvertToN    = [0123456789]
lcDelimiters    = [ )]
lcValidStruct   = [NNN-NNNN]
STORE [] TO lcNumBlock, lcConverted
CLEAR

FOR i = 1 TO ALEN(laZKette,1)

lcNumBlock    = GETWORDNUM(laZKette(i),GETWORDCOUNT(laZKette(i),lcDelimiters),lcDelimiters)
lcConverted    = CHRTRAN(lcNumBlock,lcConvertToN,REPLICATE([N],LEN(lcConvertToN)))

IF lcConverted == lcValidStruct
    ? [TelNr.: ] + lcNumblock + [ ist gültig]
ELSE
    ? [TelNr.: ] + lcNumblock + [ ist ungültig]
ENDIF

ENDFOR

In einigen Fällen kann es jedoch notwendig sein, eine einzelne Zeichenkette mit mehreren Formaten zu Vergleichen, um diejenige Formatversion zu liefern, die dem Zeichenkettenaufbau entspricht. Im folgenden Beispiel vergleichen wir drei gültige Formate mit 10 Werten.

Die in der Schleife eingesetzten CHRTRAN()-Funktionen konvertieren im ersten Schritt alle Buchstaben nach 'C' und im zweiten Schritt alle Zahlen nach 'N'. Diese Reihenfolge wurde gewählt, damit das 'N' nicht nach 'C' gewandelt wird, was bei umgekehrter Reihenfolge der Fall gewesen wäre. Natürlich hätten wir anstelle von 'N' auch die '9' oder das Nummernzeichen '#' zur Maskierung nehmen können. In diesem Fall wäre die Abarbeitungsreihenfolge irrelevant.

LOCAL    laZKette(10) as String, laStruct(3) as String ;
     lcConvertToN as String. lcConvertToC as String, ;
     lcConverted as String, liPos as Integer, lcMessage as String

laZKette( 1)  = [3UUFGP7]
laZKette( 2)  = [MCK000989]
laZKette( 3)  = [0090892LLG]
laZKette( 4)  = [9ABCD2]
laZKette( 5)  = [JKL230945]
laZKette( 6)  = [JKLM00989]
laZKette( 7)  = [JJ2000989]
laZKette( 8)  = [3U99G7]
laZKette( 9)  = [3UUFGP]
laZKette(10)  = [7XYZA1]

laStruct(1)  = [NCCCCCN]
laStruct(2)  = [CCCNNNNNN]
laStruct(3)  = [NNNNNNNCCC]

lcConvertToN = [0123456789]
lcConvertToC = [ABCDEFGHIJKLMNOPQRSTUVWXYZ]

STORE [] TO lcConverted, lcMessage
CLEAR

FOR i = 1 TO ALEN(laZKette,1)

 lcConverted = CHRTRAN(UPPER(laZKette[i]),lcConvertToC,REPLICATE([C],LEN(lcConvertToC)))
 lcConverted = CHRTRAN(UPPER(lcConverted),lcConvertToN,REPLICATE([N],LEN(lcConvertToN)))
 liPos       = ASCAN(laStruct,lcConverted,1,0,1,1+2+4)

 IF liPos > 0
     TEXT TO lcMessage NOSHOW TEXTMERGE PRETEXT 1+2
         Konto <<PADR(laZKette(i),11,[ ])>> = Struktur <<liPos>> (<<laStruct(liPos)>>)
     ENDTEXT
 ELSE
     TEXT TO lcMessage NOSHOW TEXTMERGE PRETEXT 1+2
         Konto <<PADR(laZKette(i),11,[ ])>> - ungültig -
     ENDTEXT
 ENDIF
 ?lcMessage

ENDFOR

Eine Erweiterung auf komplexere Konstrukte ist ebenfalls denkbar. So könnten Gross- und Kleinbuchstaben und auch Sonderzeichen in der Konvertierung Berücksichtigung finden.