Donnerstag, 18. Dezember 2008

Einführung in Entwurfsmuster (Teil3) / Introduction to design patterns (Part3)

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 3, der sich mit dem Strategiemuster beschäftigt.

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

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

Die Strategie (Strategy)

Was ist eine Strategie und wie setze ich sie ein?

Die Strategie beschreibt eine Lösung zum Hauptproblem beim Erstellen von generischem Code – Wie gehe ich mit nicht vorhergesehenen Anforderungen und Änderungen bei der Implementierung um.

Woran erkenne ich, wo ich eine Strategie benötige?

Die formelle Definition einer Strategie, gemäß „Entwurfsmuster – Elemente wieder verwendbarer objektorientierter Software“ von Gamma, Helm, Johnson und Vlissides lautet:
Definiere eine Familie von Algorithmen, kapsele jeden einzelnen und mache sie austauschbar. Das Strategiemuster ermöglicht es, den Algorithmus unabhängig von ihn nutzenden Klienten zu variieren.
Beim ersten Lesen hört sich das ein klein wenig obskur an. Wir sollten uns ein einfaches Beispiel ansehen, bei dem ein Strategiemuster helfen könnte. Nehmen wir das Problem, die Mehrwertsteuer für einen Einkauf zu berechnen. Wir können davon ausgehen, dass unsere Applikation weiß, welche Gegenstände verkauft wurden und die entsprechende Menge (auf Basis der Preisinformationen) und so feststellen kann, wie hoch der Einkaufsbetrag ist. Nun müssen wir die Mehrwertsteuer ausweisen bzw. berechnen.

Eigentlich gibt es da kein Problem, solange wir in einer Region leben, die nur einen Mehrwertsteuersatz kennt. Worauf zu achten ist: Wird der Gegenstand überhaupt versteuert oder nicht, und wenn ja wie ist sein augenblicklicher Mehrwertsteuersatz. Allerdings sind in den USA Mehrwertsteuersätze länder- oder sogar ortsspezifisch. Der Prozentsatz hängt davon ab, wo ich etwas kaufe (oder sogar, in einigen Fällen, wo der Sitz des Verkäufers liegt).

Beispielsweise beläuft sich in unserem Staat die Mehrwertsteuer für Kleidung auf 5.75%, aber wenn wie 20 Meilen nach Süden fahren beträgt der Steuersatz nur 5.25% und wenn wir in den nächsten Staat fahren (nur 50 Meilen entfernt) dann gibt es dort gar keine Mehrwertsteuer auf Kleidung. Ein und derselbe Gegenstand der mit $29.95 ausgeschildert ist kann uns deswegen $31.67, $31.52 oder $29.95 kosten, je nach dem, wo wir ihn kaufen.

Um das ganze etwas abstrakter zu Beschreiben, die ausgewiesene Mehrwertsteuer der Transaktion hängt vom Kontext ab, in dem die Transaktion stattfindet. Würden wir nun den entsprechenden Code in der Applikation hinterlegen könnte das wie folgt aussehen:

DO CASE

CASE lcLocale = [Akron]

    IF lcItemType = [Kleidung]

        lnTaxRate = 5.75

    ELSE

        * Weitere Einträge hier

    ENDIF

CASE lcLocale = [Canton]

    IF lcItemType = [Kleidung]

        lnTaxRate = 5.25

    ELSE

        * Weitere Einträge hier

    ENDIF

CASE lcLocale = [Grove City]

    IF lcItemType = [Kleidung]

        lnTaxRate = 0.00

    ELSE

        * Weitere Einträge hier

    ENDIF

OTHERWISE

    * Standardwert zuweisen

    lnTaxRate = 5.50

ENDCASE


Wir können sofort erkennen, was das Problem sein wird!

Was passiert, wenn wir Cleveland zu unserer Ortsliste hinzufügen, oder wenn Akron beschließt, seine Steuer zu senken, um Canton Verkaufsanteile streitig zu machen. Wir müssen unseren Code mit allen damit verbundenen Risiken ändern. Je mehr Orte wir der Liste hinzufügen umso unübersichtlicher wird der Code.

Natürlich könnten wir die Informationen innerhalb einer Tabelle hinterlegen (vielleicht eine Spalte für Orte, eine Spalte für jeden Artikel) und lesen diese Daten jedes Mal nach wenn wir sie benötigen. Auf diese Weise müssten wir nur noch einzelne Datensätze anpassen wenn sich Werte ändern, allerdings könnte das ziemlich schnell eine ganz schön große Tabelle werden und es gäbe jede Menge Duplikate und Redundanzen innerhalb dieser Datenmenge. Also auch dies wäre nicht die optimale Lösung.

Was wir doch eigentlich in unserer Applikation tun wollen ist, einfach den Artikelpreis und Kontextinformationen zu übergeben, und zwar an etwas, das uns einfach nur meldet wie hoch der Mehrwertsteuerbetrag ist. Mit anderen Worten wollen wir die Abstraktion (berechne die fällige Mehrwertsteuer) von der Implementierung (basierend auf dem Ort) trennen. Solange wir nur den Code verschieben, wird nur das Problem verlagert aber nicht gelöst.

Eine einfache Brücke ist nicht die Lösung, denn sie ermöglich nur eine einzelne Implementierung und wir benötigen multiple Implementierungen. Was wir brauchen ist die Möglichkeit zur Laufzeit zu entscheiden, welche Implementierung zum Einsatz kommen soll. Das Strategiemuster bietet uns die Möglichkeit, Klassen für jede Situation zu definieren mit der wir konfrontiert werden und dann die passende zur Laufzeit zu instanzieren.

Was sind die Komponenten einer Strategie?

Eine Strategie besteht aus drei essentiellen Komponenten. Die „abstrakte Strategie“ definiert das Interface (Schnittstelle) sowie generelle Funktionalitäten. Die „konkreten Strategien“ sind die Unterklassen welche die verschiedenen möglichen Implementierungen definieren. Die dritte Komponente, der „Kontext“, ist verantwortlich für die Referenzierung auf die aktuelle Implementierung. Die Strategie wird über eine Anfrage zur Aktion durch den Klienten initiiert.

 
Wir können uns eine Strategie als „dynamische Brücke“ vorstellen, in welcher das eine Ende (der Kontext) statisch ist, das andere (die Strategie) jedoch aus einer Auswahl von möglichen Varianten eine Implementierung auswählt, die für den Augenblick passend ist. Die Essenz des Musters ist, das die Entscheidung, welche Unterklasse instanziert werden soll, zu jeder Zeit davon abhängt, welche Informationen vom Klienten (bspw. der Applikation) benötigt werden.

Typischerweise weist das Muster die Verantwortung zur Instanzierung der konkreten Strategie dem Klienten zu, welcher dann eine Referenz an den Kontext übergibt. Um nun ein Strategiemuster zu implementieren müssen Sie eine abstrakte Strategieklasse definieren, und so viele unterschiedliche spezielle Unterklassen wie Sie benötigen. Das Objekt, welches üblicherweise die Rolle des Kontext übernimmt (in VFP ist dies üblicherweise die Form oder der Parent Container) muss dem potentiellen Klient ein passendes Interface bereitstellen. Ebenso benötigt es eine Eigenschaft die genutzt wird, um die Referenz auf das aktuelle Implementierungsobjekt aufzunehmen.

Wie implementiere ich eine Strategie?

Das folgende Beispiel zeigt auf, wie wir ein Strategiemuster als Lösung für das zuvor aufgeführte Mehrwertsteuerproblem einsetzen könnten.

Das Erste was wir tun müssen ist, dass wir eine abstrakte Klasse definieren, die über eine Eigenschaft verfügt, in der die zu verarbeitende Steuerrate hinterlegt wird. Sie verfügt zudem über eine Methode zum Berechnen der Steuer auf Basis des übergebenen Betrages und der Steuerrateneigenschaft. Nun erzeugen wir so viele Unterklassen dieser Definition wie wir Steuerratenvarianten haben – jede Variante eine Unterklasse.

Jetzt müssen wir noch herausfinden, wie wir feststellen können, wann welche der diversen Unterklassen zum Einsatz kommen soll. Um dies zu können müssen wir die verschiedenen Lokationen und verfügbaren Steuerraten mit der passenden Unterklasse in Relation setzen.

Natürlich gibt es viele Möglichkeiten dies zu tun und die einfachste wäre, einfach eine Liste der Lokationen (bspw. Städte) und die dort geltende Steuerrate zu erstellen. Da wir eine Unterklasse für jede Steuerrate haben können wir diese auf Basis der zur Lokation gehörenden Steuerrate herausfinden. Da wir jedoch immer noch eine Tabelle mit einem Datensatz je Stadt benötigen, können wir den Aufwand dadurch reduzieren, dass wir unsere Unterklasse analog zur Steuerrate benamen. Dies würde in etwa wie folgt aussehen:

