Freitag, 24. Juli 2009

Datumsspielereien (Teil 4)

Bereits in einigen früheren Postings (Datumsspielereien Teil 1-3) waren Datumswerte ein Thema. In diesem Posting dreht es sich jedoch nicht um Feiertage sondern um die Berechnung spezieller Tage. Insbesondere die aktuelle Woche des Monats, die Kalenderwoche sowie der Montag der Woche.

Zur Berechnung des Monatswoche benötigen wir drei Funktionen: CEILING(), DOW() und DAY().

CEILING gibt die nächst höhere Ganzzahl zurück.

STORE 10.1 TO num1
STORE -10.9 TO num2
? CEILING(num1) && zeigt 11
? CEILING(num2) && zeigt -10
? CEILING(10.0) && zeigt 10
? CEILING(-10.0) && zeigt -10
DOW liefert einen numerischen Tag-der-Woche Wert auf Basis eines Datums. Hierbei ist jedoch der 2. Parameter zu beachten, in dem der Erste Tag der Woche definiert wird.
Da unsere Woche üblicherweise mit einem Montag beginnt, ist als Wert für den 2. Parameter zwingend eine 2 (-> Montag = Wochenbeginn) zu setzen.

? DOW(DATE(),2)    && zeigt beim 24.07.2009 den Wert 5
DAY wiederum extrahiert von einem Datumwert den Tagesblock.

? DAY(DATE())    && zeigt beim 24.07.2009 den Wert 24

FUNCTION WeekOfMonth as Integer
LPARAMETERS vDate as Date

* // Woche des Monats bestimmen
RETURN ( CEILING( ( DAY( m.vDate ) + DOW( ( m.vDate ) - DAY( m.vDate ) + 1, 2 ) - 1 ) / 7 ) )

ENDFUNC
Die Berechnung der Kalenderwoche gestaltet sich da schon um einiges einfacher.
Die Wochenbestimmung hängt von zwei Grundeinstellungen ab, die der WEEK()-Funktion als Parameter 2 und 3 übergeben werden.

- Parameter 2 definiert die Berechnung der ersten Woche des Jahres
- Parameter 3 definiert den Wochenanfang und entspricht dem 2. Parameter von DOW()

FUNCTION WeekOfYear as Integer
LPARAMETERS vDate as Date

* // Woche des Jahres bestimmen
* // Parameter 2: Wert = 2
* // Die längere Hälfte der ersten Woche
* // (4 Tage) definiert die Jahreszugehörigkeit
* // Parameter 3: Wert = 2
* // Montag gilt als der erste Tage der Woche
RETURN ( WEEK( m.vDate, 2, 2 ) )

ENDFUNC
Zuletzt kommen wir zur Funktion zur Berechnung des Montags einer Woche.
Auch in dieser Funktion wird mit DOW() gearbeitet, und wie bei den vorherigen Einsätzen wird der 2. Parameter wieder auf den Wert 2 gesetzt wird.

FUNCTION MondayOfWeek as Date
LPARAMETERS vDate as Date

* // Montag der Woche des übergebenen Datums
RETURN ( m.vDate - ( DOW( m.vDate, 2 ) - 1) )

ENDFUNC

Mittwoch, 15. Juli 2009

Arbeiten mit Arrays

Wer mit VFP arbeitet wird irgendwann feststellen, dass nicht immer alle Informationen als Cursor zur Verfügung stehen. Viele Funktionen des Fuxes die u.a. auch auf Objekte und Verzeichnisse zugreifen, stellen ihre gesammelten Informationen in Form eines Arrays bereit.

Unter anderem gibt die Funktion ADIR() bei gezielten Verzeichnisabfragen (bspw. mit ADIR(myArray,[*],[D],1) ) grundsätzlich den aktuellen (.) sowie den übergeordneten (..) relativen Pfadnamen aus. Zu finden im Array an Position 1,x und 2,x. Sollen diese beiden Verzeichnisse in der weiteren Verarbeitung keine Berücksichtigung finden, kann es sinnvoll sein, diese beiden Arrayblöcke zu entsorgen.

