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

时间:2018-05-21 15:47:06

标签: delphi delphi-10.2-tokyo

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

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

  • btnCreateMyEdit创建myEdit并为其提供一些文字。
  • btnCreateMyString创建myString实例btnCheck显示问题:

    • 如果在myString存在之后创建了btnCheck(即在点击btnCreateMyString之后),则没有问题。
    • 如果创建了btnCheck,则在myString存在之前(即在单击btnCreateMyString之前),它会显示未创建MyString。

我正在使用Delphi Tokyo。

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.

1 个答案:

答案 0 :(得分:1)

如果您希望修改链接到您的对象的外部对象的数据(您存储了对它的引用),我认为最好使用我命名的方法&#34; Property forwarding&#34;。

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

以下是简短且备受好评的代码示例,它将向您展示此方法的工作原理:

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:这种方法并不新鲜。我很确定以前曾多次使用它。还有名称&#34;物业转发&#34;就是我如何命名它。很可能它有一些我不知道的不同名称。