Stadt      Steuerrate   Unterklasse
Akron      5,75         Tax575
Canton     5,25         Tax525
Grove City 0,00         Tax000
Kent       5,75         Tax575

Mit anderen Worten nutzen wir die zugehörige Steuerrate zur Identifikation der Unterklasse. Dies hat zur Konsequenz, dass wir die dritte Spalte der Tabelle nicht mehr benötigen. Wir können die Unterklasse direkt auf Basis der Steuerrate benennen.

Wie codieren wir dies nun in einem Formular? Nun, das ist ziemlich einfach. Alles was wir brauchen ist eine DropDownListe mit den Lokationen aus unserer Stadt/Steuerraten-Tabellen. Wenn die Stadt ausgewählt wurde, erhalten wir die zugehörige Steuerrate. Hierüber wiederum generieren wir den Namen der zu instanzierenden Unterklasse, und da alle Unterklassen aus einer zentralen Basisklasse abgeleitet sind, wissen wir, dass alles was wir nur die passende Methode im Objekt mit den richtigen Parametern - in diesem Fall zwei Parameter – den Kontextschlüssel (in diesem Fall die zugeordnete Steuerrate) und den Preis zu dem die Steuern berechnet werden sollen. Der Kontextschlüssel wird genutzt um den Namen der benötigten Unterklasse zu generieren. Wenn die aktuelle Klasse nicht die richtige ist, instanzieren wir einfach die korrekte Klasse.

Hier nun der hinterlegte Code:

LPARAMETERS tcContext, tnPrice
WITH This
    * Definiere den Namen des Strategieobjektes basierend auf der Basisklasse und Steuerrate
    lcStrategy = [CNTSTRAT] + PADR( tcContext, 3, [0] )
    * Gibt es die Klasse schon?
    IF ISNULL( .oStrategy ) OR NOT UPPER( .oStrategy.Name ) == lcStrategy
        * Diese Klasse muss noch erzeugt werden
        .oStrategy = NEWOBJECT( lcStrategy, [Ch15.vcx] )
    ENDIF
    * Jetzt wird die Berechnungsmethode aufgerufen
    lnTax = .oStrategy.CalcTax( tnPrice )
    RETURN lnTax
ENDWITH

Dies ist nur eine Möglichkeit wie eine Strategie implementiert werden kann. Sie macht im gegebenen Beispiel durchaus Sinn, in anderen Szenarien können andere Implementierungen notwendig sein. Stellen Sie sich beispielsweise einmal die Zuweisung von Rabatten an Aufträge vor. Die Maske zur Erfassung (Der Kontext) könnte diverse unterschiedliche Rabatte innerhalb eines Auftrags benötigen:
  • Mengenrabatte, zu berechnen für einen einzelnen Artikel
  • Auftragswertrabatt, zu berechnen auf den kompletten Auftrag
  • Spezialrabatte oder Werberabatte auf einzelne Artikel
  • Kundenbezogene Rabatte auf den kompletten Auftrag
Ein mögliches Interface könnten Commandbuttons nutzen (Die Klienten) so dass der Operator entscheiden kann, wann welcher spezielle Rabatt zugewiesen werden soll. In diesem Szenario wäre es durchaus sinnvoll und auch einfacher, jeder Button kann die Referenz auf eine spezielle Rabattierungs-Strategie-Unterklasse an die Form übergeben (Hierbei sollten wir bedenken, dass diese Vorgehensweise zwar in VFP möglich ist, in anderen Sprachen geht dies jedoch nicht). Die Form könnte die die jeweils benötigte Kalkulation durchführen und das Ergebnis anzeigen, so dass der Code nicht in den Button reinkopiert werden müsste.

Strategiemuster Zusammenfassung

Wir haben eine mögliche Implementierung für ein Strategiemuster gesehen und ein anderes kurz angerissen. Der Vorteil einer Strategie ist, dass sie es umgeht, alternative Implementierungen innerhalb des Codes hinterlegen zu müssen. Sie erlaubt es uns, separate Objekte die über ein gemeinsames Interface verfügen zu erzeugen. Für jede Option erfolgt eine gezielte Instanzierung des benötigten Objekts. Wie bei anderen Mustern auch gilt, dass die jeweiligen Implementierungen verschieden sein können, das Muster sich jedoch nicht ändert.

Quellennachweis:
Entwurfsmuster - Elemente wiederverwendbarer objektorientierter Software ADDISON-WESLEY ISBN 3-89319-950-0

Mittwoch, 10. Dezember 2008

Einführung in Entwurfsmuster (Teil2) / Introduction to design patterns (Part2)

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 2, der sich mit dem grundlegendsten Muster - der Brücke - beschäftigt.

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

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

Die Brücke (Bridge)

Das erste Muster, das wir uns anschauen werden ist die Brücke, oft auch als die ‚Mutter aller Entwurfsmuster’ bezeichnet. Sie ist das grundlegendste aller Muster und je mehr Sie sich in Muster im Allgemeinen einarbeiten, werden Sie feststellen, dass sie irgendwie in fast allen anderen Mustern enthalten ist.

OK, was also ist eine Brücke?

Die formelle Definition einer Brücke, gemäß „Entwurfsmuster – Elemente wieder verwendbarer objektorientierter Software“ von Gamma, Helm, Johnson und Vlissides (GoF) lautet:
Entkoppelt eine Abstraktion von ihrer Implementierung, so dass beide unabhängig voneinander variiert werden können.
Imposant, oder? Machen wir das in unserem Code tatsächlich?

Die kurze Antwort ist, dass wir das wahrscheinlich öfter tun als wir glauben. Nehmen wir einmal ein allgemeines Beispiel. Wenn wir Code innerhalb unserer Formulare und Klassen schreiben, dann möchten wir Fehler abfangen, und üblicherweise (als pflichtbewusste Entwickler) unseren Anwendern sagen, wenn ein Problem auftritt. Die offensichtlichste und simpelste Lösung ist es ein paar Zeilen Code in der betroffenen Methode zu hinterlegen. Das Ergebnis könnte wie folgt aussehen:

IF NOT < eine Funktion die TRUE/FALSE zurückliefert >

    lcText = [ Das Feld enthält keinen korrekten Wert ] + CHR(13);

           + [ Drücken sie eine beliebige Taste für eine erneute Eingabe ]

    WAIT lcText WINDOW

    RETURN .F.

ENDIF


In unserer gesamten Applikation werden wir auf Situationen stoßen, in denen wir ein „wait window“ wie oben codiert benötigen um den Anwender zu informieren. Dies wird auch so lange perfekt funktionieren bis eines von zwei Dingen eintritt. Entweder beschließt unser Anwender, dass er diese mickrigen „Wait Windows“ hasst und viel lieber eine Messagebox im Windowsstil hätte, oder viel schlimmer, wir müssen die Applikation in eine Umgebung portieren, in der „wait window“ nicht unterstützt wird. Vielleicht als COM-Objekt oder in einem Web-Formular oder das Form wird als Toplevel Form mit Desktop = .T. gesetzt.

Wie auch immer. Nun sind wir gezwungen, unsere Applikation zu durchsuchen und überall den besagten Code an die neuen Anforderungen umzustellen. Ich weiß nicht wie es bei Ihnen ist, aber meine Erfahrung sagt mir, dass die Chance, beim ersten Mal alles richtig umzusetzen (kein Auftreten übersehen und jede einzelne Umstellung fehlerfrei), gleich Null ist. Und selbst wenn dies der Fall sein sollte bleibt uns noch die Arbeit des Prüfens jeder einzelnen Umstellung.

Ein anderes Szenario tritt ein, wenn wir mit Kollegen arbeiten. Stellen Sie sich vor, in einem meiner Formulare rufe ich eine Systemfunktion auf, und da ich ein vorsichtiger Entwickler bin prüfe ich, ob das Ergebnis wie erwartet geliefert wurde und im Fehlerfall blende ich eine Meldung wie die folgende ein:

lcText = [ Fehlerhafter Rückgabewert bei Funktionsaufruf ]

Zwischenzeitig hat mein Kollege ein ähnliches Stück Code in einem anderen Teil der Applikation geschrieben und bringt im Fehlerfall die folgende Meldung:

lcText = [ Die angeforderte Aufgabe konnte nicht durchgeführt werden ]

Auch wenn wir beide eigentlich auf die gleiche Art und Weise reagieren ist der Inhalt unterschiedlich, obwohl in beiden Fällen das Selbe passiert ist. Verwirrend? Darauf können Sie wetten. Schlimmer noch, für den Anwender sieht es unsauber aus.

Nun, was hat das alles mit dem Brückenmuster zu tun? Der Grund für unser Problem ist, dass wir nicht bemerkt haben, dass wir eigentlich eine Abstraktion (Anzeigen einer Nachricht für den Anwender) an eine Implementierung (die Befehle „Wait Window“ oder „Messagebox“) binden. Hätten wir es bemerkt, würden wir wohl eine Brücke genutzt und das Problem umgangen haben. Hier nun derselbe Code unter Einsatz eines Brückenmusters:

