Die GigaGrid Klasse
von Marc Van den Berghen (VdBSoft @ web . de).

Einführung

In der 15. und 16. Ausgabe des dBulletin, zeigte uns Jean-Pierre Martel, wie wir die eingebauten Möglichkeiten des Grid-Controls verbessern können. Im ersten Artikel der Reihe wurde die grideigene Scrollbar durch eine Standard-Scrollbar ersetzt, um eine bessere Kontrolle über deren Verhalten zu erlangen. Eine clevere Idee, aber nichts im Vergleich zum nächsten Schritt, in dem eine eigene ganz neue Scrollbar entworfen wurde, die die Nachteile des ersten Entwurfs ausmerzte. Eigentlich gibt es keinen Grund, diese Vorgehensweise abzuändern, da sie uns alles gibt, was wir erwarten: eine bessere Funktionalität, ein modernes, gutaussehendes Interface und einfache Handhabbarkeit.

Jedoch wird am Ende des Artikels, im Kapitel Bugs and Shortcomings (Fehler und Nachteile), darauf hingewiesen, dass das Seeker-Kontrollelement inkompatibel mit der MegaGridScrollbar Klasse ist! Ein kurzes, aber äußerst wichtiges Statement, denn in den meisten Anwendungen sind das Grid und der Seeker eng verbunden.

Es gab also noch eine Möglichkeit, die MegaGridScrollbar-Klasse zu verbessern : die Implementierung der Seeker-Kompatibilität. Das Anliegen dieses Artikels ist die Einführung und Beschreibung einer neuen Custom-Klasse, genannt: die “Gigagrid Klasse”, wobei der Name Raum für zukünftige Verbesserungen lässt <g>.

Der folgende Text ist in fünf Kapitel unterteilt :

| Vor- und Nachteile der GigaGrid Klasse Das Setzen der Custom-Properties der Klasse Hinter den Kulissen Verbesserungswürdig Zusammenfassung

Das dritte Kapitel ist für die korrekte Nutzung der Klasse nicht unbedingt erforderlich, enthält aber einige selten benutzte Techniken und ist mit Sicherheit für denjenigen interessant, der den nächsten Artikel in dieser unendlichen Geschichte schreiben möchte ... :-)
Vor- und Nachteile der GigaGrid-Klasse

Vorteile, die von der MegaGridScrollbar Klasse geerbt wurden

- Der “Lift” (Thumb) nutzt das gleiche Design wie der der MegaGridScrollbar (beide nutzen die gleiche Resource DLL.)
- Es gibt nicht nur “Vorheriger -” und “Nächster Datensatz”-Buttons sondern auch entsprechende Buttons für PageUp, PageDn, Home und End .
- Die Scrollbar verarbeitet sowohl indizierte Rowsets als auch solche in natürlicher Reihenfolge (jede Liftposition entspricht einem Datensatz).

Zusätzliche Eigenschaften der GigaGrid-Klasse:

- Der Lift hüpft nicht mehr auf und ab, wenn er die oberste Position im Schacht erreicht.
- Der Anwender hat die freie Wahl über die Vordergrund- und Hintergrundfarbe des hervorgehobenen Datensatzes (so wie es mittlerweile in dBASE Plus auch möglich ist).
- Die Trennlinien verschwinden nicht, wenn die Eigenschaft `hasColumnHeadings` auf `false` gesetzt ist (dieser Fehler wurde in dBASE Plus ebenfalls behoben, aber im unteren Screenshot erkennt man seine Auswirkungen in dB2K, siehe erste und dritte Spalte).
- Die Farbe des Spaltenhintergrunds wird nicht zu `bgColor` wenn der Anwender in einem Grid mit `rowSelect=true` und `hasColumnLines=false` navigiert. Dieses Problem, das in dBASE Plus teilweise behoben wurde, ist ebenfalls im unteren Bild zu sehen (wobei `bgColor` auf Cyan gesetzt wurde, um das Ganze deutlicher zu machen).