Für die Arbeit mit Arrays stellt uns VFP diverse Funktionen und Befehle zur Verfügung:

DECLARE DIMENSION LOCAL PUBLIC
Deklarieren oder Redimensioneren eine Variable als 1- oder 2-dimensionalen Array

AINS()
Einfügen weiterer Arrayblöcke

ACOPY()
Kopieren von Arrayblöcken an eine andere Position

ADEL()
Löschen von Arrayblöcken

ALEN()
Abprüfen der Dimensionierung eines Arrays

Zunächst deklarieren wir uns einen leeren Array namens laArrayDemo der nur eine einzelne Zelle beinhaltet:

DECLARE laArrayDemo(1)
Standardmäßig enthält die Zelle 1 den boolschen Wert FALSE. Die Ergänzung der Deklaration um einen Datentyp zeigt nur Auswirkung im Bereich von COM-Objekten. Zudem besteht keine Verpflichtung, jeder Zelle Werte identischer Datentypen zuzuordnen. Letztlich ist ein Array nur einen Art Collection.

Wollen wir den o.a. Array um einige Zellen erweitern, so geschieht dies durch eine erneute Deklaration des Arrays mit der entsprechend erhöhten (oder auch reduzierten) Anzahl an Zellen.

DECLARE laArrayDemo(5)
Wissen wir nicht genau wie die aktuelle Dimensionierung aussieht und wollen nur sicherstellen, dass die bestehende um x Zellen erweitert wird, dann müssen wir die Funktion ALEN() zu Hilfe nehmen. Bei unserem ein-dimensionalen Array stellen wir die Anzahl der Zellen fest und erhöhen das gelieferte Ergebnis um den gewünschten Wert.

DECLARE laArrayDemo(ALEN(laArrayDemo,1) + 4)
Etwas leserlicher sähe das so aus:

* // Arbeitsvariablen definieren
LOCAL liDim1 as Integer, liZuwachs as Integer
* // aktuelle Dimensionierung feststellen
liDim1 = ALEN(laArrayDemo,1)
* // Anzahl der neuen Zellen definieren
liZuwachs = 4
* // Redimensionierung durchführen
DECLARE laArrayDemo(liDim1 + liZuwachs)
* // Arbeitsvariablen freigeben
RELEASE liDim1, liZuwachs
Sind nun alle fünf Positionen mit Werten gefüllt ...

laArrayDemo(1) = [Wert in Zelle 1]
laArrayDemo(2) = [Wert in Zelle 2]
laArrayDemo(3) = [Wert in Zelle 3]
laArrayDemo(4) = [Wert in Zelle 4]
laArrayDemo(5) = [Wert in Zelle 5]
... und wir benötigen an Stelle 2 einen zusätzlichen Eintrag (weitere Zelle), dann nehmen wir die Funktion AINS() zu Hilfe. Wichtig bei dieser Funktion ist, dass sie keine Redimensionierung des Array vornimmt. Die verschiebt ab der definierten Position alle Einträge um eine Position nach hinten. Hierbei fällt u.U. die letzte Zelle raus.

AINS(laArrayDemo,2)
Wichtig ist also, dass wir zuvor eine Redimensionierung durchführen müssen, damit der Inhalt von Zelle 5 nicht verloren geht. Korrekterweise müsste der Code folglich so lauten:

* // Redimensionierung +1 durchführen
DECLARE laArrayDemo(ALEN(laArrayDemo,1) + 1)
* // ab (inklusive) Zelle 2 alle Zellen um
* // eine Position nach hinten verschieben
AINS(laArrayDemo,2)
Wollen wir den Inhalt der Zellen 1 und 2 ans Ende des Arrays verschieben und die Zellen 3-5 auf die Positionen 1-3, dann könnte dies bspw. wie folgt durchgeführt werden:

* / Deklarieren der Arbeitsvariablen
LOCAL liAnzahl as Integer, liQuellPosition as Integer, liZielPosition as Integer
liAnzahl = 2
liQuellPosition = 1
liZielPosition = ALEN(laArrayDemo,1) + 1
* // Redimensionierung des Array um die
* // Anzahl der zu verschiebenden Zellen
DECLARE laArrayDemo(ALEN(laArrayDemo,1) + liAnzahl)
* // Kopieren der Zelleninhalte in die
* // neuen angehängten leeren Zellen
ACOPY(laArrayDemo,laArrayDemo,liQuellPosition,liAnzahl,liZielPosition)
* // Löschen der nun nicht mehr benötigten
* // ersten beiden Zellen des Arrays
ADEL(laArrayDemo,1)
ADEL(laArrayDemo,1)
* // Redimensionieren des Arrays auf die
* // ursprüngliche Größe vor dem Kopieren
DECLARE gaArryDemo(ALEN(laArrayDemo,1) - liAnzahl)
* // Freigeben der Arbeitsvariablen
RELEASE liAnzahl, liQuellPosition, liZielPosition
Eine einfachere Vorgehensweise wäre der Einsatz eines 'on the fly' generierten temporären Arrays, wie es von ACOPY() automatisch erzeugt werden kann:

* // Deklarieren der Arbeitsvariablen
LOCAL liAnzahl as Integer, liQuellPos as Integer, liZielPos as Integer
liAnzahl = 2
liQuellPos = 1
liZielPos = (ALEN(laArrayDemo,1) + 1) - liAnzahl
* // Kopieren der Zellen in den temporären Array
* // Der neue Array wird automatisch erzeugt und
* // verfügt über die selbe Struktur wie der
* // Quellarray. Allerdings sind nur die kopierten
* // Zellen gefüllt. Alle anderen stehen auf .F.
ACOPY(laArrayDemo,laArrayTemp,liQuellPos,liAnzahl,liQuellPos)
* // Nun werden die nachfolgenden Zellen
* // nach oben kopiert
ACOPY(laArrayDemo,laArrayDemo,liQuellPos + liAnzahl,liZielPos - liAnzahl,liQuellPos)
* // Jetzt kommen die Zellen des TEMP-Arrays
* // an ihre neue Position im Basis Array
ACOPY(laArrayTemp,laArrayDemo,liQuellPos,liAnzahl,liZielPos)
* // Arbeitsvariablen freigeben und hierbei
* // den temporären Array nicht vergessen!!
RELEASE liAnzahl, liQuellPos, liZielPos, laArrayTemp
Dieser Code lässt sich als Funktion wie folgt umsetzen:

AMOVE(@pSourceArrayName, nErstesQuellElement, nAnzahlElemente, nErstesZielElement)
Wichtig hierbei ist, dass der Array als Referenz übergeben wird. Hierdurch sparen wir es uns, diesen als Rückgabewert zu liefern und die Funktion kann direkt mit den 'echten' Werten arbeiten.

Aufruf:

AMOVE(@laArrayDemo,1,2,4)
Funktion:

FUNCTION AMOVE
LPARAMETERS paArrayRef, piQuellPos as Integer, piAnz as Integer, piZielPos as Integer
LOCAL llReturn as Boolean
llReturn = .T.
TRY
* // Funktion für 1-dimensionale Arrays
ACOPY( pbArrayRef , laArrayTemp , piQuellPos , piAnz , piQuellPos )
ACOPY( paArrayRef , paArrayRef , piQuellPos + piAnz , piZielPos - piAnz,piQuellPos )
ACOPY( laArrayTemp , paArrayRef , piQuellPos , piAnz , piZielPos )
RELEASE laArrayTemp
CATCH
llReturn = .F.
ENDTRY
RETURN llReturn
ENDFUNC
Nachteil dieser Funktion ist, dass nur eindimensionale Arrays verarbeitet werden können. Also ergänzen wir diese Funktion zum einen um das automatische Erkennen der Dimensionalität sowie einen Trennung in eine 1- bzw. 2-Dimensionale Verarbeitung. Anschliessend sieht die Funktion wie folgt aus:

FUNCTION AMOVE
LPARAMETERS paArrayRef, piQuellPos as Integer, piAnz as Integer, piZielPos as Integer
* // Parameterwerte:
* // paArrayRef = Referenz auf Array
* // piQuellPos = Startposition (Row) ab der verschoben werden soll
* // piAnz = Anzahl der Rows die verschoben werden sollen
* // piZielPos = Zielposition (Row) an die verschoben werden soll

LOCAL llReturn as Boolean
llReturn = .T.

IF ALEN(paArrayRef,2) = 0
TRY
* // Block für 1-dimensionale Arrays
ACOPY( paArrayRef , laArrayTemp , piQuellPos , piAnz , piQuellPos )
ACOPY( paArrayRef , paArrayRef , piQuellPos + piAnz , piZielPos - piAnz, piQuellPos )
ACOPY( laArrayTemp , paArrayRef , piQuellPos , piAnz , piZielPos )
RELEASE laArrayTemp
CATCH
* // Irgendetwas ging schief
llReturn = .F.
ENDTRY
ELSE
TRY
* // Block für 2-dimensionale Arrays
LOCAL liColumns as Integer, liRows as Integer
* // Zur besseren Übersicht die Array Dimensionen zwischenspeichern
liColumns = ALEN(paArrayRef,2)
liRows = ALEN(paArrayRef,1)
* // Berechnen der tatsächlichen Offsets auf Basis der vorhandenen Columns
piQuellPos = IIF(piQuellPos > 1, piQuellPos * liColumns - liColumns + 1, piQuellPos)
piZielPos = IIF(piZielPos > 1, piZielPos * liColumns - liColumns + 1, piZielPos )
piAnz = piAnz * liColumns
* // Jetzt kann's los gehen
ACOPY( paArrayRef , laArrayTemp , piQuellPos , piAnz , piQuellPos )
ACOPY( paArrayRef , paArrayRef , piQuellPos + piAnz , liRows - piAnz,piQuellPos )
ACOPY( laArrayTemp , paArrayRef , piQuellPos, piAnz , piZielpos )
RELEASE laArrayTemp, liColumns, liRows
CATCH
* // Irgendetwas ging schief
llReturn = .F.
ENDTRY
ENDIF
RETURN llReturn
ENDFUNC

Donnerstag, 5. März 2009

Neuigkeiten über VFPx

In den letzten vier Wochen gab es wieder jede Menge neues im Bereich VFPx.

Bereich Abgeschlossene Projekte
  • Seit dem 22. Februar steht die Version 3.01 des PEM Editors zur Verfügung.

Bereich Release Candidates
  • Seit dem 18. Februar die von Rick Schummer angekündigte korrigierte und erweiterte VFP9 SP2 Hilfedatei in der Version 1.05 bereit.
  • Seit dem 9. Februar gibt es die Version 3.5.0 der Themed Controls.

Bereich Beta Releases
  • Seit dem 28. Februar gibt es die neue Version 00.09 der FoxTabs.

In meinem Blogeintrag vom 26. Februar 2008 -VFPx auf Codeplex aktualisiert- pflege ich die Neuerungen kontinuierlich nach. Die eigentlichen Projekte können von dort direkt aufgerufen werden.

Auch wenn wir uns derzeit in der Fastenzeit befinden kann ich nur sagen: "Ran an den Speck!"

Donnerstag, 12. Februar 2009

Nostalgie als VFP-Startprogramm

Vor ein paar Tagen fiel mir beim Durchstöbern eines alten VFP-Verzeichnisses ein kleines Programm auf, dass den C64 Bootscreen simuliert. Nun war der alte Brotkasten nicht wirklich mein erster Computer (mein erster Commodore, ein C116, war eher ein überdimensionierter Taschenrechner dessen Abmessungen die der zum laden und speichern der Programme benötigten Datasette nur knapp überstieg und bald durch einem C128 ersetzt wurde). Das hinderte mich jedoch nicht, das kleine prg ein klein wenig umzustellen und als Startprogramm innerhalb von VFP/Extras/Optionen/Dateiablage/Startprogramm zu hinterlegen. Wenn ich nun VFP starte werde ich wieder vom blauen Bootscreen begrüßt...