IF NOT <eine Funktion die TRUE/FALSE zurückliefert >

    This.oMsgHandler.ShowMessage( 9011 )

    RETURN .F.

ENDIF

Sehen Sie den Unterschied? Wir wissen nicht, oder es interessiert uns nicht, welche Art Nachricht angezeigt wird oder was im Hintergrund passiert. Alles was wir wissen müssen ist, wo wir die Referenz auf das Objekt herholen, dass die Aufgabe für uns erledigt. Und natürlich den Identifier der dem Handler mitteilt, welche Nachricht wir zu Anzeige bringen möchten. Natürlich ist es essentiell wichtig, dass alle Handler auch über das passende Interface verfügen, in diesem Fall die ShowMessage() Methode.

Es ist die Herkunft der Referenz die „Brücke“ genannt wird. In diesem Beispiel stellt die „oMsgHandler“-Eigenschaft die Brücke dar zwischen dem Code der eine Nachricht verlangt und dem Mechanismus der sich um die Nachricht kümmert. Alles was wir nun tun müssen, um die Art wie die Nachricht verarbeitet wird zu ändern, ist die Objektreferenz in der Eigenschaft zu ändern. Dies kann recht einfach zur Laufzeit durchgeführt werden, hängt jedoch von der Umgebung ab, unter der das Parentobjekt instanziert wurde (der Mechanismus der sich dahinter verbirgt ist ein Beispiel für ein anderes Muster mit dem Namen „Strategie“ (Strategy), das ich später noch ansprechen werde). Dieser Ansatz entkoppelt erfolgreich die Abstraktion von seiner Implementation und als Ergebnis erhalten wir erheblich besser wieder verwendbaren Code.

Was sind die Komponenten einer Brücke?

Eine Brücke besteht aus zwei essentiellen Komponenten, der „Abstraktion“, welche das verantwortliche Objekt für die Instanzierung der Operation darstellt, sowie der „Implementierung“ bei der es sich um das Objekt handelt, das diese ausführt. Die Abstraktion kennt ihre Implementierung entweder weil sie über eine Referenz darauf verfügt, oder weil es sie besitzt (bspw. enthält). Auch wenn es passieren kann, dass die Abstraktion die Implementierung erzeugt, so ist dies keine absolute Voraussetzung für dieses Muster. Eine Brücke kann auch eine bereits existierende Referenz zu einem Implementierungsobjekt nutzen. Tatsache ist, dass dieser Art des Einsatzes von Brücken sehr effizient sein kann. Verschiedene Abstraktionsobjekte können dasselbe Implementierungsobjekt einsetzen.

Eine weitere Tatsache ist, da Visual FoxPro über ein sehr gutes Containership Modell verfügt, so dass die meisten Entwickler feststellen werden, dass sie (unabsichtlich) schon mehr Brücken implementiert haben als sie glaubten. Die meisten ‚Manager’ Objekte (bspw. Form Manager, Ini Datei Manager, Message Manager) sind letztlich Beispiele für das Brückenmuster.



Wichtig ist, eine simple Nachrichtenweiterleitung nicht mit einer Brücke zu verwechseln. Wenn eine Methode eines Command buttons eine Methode seiner Form aufruft in der die entsprechende Aktion implementiert ist, dann handelt es sich um eine einfache Nachrichtenweiterleitung und nicht um eine Brücke. Ruft diese Formularmethode jedoch ein anderes Objekt zum Durchführen der Aktion auf, dann haben wir eine Brücke.

Interessanterweise tritt eine andere Möglichkeit auf, wenn ein Form (oder ein Container) über eine Eigenschaft verfügt, die eine Referenz auf ein Implementierungsobjekt hält. Ein enthaltenes Objekt (bspw. ein Command button auf einer Form) kann direkt auf diese Property zugreifen und eine lokale Referenz auf das Implementierungsobjekt erhalten. Hierdurch kann es direkt Methoden des Implementierungsobjektes aufrufen. Nun, handelt es sich hierbei um eine Brücke oder um Nachrichtenweiterleitung?

Tatsächlich könnten wir beide Varianten rechtfertigen. Allerdings bringt uns die Antwort nicht weiter. Dieses Problem tritt nämlich nur in Visual FoxPro auf, da es standardmäßig Eigenschaften als „Public“ bereitstellt und gleichzeitig rückwärtige Zeiger implementiert, die es Objekten erlauben, ihr Parentobjekt direkt zu adressieren. In den meisten objektorientierten Entwicklungsumgebungen ist dies schlicht und ergreifend nicht möglich, auf solche Weise auf andere Objekte zuzugreifen.

Um eine Brücke zu implementieren müssen Sie somit wissen, welches Objekt die Rolle der Abstraktion und welches die Rolle der Implementierung übernimmt. Haben sie diese erst einmal definiert, dann bleibt nur noch die Entscheidung, wie die Brücke zwischen den beiden codiert werden soll. Dies hängt dann letztlich komplett vom jeweiligen Szenario ab.

Quellennachweis:

Entwurfsmuster - Elemente wiederverwendbarer objektorientierter Software ADDISON-WESLEY ISBN 3-89319-950-0

Donnerstag, 4. Dezember 2008

Einführung in Entwurfsmuster (Teil1) / Introduction to design patterns (Part1)

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 also der erste Teil.

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

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

Warum sich mit Entwurfsmustern plagen?

Entwurfsmuster sind eine Standardsprache für das Erkennen, Definieren und Beschreiben von Lösungen für Softwareproblemstellungen. Die Kenntnis von Entwurfsmustern erleichtert das Verstehen von vorhandenen Systemen sowie die Anforderungsbeschreibung von komplexen neuen Systemen.

Ich erinnere mich, das ich vor einigen Jahren mit Paul Maskens in der Lobby des Lindner Kongresshotels in Frankfurt saß, während er versuchte, mir eine Idee für eine Werkzeugkiste innerhalb einer Gridspalte zu erklären. Er beschrieb seine Lösung zu diesem Problem und plötzlich, nach ca. 20 Minuten, stellte ich fest, dass er über ein Strategiemuster sprach. Hätte er seine Ausführungen mit dem Hinweis begonnen, dass er ein Strategiemuster implementiert hatte, dann hätte ich umgehend die generellen Anforderungen und Ansätze verstanden, die er zur Umsetzung angewandt hatte. Dies hätte eine Menge Zeit und Aufwand gespart und es uns beiden erlaubt auf die Details der Implementation direkt einzugehen.

Auf jeden Fall ist es wichtig zu erkennen, das Entwurfsmuster selbst nicht die Lösung spezifischer Probleme darstellen. Sie sind einfach nur ein Weg zur Identifizierung von Problemen und beschreiben generische Lösungen die sich als erfolgreiche Vorgehensweisen etabliert haben. Die wirkliche Implementierung eines Entwurfsmusters ist immer noch Aufgabe des Entwicklers.

Was ist ein Entwurfsmuster?

Bevor wir uns den speziellen Beispielen zuwenden, die sich mit der Implementierung von Entwurfsmustern unter Visual FoxPro befassen, sollte ich mit der Definition von ‚Entwurfsmustern’ beginnen. Diverse Definitionen wurden schon angeboten und die wohl am meisten zitierte Version stammt aus dem Buch „Entwurfsmuster – Elemente wieder verwendbarer objektorientierter Software“ von Erich Gamma, Richard Helm, Ralph Johnson und John Vlissides (ursprünglich auch als ‚Die Vierer Gang -> Gang of Four’ oder einfacher GoF bekannt). Sie bieten im ersten Kapitel (Was ist ein Entwurfsmuster) ihres Buches die folgende Definition: Dies ist eine gute Definition denn sie beinhaltet die vier Schlüsselelemente eines jeden Entwurfsmusters.
Ein Entwurfsmuster benennt, abstrahiert und identifiziert die relevanten Aspekte einer allgemeinen Entwurfsstruktur. Diese Aspekte beschreiben, warum das Muster für die Entwicklung eines wieder verwendbaren objektorientierten Entwurfs nützlich ist. Das Entwurfsmuster identifiziert die teilnehmenden Klassen und Objekte, die Rollen welche sie spielen, die Interaktionen zwischen den Rollen und die ihnen zugeteilten Aufgaben.
Dies ist eine gute Definition denn sie beinhaltet die vier Schlüsselelemente eines jeden Entwurfsmusters.

- Es hat einen Namen.
Dies ist wichtig, denn es erlaubt dem Entwickler eines der fundamentalen Probleme im Softwaredesign zu umgehen, nämlich wie kommuniziere ich mit anderen über das, was ist tue. Das Beispiel mit Paul was ich zuvor angesprochen habe spiegelt dies perfekt wieder.