- Das GigaGrid unterstützt Wheelmäuse (die Scrollbar der MegaGridScrollbar Klasse war dazu nicht in der Lage).
- Es gibt keine leere, graue Zone am Ende des Grids (d.h. wenn der Lift im untersten Teil des Schachts ist, wird der letzte Datensatz am unteren Ende des Grids dargestellt). Erkennbar im ersten Bild dieses Artikels.
- Das Grid scrollt auch während der Anwender den Lift bewegt (sogar bei Datensätzen mit aktivem Index).
- Der Schacht besitzt eine Eigenschaft `proportional`, die eine schnelle Navigation im Rowset ermöglicht.
- Man kann die Navigation auch per Programm steuern (beispielsweise ist es möglich, ein Gigagrid mit einem anderen Grid, in dem gerade gescrollt wird, zu synchronisieren). Siehe dazu auch `Synchronized_Grids.wfm`, das im beigefügten Zip-File enthalten ist.
- Last but not least, das Seeker-Kontrollelement und das Gigagrid arbeiten perfekt zusammen.

Nachteile:

- Die Eigenschaft `hasIndicator` des Grids muss auf `true` gesetzt sein.``
- Es gibt einen schwerwiegenden Nachteil dieser Klasse: sie kann nur für Rowsets bis ungefähr 1400 Datensätze eingestzt werden (mehr darüber im dritten Kapitel.)

Das Setzen der Custom-Properties der Klasse

Die Gigagrid-Klasse ist genauso leicht zu benutzen wie ihr Vorgänger, die MegaGridScrollbar-Klasse: man braucht lediglich ein Formular im Designer zu öffnen, ein Gigagrid-Objekt aus der Komponentenpalette auf dieses Formular zu ziehen und die Datalink-Eigenschaft des Grids auf das gewünschte Rowset einzustellen. Das war’s! Im Designer sieht das Gigagrid ein wenig ungewöhnlich aus, was aber ganz normal ist, da der größte Teil der Arbeit geschieht, wenn das Formular geöffnet wird (Achtung: die beiden Dateien `VSB.dll` und `WndNew.dll` müssen im “Einzugsbereich” (scope) des Formulars sein. Dies bewerkstelligt man am einfachsten, indem man sie in das gleiche Verzeichnis kopiert, in der auch die Formulardatei liegt). Bleibt nur noch, das Formular zu öffnen und sich von den Vorteilen zu überzeugen, die das Gigagrid gegenüber dem normalen Grid besitzt: sein ansprechendes Äußeres (entworfen vom GUI-Master höchstselbst <g>) und sein spezielles Feature, die Farbe der hervorgehobenen Zeile mit der des Liftes abzugleichen.

Das GigaGrid beschert bereits, den Benutzern von dB2K einige der mit dBasePlus erzielten Verbesserungen. Da ist zum Beispiel die freie Wahl der Farbe des hervorgehobenen Rowsets . Diese Eigenschaft steht schon seit langer Zeit auf der Wunschliste vieler Anwender. Bisher war es so, dass bei der Einstellung `RowSelect = true`, automatisch die Farbe hingenommen werden musste, die vom Anwender für die aktive Titelleiste eingestellt wurde (normalerweise dunkelblau). Das Gigagrid erlaubt dagegen die freie Wahl der Vordergrund- und der Hintergrundfarbe.

Die folgenden drei Codezeilen reichen völlig aus, um die Farben des hervorgehobenen Datensatzes zu ändern:

| `form.gigagrid1.selectedBackgroundColor = 0xc7d8` `form.gigagrid1.selectedForegroundColor = 0xa8` `form.gigagrid1.set_custom_properties()`
| ``

Die dritte Zeile wird benutzt, um dem Gigagrid eine Veränderung seiner Custom-Properties zu melden, und es zu einem Neuzeichnen zu bewegen. Die ersten beiden Zeilen bestimmen die Hintergrund- (selectedbackgroundcolor) und die Vordergrundfarbe (selectedforegroundcolor). Zu bemerken ist noch, dass nur hexadezimale Darstellungen der gewünschten Farbe akzeptiert werden. Bei der Implementierung dieses Features wurde sozusagen en passant ein dB2K-bug behoben, der dafür verantwortlich war, dass die waagerechten Linien des Grids verschwanden, wenn sie direkt über oder unter der hervorgehobenen Zeile lagen (dieser Fehler tritt nur auf, wenn `hasColumnHeaders = false` ist). Mit dem Gigagrid ist dieser Bug behoben!

