Delphi firemonkey - 包含对另一个对象的引用的组件 - 创建一个对象实例

Delphi firemonkey - Component that holds a reference to another object - creating an object instance

我试图让 TEditDescendant 包含对另一个对象 (MyString) 的引用,这样当我编辑 TEdit 文本时,MyString 会更新,反之亦然。 这在 MyString 已经存在时有效。但是,如果我需要 MyEdit 才能创建 MyString,它就不起作用了。

下面的代码是一个最小的例子:

我正在使用Delphi东京。

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.Controls.Presentation, FMX.Edit, FMX.StdCtrls;


type
  tMyString= class(TObject)
  private
  fvalue:string;
    property value : string read fvalue write fvalue;
  end;


  TMyEdit = class(TEdit)
    constructor Create(AOwner: TComponent); override;
  private
    fMyString: tMyString;
    fOnChange : TNotifyEvent;
    procedure MyOnChange(Sender : TObject);
  public
    procedure setMyString(prop:tMyString);
    property MyProperty: tMyString read fMyString write setMyString;
    procedure load;
    property OnChange : TNotifyEvent read fOnChange write fOnChange;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  end;

var
  Form1: TForm1;
  Myedit1:TMyEdit;
  Mystring:TMyString;

implementation

{$R *.fmx}

constructor TMyEdit.Create(AOwner: TComponent);
begin
 inherited Create(AOwner);
 Inherited OnChange := MyOnChange;
end;

procedure TMyEdit.MyOnChange(Sender : TObject);
begin
  if (text<>'') and (fMyString=nil) then fMyString:=TMyString.Create;
  fMystring.value:=self.text;
end;

procedure TMyEdit.load;
begin
  if fMyString<>nil then
  text:=fMyString.value;
end;


procedure TMyEdit.setMyString(prop:tMyString);
begin
  fMyString:=prop;
  load;
end;

procedure TForm1.Button1Click(Sender: TObject);

begin
  Myedit1:=TMyEdit.Create(Form1);
  MyEdit1.Parent:=Form1;
  Myedit1.MyProperty:=MyString;
  MyEdit1.Text:='any text';
  Myedit1.MyOnChange(self);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  button2.Text:=Myedit1.myproperty.value;
  button2.Text:= Mystring.value;
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  mystring:=tmystring.Create;
  mystring.value:='123';
end;

end.

当您希望修改链接到您的对象的外部对象的数据时(您存储了对它的引用)我认为最好使用我命名为 "Property forwarding" 的方法。

现在基本上您在这种方法中所做的是在您的基础对象中定义一个 属性,它使用 getter 和 setter 方法来读取和写入数据。但是,不是像这些 getter 和 setter 方法那样从某些内部字段读取数据或将数据写入这些内部字段,而是您实际上定义 then 的方式是它们从某些外部字段读取数据或将数据写入某些外部字段通过直接访问该对象字段或使用外部对象自己的 属性 来访问其数据。我建议使用后者,因为这样可以更轻松地修改外部对象,而无需修改从该外部对象读取数据或向该外部对象写入数据的所有其他对象。

这是一个简短且注释良好的代码示例,它将向您展示这种方法的工作原理:

type
  //Our external object for storing some data
  TMyExternalObject = class (TObject)
  private
    //Field for storing some string value
    fStrValue: String;
  public
    //Property for accessing the value of fStrValue field
    property StrValue: String read fStrValue write fStrValue;
  end;

  //Out base object that will use external object for storing additionall data
  TMyBaseObject = class (TObject)
  private
    //Field for storing reference to our external object
    fMyExternalObject: TMyExternalObject;
  protected
    //Getter method that we will use to forward some data from our external object
    function GetMyExternalObjectStr: string;
    //Setter method that we will use to store some data into our external object
    procedure SetMyExternalObjectStr(AValue: String);
  public
    //Modified constructor which accepts additional optional parameter so that we can
    //set the reference to our external object upon creation of our base class
    //If this parameter is omitted when calling constructor the default value of nil will
    //be set to AMyExternalObject
    constructor Create(AMyExternalObject: TMyExternalObject = nil);
    //Property declaration that uses custom made getter and setter methods
    property ExternalObjectStr: String read GetMyExternalObjectStr write SetMyExternalObjectStr;
  end;