- Es abstrahiert ein Problem.
GoF bezeichnet dies als ‚ Absicht’ (Intent). Es sagt uns sowohl die Natur des Problems als auch die Lösung durch ein Entwurfsmuster. Nehmen wir zum Beispiel das Problem, zu verhindern dass Anwender mehrere neue Instanzen einer Applikation erzeugen können. Normalerweise versuchen Anwender neue Instanzen einer Applikation zu starten, weil sie das Ursprungsfenster verkleinert und vergessen haben, dass die Anwendung bereits gestartet wurde. Wir können sofort erkennen, dass es sich hierbei um das Singelton-Muster handelt, denn dieses hat die Absicht sicher zu stellen, dass von einer Klasse nur eine Instanz existiert und dass es einen zentralen Aufrufpunkt gibt.

- Es definiert eine Designstruktur.
Es ist wichtig zur erkennen, das Entwurfsmuster keine Lösung für ein gegebenes Problem liefern. Sie beschreiben Strukturen, die es uns erlauben, das Problem zu lösen, und das in einer Weise, die eher eine Wiederverwendung ermöglicht, als die althergebrachte Art, seinen Code zu schreiben. Wir haben alle schon einmal die Erfahrung gemacht, dass wir einen bestimmten Code bereits an einer anderen Stelle eingesetzt haben, wir ihn jedoch nicht wiederverwenden konnten, weil wir den Code nicht von der Umgebung isoliert hatten. Zweck von Entwurfsmustern ist es, solche Situationen zu erkennen und sie somit zu vermeiden.

- Es identifiziert die Verteilung von Zuständigkeiten.
Dies ist natürlich der Schlüssel aller Designanforderungen und ist nicht auf Entwurfsmuster beschränkt. Letztlich ist, sobald wir wissen was eine Klasse (oder Objekt) zu tun hat, das schreiben des Codes ein Leichtes.

Der Vorteil eines Entwurfsmusters ist, dass wenn wir das Problem erst einmal erkannt haben und wir es einem Muster zuordnen können, es uns aufzeigt, wie die Verantwortlichkeiten zugewiesen werden. Es hilft uns so, schnell die beste Lösung zu erzeugen. Es ist nicht meine Absicht, sämtliche Entwurfsmuster an dieser Stelle ausschweifend zu betrachten (dem widmen sich komplette Bücher). Aber in dieser kleinen Serie werde ich die gängigsten Muster abdecken und zeigen, wie Visual FoxPro eingesetzt werden kann, um musterbasierende Lösungen zu erstellen. Jedes Kapitel ist in sich geschlossen und deckt jeweils ein Muster ab, aber ich werde mich um eine allgemeine Beschreibung bemühen.

Quellennachweis:

Entwurfsmuster - Elemente wiederverwendbarer objektorientierter Software ADDISON-WESLEY ISBN 3-89319-950-0

Sonntag, 16. November 2008

FoxPro - The Evolution of a new Standard

Während der 2008 DEVCON Keynote in Frankfurt zeigte Ken ein Promotionvideo zu FoxPro. Rainer versuchte ihm zwar das Video 'abzuschwatzen', aber irgendwie wollte Ken es nicht rausrücken. Im März diesen Jahres war dieses Video jedoch schon auf Youtube veröffentlicht worden. Wer sich das Filmchen also nochmals geniesserisch reinziehen möchte...

hier der Link: http://www.youtube.com/watch?v=EZqzJ6f3d7w

Montag, 10. November 2008

12 Monate Bloggen rund um den Fuchs / 12 months blogging about the fox

Tja, jetzt ist es doch tatsächlich bereits ein Jahr her seit ich den ersten Eintrag in diesem Blog erfasst habe. Es waren weniger Einträge als ich geplant hatte, aber auf Grund der beständig wachsenden Zahl an Lesern werde ich diesen Blog sicherlich weiter führen.

In den nächsten Monaten habe ich vor, ein wenig über Entwurfsmuster (Design Pattern) und deren Anwendung/Umsetzung in VFP zu schreiben. Zunächst steht jedoch die diesjahrige DEVCON in Frankfurt auf dem Plan. Ich hoffe man sieht und liest sich...

Übrigens: Wer die bisherigen Ansätze zur Darstellung 'gläserner' Forms analog Windows Vista verfolgt hat, für den-/diejenige(n) hat Bernard Bout einen interessanten Blogeintrag bereit. (hier gehts zum Bernards Blog)

An dieser Stelle möchte ich auch nochmals auf die hervorragenden Projekte für VFPx auf Codeplex hinweisen. Schaut unbedingt einmal dort vorbei.

Mittwoch, 22. Oktober 2008

Beliebig formatierte Datum-/Zeitstempel / Free formatted date- and timestamps

Immer wieder besteht der Bedarf, einen Dateinamen mit einem Datum- und/oder Zeitstempel zu versehen. Da der Aufbau jedoch jedes Mal anders aussieht, und alles (wie so oft) schnell gehen muss, sammeln sich im Laufe der Zeit die verschiedensten Funktionen an um den Dateinamen aufzubereiten. Um dieses Sammelsorium ein klein wenig zu entschlacken gibt es in diesem Blogeintrag einen Mustercode für eine zentrale Formatierung die letztlich nur einen Parameter benötigt, nämlich das Formatmuster.

Zulässige Formatierungskennungen sind YYYY bzw. YY für Jahr, MM für Monat, DD für Tag, hh für Stunde, mm für Minute, ss für Sekunde. Wird bspw. als Format die folgende Zeichenkette übergeben: Invoice-Spar-DD.MM.YYYY.edi wird daraus: Invoice-Spar-22.10.2008.edi

Die Zeichenkette: Invoice-Spar-YYYY.MM.DD_hh.mm.ss.edi wird zu: Invoice-Spar-2005.10.22_07.45.56.edi

Hier nun der Code:

FUNCTION FormatDTStamp
LPARAMETERS vFormat as String

    LOCAL   lcJahr as String, lcMonat as String, lcTag as String, ;
            lcStunde as String, lcMinute as String, lcSekunde as String, ;
            lcStamp as String
         
    lcJahr      = PADL(YEAR(DATE()),4,[0])
    lcMonat     = PADL(MONTH(DATE()),2,[0])
    lcTag       = PADL(DAY(DATE()),2,[0])
    lcStunde    = PADL(HOUR(DATETIME()),2,[0])
    lcMinute    = PADL(MINUTE(DATETIME()),2,[0])
    lcSekunde   = PADL(SEC(DATETIME()),2,[0])
    lcStamp     = m.vFormat

    DO CASE
    CASE OCCURS([YYYY],lcStamp) > 0
        lcStamp = STRTRAN(lcStamp,[YYYY],lcJahr)
    CASE OCCURS([YY],lcStamp) > 0
        lcStamp = STRTRAN(lcStamp,[YY],RIGHT(lcJahr,2))
    ENDCASE

    lcStamp = STRTRAN(lcStamp,[MM],lcMonat)
    lcStamp = STRTRAN(lcStamp,[DD],lcTag)
    lcStamp = STRTRAN(lcStamp,[hh],lcStunde)
    lcStamp = STRTRAN(lcStamp,[mm],lcMinute)
    lcStamp = STRTRAN(lcStamp,[ss],lcSekunde)

    RETURN lcStamp

ENDFUNC

Die Funktion kann um beliebige weitere Platzhalter ergänzt werden. Denkbar wäre eine textuelle Darstellung von Monat und Tag durch eine dreistellige Platzhalterdefinition (Jan,Feb,Mär,... -> MMM bzw. Mon,Die,Mit,... -> DDD).

Eine solche Umsetzung unterstützt VFP mit den beiden Funktionen CMONTH() und CDOW().

Mittwoch, 1. Oktober 2008

Excel 2007 Format lesen und schreiben / read and write Excel 2007 format

Craig Boyd hat in seinem Blog hochinteressanten Code zum lesen und schreiben des aktuellen Excel 2007 Formats (xlsx) bereitgestellt.

Die direkt vom Blog herunterladbare ZIP-Datei (19,15 KB) enthält ein kleines PRG. Darin finden sich u.a. die beiden Funktionen 'AppendFromExcel()' und 'CopyToExcel()' mit denen die Bearbeitung von xlsx-Dateien ermöglicht wird, ohne das Office 2007 auf dem Rechner installiert sein muss.

Hier geht's zu Craig's Blog

Montag, 15. September 2008

Security Updates für VFP8 und VFP9

In seinem aktuellen 'Letter from the Editor' hat Milind Lele (Visual Foxpro und Visual Studio Program Manager) drei Patches zu Visual Foxpro veröffentlicht.

Die Patches stehen für VFP 8 SP1, VFP9 SP1 und VFP9 SP2 zur Verfügung.

