【问题标题】:Delphi FMX: Saving and loading container childrenDelphi FMX:保存和加载容器子项
【发布时间】:2021-03-06 07:13:09
【问题描述】:

在设计时从这个布局开始。 (以TLayout、TGridPanelLayout、TText元素为例)

在运行时,我使用 ObjectBinaryToText 将完整的对象结构保存到文件中

但是当使用 ObjectTextToBinary 从文件中加载文件时,我得到了这个结果

为什么子控件没有采用之前保存的 exqct 布局? 文件结构似乎没问题,并且包含使用 IDE 保存表单时描述的所有子控件

这是一段演示问题的代码。

PAS 文件

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes,
  system.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, 
  FMX.Dialogs, FMX.Objects, FMX.Layouts, FMX.Controls.Presentation, 
  FMX.StdCtrls;

type
  TForm1 = class(TForm)
    RecTop: TRectangle;
    ButtonSave: TButton;
    ButtonClear: TButton;
    ButtonLoad: TButton;
    Layout1: TLayout;
    GridPanelLayout1: TGridPanelLayout;
    Text1: TText;
    Text2: TText;
    Text3: TText;
    Text4: TText;
    procedure ButtonSaveClick(Sender: TObject);
    procedure ButtonClearClick(Sender: TObject);
    procedure ButtonLoadClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
    AppPath: string;
    AppDatFile: String;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation
{$R *.fmx}

uses
  System.IOUtils;

procedure TForm1.ButtonSaveClick(Sender: TObject);
var
  FileStream : TFileStream;
  MemStream : TMemoryStream;
begin
  FileStream := TFileStream.Create(AppDatFile, fmCreate);
  try
    MemStream := TMemoryStream.Create;
    MemStream.WriteComponent(Layout1);
    MemStream.Position := 0;
    ObjectBinaryToText(MemStream, FileStream);
  finally
    MemStream.Free;
    FileStream.Free;
  end;
end;

procedure TForm1.ButtonClearClick(Sender: TObject);
var
  i: Integer;
begin
  for i := pred(Layout1.ChildrenCount) downto 0 do
    Layout1.Children[i].Free;
end;

procedure TForm1.ButtonLoadClick(Sender: TObject);
var
  FileStream : TFileStream;
  MemStream : TMemoryStream;
begin
  if FileExists(AppDatFile) then
  begin
    FileStream := TFileStream.Create(AppDatFile, fmOpenRead);
    try
      MemStream := TMemoryStream.Create;
      ObjectTextToBinary(FileStream, MemStream);
      MemStream.Position := 0;
      MemStream.ReadComponent(Layout1);
      Layout1.Align:= TAlignLayout.Client;
    finally
      MemStream.Free;
      FileStream.Free;
    end;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  AppPath:= TPath.GetLibraryPath;
  AppDatFile:= TPath.Combine(AppPath, 'SaveLoadLayout.dat');
end;

end

FMX 文件

  object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 480
  ClientWidth = 640
  FormFactor.Width = 320
  FormFactor.Height = 480
  FormFactor.Devices = [Desktop]
  OnCreate = FormCreate
  DesignerMasterStyle = 0
  object RecTop: TRectangle
    Align = Top
    Size.Width = 640.000000000000000000
    Size.Height = 41.000000000000000000
    Size.PlatformDefault = False
  end
  object ButtonSave: TButton
    Position.X = 8.000000000000000000
    Position.Y = 8.000000000000000000
    TabOrder = 3
    Text = 'Save'
    OnClick = ButtonSaveClick
  end
  object ButtonClear: TButton
    Position.X = 96.000000000000000000
    Position.Y = 8.000000000000000000
    TabOrder = 2
    Text = 'Clear'
    OnClick = ButtonClearClick
  end
  object ButtonLoad: TButton
    Position.X = 184.000000000000000000
    Position.Y = 8.000000000000000000
    TabOrder = 1
    Text = 'Load'
    OnClick = ButtonLoadClick
  end
  object Layout1: TLayout
    Align = Client
    Size.Width = 640.000000000000000000
    Size.Height = 439.000000000000000000
    Size.PlatformDefault = False
    TabOrder = 4
    object GridPanelLayout1: TGridPanelLayout
      Align = Client
      Size.Width = 640.000000000000000000
      Size.Height = 439.000000000000000000
      Size.PlatformDefault = False
      TabOrder = 0
      ColumnCollection = <
        item
          Value = 50.000000000000000000
        end
        item
          Value = 50.000000000000000000
        end>
      ControlCollection = <
        item
          Column = 0
          Control = Text1
          Row = 0
        end
        item
          Column = 1
          Control = Text2
          Row = 0
        end
        item
          Column = 0
          Control = Text3
          Row = 1
        end
        item
          Column = 1
          Control = Text4
          Row = 1
        end>
      RowCollection = <
        item
          Value = 50.000000000000000000
        end
        item
          Value = 50.000000000000000000
        end>
      object Text1: TText
        Align = Client
        Size.Width = 320.000000000000000000
        Size.Height = 219.500000000000000000
        Size.PlatformDefault = False
        Text = 'Text1'
      end
      object Text2: TText
        Align = Client
        Size.Width = 320.000000000000000000
        Size.Height = 219.500000000000000000
        Size.PlatformDefault = False
        Text = 'Text2'
      end
      object Text3: TText
        Align = Client
        Size.Width = 320.000000000000000000
        Size.Height = 219.500000000000000000
        Size.PlatformDefault = False
        Text = 'Text3'
      end
      object Text4: TText
        Align = Client
        Size.Width = 320.000000000000000000
        Size.Height = 219.500000000000000000
        Size.PlatformDefault = False
        Text = 'Text4'
      end
    end
  end
