Delphi: 存储从TObjectList获取的对象的正确方式

3

这个例子当然是简化了,但基本上我有一个主表单触发另一个表单(frmSettings):

function Execute(var aSettings: TSettings):Boolean

TSettings 是我在主窗体中创建的用于跟踪设置的对象。

在这个新打开的表单 (frmSettings) 中,我获取一个继承自 TObjectListTMyObjectList。 它被填充了 TMyObj

然后,我使用 TMyObjectList 中的值填充了一个 TListBox

代码如下:

...

FMyObjectList : TMyObjectList;
property MyObjectList: TMyObjectList read getMyObjectList;

...

function TfrmSettings.getMyObjectList: TMyObjectList ;
begin
    If not Assigned(FMyObjectList) then FMyObjectList := TMyObjectList.Create(True)
    Result := FMyObjectList;
end;

function TfrmSettings.Execute(var aSettings: TSettings): Boolean;
begin

    //Fill myObjectList
    FetchObjs(myObjectList);

    //Show list to user
    FillList(ListBox1, myObjectList);

    //Show form        
    ShowModal;

    Result := self.ModalResult = mrOk;

    if Result then
    begin
        // Save the selected object, but how??

        // Store only pointer? Lost if list is destroyed.. no good
        //Settings.selectedObj := myObjectList.Items[ListBox1.ItemIndex];

        // Or store a new object? Have to check if exist already?
        If not Assigned(Settings.selectedObj) then Settings.selectedObj := TMyObj.Create;
        Settings.selectedObj.Assign(myObjectList.Items[ListBox1.ItemIndex];);
    end;

end;

procedure TfrmSettings.FillList(listBox: TListBox; myObjectList: TMyObjectList);
var
    i: Integer;
begin
    listBox.Clear;
    With myObjectList do
    begin
        for i := 0 to Count - 1 do
        begin
            //list names to user
            listBox.Items.Add(Items[i].Name);
        end;
    end;
end;

procedure TfrmSettings.FormDestroy(Sender: TObject);
begin
    FreeAndNil(FMyObjectList);
end;

仅存储指针似乎不是一个好主意,因为再次触发设置表单会重新创建列表,即使用户点击“取消”,原始对象也将丢失。

因此,存储副本似乎更好,使用assign获取所有正确的属性。并首先检查是否已经拥有一个对象。

        If not Assigned(Settings.selectedObj) then Settings.selectedObj := TMyObj.Create;
        Settings.selectedObj.Assign(myObjectList.Items[ListBox1.ItemIndex];);

我应该将这两行代码移动到一个方法中,例如:Settings.AssignSelectedObj(aMyObj:TMyObj)

这样做是否正确,或者我实现的方式有误?还需要更少或更多的东西吗?

我需要一些指南,以便我感到更加安全,不会开启内存泄漏和其他问题。

除此之外,对代码进行一些审查,真正的问题是:在设置类中存储我的SelectedObject的方式是否正确?


如果没有分配(FMyObjectList),那么 FMyObjectList.Create(True); 是不正确的 - 你应该写成: 如果没有分配(FMyObjectList),那么 FMyObjectList := TMyObjectList.Create(True); - Arnaud Bouchez
@A.Bouchez:没错,正如所述,这只是示例代码,有时候你会太用力按下删除键。感谢指出,已经进行了编辑! - Bulan
3个回答

1

这是在设置中存储所选对象的正确方式吗?

可能不是。您的设置类不应以任何方式依赖于表单。如果您决定每次用户打开设置时动态创建和销毁表单,那么在这种情况下,您的设置将持有无效的对象引用。

我认为最好将对象列表与所选对象的索引一起存储在设置中。表单只需访问设置,填充列表框并在用户确认后修改所选对象索引。

您的代码中存在内存泄漏问题。您创建了一个本地变量TObjectList,但从未释放它。如果您释放本地变量,则列表框中的对象引用将无效。您有两个选择:

  • 将对象列表存储为表单的成员变量,在FromCreate事件处理程序中创建并在FormDestroy事件处理程序中销毁它。然后,您可以安全地在列表框中使用对象引用。

  • 将对象列表存储在外部某处,并将其作为Execute方法的参数传递到表单中。在这种情况下,您也可以安全地使用对象引用。


抱歉,在尝试简化代码时,我丢失了一些代码。实际上,我有一个属性来维护这个对象列表。帖子已经被编辑过了。 - Bulan
我以为通过进行Settings.Assign,可以跳过这个问题,这样Settings对象就与表单无关了。 但是,如果存储整个列表,那不是需要更多的内存吗? 无论如何,每次打开frmSettings时都需要更新列表,因为它从数据库获取值,并且这些值可能会发生变化。 - Bulan
对于我的评论有什么想法吗?因为我在TSettings中创建了一个新对象,所以它与表单无关。 - Bulan

0

关于TSettings的序列化怎么处理?将您的设置放在一些已发布的属性中,然后让RTTI保存其内容:

type
  TSettings = class(TPersistent)
  public
    function SaveAsText: UTF8String;
  end;

function TSettings.SaveAsText: UTF8String;
begin
var
  Stream1, Stream2: TMemoryStream;
begin
  Stream1 := TMemoryStream.Create;
  Stream2 := TMemoryStream.Create;
  try
    Stream1.WriteComponent(MyComponent);
    ObjectBinaryToText(Stream1, Stream2);
    SetString(result,PAnsiChar(Stream2.Memory),Stream2.Size);
  finally
    Stream1.Free;
    Stream2.Free;
  end;
end;

然后您的设置可以存储在文本文件或文本字符串中。

这只是一个解决方案。但将设置存储为文本非常方便。我们在框架中使用这种方法通过代码生成的用户界面存储设置。创建一个设置树,从TPersistent实例的树中创建。


这个问题实际上是关于:在设置中存储所选对象的正确方法是什么? 而不是如何将我的TSettings对象存储起来以备后用,但还是谢谢你分享那段代码,看起来很有用,我可能会在其他项目中使用它! - Bulan
@Bulan 是的,我想我理解了你的问题。但是使用这样的共享TObjectList似乎不如使用TPersistent层次结构好。使用列表,您只有一个属性的线性列表;使用TPersistent层次结构,您可以在类中嵌入类,然后将设置显示为树而不是列表,这对用户体验更好,我个人认为。文本存储只是其中的“额外奖励”部分。当然,我没有纠正您的代码问题,只是提出了一种架构变更建议。 - Arnaud Bouchez

0
我会将myObjectList重命名为GlobalObjectList,并将其移出类。它可以在表单中声明,但在初始化/终止部分创建/释放。在初始化期间,在创建列表后,从ini文件(或任何其他存储位置)填充它。现在,您可以从任何使用您的单元的地方访问它。

抱歉,在我试图简化代码时,有一些代码丢失了。实际上,我有一个属性来保存对象列表。帖子现在已经编辑过了。 - Bulan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接