Rick Schummer vermutet, da es sich in allen drei Fällen um eine gepatchte GDIplus.dll handelt, dass der Download einer einzigen Patchdatei genügt, ist sich bisher jedoch nicht zu 100% sicher. Da die drei Patchdateien jeweils 1.944 KB gross sind, müssen wir aber kein Risiko eingehen. Die 6 MB sollten sicherlich noch auf jeden Rechner draufpassen.

Hier geht es zu Milind Lele's 'Latest News from the Visual Foxpro Team' http://msdn.microsoft.com/en-us/vfoxpro/bb264582.aspx

VFPx Poll von Andrew MacNeill

Offensichtlich ist die VFPx Initiative um Open Source Add-Ons für VFP auf Microsoft's Codeplex bereitzustellen, noch längst nicht so vielen Entwicklern bekannt wie bisher eigentlich zu erwarten gewesen wäre. Aus diesem Grund hat Andrew MacNeill einen kleinen Poll eingerichtet, bei dem jeder Visual Foxpro Entwickler aufgerufen ist, seine Stimme abzugeben.

Hier geht es zu Andrew's Poll:
http://meltech.freepolls.com/cgi-bin/polls/035/poll_center.htm

Hier gehts zu Andrew's Blog:
http://akselsoft.blogspot.com/2008/09/vfpx-poll.html

Hier nochmals der Link zu VFPX auf Codeplex:
http://www.codeplex.com/Wiki/View.aspx?ProjectName=VFPX

ALSO: Nichts wie hin!!! ;-)

Mittwoch, 3. September 2008

Erzeugen von UNC Pfadnamen / Creating UNC pathnames

Im folgenden Posting geht es um die Konvertierung von Verzeichnisnamen die auf gemappte Laufwerke zugreifen.

Mit Hilfe der WIN32API können wir über die dort verfügbare Funktion WNetGetConnection den UNC Namen ( Universal/Uniform Naming Convention ) einer solchen Verbindung abfragen.


FUNCTION GetUNCPath
LPARAMETERS vMappedName as String
  * // Datendeklaration und Initialisierung
  LOCAL    lcUNCBuffer as String, liLength as Integer, ;
          lcUNCName as String, llContinue as Boolean, ;
          lcMessage as String
  lcUNCBuffer    = []
  liLength    = 0
  lcUNCName    = []
  llContinue    = .T.
  lcMessage    = []
  * // Deklaration der API-Funktion
  TRY
      DECLARE INTEGER WNetGetConnection IN WIN32API ;
              STRING @ lpLocalName, ;
              STRING @ lpRemoteName, ;
              INTEGER @ lpliLength
  CATCH
      TEXT TO lcMessage NOSHOW TEXTMERGE PRETEXT 2
           Deklaration von WNetGetConnection in WIN32API
           ist fehlgeschlagen. Funktion wird vorzeitig beendet.
      ENDTEXT
      MESSAGEBOX(lcMessage,0+16+0,[Information])
      llContinue = .F.
  ENDTRY
  * // Umsetzung des lokalen Pfades in einen UNC Pfad
  IF llContinue
      IF !EMPTY(m.vMappedName) AND VARTYPE([m.vMappedName]) = [C]
          lcUNCBuffer        = REPL(CHR(0),261)
          liLength        = LEN(lcUNCBuffer)
          m.vMappedName    = ALLTRIM(m.vMappedName)
          IF LEN(m.vMappedName) = 1
              m.vMappedName = m.vMappedName + [:]
          ENDIF
          TRY
              IF WNetGetConnection(m.vMappedName, @lcUNCBuffer, @liLength) = 0
                 lcUNCName = LEFT(lcUNCBuffer,AT(CHR(0),lcUNCBuffer)-1)
              ENDIF
          CATCH
              TEXT TO lcMessage NOSHOW TEXTMERGE PRETEXT 2
                   Aufruf von WNetGetConnection in WIN32API
                   ist fehlgeschlagen. UNC-Pfad konnte nicht
                   generiert werden.
              ENDTEXT
              MESSAGEBOX(lcMessage,0+16+0,[Information])
          ENDTRY
      ENDIF
  ENDIF
  RETURN lcUNCName
ENDFUNC

Donnerstag, 7. August 2008

Einlesen grosser Verzeichnisse ohne ADIR() / Reading large directories without 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 / VFPx news

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 / Spellchecking with 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 kein 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 / Update of FoxCharts to 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 / 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.

Mittwoch, 28. Mai 2008

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

Im dritten Posting geht es um diejenigen Feiertage, die in direkter Beziehung zu Heiligabend oder vielmehr dem 1. Weihnachtsfeiertag stehen.

Im Einzelnen handelt es sich hier um den Volkstrauertag, Buss- und Bettag, Totensonntag sowie den 1. - 4. Advent.

Der folgende Code greift auf die im Teil 1 beschriebene Funktion SetYear() zurück.

Ähnlich wie beim Osterfest und den daran gekoppelten Feiertagen verhält es sich mit den an dieser Stelle aufgeführten Terminen. Basis ist der letzte Sonntag vor dem 1. Weihnachtsfeiertag.

* korrigierte Version gemäss wOOdy's Vorschlag            
* CTOD() gegen die bessere DATE()-Funktion auszutauschen
FUNCTION GetVolkstrauertag as Date
LPARAMETERS Jahr as Integer
    Jahr = SetYear(Jahr)
    ld25 = DATE(Jahr,12,25)
    RETURN ld25 - DOW(ld25,2) - 35
ENDFUNC

FUNCTION GetBussUndBettag as Date
LPARAMETERS Jahr as Integer
    Jahr = SetYear(Jahr)
    RETURN GetTotensonntag(Jahr) - 4
ENDFUNC

FUNCTION GetTotensonntag as Date
LPARAMETERS Jahr as Integer
    Jahr = SetYear(Jahr)
    ld25 = DATE(Jahr,12,25)
    RETURN ld25 - DOW(ld25,2) - 28
ENDFUNC

FUNCTION GetErsterAdvent as Date
LPARAMETERS Jahr as Integer
    Jahr = SetYear(Jahr)
    ld25 = DATE(Jahr,12,25)
    RETURN ld25 - DOW(ld25,2) - 21
ENDFUNC

FUNCTION GetZweiterAdvent as Date
LPARAMETERS Jahr as Integer
    Jahr = SetYear(Jahr)
    ld25 = DATE(Jahr,12,25)
    RETURN ld25 - DOW(ld25,2) - 14
ENDFUNC

FUNCTION GetDritterAdvent as Date
LPARAMETERS Jahr as Integer
    Jahr = SetYear(Jahr)
    ld25 = DATE(Jahr,12,25)
    RETURN ld25 - DOW(ld25,2) - 7
ENDFUNC

FUNCTION GetVierterAdvent as Date
LPARAMETERS Jahr as Integer
    Jahr = SetYear(Jahr)
    ld25 = DATE(Jahr,12,25)
    RETURN ld25 - DOW(ld25,2)
ENDFUNC

Montag, 26. Mai 2008

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

Im zweiten Posting der Datumspielereien geht es um die Berechnung von Weiberfastnacht, Rosenmontag, Fastnacht, Aschermittwoch, Karfreitag, Ostermontag, Christi Himmelfahrt, Pfingstsonntag, Pfingstmontag und Fronleichnam. Mit anderen Worten um sämtliche (mir bekannten) Ostern-abhängigen Feiertage.

Der folgende Code greift auf die im Teil 1 beschriebenen Funktionen GetOstersonntag() und SetYear() zurück.

Die zuvor genannten Feiertage liegen sozusagen in 'fester Entfernung' zum Osterfest.
Weiberfastnacht liegt genau 50 Tage vor dem Ostersonntag
Christi Himmelfahrt hingegen 39 Tage hinter dem Ostersonntag
Pfingsten wiederum liegt 49 Tage hinter dem Ostersonntag
Fronleichnam bringt es auf 60 Tage hinter dem Ostersonntag

FUNCTION GetWeiberFastnacht as Date
LPARAMETERS Jahr as Integer
    Jahr = SetYear(Jahr)
    RETURN GetOstersonntag(Jahr) - 50
ENDFUNC

FUNCTION GetRosenmontag as Date
LPARAMETERS Jahr as Integer
    Jahr = SetYear(Jahr)
    RETURN GetOstersonntag(Jahr) - 48
ENDFUNC

FUNCTION GetFastnacht as Date
LPARAMETERS Jahr as Integer
    Jahr = SetYear(Jahr)
    RETURN GetOstersonntag(Jahr) - 47
ENDFUNC

FUNCTION GetAschermittwoch as Date
LPARAMETERS Jahr as Integer
    Jahr = SetYear(Jahr)
    RETURN GetOstersonntag(Jahr) - 46
ENDFUNC

FUNCTION GetKarfreitag as Date
LPARAMETERS Jahr as Integer
    Jahr = SetYear(Jahr)
    RETURN GetOstersonntag(Jahr) - 2
