# Friday, October 12, 2007

Software Entwicklung ist ein sehr kreativer Prozess. Nur wenn der Strom weg ist, bleibt selten etwas Sichtbares zurück. Papier ist immer noch gefragt um Daten für Anwender zu zugänglich zu machen.

Der Weg von einer Datenbank zu einem Produkt Katalog wird gerne Database Publishing genannt. Vor ca. 9 Jahren haben wir begonnen für den Automobilzulieferer FTE einen Aftermarket Produkt Katalog in Papierform zu erstellen. Dieser Katalog wird seitdem alle 2 Jahre neu aufgelegt.

Der Prozess sollte vollständig automatisiert sein und die Daten mussten stark verdichtet werden. Die Verdichtung der Datenfülle war notwendig, da für jeden PKW Typ alle verfügbaren FTE Produkte ausgegeben werden sollten. Die Ausgabe redundanter Information musste vermieden werden.

Dennoch sind wir inzwischen an die Grenzen der Buchbinderei gestoßen:

fte_katalog

Ein Telefonbuch wirkt wie ein Flyer daneben. Der FTE Ersatzteil-Katalog ist 5,5 cm dick und der letzte PKW Typ, der WARTBURG 353 Tourist (10.1967-05.1991) ist auf Seite 1824 zu finden. Nur mit Spezial-Papier und einem fähigen Buchbinder konnte das Werk überhaupt noch produziert werden.

FTE PKW PDF

Die ausgelieferte PDF Datei wirkt mit 145 MB Dateigröße noch gar nicht so mächtig. Stecken doch so viele kleine Icons und Detailinformationen in Ihr:

FTE Katalog - offen

Ich freue mich jedes Mal auf den fertigen Katalog in Papierform. Da hat man wirklich etwas in der Hand. Inzwischen ist der Ablauf eingespielt und auch die Rechner sind so leistungsfähig, dass die Abwicklung nicht mehr klemmt. Aber vor 8 Jahren, war der Weg schon spürbar steinig. Auch die Wahl der Tools ist historisch begründet.

Ausgangspunkt sind die TecDoc Stammdaten zusammen mit den FTE Einspeiser Daten. Das sind standardisierte Textdateien die wir über ein Visual FoxPro Import Tool in den MS SQL Server 7 eingelesen haben. Inzwischen verwenden wir natürlich den MS SQL Server 2005 :)

Auf diese Datenbank greift ein weiteres FoxPro Programm zu. In diesem Katalog Programm werden alle Verdichtungen der Daten durchgeführt und schließlich über OLE Automatisierung in Word Dokumente ausgegeben. Richtig gelesen: Wir waren kühn genug, mit Word 97 automatisiert 1500 Seiten Dokumente zu erzeugen! Damals waren es nur 1500 Seiten.

Heute kann man darüber nur schmunzeln. Damals habe ich geschwitzt. Es hat einiger Workarounds und Tricks bedurft, um diese Dokumente zu erstellen. Die Rechenzeiten waren enorm, jeder Fehlversuch hat viel Zeit gekostet. Es liefen diverse Rechner parallel um den Vorgang zu beschleunigen. War endlich das Word Dokument erstellt, musste noch ein PDF über den Acrobat Distiller Druckertreiber erstellt werden. Das dauert noch mal und auch dabei konnte Word abstürzen.

* Word starten und die Vorlage laden
oWord=CreateObject("Word.Application")
oWord.Options.CheckSpellingAsYouType=.F.
oWord.Options.CheckGrammarAsYouType=.F.
oWord.Documents.Add(out_name,.F.)
oDoc=oWord.ActiveDocument
oDoc.Windows(1).View.ShowFieldCodes=.F.
oDoc.SaveAs(out_name)
oWord.ActiveWindow.View.Type = 1

* dann ganz viele Elemente hinzufügen
oDoc.Tables.Add()
oDoc.Paragraphs.Add()

* Fertig
oDoc.Save()
oWord.Quit(0)

So lässt sich der FoxPro Code zusammenfassen :)

Wer übrigens FTE Ersatzteile ohne diesen Katalog finden möchte, für den haben wir auch einen kleinen Web-Katalog gebastelt: FTE Katalog. Da schwitzt nur die PHP Engine und nicht der Buchbinder.

