2016-05-30 1 views
2

ÜbersichtWie TListbox Items-Eigenschaft mit meinem eigenen veröffentlichten Objektliste-basierten Typ in einem TCustomListBox-Steuerelement ersetzen?

Diese Frage ist ein zweiter Versuch auf diesem basiert Ich habe vor kurzem gefragt: How can I make a TList property from my custom control streamable?

Obwohl ich die Antwort in dieser Frage angenommen und es hat funktioniert, ich erkannte bald, dass TCollection nicht die Lösung oder Anforderung, nach der ich gesucht habe.

Anforderungen

Um meine Anforderungen so einfach und klar zu verstehen, wie möglich zu halten, ist es das, was ich versuche:

  • eine neue benutzerdefinierte Steuerung leiten basierend auf TCustomListBox
  • ersetzen die Items Eigenschaft mit meinem eigenen Items Typ, zB ein TList.
  • Die TList (Eigenschaft Items) werden die Objekte halten, die jeweils eine Beschriftung und einen Bildindex Eigenschaft usw.
  • OwnerDraw meine Listbox enthält und einen Eigenschaftseditor
  • seine Symbole und Text usw. ziehen Erstellen Sie die Items bearbeiten an Design-Zeit.

, die mit im Auge, ich weiß, wie das benutzerdefinierte Steuerelement erstellen, weiß ich, wie die Arbeit mit TList oder sogar TObjectList zum Beispiel, ich weiß, wie das Steuerelement OwnerDraw und ich weiß auch, wie der Eigenschaftseditor erstellen .

Problem

Was ich nicht weiß, ist, wie der Standard-listbox Items Typen mit meinem eigenen ersetzen? Nun, ich mache es (meine eigene Property, die den gleichen Namen teilt), nur ich muss sicherstellen, dass es vollständig mit der dfm streamable ist.

Ich habe ausführlich zu diesem Thema gesucht und haben versucht, Code zu studieren, wo TListView und TTreeView etc seine Items Art veröffentlicht, aber ich habe mich als je mehr verwirrt gefunden.

In der Tat stieß ich auf diese sehr alte Frage von jemand anderem auf einer anderen Website gefragt, die sehr viel fragt, was ich tun möchte: Streaming a TList property of a component to a dfm. Ich habe es unten zitiert, wenn der Link verloren ist:

Ich schrieb vor kurzem eine Komponente, die eine TList-Eigenschaft veröffentlicht. Ich habe dann einen Eigenschaftseditor für den TList erstellt, um das Bearbeiten der Entwurfszeit zu ermöglichen. Das Problem besteht darin, dass der TList nicht in die DFM-Datei streamt, so dass alle Änderungen verloren gehen, wenn das Projekt geschlossen wird. Ich gehe davon aus, dass TList von TObject und nicht von TPersistant erbt. Ich hatte gehofft, dass es eine leichte Arbeit für diese Situation gibt (oder dass ich das Problem von vornherein falsch verstanden habe). Im Moment kann ich nur zu einer TCollection wechseln oder die DefineProperties-Methode überschreiben. Gibt es eine andere Möglichkeit, die Informationen in der TList-Streaming zu und von der DFM zu bekommen?

ich über das kam, während an der Spitze verbunden Schlüsselwörter wie DefineProperties() gegeben, dass dies eine alternative Option Remy Lebeau kurz berührt in der vorherige Frage suchen, wie es schien auch auf diese Frage die Antwort zu sein.

Frage

ich wissen müssen, wie die Items (TStrings) Eigenschaft einer TCustomListBox abgeleiteten Steuer mit meiner eigenen Items (TList) oder Items (TObjectList) etc Art zu ersetzen, aber es vollständig streambare mit dem DFM zu machen. Ich weiß aus früheren Kommentaren TList ist nicht streambar, aber ich kann TStrings nicht verwenden, wie der Standard TListBox Kontrolle tut, muss ich meine eigene objektbasierte Liste verwenden, die streamable ist.

Ich möchte nicht verwenden TCollection, DefineProperties klingt vielversprechend, aber ich weiß nicht, wie genau ich das implementieren würde?

Ich würde sehr gerne einige Hilfe mit diesem bitte.

Vielen Dank.

+1

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. –

+0

@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

Antwort

3

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.