ENDFUNC

FUNCTION GetOstermontag as Date
LPARAMETERS Jahr as Integer
    Jahr = SetYear(Jahr)
    RETURN GetOstersonntag(Jahr) + 1
ENDFUNC

FUNCTION GetChristiHimmelfahrt as Date
LPARAMETERS Jahr as Integer
    Jahr = SetYear(Jahr)
    RETURN GetOstersonntag(Jahr) + 39
ENDFUNC

FUNCTION GetPfingstsonntag as Date
LPARAMETERS Jahr as Integer
    Jahr = SetYear(Jahr)
    RETURN GetOstersonntag(Jahr) + 49
ENDFUNC

FUNCTION GetPfingstmontag as Date
LPARAMETERS Jahr as Integer
    Jahr = SetYear(Jahr)
    RETURN GetOstersonntag(Jahr) + 50
ENDFUNC

FUNCTION GetFronleichnam as Date
LPARAMETERS Jahr as Integer
    Jahr = SetYear(Jahr)
    RETURN GetOstersonntag(Jahr) + 60
ENDFUNC

Montag, 19. Mai 2008

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

Als ich mit dem Entwickeln von Software begann, hätte ich mir nicht träumen lassen, welche Algorithmen hinter der Berechnung eines speziellen Datums liegen können.

Natürlich war ich mir der beweglichen kirchlichen Feiertage bewußt, aber um das Warum machte ich mir keine Gedanken. Erst wenn wir uns näher mit diesen diversen Feiertagen befassen stellen wir fest, dass das Osterfest für die Berechnung des Fastnachtstermins ebenso ursächliche Wirkung zeigt wie für Christi Himmelfahrt und Pfingsten.

Mit diesem Posting beginne ich eine kleine Serie rund um Datumsberechnungen im Fux. Die Berechnung der Feiertage beruht hierbei auf den Daten der westlichen Kirchen.

Den Anfang macht natürlich das schon zuvor angesprochene Osterfest, im speziellen der Ostersonntag.

Die von Carl Friedrich Gauß erstmals im Jahre 1800 veröffentlichte Gaußsche Osterformel steht bereits in Pascal und Basic und sicherlich auch anderen Sprachen im WWW zum Herunterladen bereit. Der folgende Code stellt eine Möglichkeit der Umsetzung in Visual Foxpro dar.

FUNCTION GetOstersonntag as Date
LPARAMETERS Jahr as Integer
* Erster Sonntag nach dem Frühlingsvollmond                         
* Die u.a. Formel entspricht der von Gauss entwickelten Variante 
LOCAL a as Integer, b as Integer, c as Integer, d as Integer, e as Integer, f as Integer 
    Jahr = SetYear(Jahr) 
    a = MOD(Jahr,19) 
    b = INT(Jahr/ 100) 
    c = INT((8 * b + 13) / 25 - 2) 
    d = b - INT(Jahr/ 400) - 2 
    e = MOD(19 * MOD(Jahr,19) + MOD(15 - c + d,30),30) 
    DO CASE 
    CASE e = 28 AND a > 10
        e = 27 
    CASE e = 29
        e = 28 
    ENDCASE 
    f = MOD(d + 6 * e + 2 * MOD(Jahr,4) + 4 * MOD(Jahr,7) + 6,7) 
    * CTOD entsporgt = korrigierte wOOdy-Version (Thanx :-) )     
    * die '-1' lasse ich im Gedenken an das Original (d + e + 22) 
    * noch so stehen.                                             
    RETURN DATE(Jahr,3,1) + (e + f + 22) - 1
ENDFUNC

Die o.a. Funktion greift auf eine weitere Funktion mit dem Namen 'SetYear' zurück. Sie stellt sicher, dass bei ungültiger Parametrisierung mit einem gültigen Wert gearbeitet wird.

Hier nun diese zusätzliche Funktion, auf die im Übrigen auch die Codebeispiele in den folgenden Postings zurückgreifen werden.

FUNCTION SetYear as Integer
LPARAMETERS Jahr as Integer

    * korrigierte wOOdy-Version (again Thanx :-) )
    RETURN EVL(Jahr, YEAR(DATE()))
   
ENDFUNC

Montag, 5. Mai 2008

So bekommen wir einen Cursor in die Zwischenablage / How to get a cursor into clipboard

Um Textinformationen in die Zwischenablage zu kopieren wird üblicherweise die Systemvariable _CLIPTEXT genutzt.

cMyVar = [Hallo Welt]
_cliptext = cMyVar

Mit einer Tabelle (oder einem Cursor) funktioniert dies allerdings nicht.

Um eine Datenmenge, unabhängig davon ob sie als Tabelle oder als Cursor vorliegt, in die Zwischenablage zu bekommen könnte man nun meinen, dass mühevoll per Schleifenverarbeitung jedes einzelne Feld per TRANSFORM in einen String kopiert werden muss. Dem ist jedoch nicht so.

Visual Foxpro verfügt über ein Applikationobjekt, das auf die aktuelle VFP Ínstanz verweist. Ansprechbar ist es über die Systemvariable _VFP. Geben wir bspw. im Befehlsfenster _VFP. ein, so erhalten wir über Intellisense eine Übersicht der verfügbaren Eigenschaften und Methoden. Eine davon lautet DataToClip. Um nun unsere Daten in die Zwischenablage zu kopieren genügt der folgende Code:

* Felder getrennt durch Leerzeichen
_VFP.DataToClip([myCursor],RECCOUNT([myCursor]),1)

* Felder getrennt durch Tabulatoren
_VFP.DataToClip([myCursor],RECCOUNT([myCursor]),3)


Hierbei stehen in der ersten Zeile des in die Zwischenablage kopierten Textes die Feldnamen. Im Anschluss folgen mit je einer Zeile pro Datensatz die Feldinhalte.

Montag, 28. April 2008

Neue englische Hilfedatei für VFP9 SP2 / New English help file for VFP9 SP2

Microsoft hat am 25. April eine neue, ca. 11 MB grosse Version der fehlerbehafteten Hilfedatei online gestellt.

Hier der Download Link:
http://www.microsoft.com/downloads/details.aspx?FamilyId=842ACEC8-F79C-41CD-AB1A-AE4F184387C2&displaylang=en

Um innerhalb von VFP Zugriff auf diese Datei zu erhalten sollte die Datei (dv_foxhelp.chm) umgehend in den VFP9 Installationsordner kopiert werden. Bei einer Standardinstallation handelt es sich um dieses Verzeichnis: C:\Programme\Microsoft Visual FoxPro 9\ Bleibt zu hoffen, dass dies hilft ;-)  

Nachtrag: Offensichtlich kann die o.a. Datei nicht als Ersatz für die im SP2 mitgelieferte Version angesehen werden. Es erscheint empfehlenswert, die SP2-Datei beizubehalten und die neue Datei gezielt dann zu nutzen, wenn es um 'Reporting'-Fragen geht.

Montag, 21. April 2008

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

Die beiden vorangegangenen Teile handelten von den Funktionen GETDIR() und ADDBS() sowie GETFILE(), CHDIR und CURDIR(). In diesem Eintrag geht es nun um die Zerlegung der einzelnen Bereiche eines Dateinamens inklusive vorgelagertem Pfadnamen.

Wenn wir mit Hilfe von GETFILE() eine Datei selektiert und die Auswahl überprüft haben,

cFile = GETFILE()
IF FILE(cFile)
    ...
ENDIF

kommt es immer wieder vor, dass wir nur einen Teil des in 'cFile' stehenden Wertes verarbeiten möchten.

Gehen wir einmal davon aus, dass in cFile der folgende Wert enthalten ist:

C:\Dokumente und Einstellungen\Benutzername\Lokale Einstellungen\Temp\Demodatei.doc
 
so stellt uns Visual Foxpro nun u.a. sieben Funktionen bereit, diesen String zu zerlegen.
Die Funktion JUSTDRIVE() liefert uns als Ergebnis den Laufwerksbuchstaben:

C:\Dokumente und Einstellungen\Benutzername\Lokale Einstellungen\Temp\Demodatei.doc
 
cFile = [C:\Dokumente und Einstellungen\Benutzername\Lokale Einstellungen\Temp\Demodatei.doc]
?JUSTDRIVE(cFile)

Die Funktion JUSTPATH() liefert uns als Ergebnis den Laufwerksbuchstaben inklusive des kompletten Pfades, jedoch ohne Backslash am Ende:

C:\Dokumente und Einstellungen\Benutzername\Lokale Einstellungen\Temp\Demodatei.doc
 
cFile = [C:\Dokumente und Einstellungen\Benutzername\Lokale Einstellungen\Temp\Demodatei.doc]
?JUSTPATH(cFile)

Die Funktion JUSTSTEM() liefert uns als Ergebnis den Dateinamen ohne Suffix:

