【问题标题】:Delphi firemonkey - Component that holds a reference to another object - creating an object instanceDelphi firemonkey - 包含对另一个对象的引用的组件 - 创建对象实例
【发布时间】:2018-05-21 15:47:05
【问题描述】:

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

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

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

    • 如果 btnCheck 是在 myString 存在之后创建的(即单击 btnCreateMyString 之后),则没有问题。
    • 如果在 myString 存在之前(即单击 btnCreateMyString 之前)创建了 btnCheck,则表明未创建 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.

【问题讨论】:

  • 这和你上一个问题完全一样。相同的解决方案。删除MyString,或使其与其他引用保持同步。考虑以下代码,其中xy 的类型为Integer。如果我写x := 1,然后是y := x,然后是x := 2,你希望在y 中看到什么价值?
  • 我无法删除 myString。在我的真实代码中,它是另一个对象的属性。如何使用这种构造(或其他构造)来使用 tEdit 后代来更新另一个对象的属性?
  • 那么你需要保持两个实例同步。当你修改一个时,修改另一个。我已经在骗局中说过了。现在,你能回答我的问题吗? y 的值是 1 还是 2?
  • 我只是不知道该怎么做——我的想法是 TMyEdit 会自动更新与之关联的任何实例。对于许多 TMyString 属性,我将有几个 TMyEdits - 我想简单地将每个 TMyEdit 与一个属性相关联,并以此确保同步性以最小化代码。
  • ... 无论如何,如果问题被重新打开,我可以向 Op 提供所需的答案,我将在其中准确解释他如何实现他的目标。我已经多次尝试过 Op 的尝试,我什至决定将我的这种方法称为“财产转发”。

标签: delphi delphi-10.2-tokyo


【解决方案1】:

当您希望从链接到您的对象的外部对象中修改数据时(您存储了对它的引用),我认为最好使用我命名为“属性转发”的方法。

现在,您在这种方法中所做的基本上是在您的基础对象中定义一个属性,该属性使用 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:这种方法并不新鲜。我很确定它以前被使用过很多次。 “财产转发”这个名字也是我命名它的方式。很可能它有一些我不知道的不同名称。

【讨论】:

  • 谢谢。这行得通。我确实需要添加几个属性来获取对象,而不是字符串 - 所以除了 function GetMyExternalObjectStr: String; procedure SetMyExternalObjectStr(AValue: String); 我还有 function GetMyExternalObject: TMyExternalObject; procedure SetMyExternalObject(AValue: TMyExternalObject);
猜你喜欢
  • 1970-01-01
  • 2021-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-13
  • 1970-01-01
  • 2011-04-16
  • 1970-01-01
相关资源
最近更新 更多