WITH _screen
.BackColor = RGB(1,0,162)
.ForeColor = RGB(83,81,240)
.FontSize = 24

CLEAR
DECLARE Sleep IN WIN32API INTEGER
LOCAL liWait as Integer
liWait = 500

Sleep(liWait)
? [ **** COMMODORE 64 ROM V1.1 ***]
Sleep(liWait)
?
Sleep(liWait)
? [ 64K RAM SYSTEM 38911 BYTES FREE]
?
Sleep(liWait)
? [READY.]
Sleep(liWait)
? [LOAD"VISUAL FOXPRO 9",8,1]
?
Sleep(liWait)
? [SEARCHING FOR VISUAL FOXPRO 9]
Sleep(liWait)
? [LOADING ]
FOR i = 1 TO 5
Sleep(liWait)
??[*]
ENDFOR
Sleep(liWait)

.BackColor = RGB(255,255,255)
.ForeColor = RGB(0,0,0)
CLEAR
?? [VISUAL FOXPRO 9 ...READY AND WAITING FOR INPUT]

.FontSize = 9
ENDWITH

Donnerstag, 5. Februar 2009

Einführung in Entwurfsmuster (Teil 5)

Im Dezember 2006/Januar 2007 schrieb Andy Kramek in seinem Blog über die Implementierung von Entwurfsmustern in Visual Foxpro. Andy hat mir erlaubt, meine deutsche Übersetzung seiner Artikelserie hier zu veröffentlichen. An dieser Stelle für seine Erlaubnis nochmals herzlichen Dank. Heute nun Teil 5, der sich mit dem Dekorierer beschäftigt.

Hinweis: Die Überschriften der jeweiligen Kapitel sind direkt mit Andy's Originalbeitrag verknüpft.

Einführung in Entwurfsmuster (Design Patterns)
Von Andy Kramek

Der Dekorierer (The Decorator)

Der Dekorierer beschreibt eine Lösung zu dem Problem, ein Objekt in seiner Funktionalität zu erweitern, ohne den eigentlichen Code des Objektes zu verändern.

Wie erkenne ich einen Dekorierer?

Die formale Definition des Dekorierers gemäß „GoF“ lautet:

Erweitere ein Objekt dynamisch um Zuständigkeiten.
Der Bedarf, Funktionalitäten (oder Verhalten) eines Objektes dynamisch zu ändern tritt in zwei Situationen auf. Erstens, wenn der Quellcode eines Objektes schlicht nicht verfügbar ist, vielleicht handelt es sich um ein ActiveX-Control oder es kommt eine Klassenbibliothek eines Drittanbieters zum Einsatz. Zweitens, wenn die Klasse intensiv genutzt wird, die spezifische Verantwortlichkeit jedoch nur in ein oder zwei bestimmten Situationen benötigt wird und es nicht angebracht wäre, den Code der Klasse einfach hinzuzufügen.

In den vorangegangenen Beiträgen haben wir verschiedene Wege zu Feststellung der Steueraufschläge in Abhängigkeit der Lokation betrachtet. Das grundsätzliche Steuerberechnungsobjekt arbeitet in jedem Fall mit einer sichtbaren Methode namens CalcTax() welche zwei Parameter erwartet (einen Wert und eine Rate), und gibt den fälligen Steuerbetrag zurück. Wir bekamen das Grundproblem bezüglich des Umgangs mit den Steuern mit Hilfe des Brückenmusters in den Griff, indem wir die Implementierung von der Schnittstelle separierten. Obwohl es flexibler war als eine einfache direkte Codierung, stellten wir jedoch sehr schnell fest, dass es nicht flexibel genug war, um mit unterschiedlichen Steuerraten in unterschiedlichen Lokationen zurechtzukommen. Sowohl die Strategie als auch die Zuständigkeitskette kommen mit dieser Anforderung zurecht, allerdings bedeuten beide Lösungswege das Klassenableitungen eingesetzt werden müssen.

