【问题标题】:Create and free FMX components inside a TPanel at runtime在运行时在 TPanel 内创建和释放 FMX 组件
【发布时间】:2017-03-24 10:27:02
【问题描述】:

以下错误仅在 MacOS 下测试时发生。在 Windows 下它似乎可以工作。我正在尝试将字符串的动态列表显示到 TPanel 中,每个在它自己的 TPanel 中。字符串列表保存在 TStringList 对象中,每次添加或删除新字符串时,我都会调用一个方法来通过清除存在的内容来更新显示,然后重新构建可视组件。更新面板显示方式如下:

procedure TTestsScreen.UpdatePanelDisplay3;
var
  i : Integer;
  myPanel : TPanel;
  myLabel : TLabel;
  myImage : TImage;
begin
  //dispose of existig entities
  for i := (Length(search_entities_labels) - 1) downto 0 do
  begin
    search_entities_labels[i].Free;
    search_entities_labels[i]:= nil;
  end;
  for i := (Length(search_entities_images) - 1) downto 0 do
  begin
    search_entities_images[i].Free;
    search_entities_images[i]:= nil;
  end;
  for i := (Length(search_entities_panels) - 1) downto 0 do
  begin
    search_entities_panels[i].Free;
    search_entities_panels[i]:= nil;
  end;
  //adjust dynamic arrays
  SetLength(search_entities_images,main_search_entities_string_list.Count);
  SetLength(search_entities_labels,main_search_entities_string_list.Count);
  SetLength(search_entities_panels,main_search_entities_string_list.Count);
  //rebuild
  for i := 0 to main_search_entities_string_list.Count-1 do
  begin
    myPanel:= TPanel.Create(Self);
    myPanel.Parent := Panel1;
    myPanel.Height:= 30;
    myPanel.Width:= Panel1.Width;
    myPanel.Position.X:= 0;
    myPanel.Position.Y:= i*30;
    search_entities_panels[i]:= myPanel;
    //
    myLabel:= TLabel.Create(Self);
    myLabel.Parent:= myPanel;
    myLabel.Text:= main_search_entities_string_list[i];
    myLabel.Width:= (3*myPanel.Width) / 4;//display left
    search_entities_labels[i]:= myLabel;
    //
    myImage:= TImage.Create(Self);
    myImage.Parent:= myPanel;
    myImage.Tag:= i;
    myImage.Width:= 15;
    myImage.Height:= 15;
    myImage.Position.X:= myPanel.Width - myImage.Width;//display right
    myImage.MultiResBitmap.Assign(AppResources.delete_white_image.MultiResBitmap);
    myImage.OnClick:= DeleteSearchEntity;
    search_entities_images[i]:= myImage;
  end;
  //
  Panel1.Height:= 30 * main_search_entities_string_list.Count;
end;

我正在向面板中添加新元素,如下所示:

procedure TTestsScreen.AddClick(Sender: TObject);
begin
  main_search_entities_string_list.Add(DateTimeToStr(now));
  UpdatePanelDisplay3;
end;

添加过程正常,没有错误。问题是当我尝试删除这样的元素时

procedure TTestsScreen.DeleteSearchEntity(Sender: TObject);
begin
  main_search_entities_string_list.Delete((Sender as TImage).Tag);
  UpdatePanelDisplay3;
end;

我随机得到

Runtime error 231 

Exception EAccessViolation in module TestApp at 00039F33.
Access violation at address 00E7BBBC, accessing address 00000158.
Runtime error   0 at 00039F33 

在我正在做的 FormCreate 事件中

procedure TTestsScreen.FormCreate(Sender: TObject);
begin
  SetLength(search_entities_labels,0);
  SetLength(search_entities_images,0);
  SetLength(search_entities_panels,0);
  main_search_entities_string_list:= TStringList.Create;
end;

数组被声明为

search_entities_labels : array of TLabel;
search_entities_images : array of TImage;
search_entities_panels : array of TPanel;

【问题讨论】:

  • 如果不使用大小数组,而是尝试使用泛型...例如 TObjectList,会怎样?这将大大简化代码。但我认为你真正的问题是你想从它的一个事件处理程序中释放一个视觉组件......这是一个禁忌。 OnClick 事件处理程序是破坏事件发送者的一个非常错误的地方。
  • @Frazz,刚刚尝试将 UpdatePanelDisplay3 推迟到手动触发的事件(在从 TStringList 中删除之后)并且错误不再发生。自动进行视觉删除的最佳方法是什么?谢谢。
  • 最好的办法是使用事件处理程序将删除消息入队,然后在有时间的时候在UI线程中处理该消息。
  • @Frazz,你能把这个作为答案发布吗?

标签: delphi firemonkey


【解决方案1】:

您不应该从组件本身调用的事件处理程序中释放组件。在这种情况下,您正在从其 OnClick 事件处理程序中释放一个 TImage。

您必须将此操作分为两个阶段。

第一阶段

在事件处理程序中,您可以将表单消息排入队列,或者只需将组件集(图像、标签和面板)标记为“待删除”。您可以通过多种方式做到这一点...通过保留要删除的组件列表...或包含它们的数组或 TObjectList 中的索引。

第二阶段

在表单中,当你有时间时,你最终释放了不需要的组件。您可以在收到排队消息时执行此操作(如果您选择了该技术),也可以使用 Application.OnIdle(如果您只是将它们标记为删除)。

进一步通知...我谈到了排队消息,恕我直言,这是最好的解决方案。请注意,标准 FireMonkey 消息不是以异步方式排队,而是立即以同步方式分派。所以他们无法解决你的问题......你必须使用 TThread.Queue 来摆脱困境。

【讨论】:

    【解决方案2】:

    当您创建这些控件时:

    myPanel:= TPanel.Create(Self)
    

    self 表示该表单拥有该控件,因此您不能对其进行 free 调用,因为此时父级(面板)不知道它已消失。

    您需要将其更改为面板。

    【讨论】:

    • 这不是真的。组件通知它的所有者它正在被销毁。释放拥有所有者的组件不是一个好习惯……但它应该可以工作。通常你设置 Owner 让它负责组件的生命周期。如果您打算自己做,您可以使用 nil 所有者创建新组件。但这只是最佳实践......不是必须做的。
    • 至少我知道它在 VCL 中可以正常工作。我不记得尝试过 FMX,但我认为它具有相同的行为,考虑到它们仍然来自 TComponent
    猜你喜欢
    • 2016-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-16
    • 1970-01-01
    • 2010-11-03
    • 1970-01-01
    相关资源
    最近更新 更多