I'm trying to have a TEditDescendant that contains a reference to another object (MyString), so that when I edit the TEdit text, MyString gets updated and vice-versa. This works when MyString is already existing. However, if I need MyEdit to be able to create a MyString, it does not work.
The code below is a minimal example:
- btnCreateMyEdit creates the myEdit and gives it some text.
btnCreateMyString creates an instance of myString btnCheck shows the issues:
- If btnCheck is created AFTER myString exists (i.e. after clicking btnCreateMyString), no issue.
- If btnCheck is created Before myString exists (i.e. before clicking btnCreateMyString), it shows MyString was not created.
I'm using 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.
When you wish to modify data from an external object that is linked to your object (you have a reference to it stored) I think it might be best to use approach which I named "Property forwarding".
Now basically what you do in such approach is to define a property in your base object that is making use of both getter and setter methods for reading and writing data. But instead of reading data from or writing data to some internal field as these getter and setter methods most commonly doe you actually define then in a way that instead they are reading data from or writing data to some external object either by directly accessing that objects field or use external object own property for accessing its data. I suggest using the latter because this way you make it easier to modify the external object without the need to modify every other object that is reading data from or writing data to this external object.
Here is short and hopefully well commented code example that will show you how such approach works:
Note this code example does not have proper error checking (would need several try..except blocks there. I purposely omitted them in order to make code more readable.
Also my code is written to work with classes and not components. So you will have to modify it to work with your derived TEdit component. So you will have to modify the constructor declaration in a way that it won't hide default parameters of TEdit's constructor.
NOTE: While my code example will allow you to have multiple TEdit boxes reading and modifying the same string value that is stored in external object it will not cause all of those TEdit boxes to automatically update their text when the external objects string value is changed. Thre reason for this is that my code example does not have any mechanism for notifying the other TEdit boxes to redraw themselves and thus show the new updated text.
For this you will have to design a special mechanism which will notify all of the TEdit components so that they need to update themselves. Such mechanism would also require your external object to store the reference to all the TEdit components that are linking to it. If you decide to go and implement such system pay special attention because such system would be causing circular referencing and could prevent Automatic Reference Counting to properly free up the objects when there are no longer needed.
Here it might not be bad to go read some more about component notification system and how it works. Why? Because the purpose of component notification system is to provide such functionality that allows you to notify multiple component of some events.
WARNING: Since the above code example is creating these external objects when necessary you will have to make sure there is also proper code for destroying those created external objects otherwise you risk leaking them.
Now if you have just one TEdit box connecting to such external object you can destroy it in TEdit destructor. But if you plan on connection multiple TEdit components to same external object you will have to devise some other mechanism to track the life of these external objects.
I hope my answer will come helpful to you.
Any way I recommend you read more about using of getter and setter methods. They can be pretty powerful when used correctly.
PS: This approach is no novelty. I'm pretty sure it was used many times before. Also the name "Property forwarding" is how I named it. It is quite possible that it has some different name that I don't know off.