Eine weitere Eigenschaft des Gigagrids trägt den Namen `selfcount`: sie hat keinen sichtbaren Einfluss auf das Aussehen des Grids, sondern wird benutzt um die Performance des GigaGrids zu steuern. Um eine Beziehung zwischen der Position des Liftes im Schacht und der aktuell dargestellten Datensätze im Grid herzustellen, braucht das Gigagrid die Information über die Gesamtzahl der Datensätze im verbundenen Rowset. Diese Zahl ändert sich natürlich, wenn man Datensätze hinzufügt oder löscht. Auch beim Setzen oder Zurücknehmen von Filtern ändert sich die Anzahl der Sätze, die vom Grid verwaltet werden. Das Gigagrid speichert diese für viele Berechnungen wichtige Zahl in der Eigenschaft `nrows` .

Um sicher zu sein, dass `nrows` stets den korrekten Wert enthält, müsste das Gigagrid bei jeder Aktion einen `count()-`Befehl durchführen. Bei kleinen Datenbeständen (z.B. 50 Einträge), ist dies noch ohne größeren Zeitverlust möglich. Sobald jedoch größere Rowsets dargestellt werden sollen, wird der Aufwand zu groß und die Geschwindigkeit der Darstellung leidet zu sehr. Aus diesem Grund kann der Entwickler selbst entscheiden, wie sich das Gigagrid verhalten soll. Hierzu benutzt er die Eigenschaft `selfcount`: ist sie `true`, so führt das Gigagrid bei jeder Aktion ein `rowset.count()` durch. Der Entwickler braucht sich um den Wert von `nrows` nicht zu kümmern: die Klasse übernimmt selbst die ganze Arbeit.

Um das Gigagrid von dieser zeitaufwendigen Arbeit zu entlasten, kann man `selfcount` auch auf `false` setzen (dies ist der voreingestellte Wert). In diesem Fall ist der Entwickler selbst dafür verantwortlich, dass in `nrows` stets der korrekte Wert gespeichert ist. Das bedeutet, dass er den Wert erhöhen muss, wenn Daten angefügt werden bzw. ihn verringern, wenn Datensätze gelöscht werden. Beim Aktivieren oder Löschen eines Filters, muß `nrows` mit dem Resultat eines frischen `count()` belegt werden. (Falls das Anfügen, Löschen oder Filtern von Datensätzen nicht erlaubt ist, braucht man sich natürlich um `nrows` nicht weiter zu kümmern, selbst wenn `selfcount = false`) .

Folgende Fehler können auftreten, falls der Entwickler falsche Werte in `nrows` zulässt. Beim Löschen eines Satzes ohne gleichzeitiges dekrementieren von `nrows` wird die unterste Zeile im Gigagrid leer dargestellt, wenn der Lift seinen unteren Endpunkt erreicht. Im anderen Fall, dass Daten angefügt wurden ohne `nrows` zu erhöhen, werden die letzten Datensätze im Grid gar nicht dargestellt. Es sieht dann so aus, als ob das Grid auf dem vorletzten Satz stehen bleibt.

Die nächste Eigenschaft des GigaGrid heißt `refreshWhileScrolling`. Falls sie auf `true` gesetzt ist (Standard), aktualisiert das Grid laufend den darzustellenden Bereich. Das Grid verschiebt (scrollt) den Datensatzbereich, so dass ständig der der aktuellen Liftposition entsprechende Abschnitt des Rowsets zu sehen ist. Ist die Eigenschaft `false`, wartet das Gigagrid mit der Aktualisierung, bis der Lift an der gewünschten Position steht und der User die Maustaste loslässt. Erst dann wird das Grid so gescrollt, dass die der Liftposition entsprechenden Zeilen sichtbar werden.