C:\Dokumente und Einstellungen\Benutzername\Lokale Einstellungen\Temp\Demodatei.doc
 
cFile = [C:\Dokumente und Einstellungen\Benutzername\Lokale Einstellungen\Temp\Demodatei.doc]
?JUSTSTEM(cFile)

Die Funktion JUSTFNAME() liefert uns als Ergebnis den Dateinamen mitsamt dem Suffix:

C:\Dokumente und Einstellungen\Benutzername\Lokale Einstellungen\Temp\Demodatei.doc
 
cFile = [C:\Dokumente und Einstellungen\Benutzername\Lokale Einstellungen\Temp\Demodatei.doc]
?JUSTFNAME(cFile)

Die Funktion JUSTEXT() liefert uns als Ergebnis nur das Suffix:

C:\Dokumente und Einstellungen\Benutzername\Lokale Einstellungen\Temp\Demodatei.doc
 
cFile = [C:\Dokumente und Einstellungen\Benutzername\Lokale Einstellungen\Temp\Demodatei.doc]
?JUSTEXT(cFile)

Mit Hilfe dieser Funktionen können wir den Datei- bzw. Verzeichnisnamen in 5 Segemente zerlegen und ggf. durch neue Teile ersetzen. Wollen wir nun bspw. eine Backup-Datei von Demodatei.doc im selben Verzeichnis anlegen, dann greift die Funktion FORCEEXT(). Mit ihrer Hilfe wird aus Demodatei.doc der String Demodatei.bak Hierbei steht es uns frei, den kompletten Verzeichnisnamen oder nur den reinen Dateinamen mitzugeben.

cFile = [C:\Dokumente und Einstellungen\Benutzername\Lokale Einstellungen\Temp\Demodatei.doc]
?FORCEEXT(cFile,[bak])
?FORCEEXT(JUSTSTEM(cFile),[bak])
?FORCEEXT(JUSTFNAME(cFile),[bak])


Soll die Backup-Datei in einem Unter- oder Parallelordner abgelegt werden, so könnte die Funktion FORCEPATH() zum Zuge kommen.

Für den Unterordner 'Backups':

C:\Dokumente und Einstellungen\Benutzername\Lokale Einstellungen\Temp\Backups\Demodatei.doc
 
cFile = [C:\Dokumente und Einstellungen\Benutzername\Lokale Einstellungen\Temp\Demodatei.doc]
?FORCEPATH(cFile,ADDBS(JUSTPATH(cFile)) + [Backups])

Im Parallelordner 'Backups':

C:\Dokumente und Einstellungen\Benutzername\Lokale Einstellungen\Backups\Demodatei.doc
 
cFile = [C:\Dokumente und Einstellungen\Benutzername\Lokale Einstellungen\Temp\Demodatei.doc]
?FORCEPATH(cFile,ADDBS(JUSTPATH(JUSTPATH(cFile))) + [Backups])

Im Verbund mit der Funktion FORCEEXT() ergeben sich daraus nun bspw. die folgenden Funktionen:

LOCAL    lcBackupExt as String, lcBackupPath as String, ;
    cFile as String, cFNew as String, cSubDir as String, cParall as String
       
lcBackupExt     = [bak]
lcBackupPath    = [Backups]
cFile           = GETFILE()
cFNew           = FORCEEXT(JUSTSTEM(cFile),lcBackupExt)
cSubdir         = GetSubdirectory(JUSTPATH(cFile),lcBackupPath,.T.)
cParall         = GetParallelDirectory(JUSTPATH(cFile),lcBackupPath,.T.)

CLEAR

=CreateBackupFile(cFile,cSubDir + cFNew)
=CreateBackupFile(cFile,cParall + cFNew)

FUNCTION CreateBackupFile
LPARAMETERS vSourceFile as String, vTargetFile as String

    LOCAL llReturn as Boolean
   
    ?[kopiere ] + vSourceFile
    ?[nach    ] + vTargetFile
    TRY
        COPY FILE (m.vSourceFile) TO (m.vTargetFile)
        ?[Kopiervorgang erfolgreich durchgeführt]
        llReturn = .T.
    CATCH
        ?[Kopiervorgang ist fehlgeschlagen]
    ENDTRY

    RETURN llReturn
   
ENDFUNC

FUNCTION GetSubdirectory
LPARAMETERS vPath as String, vSubDir as String, vCreate as Boolean

    LOCAL lcPath as String
    m.vPath    = ADDBS(m.vPath)
    lcPath     = FORCEPATH(m.vPath,m.vPath + m.vSubDir)
   
    IF m.vCreate = .T. AND !DIRECTORY(lcPath)
        MKDIR (lcPath)
    ENDIF
   
    RETURN lcPath
   
ENDFUNC

FUNCTION GetParallelDirectory
LPARAMETERS vPath as String, vSubDir as String, vCreate as Boolean

    LOCAL lcPath as String
    lcPath    = ADDBS(ADDBS(JUSTPATH(JUSTPATH(ADDBS(m.vPath)))) + m.vSubDir)

    IF m.vCreate = .T. AND !DIRECTORY(lcPath)
        MKDIR (lcPath)
    ENDIF

    RETURN lcPath
   
ENDFUNC

Mittwoch, 16. April 2008

Undokumentierte Parameterwerte in der Messagebox

Gemäß der FAQs von George Tasker gibt es bei der VFP Messagebox zwei undokumentierte Werte beim zweiten Parameter.

Hierbei handelt es sich um:

#DEFINE MB_SYSTEMMODAL 4096
#DEFINE MB_TASKMODAL 8192

Der Einsatz dieser beiden Werte beeinflußt ausschließlich Modalitätslevel und -bereich des Messagebox-Dialogs.

MB_TASKMODAL (8192) hat üblicherweise einen wenig sichtbaren Effekt auf die Dialogbox. (es handelt sich mehr oder weniger um interne Auswirkungen).

MB_SYSTEMMODAL (4096) hingegen sorgt dafür, daß die Messagebox nicht nur innerhalb der VFP-Applikation im Vordergrund steht, sondern auch vor allen anderen vorgelagerten Fremdapplikationen.

Dienstag, 15. April 2008

Welche Farbe war das noch mal? / What color was that again?

Immer wieder stehe ich vor dem Problem, dass ich nicht mehr weiss, welche Farbtöne ich innerhalb einer visuellen Klasse benutzt habe.

In Expression Web stellt dies bspw. kein Problem dar. Dort ist ein 'Color Picker' integriert. Im Fux ist dem leider nicht so.

Bisher kam deswegen immer das kleine Tool 'Pixie' von Nattyware zum Einsatz. Im UT stolperte ich vor ein paar Tagen dann über ein kleines Tool (MousetrackedColorPicker). Diesem Programm fehlten jedoch zwei Kleinigkeiten, um einen Mehrwert zu Pixie darzustellen. Deshalb habe ich den Code um die entsprechenden Features erweitert und umgestellt.

Punkt 1: Es genügt nicht, nur den Farbwert unter dem Mousecursor angezeigt zu bekommen. Wenn, dann sollte dieser Wert auch in die Zwischenablage kopiert werden können.

Punkt 2: Je nach Bedarf muss der Wert in der Zwischenablage den Integer-Farbwert, den RGB-Wert oder den Hex-Wert darstellen.


Gesteuert wird dies zum Einen über eine kleine Optiongroup direkt hinter den Anzeigewerten. Die Übernahme des selektierten Farbtyps in die Zwischenablage erfolgt über STRG+m.

Wer möchte legt sich den Aufruf des kleines PRGs auf eine F-Taste (Extras\Makros) und sofort steht der kleine ColorPicker jederzeit auf Abruf bereit.

PUBLIC oForm
oForm = CREATEOBJECT([frmcolorpicker])
oForm.Show
READ EVENTS
RELEASE oForm

