【问题标题】:How to correctly stream a TCollection property of a subcomponent, e.g. the Columns property of an embedded TDBGrid如何正确流式传输子组件的 TCollection 属性,例如嵌入式 TDBGrid 的 Columns 属性
【发布时间】:2016-11-12 13:57:55
【问题描述】:

我一直试图归结为一个 MCVE,一些代码是另一个 q 的作者发给我的 说明自定义组件的问题。

该组件只是一个包含嵌入式 TDBGrid 的 TPanel 后代。 我的源代码版本和测试项目如下。

问题在于,如果嵌入式 DBGrid 是使用持久列创建的, 在 IDE 中重新打开其测试项目时,会引发异常

读取 TColumn.Grid.Expanded 时出错。属性Grid不存在。

执行测试项目的Stream方法可以看出这个问题是怎么出现的:

为了比较,我的表单上还有一个普通的 TDBGrid,DBGrid1。而此 DBGrid1 的列流式传输为

Columns = <
  item
    Expanded = False
    FieldName = 'ID'
    Visible = True
  end
[...]

嵌入式网格的列像这样流式传输

Grid.Columns = <
  item
    Grid.Expanded = False
    Grid.FieldName = 'ID'
    Grid.Visible = True
  end
[...]

显然是Grid.ExpandedGrid 前缀和其他列属性导致了问题。

我认为问题与 DBGridColumns 的事实有关 是 TCollection 的后代,并且嵌入的网格不是顶级对象 DFM。

我的问题是:TMyPanel的代码应该如何修改,使得网格的 列是否正确流式传输?

组件来源:

unit MAGridu;

interface

uses
  Windows, SysUtils, Classes, Controls, ExtCtrls, DBGrids;

type
  TMyPanel = class(TPanel)
  private
    FGrid : TDBGrid;
  public
    constructor Create(AOwner : TComponent); override;
  published
    property Grid : TDBGrid read FGrid;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Standard', [TMyPanel]);
end;

constructor TMyPanel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FGrid := TDBGrid.Create(Self);
  FGrid.SetSubcomponent(True);
  FGrid.Parent := Self;
end;

end.

测试项目来源:

type
  TForm1 = class(TForm)
    DBGrid1: TDBGrid;
    CDS1: TClientDataSet;
    DataSource1: TDataSource;
    MyPanel1: TMyPanel;
    Memo1: TMemo;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    procedure Stream;
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  Stream;
end;

procedure TForm1.Stream;
//  This method is included as an easy way of getting at the contents of the project's
//  DFM.  It saves the form to a stream, and loads it into a memo on the form.
var
  SS : TStringStream;
  MS : TMemoryStream;
  Writer : TWriter;
begin
  SS := TStringStream.Create('');
  MS := TMemoryStream.Create;
  Writer := TWriter.Create(MS, 4096);

  try
    Writer.Root := Self;
    Writer.WriteSignature;
    Writer.WriteComponent(Self);
    Writer.FlushBuffer;
    MS.Position := 0;
    ObjectBinaryToText(MS, SS);
    Memo1.Lines.Text := SS.DataString;
  finally
    Writer.Free;
    MS.Free;
    SS.Free;
  end;
end;
end.

procedure TForm1.FormCreate(Sender: TObject);
var
  Field : TField;
begin
  Field := TIntegerField.Create(Self);
  Field.FieldName := 'ID';
  Field.FieldKind := fkData;
  Field.DataSet := CDS1;

  Field := TStringField.Create(Self);
  Field.FieldName := 'Name';
  Field.Size := 20;
  Field.FieldKind := fkData;
  Field.DataSet := CDS1;

  CDS1.CreateDataSet;
  CDS1.InsertRecord([1, 'One']);

end;

end.

【问题讨论】:

  • 只是想知道:为什么将嵌入式网格的父级设置为底层 MyPanel 的父级?在 TMyPanel.Create 期间,网格是 MyPanel 的父级。不应该一直这样吗?
  • @UweRaabe:感谢您的关注。 “在 TMyPanel.Create 期间,网格是 MyPanel 的父级。”不确定我是否遵循,从 TMyPanel.Create 退出时,FGrid 的父级为 Nil(无论如何在 D7 中),因此如果没有 TMyPanel.SetParent 中的分配,则 Grid 是不可见的。
  • @MartynA:您是否打算让TDBGrid 始终成为您小组的子项?如果是这样,那么 Panel 的构造函数应该有一个 FGrid.Parent := Self; 语句,并且应该完全删除 SetParent() 覆盖。
  • 谢谢@RemyLebeau。我已经按照您的建议在我的代码和我的 q 中更新了 TMyPanel 构造函数。不幸的是,流式持久化 TColumns 的问题仍然存在。
  • 我真的很好奇您是否未能使用我的答案中的代码正确地流式传输子组件的集合属性。你能评论一下吗?

