Override DefineProperties
Prozedur in Ihrem TCustomListBox
(nennen wir es TMyListBox
hier). Dort können Sie beliebig viele Felder "registrieren", sie werden wie andere Felder in dfm gespeichert, aber Sie sehen sie nicht im Objektinspektor. Um ehrlich zu sein, habe ich noch nie mehr als eine Eigenschaft definiert, die "Daten" oder "Strings" genannt wird.
Sie können eine 'normale' Eigenschaft oder eine binäre Eins definieren. 'Normale' Eigenschaften sind sehr praktisch für Strings, Integer, Enumerationen und so weiter. Hier ist, wie Gegenstände mit caption
und ImageIndex
kann realisiert werden:
TMyListBox = class(TCustomListBox)
private
//other stuff
procedure ReadData(reader: TReader);
procedure WriteData(writer: TWriter);
protected
procedure DefineProperties(filer: TFiler); override;
//other stuff
public
//other stuff
property Items: TList read fItems; //not used for streaming, not shown in object inspector. Strictly for use in code itself. We can make it read-only to avoid memory leak.
published
//some properties
end;
die DefineProperties
Implementierung ist:
procedure TMyListBox.DefineProperties(filer: TFiler);
begin
filer.DefineProperty('data', ReadData, WriteData, items.Count>0);
end;
vierte Argument, hasData
ist Boolean. Wenn Ihre Komponente in dfm gespeichert wird, wird DefineProperties
aufgerufen, und es ist möglich, zu diesem Zeitpunkt zu entscheiden, ob irgendwelche Daten gespeichert werden können. Wenn nicht, wird die Eigenschaft 'data' weggelassen. In diesem Beispiel haben wir diese Eigenschaft nicht, wenn keine Elemente vorhanden sind.
Wenn wir erwarten, jemals die visuelle Vererbung dieses Steuerelements zu verwenden (z. B. einen Rahmen mit dieser listBox mit vordefinierten Werten erstellen und diese dann eventuell ändern, wenn sie in Form gebracht werden), besteht die Möglichkeit zu überprüfen, ob es sich um einen Wert handelt Eigentum anders als auf unserem Vorfahren. Die Filer.Ancestor-Eigenschaft wird dafür verwendet. Sie können beobachten, wie es in TStrings
getan:
procedure TStrings.DefineProperties(Filer: TFiler);
function DoWrite: Boolean;
begin
if Filer.Ancestor <> nil then
begin
Result := True;
if Filer.Ancestor is TStrings then
Result := not Equals(TStrings(Filer.Ancestor))
end
else Result := Count > 0;
end;
begin
Filer.DefineProperty('Strings', ReadData, WriteData, DoWrite);
end;
Dies würde sparen ein wenig Raum (oder viel Platz, wenn Bild gespeichert ist, innerhalb) und sicher ist elegant, aber in der ersten Implementierung es auch weggelassen werden kann.
Jetzt der Code für WriteData und ReadData.Das Schreiben ist viel einfacher, in der Regel, und wir können damit beginnen:
procedure TMyListBox.WriteData(writer: TWriter);
var i: Integer;
begin
writer.WriteListBegin; //in text dfm it will be '(' and new line
for i:=0 to items.Count-1 do begin
writer.WriteString(TListBoxItem(items[I]).caption);
writer.WriteInteger(TListBoxItem(items[I]).ImageIndex);
end;
writer.WriteListEnd;
end;
In DFM wird es wie folgt aussehen:
object MyListBox1: TMyListBox
data = (
'item1'
-1
'item2'
-1
'item3'
0
'item4'
1)
end
Ausgabe von TCollection elegantere mir scheint (dreieckige Klammern und dann Elemente, ein nach dem anderen), aber was wir hier haben, würde ausreichen. Jetzt
es zu lesen:
procedure TMyListBox.ReadData(reader: TReader);
var item: TListBoxItem;
begin
reader.ReadListBegin;
while not reader.EndOfList do begin
item:=TListBoxItem.Create;
item.Caption:=reader.ReadString;
item.ImageIndex:=reader.ReadInteger;
items.Add(item); //maybe some other registering needed
end;
reader.ReadListEnd;
end;
Das ist es. Auf diese Weise können ziemlich komplexe Strukturen mit Leichtigkeit gestreamt werden, zum Beispiel zweidimensionale Arrays, WriteListBegin beim Schreiben einer neuen Zeile und dann beim Schreiben eines neuen Elements.
Vorsicht vor WriteStr
/ReadStr
- diese einige archaische Verfahren, die für die Abwärtskompatibilität vorhanden sind, IMMER WriteString
/ReadString
statt!
Eine andere Möglichkeit ist, binäre Eigenschaften zu definieren. Dies wird hauptsächlich zum Speichern von Bildern in dfm verwendet. Nehmen wir beispielsweise an, dass listBox Hunderte von Elementen enthält und wir Daten darin komprimieren möchten, um die Größe der ausführbaren Datei zu reduzieren. Dann:
TMyListBox = class(TCustomListBox)
private
//other stuff
procedure LoadFromStream(stream: TStream);
procedure SaveToStream(stream: TStream);
protected
procedure DefineProperties(filer: TFiler); override;
//etc
end;
procedure TMyListBox.DefineProperties(filer: TFiler);
filer.DefineBinaryProperty('data',LoadFromStream,SaveToStream,items.Count>0);
end;
procedure TMyListBox.SaveToStream(stream: TStream);
var gz: TCompressionStream;
i: Integer;
value: Integer;
item: TListBoxItem;
begin
gz:=TCompressionStream.Create(stream);
try
value:=items.Count;
//write number of items at first
gz.Write(value, SizeOf(value));
//properties can't be passed here, only variables
for i:=0 to items.Count-1 do begin
item:=TListBoxItem(items[I]);
value:=Length(item.Caption);
//almost as in good ol' Pascal: length of string and then string itself
gz.Write(value,SizeOf(value));
gz.Write(item.Caption[1], SizeOf(Char)*value); //will work in old Delphi and new (Unicode) ones
value:=item.ImageIndex;
gz.Write(value,SizeOf(value));
end;
finally
gz.free;
end;
end;
procedure TMyListBox.LoadFromStream(stream: TStream);
var gz: TDecompressionStream;
i: Integer;
count: Integer;
value: Integer;
item: TListBoxItem;
begin
gz:=TDecompressionStream.Create(stream);
try
gz.Read(count,SizeOf(count)); //number of items
for i:=0 to count-1 do begin
item:=TListBoxItem.Create;
gz.Read(value, SizeOf(value)); //length of string
SetLength(item.caption,value);
gz.Read(item.caption[1],SizeOf(char)*value); //we got our string
gz.Read(value, SizeOf(value)); //imageIndex
item.ImageIndex:=value;
items.Add(item); //some other initialization may be needed
end;
finally
gz.free;
end;
end;
In DFM es würde wie folgt aussehen:
object MyListBox1: TMyListBox1
data = {
789C636260606005E24C86128654865C064386FF40802C62C40002009C5607CA}
end
78
ist eine Art Unterschrift von ZLib bedeutet 9C
Standardkompression, so dass es funktioniert (es gibt nur zwei Elemente tatsächlich, nicht Hunderte). Natürlich ist dies nur ein Beispiel, mit BinaryProperties
kann jedes mögliche Format verwendet werden, zum Beispiel das Speichern in JSON und das Einfügen in den Stream oder XML oder etwas anderes. Aber ich würde nicht empfehlen, Binär zu verwenden, es sei denn, es ist absolut unvermeidlich, weil es schwierig ist, von dfm zu sehen, was in der Komponente passiert.
Es scheint mir eine gute Idee zu sein, beim Implementieren der Komponente aktiv Streaming zu verwenden: Wir können überhaupt keinen Designer haben und alle Werte setzen, indem wir dfm manuell bearbeiten und sehen, ob sich die Komponente korrekt verhält. Das Lesen/Laden selbst kann einfach getestet werden: Wenn die Komponente geladen, gespeichert und der Text identisch ist, ist alles in Ordnung. Es ist so "transparent", wenn das Streaming-Format "menschenlesbar" ist, und erklärt, dass es oft Nachteile (wie die Dateigröße) überwiegt, wenn es welche gibt.
Haben Sie sich das Beispiel 'DefineProperties' angesehen, mit dem ich in Ihrer vorherigen Frage verlinkt habe? Sie müssen 'DefineProperties' verwenden, um Daten zu streamen, die nicht nativ streambar sind. Warum willst du nicht 'TCollection' verwenden? Es löst all Ihre Probleme mit minimalem Codeaufwand - es ist nativ streamfähig, nativ gestaltbar und einfach zu bedienen. –
@RemyLebeau natürlich habe ich das Beispiel gelesen, aber ich habe es nicht ganz verstanden, daher habe ich diese neue Information ('DefineProperties') genommen und mehr darüber gesucht. Ich entschied, dass 'TCollection' nicht geeignet war, weil ich nicht ganz sicher bin, wie es am besten umgesetzt werden sollte, ich habe es mehr Glück als alles andere und ich mochte es nicht, wie es im Strukturbaum zur Designzeit angezeigt wurde verwirrende Ausführlichkeit zu meiner Steuerung, wie 'MyListBox1.Items.Items'. 'TListView' und' TTreeView' zeigen keine Objekte unter dem Strukturbaum an und das ist es was ich mit meiner Listbox gesucht habe. – Craig