Delphi firemonkey - 包含对另一个对象的引用的组件 - 创建一个对象实例
Delphi firemonkey - Component that holds a reference to another object - creating an object instance
我试图让 TEditDescendant 包含对另一个对象 (MyString) 的引用,这样当我编辑 TEdit 文本时,MyString 会更新,反之亦然。
这在 MyString 已经存在时有效。但是,如果我需要 MyEdit 才能创建 MyString,它就不起作用了。
下面的代码是一个最小的例子:
- btnCreateMyEdit 创建 myEdit 并给它一些文本。
btnCreateMyString 创建 myString 的实例 btnCheck 显示问题:
- 如果 btnCheck 在 myString 存在之后创建(即在单击 btnCreateMyString 之后),则没有问题。
- 如果 btnCheck 在 myString 存在之前创建(即在单击 btnCreateMyString 之前),则显示 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" 这个名字是我给它命名的。很可能它有一些我不知道的不同名称。
我试图让 TEditDescendant 包含对另一个对象 (MyString) 的引用,这样当我编辑 TEdit 文本时,MyString 会更新,反之亦然。 这在 MyString 已经存在时有效。但是,如果我需要 MyEdit 才能创建 MyString,它就不起作用了。
下面的代码是一个最小的例子:
- btnCreateMyEdit 创建 myEdit 并给它一些文本。
btnCreateMyString 创建 myString 的实例 btnCheck 显示问题:
- 如果 btnCheck 在 myString 存在之后创建(即在单击 btnCreateMyString 之后),则没有问题。
- 如果 btnCheck 在 myString 存在之前创建(即在单击 btnCreateMyString 之前),则显示 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" 这个名字是我给它命名的。很可能它有一些我不知道的不同名称。