implementation

{ TMyBaseObject }

//Modified constructor which can set fMyExternalObject reference to the object that is passed
//as constructor parameter
constructor TMyBaseObject.Create(AMyExternalObject: TMyExternalObject);
begin
  inherited Create;
  //Set the reference of external object to the object that was passed as AMyExternalObject parameter
  //If parameter was omitted the default value of nil which was defined in constructor will be used
  fMyExternalObject := AMyExternalObject;
end;

//Getter method used to forward data from our external object
function TMyBaseObject.GetMyExternalObjectStr: string;
begin
  //Always check to se if fMyExternalObject reference is set to point to existing object otherwise you
  //will cause AccessVialation by trying to read data from nonexistent object
  if fMyExternalObject <> nil then
  begin
    //Simply assign the returned value from our external object property to the result of the method
    result := fMyExternalObject.StrValue;
  end
  else
  begin
    //If fmyExternalObject field does not reference to an existing object you will sill want to return
    //some predefined result. Not doing so could cause code optimizer to remove this entire method from
    //the code before compilation.
    //Delphi should warn you about possibility that function might not have a defined result
    result := 'No external object attached';
  end;
end;

//Setter method used to store some data to external object.
//This method also takes care of creating and linking the external object if one hasn't been linked already
procedure TMyBaseObject.SetMyExternalObjectStr(AValue: String);
begin
  //Check to see if fMyExternalObject already references to an existing external object.
  //If it does not create external object and set fMyExgternalObject to point to it
  if fMyExternalObject = nil then
  begin
    //Create the external object and set fMyExternalObject field to point to it
    fMyExternalObject := TMyExternalObject.Create;
  end;
  //Write our data to external object
  fMyExternalObject.StrValue := AValue;
end;

请注意此代码示例没有正确的错误检查(需要几个 try..except 块。我故意省略它们以使代码更具可读性。

我的代码也是为 类 而不是组件编写的。因此,您必须修改它才能与派生的 TEdit 组件一起使用。因此,您必须以不会隐藏 TEdit 构造函数默认参数的方式修改构造函数声明。

注意:虽然我的代码示例允许您让多个 TEdit 框读取和修改存储在外部对象中的相同字符串值,但它不会导致所有这些 TEdit 框在外部对象出现时自动更新它们的文本字符串值已更改。这样做的第三个原因是我的代码示例没有任何机制来通知其他 TEdit 框重新绘制它们自己并因此显示新的更新文本。

为此,您必须设计一种特殊的机制来通知所有 TEdit 组件,以便它们需要自行更新。这种机制还需要您的外部对象存储对链接到它的所有 TEdit 组件的引用。如果您决定实施此类系统,请特别注意,因为此类系统会导致循环引用,并可能阻止自动引用计数在不再需要时正确释放对象。
在这里阅读更多关于组件通知系统及其工作原理的内容可能还不错。为什么?因为组件通知系统的目的是提供这样的功能,允许您将某些事件通知多个组件。

警告:由于上面的代码示例是在必要时创建这些外部对象,因此您必须确保还有适当的代码来销毁那些已创建的外部对象,否则您可能会泄露它们。
现在,如果您只有一个 TEdit 框连接到这样的外部对象,您可以在 TEdit 析构函数中销毁它。但是,如果您计划将多个 TEdit 组件连接到同一个外部对象,您将不得不设计一些其他机制来跟踪这些外部对象的生命周期。

希望我的回答对您有所帮助。

无论如何,我建议您阅读更多关于使用 getter 和 setter 方法的信息。如果使用得当,它们会非常强大。

PS:这种方法并不新颖。我敢肯定它以前用过很多次。另外 "Property forwarding" 这个名字是我给它命名的。很可能它有一些我不知道的不同名称。