Friday, October 12, 2007 4:06:47 PM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  | 
# Thursday, September 06, 2007

Ein Spruch, der sich immer wieder bewahrheitet:

Daten leben länger als die Programme, die die Daten erzeugen

Mein Fall ist nicht so drastisch, das Programm, das die Daten erzeugen kann, lebt noch. Eine Visual Foxpro Applikation hat die Daten erzeugt, eine .NET Applikation soll jetzt damit weiter arbeiten.

Über den Visual FoxPro OLE DB Provider kann man direkt auf die VFP Daten zugreifen. Die restliche Logik kann in der inzwischen eher vertrauten Visual Studio Umgebung programmiert werden:

            string connectionString = "Provider=vfpoledb.1;Data Source=c:\\data.dbc";
            OleDbConnection connection = new OleDbConnection(connectionString);
            connection.Open();
            string selectCmd = "SELECT * FROM table";
            OleDbCommand command = new OleDbCommand(selectCmd, connection);
            OleDbDataReader reader = command.ExecuteReader();
            while (reader.Read())
            {
                // Daten verarbeiten
            }
            reader.Close();
            connection.Close();

Wenn die VFP Anwendung bereits eine saubere Business Schicht implementiert hätte, wäre es besser, auf dieser Ebene die Informationen auszutauschen und nicht auf die rohen Daten zuzugreifen. Ist aber hier nicht der Fall.

Wenn kein Visual Foxpro installiert ist, sollte obiger Download genügen um den OLE DB Provider zu installieren. Unter Vista x64 klappt die Installation zwar problemlos. Nur der Treiber ist eine reine 32Bit Anwendung und Visual Studio erzeugt normalerweise Code für Any CPU. Dieser Code läuft auf einem 64Bit OS, dann entsprechend als 64Bit Anwendung. Der Versuch auf vfpoledb zuzugreifen scheitert:

The 'vfpoledb.1' provider is not registered on the local machine.

Die einzige Lösung, die ich kenne, ist die komplette Solution auf Platform target x86 zu schalten:

VS Build Configuration Manager

Jetzt funktioniert der Zugriff auf die VFP Daten einwandfrei.

Wieder holt einen die Vergangenheit ein und das tolle neue System kann nicht so agieren wie es möchte. Aber das ist der Preis, wenn man nicht alles immer neu erfinden will.

Thursday, September 06, 2007 2:16:56 PM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  | 
# Wednesday, November 16, 2005

Ich habe bereits in Ersatzteilkatalog - Bildnavigation mit GDI+ erzählt, dass ich versuche in Visual Foxpro 9 mit GDI+ direkt auf VFP Formen zu zeichnen. Inzwischen funktioniert das eigentlich ganz gut. Einige Probleme waren noch zu lösen und den Bildaufbau habe ich noch optimiert.

In Craig Boyd's Artikelserie in FoxTalk 2.0 wird eine komplette Form mit GDI+ bearbeitet. In unserer SPCat Applikation ist aber nur ein Bereich der Form für die Darstellung der Bilder genutzt. Der restliche Bereich der Form soll weiterhin von Foxpro selbst gezeichnet werden. Deswegen wollte ich die ganze Bilddarstellung in eine Container Klasse verpacken und die eigentliche Form so wenig wie möglich verändern.

Die eigentlichen GDI+ Zeichenroutinen stelle ich nicht vor. Es geht mir jetzt mehr darum wie ich diese Routinen mit den Mechanismen von VFP verheiratet habe, damit Foxpro und GDI+ Elemente auf einer Form dargestellt werden. Foxpro weiß ja nichts davon, dass man ihm mit eigenen Routinen in die Bildschirmdarstellung pfuscht. Deswegen wird es bei jedem Update des Bildschirminhalts einfach seine eigenen Elemente zeichnen. Mit BINDEVENT hat man die Möglichkeit in die Nachrichten von Windows an das Foxpro Fenster zum Neuzeichnen des Inhalts einzugreifen. Hiermit werden die relevanten Events auf die eigene Prozedur WinEventHandler umgeleitet:

#DEFINE WM_ERASEBKGND 0x0014
#DEFINE WM_PAINT 0x000F

=BINDEVENT(THISFORM.hwnd, WM_ERASEBKGND, THIS, 'WinEventHandler')
=BINDEVENT(THISFORM.hwnd, WM_PAINT, THIS, "WinEventHandler")

