Donnerstag, 21. April 2016

PDFs über das olebrowser Control anzeigen / using the olebrowser control to display PDF files

Vor vielen Jahren stand ich vor der Aufgabe, eine PDF Datei innerhalb einer VFP Maske anzuzeigen. Gelöst habe ich dies über das Einbetten des olebrowser activeX Controls. Dieses Objekt bekam als Zielseite (über .Navigate ) einfach die anzuzeigende PDF Datei hinterlegt und fertig war die Laube.

Was passiert jedoch, wenn der Anwender auf die Idee kommt, die angezeigte Datei aus der VFP Anwendung heraus zu löschen? GENAU....es poppt eine Fehlermeldung auf, dass die Datei von einer anderen Applikation gesperrt wird....

Grund ist, dass das ole Control und der darin instanziierte PDF Reader die Datei zu diesem Zeitpunkt noch anzeigen und somit diese auch sperren, was das Löschen derselbigen unmöglich macht.

Um die Datei wieder frei zu bekommen genügt es leider nicht, dem Browser Control als neue anzuzeigende Seite "about:blank" zuzuweisen. Bis das Control hinter sich aufgeräumt hat, sprich der PDF Reader geschlossen wurde, dauert es leider ein paar Millisekunden. Ein einfaches WAIT WINDOW mit timeout und anschliessendem Löschen ist keine Option, denn je nach Systemauslastung ist die Aktion in kürzerer oder in längerer Zeit durchlaufen. Um auf Nummer sicher zu gehen muss eine Warteschleife mit Abprüfung der Control Eigenschaft '.ReadyState' her.

https://msdn.microsoft.com/en-us/library/bb268229%28v=vs.85%29.aspx

Wie im obigen Link nachzulesen ist, verfügt diese Eigenschaft über fünf Zustände

    READYSTATE_UNINITIALIZED = 0
    READYSTATE_LOADING = 1
    READYSTATE_LOADED = 2
    READYSTATE_INTERACTIVE = 3
    READYSTATE_COMPLETE = 4

von denen uns an dieser Stelle nur READYSTATE_COMPLETE = 4 interessiert.

Hier nun das notwendige Codesnippet:

?DeleteFileInBrowser( [c:\temp\myFile.pdf] )

FUNCTION DeleteFileInBrowser as Boolean
LPARAMETERS vFile as String

    LOCAL llReturn as Boolean
    llReturn = .F.
    
    IF FILE( vFile )

        DECLARE Sleep IN WIN32API INTEGER

        oBrowser = Thisform.oleBrowser
        oBrowser.Navigate( [about:blank] )
        DO WHILE oBrowser.ReadyState <> 4
                Sleep(200)                
        ENDDO 

        TRY 
            DELETE FILE ( vFile )
            llReturn = .T.
        CATCH 
            * Something went wrong
            * llReturn stays .F.
        ENDTRY 
        
    ENDIF 

    RETURN llReturn
    
ENDFUNC 

Mittwoch, 11. Juli 2012

Einfaches arbeiten mit ZIP Archiven / Working with ZIP files the easy way

Das Shell.Application Objekt kennt unter anderem eine Methode namens 'CopyHere'. Diese auf den ersten Blick recht harmlos erscheinende Methode beinhaltet jedoch auch das Verarbeiten von ZIP Archiven.

Wenn wir komplette Verzeichnisse archivieren bzw. wiederherstellen wollen, dann ist dies mit CopyHere auf relative einfache Weise möglich.

Das heutige Code Beispiel besteht aus zwei kleine Routinen zum Packen und Entpacken von Verzeichnissen unter Nutzung von CopyHere:

Among other things the Shell.Application object has a method called 'CopyHere'. At first glance, it appears to be rather harmless. However it contains complete processing of ZIP archives.

So, as long as we only compute complete directories, CopyHere offers a rather easy way of doing this.

Today's code example contains two small routines for un-/packing directories via CopyHere.

ZIP
CLEAR

lcFile    = [D:\Archive\myZipArchive.zip]
lcDir    = [D:\TEMP]
?
?Zip2Archive( lcDir , lcFile )