Das Dekorierer-Muster erlaubt es uns, dieses Problem ohne Ableitungen der Originalklasse zu lösen. Stattdessen definieren wir ein neues Objekt mit gleichem Interface wie der Steuerrechner, welches jedoch den notwendigen Code beinhaltet, um die passende Steuerrate der gegebenen Lokation festzustellen. Dieses Objekt sieht genauso aus wie der Steuerrechner, es kann jedoch, weil es über eine Referenz auf den echten Steuerrechner verfügt, jedwede Berechnungsanfrage vorverarbeiten und anschließend einfach die echte Implementierung aufrufen.

Was sind die Komponenten eines Dekorierer?

Ein Dekorierer stellt zwei grundsätzlich Anforderungen. Zunächst einmal benötigen wir die Implementierungsklasse, welche die Schnittstelle und die grundlegende Funktionalität definiert. Desweiteren benötigen wir einen Dekorierer der die Schnittstelle der Implementierung reproduziert und über eine Referenz auf diese verfügt. Das Klient-Objekt leitet nun Aufrufe die eigentlich direkt an die Implementierung gegangen wären, an das Dekorierer Objekt. Die Basisstruktur für das Dekorierer-Muster sieht wie folgt aus:

Eigentlich ist dieses Muster eine erweiterte Brücke. Soweit es den Klienten betrifft kann er den Dekorierer aufrufen, als ob es sich um die Implementierung am Ende einer Standardbrücke handelt, da beide Schnittstellen identisch sind. Soweit es die Implementierung betrifft sieht der Aufruf genauso aus, wie wenn er direkt vom Klienten gekommen wäre. Letztlich muss sie nicht einmal wissen, dass es den Dekorierer überhaupt gibt.

Wie implementiere ich einen Dekorierer?

Eine neue Klasse mit dem Namen ‚cntDekorator‘ wird definiert um das Dekorierer-Beispiel zu implementieren. Es hat zwei spezielle Eigenschaften und eine spezielle Methode wie im folgenden beschrieben:

Name          Beschreibung
cImpClass Name der für diesen Dekorierer zu instanziierenden Klasse
cImpLib Klassenbibliothek der zu instanziierenden Implementierungsklasse
oCalculator Objektreferenz auf eine Instanz der real implementierten Klasse
der CalcTax() Methode
Dieses Objekt wird innerhalb der INIT() Methode des Dekorierers
instanziiert.
CalcTax Die Implementierung des Dekorierers für die äquivalente Methode
der realen Implementierung.

Eigentlich ist die Dekorierer-Klasse sehr einfach aufgebaut. Wenn sie erzeugt wird instanziiert sie die reale Implementierungsklasse, welche über die Namens- und Bibliotheks-Eigenschaften definiert ist. Zusätzlich implementiert sie eine operationale Methode (in diesem Fall ‚CalcTax‘), welche genauso benahmt ist und über dieselbe Signatur wie die reale Methode verfügt.

Der Code in der CalcTax()-Methode des Dekorierer ist ziemlich gradlinig. Er erwartet zwei Parameter, die LokationsID als String und den Preis für den die Steuer berechnet werden soll. Beachten Sie, dass dies nicht dieselben Parameter sind, die von der CalcTax() Methode der realen Klasse benötigt werden (dort übergeben wir Preis und Steuerrate). Die CalcTax() Methode des Dekorierers stellt die passende Rate basierend auf der LokationsID fest und ruft dann die CalcTax() Methode des Implementierungsobjekts mit den realen Parameters auf. Der Rückgabewert wird unverändert einfach nur an den Klienten durchgereicht.