Der Vorteil gegenüber einem normalen Grid ist, dass das Gigagrid überhaupt eine solche Kontrollmöglichkeit bietet. Besitzt das im Standard-Grid dargestellte Rowset keinen aktiven Index, so wird immer augenblicklich aktualisiert (es gibt keinen einfachen Weg dies zu verhindern). Im Gegensatz dazu wird bei einem aktiven Index überhaupt keine sinnvolle Aktualisierung vorgenommen. Das Gigagrid gibt dem Entwickler die Möglichkeit, selbst zu entscheiden, wie der dargestellte Bereich erneuert wird. Bei “relativ” großen Rowsets- um die 1000 Datensätze- erscheint das Scrollen ein wenig sprunghaft (besonders bei langsameren Rechnern). In solchen Fällen empfehle ich,`refreshWhileScrolling` auf `false` zu setzen.

Die letzte Custom-Property heißt `proportional`. Diese wird benutzt, um die Funktionalität des Schachts zu steuern. Ist sie `false`, so wertet das Grid einen Mausklick im Schacht (oberhalb des Lifts) als das Betätigen des Page-Up Schalters und das Gigagrid wird eine Seite nach oben gescrollt. Ein Anklicken unterhalb des Lifts führt zu einem Scrollen um eine Seite nach unten. Setzt man die Eigenschaft `proportional` hingegen auf `true`, so erhält der Schacht eine erweiterte Funktionalität: in diesem Fall wertet das Gigagrid die genaue Position aus, an der geklickt wurde und verschiebt den sichtbaren Bereich des Grids zur entsprechenden Stelle im Rowset. Nehmen wir einmal an, dass der Schacht 300 Pixel hoch ist und der User eine Stelle anklickt, die 200 Pixel unterhalb der oberen Kante liegt. Das GigaGrid wird dann so scrollen, dass der Datensatz sichtbar wird, der in der momentanen Sortierreihenfolge (ob mit Index oder nicht) an der Stelle steht, die zwei Drittel der Gesamtanzahl entspricht: besteht das Rowset etwa aus 1000 Datensätzen, so wird die oberste Zeile des Grids genau den 600. Satz darstellen, wie er durch die Sortierreihenfolge festgelegt ist. Um die ganze Sache noch benutzerfreundlicher zu machen, braucht man die Maustaste nicht loszulassen, sondern kann von der angeklickten Position aus den Lift einfach weiterziehen.

Kommen wir nun zur Kompatibilität zwischen Gigagrid und dem Seeker. Ähnlich wie mit dem Standard-Grid gibt es nicht viel zu tun, wenn ein Gigagrid zusammen mit einem Seeker-Steuerelement eingesetzt wird. Nur für den Fall, dass das mit dem Gigagrid verbundene Rowset nicht dasselbe ist wie das `form.rowset`, gibt es einen minimalen zusätzlichen Programmieraufwand. Man muss den Seeker mit dem entsprechenden Datensatzbereich verbinden :

| `Function SEEKER1_onOpen` `this.rowset = form.clients1.rowset` `return`
| ``

Ansonsten (falls Seeker und Gigagrid sich den gleichen Bereich teilen) erzwingt eine Eingabe im Seeker ein sofortiges Anpassen des Grids, sowohl des Datenbereichs als auch der Scrollbar. Einfacher geht es nicht!

Zusammengefasst hier noch einmal die wichtigsten Eigenschaften des GigaGrids :