DEFINE CLASS frmcolorpicker AS form

    Top                     = 0
    Left                    = 0
    Height                  = 150
    Width                   = 235
    ShowWindow              = 2
    DoCreate                = .T.
    BorderStyle             = 0
    Caption                 = [Color Picker]
    BackColor               = RGB(255,255,255)
    posx                    = 0
    posy                    = 0
    pixelcolor              = 0
    rgbcolor                = []
    hexcolor                = []
    errorstatus             = 0
    errortext               = []
    Name                    = [frmcolorpicker]

    ADD OBJECT lblxpos AS label WITH ;
        BackStyle           = 0, ;
        Caption             = [XPos], ;
        Height              = 17, ;
        Left                = 7, ;
        Top                 = 4, ;
        Width               = 40, ;
        Name                = [lblxpos]

    ADD OBJECT lblypos AS label WITH ;
        BackStyle           = 0, ;
        Caption             = [YPos], ;
        Height              = 17, ;
        Left                = 7, ;
        Top                 = 32, ;
        Width               = 40, ;
        Name                = [lblypos]

    ADD OBJECT lblcolor AS label WITH ;
        BackStyle           = 0, ;
        Caption             = [Color], ;
        Height              = 17, ;
        Left                = 7, ;
        Top                 = 60, ;
        Width               = 40, ;
        Name                = [lblColor]

    ADD OBJECT lblrgbcolor AS label WITH ;
        BackStyle           = 0, ;
        Caption             = [RGB], ;
        Height              = 17, ;
        Left                = 7, ;
        Top                 = 88, ;
        Width               = 40, ;
        Name                = [lblrgbcolor]

    ADD OBJECT lblhexcolor AS label WITH ;
        BackStyle           = 0, ;
        Caption             = [Hex], ;
        Height              = 17, ;
        Left                = 7, ;
        Top                 = 116, ;
        Width               = 40, ;
        Name                = [lblhexcolor]

    ADD OBJECT txtxpos AS textbox WITH ;
        FontBold            = .T., ;
        BorderStyle         = 0, ;
        ControlSource       = [Thisform.PosX], ;
        Height              = 23, ;
        Left                = 45, ;
        ReadOnly            = .T., ;
        Top                 = 2, ;
        Width               = 100, ;
        DisabledBackColor   = RGB(255,255,255), ;
        Name                = [txtXpos]

    ADD OBJECT txtypos AS textbox WITH ;
        FontBold            = .T., ;
        BorderStyle         = 0, ;
        ControlSource       = [Thisform.PosY], ;
        Height              = 23, ;
        Left                = 45, ;
        ReadOnly            = .T., ;
        Top                 = 30, ;
        Width               = 100, ;
        DisabledBackColor   = RGB(255,255,255), ;
        Name                = [txtYpos]

    ADD OBJECT txtcolor AS textbox WITH ;
        FontBold            = .T., ;
        BorderStyle         = 0, ;
        ControlSource       = [Thisform.PixelColor], ;
        Height              = 23, ;
        Left                = 45, ;
        ReadOnly            = .T., ;
        Top                 = 58, ;
        Width               = 100, ;
        DisabledBackColor   = RGB(255,255,255), ;
        Name                = [txtColor]

    ADD OBJECT txtrgbcolor AS textbox WITH ;
        FontBold            = .T., ;
        Alignment           = 1, ;
        BorderStyle         = 0, ;
        ControlSource       = [Thisform.RgbColor], ;
        Height              = 23, ;
        Left                = 45, ;
        ReadOnly            = .T., ;
        Top                 = 86, ;
        Width               = 100, ;
        DisabledBackColor   = RGB(255,255,255), ;
        Name                = [txtRgbColor]

    ADD OBJECT txthexcolor AS textbox WITH ;
        FontBold            = .T., ;
        Alignment           = 1, ;
        BorderStyle         = 0, ;
        ControlSource       = [Thisform.HexColor], ;
        Height              = 23, ;
        Left                = 45, ;
        ReadOnly            = .T., ;
        Top                 = 114, ;
        Width               = 100, ;
        DisabledBackColor   = RGB(255,255,255), ;
        Name                = [txtHexColor]

    ADD OBJECT timer1 AS timer WITH ;
        Top                 = 0, ;
        Left                = 0, ;
        Height              = 23, ;
        Width               = 23, ;
        Interval            = 100, ;
        Name                = [Timer1]

    ADD OBJECT colorshower AS container WITH ;
        Top                 = 5, ;
        Left                = 165, ;
        Width               = 65, ;
        Height              = 130, ;
        BorderWidth         = 0, ;
        Name                = [colorshower]

    ADD OBJECT lblinfo AS label WITH ;
        AutoSize            = .T., ;
        FontSize            = 7, ;
        BackStyle           = 0, ;
        Caption             = [STRG+m kopiert markierten Wert in die Zwischenablage], ;
        Height              = 14, ;
        Left                = 0, ;
        Top                 = 138, ;
        Width               = 235, ;
        ForeColor           = RGB(128,128,128), ;
        Name                = [lblInfo]

    ADD OBJECT opgclipselect AS optiongroup WITH ;
        ButtonCount         = 3, ;
        BackStyle           = 0, ;
        BorderStyle         = 0, ;
        Value               = 2, ;
        Height              = 77, ;
        Left                = 149, ;
        Top                 = 56, ;
        Width               = 15, ;
        Name                = [opgClipselect], ;
        Option1.Caption     = [], ;
        Option1.Value       = 0, ;
        Option1.Height      = 17, ;
        Option1.Left        = 0, ;
        Option1.Top         = 5, ;
        Option1.Width       = 15, ;
        Option1.Name        = [Option1], ;
        Option2.Caption     = [], ;
        Option2.Value       = 1, ;
        Option2.Height      = 17, ;
        Option2.Left        = 0, ;
        Option2.Top         = 31, ;
        Option2.Width       = 15, ;
        Option2.Name        = [Option2], ;
        Option3.Caption     = [], ;
        Option3.Height      = 17, ;
        Option3.Left        = 0, ;
        Option3.Top         = 58, ;
        Option3.Width       = 15, ;
        Option3.Name        = [Option3]

    PROCEDURE color2rgb
        LPARAMETERS tnColor
        LOCAL lnRed as Integer, lnGreen as Integer, lnBlue as Integer

        lnRed    = BITRSHIFT(BITAND(tnColor, 0x0000FF),0)
        lnGreen  = BITRSHIFT(BITAND(tnColor, 0x00FF00),8)
        lnBlue   = BITRSHIFT(BITAND(tnColor, 0xFF0000),16)

        RETURN TRANSFORM(lnRed) + [,] + TRANSFORM(lnGreen) + [,] + TRANSFORM(lnBlue)
    ENDPROC

    PROCEDURE timerbasedrefresh
        LOCAL lpPoint as String, liPosX as Integer, liPosY as Integer
        lpPoint = SPACE(8)

        WITH Thisform

            IF GetCursorPos(@lpPoint) # 0

                liPosX                   = ASC(SUBSTR(lpPoint,1)) * 256 ^ 0 + ;
                                         ASC(SUBSTR(lpPoint,2)) * 256 ^ 1 + ;
                                         ASC(SUBSTR(lpPoint,3)) * 256 ^ 2 + ;
                                         ASC(SUBSTR(lpPoint,4)) * 256 ^ 3

                liPosY                   = ASC(SUBSTR(lpPoint,5)) * 256 ^ 0 + ;
                                         ASC(SUBSTR(lpPoint,6)) * 256 ^ 1 + ;
                                         ASC(SUBSTR(lpPoint,7)) * 256 ^ 2 + ;
                                         ASC(SUBSTR(lpPoint,8)) * 256 ^ 3
                .PosX                    = IIF(liPosX > 10000,INT(liPosX) - 4294967295,INT(liPosX))
                .PosY                    = INT(liPosY)
                .PixelColor              = GetPixel(GetWindowDC(0), .posx, .posy)
                .RGBColor                = .Color2RGB(.PixelColor)
                .HexColor                = STUFF(TRANSFORM(.PixelColor,[@0]),3,2,[])
                .ColorShower.BackColor   = .PixelColor
                .Refresh()

            ENDIF

        ENDWITH
    ENDPROC

    PROCEDURE Release
        LPARAMETERS vKey

        WITH Thisform

            IF VARTYPE(m.vKey) = [C] AND m.vKey = [m]

                * Selektierten Wert in die Zwischenablage kopieren
                DO CASE
                CASE .opgClipSelect.Value = 1    && Color
                    _cliptext = ALLTRIM(CAST(.txtColor.Value as c(10)))

                CASE .opgClipSelect.Value = 2    && RGB
                    _cliptext = .txtRgbColor.Value

                CASE .opgClipSelect.Value = 3    && Hex
                    _cliptext = .txtHexColor.Value

                ENDCASE

            ENDIF

        ENDWITH
    ENDPROC

    PROCEDURE Unload
        CLEAR EVENTS
    ENDPROC

    PROCEDURE Init
        TRY

            DECLARE Sleep IN Win32API Integer
            DECLARE Short GetCursorPos IN win32api String @ lpPoint
            DECLARE Integer GetWindowDC IN Win32API Integer HWnd
            DECLARE Integer GetPixel IN win32API Integer hdc, Integer nXPos, Integer nYPos

            ON KEY LABEL CTRL+m _screen.ActiveForm.Release([m])

            BINDEVENT(Thisform.Timer1,[Timer],Thisform,[TimerBasedRefresh])

        CATCH

            TEXT TO lcMsg TEXTMERGE NOSHOW PRETEXT 3
                ColorPicker kann auf Grund fehlender Win32 API
                Unterstützung auf diesem System nicht genutzt werden.
            ENDTEXT

            MESSAGEBOX(lcMsg,0+16+0,[Programminformation])

            This.Release

        ENDTRY
    ENDPROC

ENDDEFINE