Delphi firemonkey - Компонент, который содержит ссылку на другой объект - создание экземпляра объекта - PullRequest
0 голосов
/ 21 мая 2018

Я пытаюсь создать TEditDescendant, который содержит ссылку на другой объект (MyString), поэтому при редактировании текста TEdit MyString обновляется и наоборот.Это работает, когда MyString уже существует.Однако, если мне нужно, чтобы MyEdit мог создавать MyString, он не работает.

Приведенный ниже код является минимальным примером:

  • btnCreateMyEdit создает myEdit и дает ему несколькоtext.
  • btnCreateMyString создает экземпляр myString. btnCheck показывает проблемы:

    • Если создан btnCheck ПОСЛЕ myString (то есть после нажатия 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 голосов
/ 21 мая 2018

Когда вы хотите изменить данные из внешнего объекта, который связан с вашим объектом (у вас есть ссылка на него), я думаю, что может быть лучше использовать подход, который я назвал «Переадресация свойства».

Теперь, в основном, при таком подходе вы определяете свойство в базовом объекте, которое использует как методы получения, так и установки для чтения и записи данных.Но вместо того, чтобы читать данные или записывать данные в какое-то внутреннее поле, как это обычно делают методы получения и установки, вы фактически определяете их таким образом, что вместо этого они читают данные или записывают данные в некоторый внешний объект, либо напрямую обращаясь к этому полю объектовили использовать собственное свойство внешнего объекта для доступа к его данным.Я предлагаю использовать последний, потому что таким образом вы упростите модификацию внешнего объекта без необходимости изменять любой другой объект, который считывает данные или записывает данные в этот внешний объект.

Здесь коротко и, надеюсь, хорошопример закомментированного кода, который покажет вам, как работает такой подход:

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 к одному внешнему объекту, вам придется разработать какой-то другой механизм для отслеживания жизни этих внешних объектов.

Надеюсь, мой ответ окажется для вас полезным.

В любом случае, я рекомендую вам прочитать больше об использовании методов получения и установки.Они могут быть довольно мощными при правильном использовании.

PS: Этот подход не новость.Я уверен, что он использовался много раз раньше.Также название «Пересылка собственности», как я его назвал.Вполне возможно, что у него другое имя, которое я не знаю.

...