- `selectedBackgroundColor`: Hintergrundfarbe der hervorgehobenen Zeile (Hexadezimaler Wert)
- `selectedForegroundColor`: Schriftfarbe der hervorgehobenen Zeile (Hex-Wert)
- `selfcount`: wenn diese Eigenschaft `true` ist, kümmert sich das Gigagrid selbständig um die Bestimmung der Anzahl Zeilen im Rowset. Ist diese Eigenschaft `false`, so muss der Entwickler dafür sorgen, dass in `nrows` immer die korrekte Anzahl von Sätzen im Rowset abgelegt ist.
- `refreshWhileScrolling`: wenn diese Eigenschaft `true` ist, dann reagiert das Gigagrid augenblicklich auf jede Bewegung mit der Scrollbar. Bei `false` kann der Lift im Schacht bewegt werden, ohne das Grid zu aktualisieren. Erst wenn die Maustaste losgelassen wird reagiert das Grid.
- `proportional`: Wenn `true`, dann wird der Lift genau zu der Stelle bewegt, an der der Benutzer geklickt hat (und das Grid wird dementsprechend aktualisiert). Ist diese Eigenschaft `false`, so entspricht ein Mausklick im Schacht (egal an welcher Position) einem Drücken des PageUp- oder PageDn-Buttons (je nachdem, ob über oder unter dem Lift geklickt wurde).

Hinter den Kulissen

Es wurden drei verschiedene Techniken eingesetzt, um die Funktionalität des Gigagrids zu implementieren: eine zur Aktualisierung der Daten im Grid, eine um die korrekte Position des Liftes auch bei aktivem Index zu gewährleisten und schließlich eine zur Darstellung der “highlighted row”.

Das scrollende Grid (wie wird aktualisiert?)

Im Vorgänger des Gigagrids benutzte Jean-Pierre Martel die Tatsache, dass das Grid sich bei Datensatznavigation automatisch aktualisiert. Seine Implementierung der MegaGridScrollbar basierte auf diesem Verhalten. Leider ist dies aber auch der Grund, warum der Seeker mit seiner Grid-Version inkompatibel ist. Es musste ein anderer Weg gefunden werden, der es erlaubt, das Gigagrid zu aktualisieren und der gleichzeitig den Einsatz von Seekern zulässt. Der naheliegendste Weg schien mir der Einsatz von Windows-Nachrichten und glücklicherweise ist der interne Aufbau des Grids so gestaltet, dass es automatisch auf verschiedene Messages reagiert.

Jede Windows-Scrollbar lauscht ununterbrochen auf spezielle System-Nachrichten, unter anderem auf solche des Typs `WM_VSCROLL` .Diese Nachricht übergibt zusätzliche Parameter:`SB_LINEUP`,`SB_LINEDOWN`,`SB_PAGEUP`, und `SB_PAGEDOWN`. Ein kurzer Test förderte schnell zutage, dass es ausreicht, der eingebauten Scrollbar des Grids eine solche Nachricht zu senden, um eine entsprechende Reaktion des ganzen Grids zu erzwingen. Das bedeutet, dass ein Klick auf den NextRow-Button in einen Befehl wie der folgende umgewandelt werden kann:

| `SendMessage(grid.hwnd,WM_VSCROLL,SB_LINEDOWN,0)`
| ``

Hierbei ist `SendMessage()` eine API-Funktion, die genau das hält, was ihr Name verspricht. Sie sendet eine Nachricht an des durch den ersten Parameter festgelegte Fenster (`grid.hwnd`). Der zweite Parameter bestimmt die Art der Nachricht (es gibt an die tausend verschiedene Windows-Messages) und der dritte schließlich enthält zusätzliche Angaben. Führt man diese Anweisung aus, so zwingt man das Grid eine Zeile nach unten zu scrollen (wohlgemerkt ohne dabei den Datensatzzeiger zu verschieben). Die anderen Buttons in der Scrollbar werden auf ähnliche Weise implementiert: PageUp z.B. wird mit einer `SB_PAGEUP` Message realisiert usw. Schaut man sich einmal die Windows-API-Hilfe unter dem Stichwort WM_VSCROLL an, so sieht man, dass es noch andere Zusatzparameter gibt, etwa `SB_TOP` und `SB_BOTTOM.` Leider werden diese vom Grid schlicht und einfach ignoriert.

Das Grid ist in der Lage, ohne weiteren Programmieraufwand, Page-Nachrichten zu verarbeiten. Da es immer selbst weiß, wieviele Zeilen es darstellt, weiß es auch wieviele Zeilen gescrollt werden müssen, um eine ganze Seite vor oder zurück zu blättern. All das wird vom Grid automatisch richtig durchgeführt. (Dies ist auch der Grund, warum keine weiteren Parameter bei `SB_PAGEUP` und `SB_PAGEDOWN` nötig sind.)

