Sperriger Begriff, ich weiß...
Vorweg: bin Delphi-7-Routinier beim Umstieg auf XE6. Für die Lösung darf also XE6 und ein bisschen Delphi-Erfahrung vorausgesetzt werden
Ich habe die Aufgabe, Daten in einem Grid zu editieren, das etwa so aussieht:
Code:
Eigenschaft |Wert |Datentyp |Aktiv
Kunde |Mustermann AG |String |JA
AutomVerbinden |JA |Bool |JA
Port |16387 |Int |NEIN
...
Die Funktionalität ähnelt dem des Windows Registry Editors, nur dass ich alle Daten direkt im Grid editieren möchte, und keine Popup-Eingabefenster haben will.
Nun möchte ich pro Datentyp der jeweiligen Zelle (der teils vom Inhalt einer anderen Zelle abhängt, siehe "Wert"!) nur gemäß Datentyp zulässige Daten eingeben lassen, d. h. in Integer-Feldern keine Buchstaben etc., und boolsche Felder am liebsten durch eine Checkbox ein- und ausgeben.
* Die Daten kommen nicht (immer) aus der Datenbank, d. h. ich vermute
TDbGrid
zu verwenden wäre ungünstig. Ich tendiere dazu
TStringGrid
zu verwenden (bin aber für Vorschläge offen)
* Ich möchte keine Third-Party-Komponenten (DevExpress & Co.) einsetzen, da wir ein großes Entwickler-Team sind welches eine große Zahl an Anwendungen mit einem recht langen Lebenszyklus betreut, und Reproduzierbarkeit der Binaries ist extrem wichtig. Diese Aspekte beißen sich damit, dass diese Third Parties (im Grunde dankenswerterweise) öfters mal neue Versionen ihrer Komponenten veröffentlichen, dass man aber den Sourcecode dieser Komponenten nur begrenzt zusammen mit seinem eigenen Sourcecode "konservieren" kann. Somit ein anderes Binary je nach aktuell installierter Version der Third Party Komponenten.
* Aus ähnlichen Gründen möchte ich keine eigenen Komponenten entwickeln. Dieses Konzept hatten wir bislang verfolgt (seit Delphi 1) und sind immer unglücklicher damit geworden, wollen so nicht weitermachen, insbesondere weil XE6 nun sehr strikt ist was den Grundsatz angeht, eine
Unit immer nur in einem
Package zu verwenden. Prinzipbedingt mussten wir unsere Komponenten auf etwa 6 Packages aufteilen...
* Ich hatte gehofft, dass XE6 bereits alles an Bord hat, was man hierzu braucht (dem ist aber wohl nicht so). Trotzdem möchte ich möglichst viel mit "Bordmitteln" erschlagen.
* Das Implementieren der Funktionalität direkt in der jeweiligen
Unit ist unerwünscht, da ich Grids in der Art wie oben beschrieben an etlichen Stellen benötige.
* Aus genau diesem Grund möchte ich das Grid per Formular-Designer auf meine Form ziehen und im Objekt-Inspektor die Eigenschaften zuweisen können, d. h. kein dynamisches Erzeugen von Controls.
Mein Lösungsansatz sieht so aus:
Ich erschaffe in einer (oder mehreren)
Unit(s) "Interceptor-Klassen" (Beispiel:
type TStringGrid = class(Grids.TStringGrid)
) für
TStringGrid
und
TEdit
und erweitere sie so um Eigenschaften "DataType" (fürs StringGrid einmal ein Array über alle Spalten und einmal zweidimensional über alle Zellen). Dann überlagere ich die Ereignisroutinen.
Bei "meinem"
TEdit
wird bei jedem
Change
-Ereignis geprüft (das überlagere ich), ob
Text
in den vorgegebenen Datentyp gewandelt werden kann, andernfalls nehme ich die letzte Änderung (z. B. Eingabe eines Buchstabens) wieder zurück. Weiterhin kann ich so gegen hinzugefügte Eigenschaften "MinValue", "MaxValue" prüfen usw.. Das funktioniert prima. Nachteil (aber damit kann ich leben): "DataType" muss ich den
TEdit
s zur Laufzeit, d. h. im Code zuweisen - der Objektinspektor weiß nicht, dass ich
TEdit
überlagert habe.
Bei "meinem"
TStringGrid
habe ich es immerhin geschafft, durch Überlagern von
DrawCell
eine Checkbox statt des aktuellen Zelleninhalts zu malen (angehakt wenn Inhalt="T", kein Haken wenn Inhalt="F", ansonsten ausgegraut), und durch Überlagern von
GetEditText
kann ich den Zelleninhalt toggeln. Selber Nachteil wie bei
TEdit
: DataType kann nur zur Laufzeit zugewiesen werden, aber das ist ok. Was mir schon nicht gefällt: eigentlich bräuchte ich ein Ereignis "Jetzt wird die Zelle bearbeitet".
GetEditText
kommt dem zwar nahe, aber nicht zu 100%.
Jetzt würde ich gerne eine Integer-Zelle handhaben. Dafür könnte ich ja meine "typbewusste"
TEdit
als Editor einsetzen. Jetzt wird's aber doof: ich beginne zu editieren,
GetEditText
wird gerufen, ich erzeuge meine "typbewusste"
TEdit
(oder mache sie sichtbar), dadurch verliert das Grid den Fokus und ruft
SetEditText
. An
OnSetEditText
hängt sich aber meine übergeordnete Anwendung ran um beim bzw. nach dem Bearbeiten eines Zelleninhalts die Daten z. B. wegzuspeichern. Ja,
SetEditText
ist nicht wirklich das Ereignis "Bearbeiten fertig", aber das gibt es halt nicht bei einem StringGrid.
Noch doofer: das überlagerte
TEdit.Change
wird bei dieser Editbox nicht mehr gerufen. Ich kann also völligen Blödsinn eingeben. Es wirkt fast so, als würde es nur begrenzt unterstützt werden, innerhalb einer Interceptor-Klasse eine weitere "aufgeborte" Klasse zu verwenden. Methoden und Eigenschaften sind kein Problem, aber es werden wohl "nur" die Ereignisse der Basisklasse gerufen...
Ich habe auch schon versucht, den Inplace-Editor des StringGrids zu überlagern (dadurch würden vermutlich meine ganzen Ereignisroutinen-Missbrauchs-Versuche wegfallen), aber da bin ich nicht wirklich weitergekommen. Es gibt zwar Anleitungen dazu, aber die ersetzen den Inplace-Editor eines StringGrids für das gesamte Grid. Ich brauche halt je nach Zelle die eine oder die andere Eingabemethode.
Was würdet ihr mir raten? Wie realisiert man am besten ein Grid, das verschiedene Datentypen pro Zelle unterstützt, inklusive jeweils dazu passendem Editor?