Delphi 如何在TCustomListBox控件中用我自己发布的基于对象列表的类型替换TListbox项属性?
概述 这个问题是我最近提出的第二个问题: 虽然我接受了这个问题的答案,而且它奏效了,但我很快意识到,Delphi 如何在TCustomListBox控件中用我自己发布的基于对象列表的类型替换TListbox项属性?,delphi,Delphi,概述 这个问题是我最近提出的第二个问题: 虽然我接受了这个问题的答案,而且它奏效了,但我很快意识到,t收集并不是我想要的解决方案或要求 要求 为了使我的要求尽可能简单明了,我正试图: 基于TCustomListBox 将Items属性替换为我自己的Items类型,例如TList TList(Items属性)将保存对象,每个对象都包含标题和图像索引属性等 Owner绘制我的列表框并绘制其图标和文本等 创建属性编辑器,以便在设计时编辑项 记住这一点,我知道如何创建自定义控件,我知道如何使用TLi
t收集
并不是我想要的解决方案或要求
要求
为了使我的要求尽可能简单明了,我正试图:
- 基于
TCustomListBox
- 将
属性替换为我自己的Items
类型,例如Items
TList
(Items属性)将保存对象,每个对象都包含标题和图像索引属性等TList
- Owner绘制我的列表框并绘制其图标和文本等
- 创建属性编辑器,以便在设计时编辑
项
TList
甚至TObjectList
例如,我知道如何拥有该控件,我还知道如何创建属性编辑器
问题
我不知道的是如何用我自己的替换标准的listboxItems
type?嗯,我是这样做的(发布我自己共享同一名称的属性),只是我需要确保它完全可以通过dfm进行流化
我在这个主题上进行了广泛的搜索,并尝试研究了TListView
和TTreeView
etc发布其项目
类型的代码,但我发现自己比以往任何时候都更加困惑
事实上,我在另一个网站上遇到了别人问的一个非常老的问题,这个问题问了我很多想做的事情:。如果链接丢失,我在下面引用了它:
我最近编写了一个发布TList属性的组件。然后,我为TList创建了一个属性编辑器,以启用设计时编辑。问题是TList不会流式传输到dfm文件,因此当项目关闭时,所有更改都会丢失。我认为这是因为TList继承自TObject,而不是TPersistant。我希望有一个简单的方法来解决这个问题(或者我一开始就误解了这个问题)。现在我能想到的就是切换到TCollection或重写DefineProperties方法。是否有其他方法可以从dfm获取TList中的信息
我在搜索诸如DefineProperties()
之类的关键词时发现,鉴于这是Remy Lebeau在顶部链接的前一个问题中简要提到的另一个选项,它似乎也是该问题的答案
问题
我需要知道如何用我自己的Items(TList)
或Items(TObjectList)
etc类型替换TCustomListBox
派生控件的Items(TStrings)
属性,但要使用dfm使其完全可流化。从前面的评论中我知道TList
是不可流化的,但是我不能像标准TListBox
控件那样使用TStrings
,我需要使用我自己的可流化的基于对象的列表
我不想使用TCollection
,DefineProperties
听起来很有希望,但我不知道该如何具体实现这一点
我将非常感谢您的帮助
谢谢。在
TCustomListBox
中重写DefineProperties
过程(在这里命名为TMyListBox
)。在那里可以“注册”任意多的字段,它们将以与其他字段相同的方式存储在dfm中,但在对象检查器中看不到它们。老实说,我从未遇到过这样定义多个属性,称为“数据”或“字符串”
您可以定义“普通”属性或二进制属性“Normal”属性对于字符串、整数、枚举等非常方便。下面是如何实现带有标题
和图像索引
的项目:
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;
这就是DefineProperties
实现:
procedure TMyListBox.DefineProperties(filer: TFiler);
begin
filer.DefineProperty('data', ReadData, WriteData, items.Count>0);
end;
第四个参数,hasData
是布尔值。当您的组件保存到dfm时,将调用DefineProperties
,此时可以确定是否有值得保存的数据。如果不是,则省略“数据”属性。在本例中,如果不存在任何项,则不具有此属性
如果我们希望使用此控件的可视继承(例如,使用此列表框创建一个具有预定义值的框架,然后在形成时最终对其进行更改),则可以检查此属性的值是否与我们的祖先的值有任何不同。祖先属性用于它。您可以在t字符串中查看它是如何完成的:
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;
在dfm中,它将如下所示:
object MyListBox1: TMyListBox
data = (
'item1'
-1
'item2'
-1
'item3'
0
'item4'
1)
end
object MyListBox1: TMyListBox1
data = {
789C636260606005E24C86128654865C064386FF40802C62C40002009C5607CA}
end
对我来说,TCollection的输出似乎更优雅(三角括号,然后是项目,一个接一个),但我们这里的就足够了
现在读它:
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;
就这样。通过这种方式,相当复杂的结构可以很容易地流式处理,例如,二维数组,我们在写新行时开始写列表,然后在写新元素时开始写列表
当心WriteStr
/ReadStr
-这些是为了向后兼容而存在的一些古老过程,请始终使用WriteString
/ReadString
另一种方法是定义二进制属性。这主要用于将图像保存到dfm中。比如说,listBox有数百个条目,我们希望压缩其中的数据以减小可执行文件的大小。然后:
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;
在dfm中,它将如下所示:
object MyListBox1: TMyListBox
data = (
'item1'
-1
'item2'
-1
'item3'
0
'item4'
1)
end
object MyListBox1: TMyListBox1
data = {
789C636260606005E24C86128654865C064386FF40802C62C40002009C5607CA}
end
78
是ZLib的一种签名,9C
表示默认压缩,因此它可以工作(