Ist die Eigenschaft `proportional` auf `true` gesetzt, wird die Angelegenheit etwas komplizierter. Die Navigation mit dem Lift in der Scrollbar muß dann in korrespondierende Sendmessage-Befehle umgewandelt werden. Der Trick, der das Ganze erst möglich macht, besteht darin, die Anzahl der vom Grid dargestellten Zeilen ständig zu ändern, ohne dass der Anwender dies mitbekommt. Auf diese Art können wir die Amplitude der Page-Nachrichten kontrollieren. Nehmen wir an, wir haben eine Tabelle mit 400 Einträgen, diese wird in einem Grid mit 15 Zeilen dargestellt. Ist der Lift ganz oben im Schacht, so sehen wir die ersten 15 Datensätze der Tabelle. Ziehen wir den Lift nun genau in die Mitte des Schachts, so soll das Grid natürlich den zweihundertsten und die 14 folgenden Datensätze anzeigen. Wir könnten nun einfach 200 `SB_LINEDOWN` Nachrichten senden. Da dies jedoch extrem langsam ist, ändern wir stattdessen heimlich (`LockWindowUpdate`) die Höhe des Grids! Nämlich genau so, dass es jetzt 200 Zeilen darstellen kann (wegen `LockWindowUpdate` bemerkt der Benutzer nichts davon). Mit einem einzigen `SB_PAGEDOWN` Befehl scrollen wir also jetzt zur Mitte der Tabelle (die 200. Zeile ist ja entsprechend der augenblicklichen Höhe nur eine Seite entfernt). Danach bringen wir das Grid wieder auf seine ursprüngliche Höhe von 15 Zeilen zurück, und für den User sieht es aus als hätten wir 200 einzelne `SB_LINEDOWNS` durchgeführt, nur 200 mal schneller !

Leider ist diese Vorgehensweise auch der Grund für die Beschränkung der Tabellengröße. Es gibt eine Obergrenze von 32768 Pixeln für die Höhe eines Fensters (technisch gesehen, ist das Grid nichts anderes als ein besonderes Fenster). Größere Werte werden einfach auf 32768 gekappt, wobei ich nicht weiß ob dafür dBase oder Windows verantwortlich ist. In einem “normalen” Grid beträgt die Höhe einer Zeile etwa 22 Pixel. Das heißt mit der obigen Technik können nicht mehr als (32768/22) Zeilen von einer Page-Nachricht abgedeckt werden. Würde man größere Rowsets nutzen könnten weite, schnelle Liftbewegungen zu einem Verlust der Synchronisierung zwischen der Position des Lifts und den tatsächlich dargestellten Zeilen führen. Daher die Einschränkung auf etwa 1400 Sätze.

Auf der anderen Seite ist es natürlich so, dass bereits 1400 Datensätze eine riesige Informationsmenge darstellen und alles andere als benutzerfreundlich sind. Es wird unheimlich schwer irgend etwas Signifikantes dort herauszuholen. Man sollte in so einem Fall erst einen Filter setzen, um die Anzeige auf das Wesentliche einzuschränken. Da das Gigagrid zu allen Filtern kompatibel ist, haben wir hier ein mögliches Workaround für das 1400-Zeilen-Limit.

Die Liftposition bei Tabellen mit aktivem Index

Wenn ein Rowset keinen aktiven Index besitzt, so kann die Datensatznummer herangezogen werden, um die relative Position eines Datensatzes im Bestand zu ermitteln. Bei einem aktiven Index geht dies natürlich nicht mehr (der zuletzt angefügte Datensatz hat die höchste Recordnummer, könnte aber, je nach Index, der erste darzustellende sein). Wir müssen uns also etwas einfallen lassen, um die relative Position eines Satzes in einem indizierten Rowset zu finden.