LPARAMETERS tcLocation, tnPrice

LOCAL lnRate, lnTax
STORE 0 TO lnRate, lnTax
*** Korrekte Steuerrate feststellen
DO CASE
CASE tcLocation = [01]
lnRate = 5.75
CASE tcLocation = [02]
lnRate = 5.25
CASE tcLocation = [03]
lnRate = 0.00
OTHERWISE
lnRate = 0
ENDCASE

*** Nun die reale Implementierung aufrufen
lnTax = This.oCalculator.CalcTax( tnPrice, lnRate )

*** Und das Ergebnis zurückgeben
RETURN lnTax

Um den Dekorierer nutzen zu können müssen wir nur eine kleine Änderung im Klienten durchführen. Anstatt die reale Implementierung zu instanziieren muss nun das Dekorierer Objekt instanziiert werden. Denken Sie daran, dass die Aufrufsignatur des Dekorierers mit der realen Methode identisch ist. Somit müssen keine weiteren Änderungen durchgeführt werden.

LPARAMETERS tcContext, tnPrice
LOCAL lnTax
WITH ThisForm
*** Pruefen, ob der Dekorierer verfuegbar ist
IF VARTYPE( This.oCalc ) # [O]
*** Ist er nicht, also erzeugen wir ihn
.oCalc = NEWOBJECT( [cntDecorator], [calclass.vcx] )
ENDIF
*** Nun einfach die CalcTax() Methode aufrufen und Lokation und Preis uebergeben
lnTax = .oCalc.CalcTax( tcContext, tnPrice )
IF ISNULL( lnTax )
*** Die Daten konnten nicht verarbeitet werden
MESSAGEBOX( [Unable to Process this Location], 16, [Failed] )
lnTax = 0
ENDIF
RETURN lnTax
ENDWITH


Obwohl es sich hier um ein sehr einfaches Beispiel handelt, können Sie sehen, wie leicht es ist, die Funktionalität mit Hilfe eines Dekorierers zu erweitern. Eine typisches ‚real life‘ Problem, das mit Hilfe dieses Musters angegangen werden kann ist, wie man implementierungsspezifische Validierungen bei generischen Speicherroutinen unterstützt.

Dekorierer Zusammenfassung

Das Dekorierer Muster kommt zum Einsatz, wenn wir das grundsätzliche Verhalten einer spezifischen Instanz modifizieren möchten, ohne dabei entweder eine Klassenableitung zu erzeugen, oder den Originalcode zu ändern. Letztlich agiert der Dekorierer als Prä-Prozessor der Implementierung wobei er sich zwischen Klient und Implementierung schaltet.

Freitag, 23. Januar 2009

Reparierte VFP9 SP2 Hilfedatei wird unter VFPX bereitgestellt

Gute Nachricht von Rick Schummer zur im letzten Jahr im April erschienenen defekten SP2 Hilfedatei.

Nachdem diverse (noch nicht genannte) Entwickler in den letzten Monaten die Hilfedatei durch Decompilieren, Reparieren und Neukompilieren von ihren ursprünglichen Problemen befreit haben, hat YAG von Microsoft Seite nun die Genehmigung erteilt, die Hilfedatei auf VFPX unter der Creative Common License zu veröffentlichen. Diese ermächtigt die VFP Community Verbesserungen an der Hilfedatei vorzunehmen.

Rick kündigt an, dass sobald die letzten Fehler bereinigt sind, die Datei auf VFPX bereitsteht.

Donnerstag, 15. Januar 2009

WAV-Sounds mit VFP abspielen

Vor einiger Zeit hatte ich mir einen kleinen TeaTimer gebaut, damit mein schwarzer Tee nicht dauernd 10 bis 20 Minuten zieht. Das Ganze passte auch prima, solange ich die Timer-Form im Auge behielt. Dummerweise schaue ich während der Arbeit mit Foxpro aber auf mein Codefenster und so passierte es recht häufig, dass ich doch wieder einen 20 Minuten Tee da stehen hatte. Lange Rede kurzer Sinn, es musste etwas 'ohrenfälligeres' her, denn auch das Flackern des Timers bei abgelaufener Zeit half nicht wirklich.

