Donnerstag, 7. August 2008

Einlesen grosser Verzeichnisse ohne ADIR()

Im UT kam heute die Frage bzgl. des Einlesens von Dateien aus Verzeichnissen mit mehr als 12800 Dateien auf. ADIR() lieferte einen Error 31 - Invalid subscript reference.

U.a. für solche Fälle steht in Foxpro die Funktion SYS(2000, Skeleton [, 1]) zur Verfügung. Mit ihrer Hilfe können Verzeichnisse über eine Verarbeitungsschleife eingelesen werden. Im folgenden ein kleines Beispiel das sämtliche EXE-Dateien aus dem Windowsverzeichnis zur Anzeige bringt.
LOCAL lcCurDir as String, lcWinDir as String, lcFile as String

lcCurDir = FULLPATH(CURDIR())
lcWinDir = GETENV([windir])

CHDIR (lcWinDir)

lcFile = SYS(2000,[*.exe])

IF !EMPTY(lcFile)
CLEAR
?lcFile
DO WHILE .T.
lcFile = SYS(2000,[*.exe],1)
IF !EMPTY(lcFile)
?lcFile
ELSE
EXIT
ENDIF
ENDDO
ENDIF

CHDIR (lcCurDir)
RELEASE lcCurDir, lcWinDir, lcFile

Montag, 4. August 2008

Neues bei VFPx

Nachdem das OOP Menu Projekt den Status 'Production Release' erreicht hat, gibt es auch schon das nächste Menüprojekt. Das von LingFeng Shi gemanagte PopMenu Projekt steht als Alfa Release zum Download bereit.

Eine regelmäßig aktualisierte Übersicht der VFPx Projekte gibts hier
http://tomsvfpblog.blogspot.com/2008/02/vfpx-auf-codeplex-aktualisiert.html

und hier (in Englisch)
http://www.codeplex.com/Wiki/View.aspx?ProjectName=VFPX

Dienstag, 15. Juli 2008

Rechtschreibkorrektur über Word

Wenn wir dem Anwender die Möglichkeit geben, innerhalb unserer Applikation Texte zu erfassen, die über ein paar Worte hinausgehen, dann wäre es ganz praktisch, diese erfassten Daten, auf Anfrage, einer Rechtschreibprüfung zu unterziehen. Ist auf dem Kundensystem Word installiert, so stellt sich diese Option als leicht realisierbar dar.
Der folgende Beispielcode greift auf eine Funktion zu, die ich bereits in einem früheren Posting veröffentlicht habe. Dort ging es um die Prüfung der aktuellen Word Version. Um die neue Funktion nutzen zu können, sollte der Code aus dem o.a. Posting somit ebenfalls als Funktion verfügbar sein. Sie stellt bei einer gültigen Word-Version innerhalb des _screen Objektes eine Referenz auf das erzeugte Word Objekt bereit (_screen.oWord)
* // Die Funktion SpellCheck verarbeitet entweder einen übergebenen Parameterstring       
* // oder, falls keine Wert übergeben wird, den Inhalt der Zwischenablage
FUNCTION SpellCheck
LPARAMETERS vText as String

LOCAL lcSource as String, loDoc AS Word.Document

DO CASE
CASE VARTYPE(m.vText) = [C]
lcSource = m.vText
CASE EMPTY(lcSource) AND !EMPTY(_CLIPTEXT)
lcSource = _CLIPTEXT
ENDCASE

* // Wenn Source gefüllt ist und Word geladen werden konnte, dann kann es losgehen
IF !EMPTY(lcSource) AND LoadWord() > 0

* // Neues leeres Worddokument erzeugen
loDoc = _screen.oWord.Documents.Add(,,1,.T.)

* // Das neue leere Worddokument mit dem zu prüfenden Text füllen
loDoc.Content.Text = lcSource

* // Rechtschreibkorrektur anwerfen
* // Wenn Rechtschreibfehler vermutet werden öffnet sich das Korrekturfenster
loDoc.CheckSpelling()

* // Jetzt steht im Worddokument der korrigierte Text, den wir unserer
* // Arbeitsvariablen bzw. Ziel-Property zuweisen
lcSource = loDoc.Content.Text

* // Sicherstellen, das Word weiterhin unsichtbar bleibt
_screen.oWord.Visible = .F.

* // Das zuvor erzeugte Worddokument schliessen,
* // ohne dass eine Sicherheitsabfrage aufpoppt
loDoc.Close(.F.)

* // Die nicht mehr benötigte Referenzvariable nullen
loDoc = .NULL.

* // Word schliessen ohne das eine Abfrage aufpoppt
_screen.oWord.Quit(.F.)

* // Referenzproperty nullen
_screen.oWord = .NULL.

RELEASE loDoc

ENDIF

RETURN lcSource

ENDFUNC
Anmerkung: Diese Funktion wurde nicht unter der Version 2007 getestet, da mir diese derzeit nicht zur Verfügung steht.

Dienstag, 1. Juli 2008

Aktualisierung von FoxCharts auf Version 0.40 Beta

Cesar Chalom hat eine neue Version seiner Diagramm Bibliothek ins Netz gestellt. Wer dieses Tool bisher noch nicht kennt, sollte das unbedingt nachholen!

Hier der Link zum VFP-Imaging Blog. Dort stehen auch die neuen Features zum Nachlesen.
http://weblogs.foxite.com/vfpimaging/archive/2008/06/30/6353.aspx

Hier der Link zur Downloadseite auf Codeplex/VFPx
http://www.codeplex.com/VFPX/Release/ProjectReleases.aspx?ReleaseId=14851

Cesar lässt in einem Kommentar zum Blog durchblicken, dass auch Balken- und gestapelte Balkendiagramme geplant sind. Somit schliesst sich die Variationenlücke zur Diagrammdarstellung innerhalb von Excel um ein weiteres Stück.

Montag, 30. Juni 2008

Kopieren, verschieben und löschen von Verzeichnisstrukturen

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

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

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.