Mit Paradox-Tabellen ist die Sache einfach, die Borland Database Engine stellt uns die sogenannte “sequence number” zur Verfügung; sie stellt die Entfernung des aktuellen Satzes zum Datensatzanfang dar, unabhängig davon, ob ein Index aktiv ist oder nicht. Leider gibt es diese sequence number für .dbf- oder andere Tabellen nicht. Wir müssen also unsere eigene Sequenznummer berechnen und benutzen dazu einen binären Suchbaum, der sich wegen des Index natürlich aufdrängt. Als erstes wird `notifycontrols` des Rowsets disabled (wegen der nötigen Navigation, von der der Benutzer natürlich nichts mitbekommen soll), dann bewegen wir den Datensatzzeiger in die Mitte des Rowsets, (bei 100 Sätzen starten wir also bei Position 50). Anschließend prüfen wir, ob der dortige Schlüssel größer oder kleiner ist als der, den wir suchen und springen in die Mitte der entsprechenden Hälfte (Position 75 oder 25). Das geht so lange weiter, bis die Schlüssel gleich sind. Die so gewonnene Zahl entspricht der gesuchten relativen Position in Abhängigkeit vom verwendeten Index und kann herangezogen werden, um die richtige Position des Lifts im Schacht zu berechnen!

Wie man sieht, bedeutet dies eine Menge Rechenaufwand und ständiges Bewegen des Datensatzzeigers. Das ist der Grund, warum das Gigagrid mit aktivem Index langsamer ist als ein normales Grid ohne Index. Auf der anderen Seite ist es die einzige Chance, die Scrollbar auch bei indizierten Tabellen sinnvoll zu nutzen. Wer häufig das normale Grid mit aktivem Index nutzt, wird es sicher zu schätzen wissen, dass das Gigagrid mehr als die drei Positionen der normalen Scrollbar kennt (oben = erster Satz, unten = letzter Satz und in der Mitte, wenn der Datensatzzeiger auf irgendeiner anderen Position steht).

Die Anzeige der “Highlighted Row”

Diese Technik benötigt sowohl `Wndnew.dll` als auch `GigaGrid.cc`. Es liegt hier einer der wenigen Fälle vor, wo die Fähigkeiten von dBase nicht ausreichen, um das angestrebte Ziel zu erreichen. Wir benutzen ein subClassing genanntes Verfahren: hierbei wird die Standard-Windows-Prozedur eines Fensters mit einer eigenen Prozedur überschrieben (diese Prozedur ist eine Callback-Funktion, die sämtliche eingehenden Windows-Nachrichten verarbeitet). Das Grid verarbeitet zwar Nachrichten des Typs `WM_PAINT`, gibt sie dem Entwickler aber nicht weiter, so dass dieser nicht per Programm herausfinden kann, ob sich im Grid etwas geändert hat. Um dies doch zu ermöglichen, haben wir eine kurze dll-Datei geschrieben (in Delphi), die dem Entwickler eine Art “onrepaint-event” bietet. Der Pseudo-Code dieser Dll sieht in etwa wie folgt aus:

| `Die Prozedur nutzt die Indicator-Spalte links vom Grid (deshalb muss die Eigenschaft hasIndicator unbedingt true sein)` `Erhält der Indicator eine WM_PAINT-Nachricht? (Das ist der Fall, wenn sich im Grid etwas ändert.)` `Wenn ja, wird eine leftMouseUp-Nachricht an die unsichtbare Paintbox im Gigagrid gesendet und dann die normale Ausführung fortgesetzt.` `Wenn nicht, wird einfach die Standard-Windows-Prozedur angesprungen und die normale Nachrichtenverarbeitung fortgesetzt.`
| ``

Die oben erwähnte unsichtbare Paintbox wird also benutzt, um mit Hilfe der dll-Datei ein Event zu erzeugen, dasdBase eigentlich nicht zur Verfügung stellt! Wird im Grid navigiert oder gescrollt, erhält die Indikator-Spalte eine `WM_PAINT` Nachricht, die von unserer Prozedur abgefangen und als leftmousedown-Nachricht an die Paintbox geschickt (und außerdem normal weiterverarbeitet) wird. Da die Paintbox einen eingebauten onleftmousedown-Eventhandler besitzt, können wir mit der Auswertung der Grid-Veränderungen bequem in dBase fortfahren.