标签: delphi object-persistence


【解决方案1】:

似乎您对此无能为力。当您查看过程WriteCollectionProp(本地到TWriter.WriteProperties)时,您会看到FPropPath 在调用WriteCollection 之前被清除。

TDBGrid 或更好的TCustomDBGrid 的问题在于集合被标记为stored false 并且流被委托给DefineProperties,它使用TCustomDBGrid.WriteColumns 来完成工作。

检查那个方法发现,虽然它也调用了WriteCollection,但是FPropPath的内容之前并没有被清除。这是意料之中的,因为FPropPath 是一个私有字段。

它在标准用例中仍然有效的原因是在撰写本文时FPropPath 只是空的。

因为即使是 Delphi 10.1 Berlin 的行为也与 Delphi 7 相同,我建议仅连同此示例一起提交 QP 报告。

【讨论】:

  • 非常感谢。我一直在使用 D7 只是因为我不确定原始 q 的 OP 使用的是什么。我会检查它在西雅图的行为是否类似(柏林尚未安装),然后按照您的建议提交 QP 报告。
【解决方案2】:

该解决方案将涉及嵌入式网格,而不是拥有面板作为流根的表单,而是面板本身。这将防止网格的属性被“网格”限定,这实际上将消除列属性被错误地限定。也就是说,以下是错误行为的解决方法。

为实现上述目的,删除SetSubComponent调用,

constructor TMyPanel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FGrid := TDBGrid.Create(Self);
//  FGrid.SetSubcomponent(True);
  FGrid.Parent := Self;
end;

csSubComponent 样式被移除,现在网格根本没有流式传输。

然后覆盖GetChildren 让面板通过面板流式传输网格。 GetChildrendocumented 一样,用于确定保存(流式传输)控件的哪些子控件。由于我们只有一个控件(网格),我们不需要进行区分,而是可以调用继承的处理程序修改根。

type
  TMyPanel = class(TPanel)
  private
    FGrid : TDBGrid;
  public
    constructor Create(AOwner : TComponent); override;
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
  published
    property Grid : TDBGrid read FGrid;
  end;

...

procedure TMyPanel.GetChildren(Proc: TGetChildProc; Root: TComponent);
begin
  inherited GetChildren(Proc, Self);
end;


然后仍然解决子组件并发症。这里的复杂之处是在面板前面创建了第二个网格,它假定流属性。非常像this 未回答的问题。请注意,此问题与上面提供的解决方案无关。原始代码显示相同的问题。

阅读了上面提到的问题,this 一个,this 一个,this 一个,但仍然无法借助其中的代码、线索、建议来解决,我追踪了流媒体系统,并提出了我的解决方案,如下所示。

我并不是说它应该是这样的。这就是我如何让它发挥作用。主要修改是,子网格现在是可写的(这将需要在生产代码中设置一个设置器),网格的条件创建,以及面板的覆盖 GetChildOwner。下面是具有TMyPanel2 的整个单元(TMyPanel 无法做到...)。

unit TestPanel2;

interface

uses
  Windows, SysUtils, Classes, Controls, ExtCtrls, DBGrids;

type
  TMyPanel2 = class(TPanel)
  private
    FGrid : TDBGrid;
  protected
    function GetChildOwner: TComponent; override;
  public
    constructor Create(AOwner : TComponent); override;
    destructor Destroy; override;
    procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override;
  published
    property Grid : TDBGrid read FGrid write FGrid;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Test', [TMyPanel2]);
end;

constructor TMyPanel2.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  if not (csReading in AOwner.ComponentState) then begin
    FGrid := TDBGrid.Create(Self);
    FGrid.Name := 'InternalDBGrid';
    FGrid.Parent := Self;
  end else
    RegisterClass(TDBGrid);
end;

destructor TMyPanel2.Destroy;
begin
  FGrid.Free;
  inherited;
end;

function TMyPanel2.GetChildOwner: TComponent;
begin
  Result := Self;
end;

procedure TMyPanel2.GetChildren(Proc: TGetChildProc; Root: TComponent);
begin
  Proc(Grid);
end;

end.

【讨论】:

    猜你喜欢
    • 2022-12-23
    • 2016-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-06
    • 1970-01-01
    相关资源
    最近更新 更多