end

【问题讨论】:

  • 也许您会编辑您的问题以添加 .pas 和 .fmx 文件,以便有兴趣的人可以测试?
  • StackOverflow 的要求之一是问题必须是自包含的,即不允许链接到可能会失效的外部站点。因此,请编辑您的问题以包含 minimal reproducible example,包括 .fmx 和 .pas 文件。请专注于最小的问题,这仍然会重现问题。
  • 好吧,我尽力用截图来说明我的问题。大多数代码已经在那里理解我的问题。按照建议在问题中添加更多内容会使阅读变得更加困难
  • 当问题与 UI 布局有关时,提供屏幕截图是可以的,就像您的情况一样。两个屏幕截图就足够了:预期和失败。但是,屏幕截图不显示 UI 的结构,例如父子关系,尤其不是个人属性设置。您的问题可能与这些细节有关。根据您模糊的描述,我已经尝试过我的看法,而且效果很好。显然我无法重现您的设置。因此,提供minimal reproducible example 非常重要,具体包括.fmx 文件。
  • 感谢 .pas 和 .fmx 文件。我能够重现该问题。代码创建的数据文件并不完全正确:ControlCollection 具有“Control = Form1.Text1”项,而 .fmx 具有“Control = Text1”。当我手动修复数据文件以删除“Form1.”时,它可以正常工作。所以这是 WriteComponent 的罪魁祸首。它看起来很困惑,因为它不知道 Form1 并将其视为外部引用。 [还] 不知道如何在 WriteComponent 中解决这个问题。 [临时] 解决方案是在创建数据文件之后或加载之前对其进行修复。

标签: delphi firemonkey delphi-10.4-sydney


【解决方案1】:

正如我在评论中所说,问题在于 WriteComponent 错误地使用以下格式写入项目:

Control = Form1.Text1

这不正确,应该是

Control = Text1

这种行为可能是由于使用其他组件序列化一个组件,它们的所有者被一起保存。

解决方法是更正 WriteComponent 写入的内容。使用简单 ReplaceString 的简单实现如下:

procedure TForm1.ButtonSaveClick(Sender: TObject);
var
    StringStream : TStringStream;
    MemStream    : TMemoryStream;
    Buf          : String;
begin
    MemStream    := nil;
    StringStream := TStringStream.Create;
    try
        MemStream := TMemoryStream.Create;
        MemStream.WriteComponent(Layout1);
        MemStream.Position := 0;
        ObjectBinaryToText(MemStream, StringStream); 
        Buf := StringReplace(StringStream.DataString,
                             '    Control = ' + Self.Name + '.',
                             '    Control = ', [rfReplaceAll]);
        TFile.WriteAllText(AppDatFile, Buf);
    finally
        MemStream.Free;
        StringStream.Free;
    end;
end;

请注意,此解决方法实现适用于您的示例,但可能会造成混淆,因为搜索和替换不使用真正的解析器,并且可以替换具有相同形式的其他内容(例如字符串属性)。

【讨论】:

  • 要更挑剔一点,可以在要替换的字符串和要替换的字符串前面添加空格等于空格'='。
  • @Brian 用这个想法编辑了我的答案。谢谢。
猜你喜欢
  • 2021-06-07
  • 2021-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多