Jedesmal wenn das Ereignis `onLeftMouseUp()` der Paintbox ausgelöst wird, wissen wir, dass sich im Grid etwas getan hat, und dass wir eventuell die Darstellung der aktuell selektierten Zeile anpassen müssen. Im Gigagrid ist die hervorgehobene Zeile nichts anderes als ein gefärbter Container, der genau die Ausmaße einer Grid-Zeile besitzt und Eingabefelder enthält, die die im Grid dargestellten Daten enthalten. Bei einer Veränderung (aufgefangen durch die Paintbox ) wird dann einfach die Eigenschaft `top` des Containers so angepasst, dass er die richtige Zeile wiedergibt (falls die hervorgehobene Zeile gerade außerhalb des sichtbaren Bereichs liegt, wird die Eigenschaft `visible` des Containers einfach auf false gesetzt). Da ein Container im Gegensatz zum Grid (vor dBase Plus) eine Eigenschaft `colorNormal` besitzt, ist es ein Leichtes, unsere eigene hervorgehobene Zeile nach Belieben farbig zu gestalten.
Verbesserungswürdig

Das Scrollrad der Maus funktioniert nicht in der IDE, solange eine Instanz von dQuery offen ist. Sobald dQuery geschlossen wird, klappt’s auch mit dem Nachbarn ähm der Maus ;-).

Da `WndNew.dll` nur Messages an die Indikator-Spalte abfängt, muß `hasIndicator` des Grids `true` sein.

Die Gigagrid-Klasse bearbeitet Rowsets nur bis etwa 1400 Sätze korrekt, bei mehr Datensätzen geht die Synchronisation zwischen Scrollbar und dargestelltem Bereich verloren.
Zusammenfassung

Die Gigagrid-Klasse ist die konsequente Weiterführung des Konzepts der MegaGridScrollbar-Klasse . Etwa 99.5% des Codes ist in dBL geschrieben. Wie das Beispiel zeigt, ermöglicht es die offene Architektur von dBase, auf einfache Art Verbesserungen oder Anpassungen durchzuführen. Führt man sich den enormen Rechenaufwand, der zusätzlich zu den normalen Gridaufgaben durchgeführt wird, vor Augen, so schneidet dBase auch in Punkto Geschwindigkeit gar nicht so schlecht ab! Zugegeben, unser aufgemotztes Daten-Darstellungs-Steuerelement ist nicht so schnell wie ein Ferrari, es bietet uns aber zusätzliche Funktionalität im Vergleich zu dem eingebauten Standard-Grid.

Das Gigagrid hat den Nachteil einer Obergrenze für die Rowset-Größe. Dieser Nachteil kann jedoch in vielen sauber gestylten Anwendungen ausgehebelt werden (Filter). Ich weiß, dass dies nicht immer möglich ist, und dass das Gigagrid in diesen Fällen nicht zu verwenden ist. Schade, aber nicht zu ändern.....noch nicht.

Einige der dargestellten Verbesserungen stehen mittlerweile in dBASE Plus ebenfalls zur Verfügung. Doch nur das Gigagrid lässt auch die Benutzer der älteren Versionen dB2K und VdB7.5 in den Genuss dieser Verbesserungen kommen. In einer der späteren Versionen könnte dank dBASE Plus ein Teil der Berechnungen eingespart werden und die Performance der Klasse verbessert werden.

Ich hoffe, dass sich diese Klasse zumindest für einige Leser als nützlich erweist.

Um die beigefügten Beispiele herunterzuladen, bitte hier klicken
( 50 Kb als Zip-Datei)

Ich möchte mich ganz herzlich bei Roland Wingerter für seine zahllosen Anregungen und Verbesserungsvorschläge bedanken. Vielen Dank auch an Jonny Kwekkeboom, der die Bitmaps für das XP-Aussehen beigesteuert hat.