FUNCTION zip2Archive as Boolean
     LPARAMETERS vSourceDir as String, vZipFile as String
    DECLARE Sleep IN WIN32API INTEGER
    LOCAL    llReturn as Boolean, liOption as Integer, ;
            loShellObj as Object, loInputObj as Object, loOutputObj as Object, loFile as Object
    llReturn = .F.
    loShellObj = CreateObject( [Shell.Application] )
    IF TYPE( [loShellObj] ) = [O]
        IF NOT FILE( vZipFile )
            STRTOFILE( [] , vZipFile )
        ENDIF 
        loInputObj    = loShellObj.NameSpace( vSourceDir )
        loOutputObj    = loShellObj.NameSpace( vZipFile )
        liOption    = 4 && Do not display a progress dialog box 
        IF TYPE( [loInputObj] ) = [O] AND TYPE( [loOutputObj] ) = [O]
            TRY 
                FOR EACH loFile IN loInputobj.Items
                    loTargetObjExists = loOutputObj.ParseName( loFile.Name )
                    loSourceObjExists = loInputObj.ParseName( loFile.Name )
                    IF ISNULL( loTargetObjExists )
                        loOutputObj.CopyHere( loFile , liOption )
                        SLEEP( 200 )
                    ELSE  
                        IF loOutputObj.ParseName( loFile.Name ).ModifyDate < loFile.ModifyDate
                            ?loFile.Name + [ kann aktualisiert werden]
                        ELSE 
                            ?loFile.Name + [ ist aktuell]
                        ENDIF 
                    ENDIF 
                ENDFOR 
                llReturn = .T.
            CATCH 
                * Place messagebox here
            ENDTRY
        ENDIF 
    ENDIF
    RELEASE loInputObj, loOutputObj, loShellObj, loFile
    RETURN llReturn
ENDFUNC 
Paste your text here.

UNZIP
lcFile    = [D:\Archive\myZipFile.zip]
lcDir    = [D:\TEMP]
UnzipArchive( lcFile , lcDir )

FUNCTION UnzipArchive as Boolean
    LPARAMETERS vZipFile as String, vTargetDir as String
SET STEP ON 
    LOCAL    llReturn as Boolean, liOption as Integer, ;
            loShellObj as Object, loInputObj as Object, loOutputObj as Object
    llReturn = .F.
    
    loShellObj = CreateObject( [Shell.Application] )
    IF TYPE( [loShellObj] ) = [O]
        loOutputObj    = loShellObj.NameSpace( vTargetDir )
        loInputObj    = loShellObj.NameSpace( vZipFile )
        liOption    = 4 && Do not display a progress dialog box.
        IF TYPE( [loInputObj] ) = [O] AND TYPE( [loOutputObj] ) = [O]
            TRY 
                loOutputObj.CopyHere( loInputObj.Items , liOption )
                llReturn = .T.
            CATCH 
                * Place messagebox here
            ENDTRY
        ENDIF 
    ENDIF
    RELEASE loInputObj, loOutputObj, loShellObj 
    RETURN llReturn
ENDFUNC 

Der oben stehende Code kann natürlich auf die Verarbeitung bestimmter Dateien/Dateitypen umgestellt bzw. erweitert werden.
Above code can easily be modified to work with specific files/filetypes.

Weiterführende Infos zur Arbeit mit Verzeichnissen und Archiven gibt es hier:
Additional Infos about working with directories and archives can be found here:

http://technet.microsoft.com/en-us/library/ee176625

Montag, 7. Mai 2012

Datumsspielereien (Teil 9) / Date gadgets (Part 9)

Irgendwie sind Datumberechnungen ein Faß ohne Boden. Nach Teil 4 der Datumsspielereien war ich bereits der Meinung, nichts neues mehr zu diesem Thema zu finden. Aber irgendwie poppt in unregelmäßigen Abständen immer wieder eine neue Frage dazu auf.

Aktuell dreht es sich darum herauszufinden, wieviele Jahre, Monate und Tage zwischen zwei Datumswerten liegen.

Im folgenden Mustercode erledigt diese Aufgabe eine kleine Schleife die zunächst die Jahre, danach die Monate und letztendlich die Tage aufaddiert und uns so den genauen Zeitraum übermittelt.

Somehow, date calculations are a bottomless pit. With Part 4 of my date gadgets I was sure to find nothing new on this subject. But somehow every now and then, a new question pops up.

This post is about finding out the period between two dates.

The following sample code does this by using a loop that first adds years, then months and finally the days to give us the exact time between the given date values.


LOCAL ldDate1 AS Date, ldDate2 AS Date, liYear AS Integer, liMonth AS Integer, liDay AS Integer, llStatus as Boolean
ldDate1 = {28.8.1961}
ldDate2 = DATE()
STORE 0 TO liYear, liMonth, liDay

llStatus = GetPeriodBetweenDates( ldDate1 , ldDate2 , @liYear , @liMonth , @liDay )

CLEAR 
IF llStatus = .T.
    ? [Zeitraum zwischen ] + DTOC( ldDate1 ) + [ und ] + DTOC( ldDate2 )
    ?  TRANSFORM( liYear )  + [ Jahr]  + IIF( liYear  <> 1 , [e ] , [ ] )
    ?? TRANSFORM( liMonth ) + [ Monat] + IIF( liMonth <> 1 , [e ] , [ ] )
    ?? TRANSFORM( liDay )   + [ Tag]   + IIF( liDay   <> 1 , [e ] , [ ] )