Wir brauchen auch noch zwei Aufrufe in die Windows API:

DECLARE LONG CallWindowProc IN User32 ; INTEGER lpPrevWndFunc, LONG HWND, ; INTEGER Msg ,INTEGER wParam, ; INTEGER lParam
DECLARE INTEGER GetUpdateRect IN User32 LONG HWND, STRING @lpRect, INTEGER bErase

CallWindowProc ruft die Routine auf, die normalerweise den WM_PAINT Event behandelt. Also in diesem Fall die interne Foxpro Funktion, die dessen Elemente zeichnet.
GetUpdateRect liefert das Rechteck innerhalb dessen etwas zu zeichnen ist. Damit kann man den Einsatz der eigenen Zeichenroutinen auf die benötigten Fälle reduzieren. Dieses Rechteck ist als folgendes Struct definiert:

typedef struct _RECT { 
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT, *PRECT;

In der WinEventHandler Prozedur werden die Windows Nachrichten bearbeitet:

PROCEDURE WinEventHandler
LPARAMETERS HWND, msg, wParam, LPARAM

#DEFINE WM_ERASEBKGND 0x0014
#DEFINE WM_PAINT 0x000F

DO CASE
    CASE msg = WM_PAINT
        WITH THIS
            IF .autodraw
                LOCAL lpRect,lnUpdate
                lpRect=SPACE(16)
                * Das Rechteck holen, in dem gezeichnet werden muss.
                lnUpdate=GetUpdateRect(HWND, @lpRect, 0)
                * Dieser Aufruf führt dazu, dass VFP alles zeichnt, wofür es zuständig ist.
                CallWindowProc(.WinProcAddress, HWND, msg, wParam, lParam)
                IF lnUpdate=1
                    * In hitdetection() wird geprüft, ob dieses Control im zu zeichnenden Rechteck liegt.
                    IF .hitdetection(lpRect)
                        .drawdb()
                    ENDIF
                ENDIF
            ELSE
                * Hier ist nur VFP für das Zeichnen verantwortlich.
                CallWindowProc(.WinProcAddress, HWND, msg, wParam, lParam)
            ENDIF
        ENDWITH
    CASE msg = WM_ERASEBKGND
        RETURN 1
ENDCASE

Die Nachricht zum Löschen des Hintergrunds WM_ERASEBKGND wird komplett ignoriert. Mit WM_PAINT treffen alle relevanten Zeichnungsanforderungen ein. Mit der autodraw Eigenschaft kann ich die eigenen GDI+ Routinen an- und ausschalten. Das ist notwendig, da das Control auch unsichtbar sein kann. Dann dürfen natürlich meine GDI+ Routinen nicht auf der Form zeichnen. Bei der Behandlung des WM_PAINT Events darf zuerst Foxpro seine Aufgabe erledigen. Danach teste ich, ob der Container im zu zeichnenden Bereich liegt und starte entsprechende meine Zeichenroutine. Diese Reihenfolge ist wichtig, damit am Ende auch meine Ausgabe zu sehen ist. Sonst würde ja Foxpro den tatsächlichen Container darüber malen.

Damit kann ich jetzt grundsätzlich auf die Fläche des Containers zeichnen und zusätzlich auf der Form weitere Foxpro Elemente anzeigen. Ich möchte aber den Container und damit die GDI+ Zeichnung unsichtbar machen können. Genau genommen möchte ich an der Stelle des Containers auch andere Objekte anzeigen können. Das läuft aber auch auf einen unsichtbaren Container hinaus.
Natürlich kann ich nicht einfach die Visible Eigenschaft auf .F. stellen. Dann würde zwar Foxpro den leeren Container nicht mehr zeichnen, aber meine Routinen würden munter weiter zeichnen. Über visible_assign werden bei Änderungen der Sichtbarkeit auch meine Zeichenroutinen mit umgestellt.

Es hat sich aber gezeigt, dass VFP seltsamerweise Teile des Containers zeichnet wenn Visible auf .T. gesetzt, obwohl der Container bereits sichtbar ist. Das führt leider zu einem Flackern. Da ich ja selbst erst nach Foxpro zeichne und damit darunterliegende Objekte kurz aufblitzen. Also wird dieser Fall auch in visible_assign abgefangen:

PROCEDURE visible_assign
LPARAMETERS vNewVal
WITH THIS
    IF m.vNewVal
        IF .Visible
            * Wenn das Control schon sichtbar ist, nicht nochmal auf sichtbar schalten.
            * Andernfalls würde VFP das Control zeichnen und es flackert.
            .clearborder()
        ELSE
            * Die eigene GDI+ Zeichenroutine anschalten.
            .redraw()
            .Visible=.T.
        ENDIF
    ELSE
        * Die eigene GDI+ Zeichenroutine abschalten.
        .autodraw=.F.
        .Visible=.F.
    ENDIF
ENDWITH

Es kann sein, dass das Bild, das mit GDI+ gezeichnet wird, kleiner als der Container ist. In WM_PAINT Routine wird aus Performance Gründen nur dieser Teil gezeichnet. Damit das Rest des Containers in der Hintergrundfarbe erscheint, lösche ich diesen Bereich von Hand mit clearborder(). Da bei einem Wechsel des Bildes auch die Visible Eigenschaft auf .T. gesetzt wird, ist das die einzige Stelle, wo dieser Rand manuell gelöscht werden muß.

Insgesamt bin ich mit dem Ergebnis jetzt sehr zufrieden. Leider feuert aus mir unbekannten Gründen unregelmässig ein WM_PAINT Event, der offensichtlich grundlos die ganze Form neu zeichnen lässt. Das führt zu einem Flackern. Insbesondere auf langsameren Rechner kann das stören. Trotzdem bin ich beeindruckt, was mit VFP 9, GDI+ und ein paar Tricks möglich ist.

Wednesday, November 16, 2005 8:55:56 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Tuesday, October 18, 2005

In unserem Ersatzteilkatalog SPCat ist die Navigation in der Hierarchie der Ersatzteile sehr wichtig. Unter anderem besteht die Möglichkeit über graphische Links in einer Baugruppenzeichnung zu entsprechenden Unterteilen zu  springen. Diese Links in der Zeichnung werden zur Laufzeit in die Graphik gezeichnet.

Der aktuelle SPCat 6.5 ist mit Visual Foxpro 8 programmiert. Bis zu dieser Version ist Visual Foxpro  sehr limitiert in den Möglichkeiten zur Modifikation und Darstellung von Graphiken. Deswegen wurde die Darstellung der verlinkten Zeichnungen über eine ActiveX Komponente realisiert. Diese Komponente ist in Visual Basic 6 programmiert.

Mit Visual Foxpro 9 ist der Zugriff auf GDI+, der Windows Graphik Schnittstelle, sinnvoll möglich. Damit können modernere Bildformate geladen werden, unter anderem auch PNG Bilder. Alle grundlegenden graphischen Funktionen stehen zur Verfügung. Es ist aber nicht einfach möglich das Ergebnis dieser Manipulationen zusammen mit anderen Visual Foxpro Bildschirmelement darzustellen. Zu grundlegend anders ist die Art, wie Visual Foxpro seine Ausgabe erzeugt.

Craig Boyd hat einen sehr hilfreichen Artikel "GDI+ on VFP 9 Forms: Solving the Paint Problem" in FoxTalk 2.0 veröffentlicht. Die Lösung besteht im Wesentlichen darin, die Windows Ereignisse abzufangen und mit GDI+ das erzeugte Bild innerhalb von Visual Foxpro selbst zu zeichnen. Der Nachteil ist, daß in häufig aufgerufenen Ereignissen die eigene Logik zusätzlich mitläuft. Deswegen wird der zu zeichnenende Ausschnitt des Bildes mit allen Links zwischengespeichert und nur dieses Ergebnis gezeichnet (double buffer). Lediglich sich ändernde Teile, wie der Hovereffekt und Tooltips werden neu erzeugt.
Der Vorteil liegt darin, daß man die volle Kontrolle über die Ausgabe hat und Effekte möglich sind, die bisher weder Visual Foxpro noch reines Visual Basic 6 konnte.

Das Ergebnis sieht besser als die bisherige Lösung aus und eine weitere externe Abhängigkeit ist aus unserem elektronischen Ersatzteilkatalog verbannt. Ich bin jedesmal froh, wenn ich VB 6 Ballast abwerfen kann.

Tuesday, October 18, 2005 1:40:36 PM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  |