Nachdem also der Entschluss zum Abspielen einer WAV-Datei gefallen war, musste ich feststellen, dass mir das Beispiel in den Solution Samples nicht auf Anhieb die Informationen preisgab, die mich interessierten. Irgendwo im Web fand ich dann ein Stück Mustercode zum Aufrufen der MCI API-DLL, welches ich als Grundlage für den u.a. Beispielcode verwendet habe. Seitdem funktionieren auch meine 3 und 4 Minuten Tees... ;-)

Die notwendige API befindet sich im Windows-System32 Verzeichnis und heisst WinMM.dll. Sie beinhaltet das Media Control Interface aus dem letztlich nur die Funktion PlaySound deklariert werden muss.

Die u.a. Funktion PlayWAV kann mit oder ohne Parameter aufgerufen werden. Wird der Name einer WAV-Datei übergeben, so wird versucht, diese abzuspielen. Andernfalls wird eine Standarddatei aus dem Windows Media Verzeichnis abgespielt. Die möglichen Werte des zweiten Parameters können dem u.a. DEFINE-Block entnommen werden.
?PlayWAV()

FUNCTION PlayWAV

LPARAMETERS vWavFile as String, vFlags as Integer

LOCAL llReturn as Boolean
llReturn = .T.

* // -----------------------------------------------------------
* // vWavFile = (optional)
* // Pfad und Dateiname der abzuspielen WAV-Datei
* //
* // vFlag = (optional)
* // gezielte Abspielvariante gem. u.a. DEFINEs
* // -----------------------------------------------------------

#DEFINE SND_SYNC 0x00000000 && synchron abspielen (default)
#DEFINE SND_ASYNC 0x00000001 && asynchron abspielen
#DEFINE SND_NODEFAULT 0x00000002 && silence (!default) wenn WAV nicht gefunden
#DEFINE SND_MEMORY 0x00000004 && zeigt auf eine im RAM befindliche Datei
#DEFINE SND_LOOP 0x00000008 && solange abspielen bis neue WAV übergeben wird
#DEFINE SND_NOSTOP 0x00000010 && derzeit ablaufende Sounds nicht unterbrechen
#DEFINE SND_NOWAIT 0x00002000 && nicht warten wenn der Treiber 'busy' ist
#DEFINE SND_ALIAS 0x00010000 && Name ist ein registrierter ALIAS
#DEFINE SND_ALIAS_ID 0x00110000 && Alias ist eine vordefinierte ID
#DEFINE SND_FILENAME 0x00020000 && Name ist ein Dateiname
#DEFINE SND_RESOURCE 0x00040004 && Name ist eine Ressource oder Atom
#DEFINE SND_PURGE 0x00000040 && entferne zum Abspielen nicht-statische Tasks
#DEFINE SND_APPLICATION 0x00000080 && Suche nach assoziierter Applikation

* // WinMM.dll -> MCI API-DLL -> MCI = Media Control Interface
TRY
DECLARE INTEGER PlaySound IN WinMM.dll AS PLAYSOUNDOVERAPI ;
STRING @ pszSound, INTEGER HMODULE_hmod, LONG DWORD_fdwSound

m.vWavFile = IIF( ;
VARTYPE(m.vWavFile) <> [C] OR !FILE(m.vWavFile), ;
ADDBS(GETENV([windir])) + [media\tada.wav], ;
m.vWavFile ;
)
m.vFlags = IIF( ;
VARTYPE(m.vFlags)=[N], ;
m.vFlags, ;
SND_ASYNC+SND_FILENAME ;
)
PLAYSOUNDOVERAPI(m.vWavFile,0,m.vFlags)
CATCH
* // API Aufruf schlug fehl, also nix mit TADA
llReturn = .F.
ENDTRY

RETURN llReturn

ENDFUNC