ENDIF 

FUNCTION GetPeriodBetweenDates as Boolean

    * // Parameter 3-5 als Referenz übergeben!
    * // Parameters 3-5 by reference
    LPARAMETERS vDate1 as Date, vDate2 as Date, vYear as Integer, vMonth as Integer, vDay as Integer

    LOCAL ldDate1 as Date, ldDate2 as Date
    ldDate1 = IIF( vDate1 > vDate2 , vDate2 , vDate1 )
    ldDate2 = IIF( vDate1 > vDate2 , vDate1 , vDate2 )

    STORE 0 TO vYear , vMonth , vDay

    DO WHILE ldDate1 < ldDate2
        DO CASE 
        CASE GOMONTH( ldDate1 , 12 ) < ldDate2
            vYear = vYear + 1 
            ldDate1 = GOMONTH( ldDate1 , 12 )
        CASE GOMONTH( ldDate1 , 1 ) < ldDate2
            vMonth = vMonth + 1 
            ldDate1 = GOMONTH( ldDate1 , 1 )
        OTHERWISE 
            vDay = vDay + 1 
            ldDate1 = ldDate1 + 1 
        ENDCASE 
    ENDDO 

    RETURN .T.

ENDFUNC 

Mittwoch, 21. Dezember 2011

Datumsspielereien (Teil 8) / Date gadgets (Part 8)

Die TIME() Funktion von Visual Foxpro liefert uns eine Uhrzeit als Characterstring. Wenn wir zu dieser Zeit jedoch Stunden, Minuten oder Sekunden hinzuaddieren möchten, dann ist das bei einem String eine mühsame Arbeit.

Anstatt mit TIME() sollten wir für Berechnungen immer mit DATETIME() arbeiten. Hier steht uns neben dem Datum auch die Uhrzeit für Berechnungen zur Verfügung. Allerdings wird ein DATETIME() + 1 ein gänzlich anderes Ergebnis liefern als DATE() + 1. Wenn DATE() grundsätzlich auf Tagesbasis seine Berechnungen durchführt, so reagiert DATETIME() auf Sekundenbasis.
Um mit DATETIME() also Stunden oder Minuten zu berechnen müssen wir entweder x * 60 * 60 für Stunden oder x * 60 für Minuten eingeben.

Zur Extraktion der Uhrzeit vom Datum/Zeit-Wert steht uns dann im Anschluß die Funktion TTOC() zur Verfügung. Wichtig hierbei ist der zweite Parameterwert den wir zwingend auf 2 setzen müssen.

CLEAR
ltDatetime = DATETIME()
? ltDatetime
? CalcTime( 5 , 0 , 0 , ltDatetime )
? CalcTime( 0 , 5 , 0 , ltDatetime )
? CalcTime( 0 , 0 , 5 , ltDatetime )
? CalcTime( 5 , 5 , 5 , ltDatetime )

? CalcTime( -5 )


FUNCTION CalcTime as String
LPARAMETERS vHours as Integer, vMinutes as Integer, vSeconds as Integer, vDatetime as Datetime

    vHours      = EVL( vHours    , 0 )
    vMinutes    = EVL( vMinutes  , 0 )
    vSeconds    = EVL( vSeconds  , 0 )
    vDatetime   = EVL( vDatetime , DATETIME() )

    vDatetime = vDatetime + ( vHours   * 60 * 60 )
    vDatetime = vDatetime + ( vMinutes * 60 )
    vDatetime = vDatetime + ( vSeconds )

    RETURN TTOC( vDatetime , 2 )

ENDFUNC 

Mittwoch, 14. Dezember 2011

Erstellen von Tablet-PC Anwendungen mit VFP / Creating Tablet-PC Applications with VFP

Vor ein paar Wochen stolperte ich bei der Suche nach Informationen zur Erstellung von Tablet PC Anwendungen mit Visual Foxpro über den folgenden Blogbeitrag:

http://www.tabletpcblog.de/2010/03/11/erstellen-von-tablet-pc-anwendungen-mit-visual-foxpro/

Bei diesem Posting handelt es sich um eine Übersetzung eines MSDN Artikels von Mike Stewart aus dem Jahr 2004:

http://msdn.microsoft.com/en-us/library/ms965060

Damals, zu XP Zeiten waren die notwendigen Vorbereitungen noch ein wenig aufwändiger als heute unter Windows 7, welches die benötigten Bibliotheken bereits 'ab Werk' mit sich führt. Und erstaunlicherweise funktioniert der in den Beiträgen enthaltene Beispielcode unter Windows 7 ohne Probleme.

Wer also mit dem Gedanken spielt, seine Anwendung für Touchscreens fit zu machen, der sollte sich die Beiträge einmal genauer zu Gemüte führen!