Deleting an FMXobject inside its event handler

2019-03-04 17:48发布

问题:

I have the following components, tncrdragdata (tframedscrollbox) tdragdata (tgroupbox)

The main Idea is to combine them and use them as a list box (I need it this way).

The groupbox contains five tedit, one tcombobox and one tbutton.

The problem is when I try to free the tdragdata inside its event handler.

I use the FreeNotification method to relocate the group boxes in the framedscrollbox. The problem is that the overridden notification method is executed twice for some reason I don't know.

My question is: Why the overridden method is executed twice?

If I remove the condition (self.components[index]<>AComponent) in the relocate items method I get an AV. When I debugged this I noticed that the method is executed twice.

This is the code for the two components:

unit ncrdragdataunit;

interface

uses
  System.SysUtils, System.Classes, FMX.Layouts, FMX.Controls.Presentation,
  FMX.StdCtrls, system.Generics.collections, dragdataunit, FMX.objects, 
  system.types, FMX.graphics, FMX.dialogs, System.Messaging;

type
  Tncrdragdata = class(TFramedScrollBox)
    private
      { private declarations }
      Faddimage: timage;
      Fnextcoor: tpointf;
      Fitemcounter: integer;
      Fncrdata: tlist<tdragdata>;
      Flocate: boolean;
      function calculate_next_coor: tpointf;
      procedure additem(Aname: string);
      procedure relocate_items(AComponent: TComponent);
      procedure createaddimage(path: unicodestring);
      procedure clickaddimage(sender: tobject);
      procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    protected
      { protected declarations }
    public
      { public declarations }
      constructor Create(AOwner: TComponent); override;
      destructor Destroy; override;
      procedure extract_dragdata(var dragdata: tlist<tdragdatafields>);
    published
      { published declarations }
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('ncrcontrols', [Tncrdragdata]);
end;

{tncrdragdata}

constructor tncrdragdata.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
    {spesific data}
  Fncrdata: = tlist<tdragdata>.create;
  Flocate: = true;
  Fnextcoor.X: = 0;
  Fnextcoor.Y: = -60;
  Fitemcounter: = 0;
  if not(csDesigning in ComponentState) then
  begin
    createaddimage('C:\Users\nacereddine\Desktop\down-arrow-2.png');
    additem('item' + inttostr(Fitemcounter));
  end;
end;

destructor tncrdragdata.Destroy;
begin
  Flocate: = false;
  Faddimage.Free;
  Fncrdata.Free;
  inherited;
end;

function Tncrdragdata.calculate_next_coor: tpointf;
begin
  if(self.componentcount = 0) then
  begin
    result.x: = 20;
    result.y: = 20;
  end
  else
  begin
    result.x: = 20;
    result.y: = Fnextcoor.y + 80;
  end;
end;

procedure Tncrdragdata.additem(Aname: string);
var
  a: tdragdata;
begin
  Fnextcoor: = calculate_next_coor;
  a: = tdragdata.create(self);
  Fncrdata.Add(a);
  inc(Fitemcounter);
  with a do
  begin
    name: = Aname;
    text: = '';
    position.y: = Fnextcoor.y;
    position.x: = Fnextcoor.x;
    parent: = self; // parent name
    a.FreeNotification(self);           <---- this is the problem 
  end;
  Faddimage.Position.X: = Fnextcoor.x + 260;
  Faddimage.Position.y: = Fnextcoor.y + 60;
end;

procedure Tncrdragdata.relocate_items(AComponent: TComponent);
var
  index: Integer;
begin
  if self.componentcount<1 then exit;
  Fnextcoor.X: = 0;
  Fnextcoor.Y: = -60;
  for index: = 1 to self.componentCount-1 do
  begin
    if (self.components[index] is Tdragdata)and(self.components[index]<>AComponent) then
    begin
      Fnextcoor: = calculate_next_coor;
      (self.components[index] as Tdragdata).Position.Y: = Fnextcoor.y;
      (self.components[index] as Tdragdata).Position.x: = Fnextcoor.x;
    end;
  end;
  Faddimage.Position.X: = Fnextcoor.x + 260;
  Faddimage.Position.y: = Fnextcoor.y + 60;
end;

procedure Tncrdragdata.createaddimage(path: unicodestring);
begin
  Faddimage: = timage.Create(self);
  Faddimage.Parent: = self;
  Faddimage.Width: = 40;
  Faddimage.Height: = 40;
  Faddimage.Bitmap.LoadFromFile(path);
  Faddimage.onclick: = clickaddimage;
end;

procedure Tncrdragdata.clickaddimage(sender: tobject);
begin
  additem('item' + inttostr(Fitemcounter));
end;

procedure Tncrdragdata.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited;
  if (Operation = opRemove) and (AComponent is Tdragdata)and Flocate then
  begin
    relocate_items(AComponent);
    Fncrdata.remove(Tdragdata(AComponent));
  end;
end;

procedure Tncrdragdata.extract_dragdata(var dragdata: tlist<tdragdatafields>);
var
  I: Integer;
begin
  for I: = 0 to Fncrdata.Count-1 do
  begin
    dragdata.Add(Fncrdata.Items[I].dragdatafields);
  end;
end;

end.

unit dragdataunit;

interface

uses
  System.SysUtils, System.Classes, FMX.Types, FMX.Controls,
  FMX.Controls.Presentation, FMX.StdCtrls, FMX.listbox, FMX.edit, System.Messaging;

type
  tsectiontype = (ST_vertical, ST_curved, ST_straight);

  tdragdatafields = record
  TVD, MD, VS, Inc, Alfa30: single;
  sectiontype: tsectiontype;
  end;

  tdragdatafield = (df_TVD, df_MD, df_VS, df_Inc, df_Alfa30);

  tdragdata = class(tgroupbox)
    private
      (* private declarations *)
      Fdata: array[0..4] of single;
      OTVD, OMD, OVS, OInc, OAlfa30: tedit;
      Fsectiontype: tsectiontype;
      Osectiontype: tcombobox;
      headerlabel: tlabel;
      Odeletebtn: tbutton;
      procedure onchangevalue(sender: tobject);
      procedure ondeletebtnclick(sender: tobject);
      function getdata: tdragdatafields;
    protected
      (* protected declarations *)
    public
      (* public declarations *)
      constructor Create(AOwner: TComponent); override;
      destructor Destroy; override;

    published
      (* published declarations *)
      property dragdatafields: tdragdatafields read getdata;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('ncrcontrols', [Tdragdata]);
end;

{tdragdata}
constructor tdragdata.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
    {spesific data}
  SetBounds(10, 10, 550, 60);
  self.Text: = '';
  OTVD: = tedit.create(self);
  with OTVD do
  begin
    text: = '';
    SetBounds(10, 30, 80, 21);
    onchange: = onchangevalue;
    parent: = self;
  end;
  OMD: = tedit.create(self);
  with OMD do
  begin
    text: = '';
    SetBounds(100, 30, 80, 21);
    onchange: = onchangevalue;
    parent: = self;
  end;
  OVS: = tedit.create(self);
  with OVS do
  begin
    text: = '';
    SetBounds(190, 30, 80, 21);
    onchange: = onchangevalue;
    parent: = self;
  end;
  OInc: = tedit.create(self);
  with OInc do
  begin
    text: = '';
    SetBounds(280, 30, 80, 21);
    onchange: = onchangevalue;
    parent: = self;
  end;
  OAlfa30: = tedit.create(self);
  with OAlfa30 do
  begin
    text: = '';
    SetBounds(370, 30, 80, 21);
    onchange: = onchangevalue;
    parent: = self;
  end;
  Osectiontype: = tcombobox.create(self);
  with Osectiontype do
  begin
    SetBounds(460, 30, 80, 21);
    items.Add('STvertical');
    items.Add('STcurved');
    items.Add('STstraight');
    //Selected.Text: = 'STvertical';
    onchange: = onchangevalue;
    parent: = self;
  end;

  headerlabel: = tlabel.create(self);
  with headerlabel do
  begin
    text: = 'TVD (m)              MD (m)                VS (m)                '
         + 'Inc (°)                  Alfa (°/30m)         Section type';
    SetBounds(10, 9, 560, 21);
    parent: = self;
  end;
  Odeletebtn: = tbutton.create(self);
  with Odeletebtn do
  begin
    text: = '';
    SetBounds(537, 9, 10, 10);
    parent: = self;
    onclick: = ondeletebtnclick;
  end;

end;

destructor tdragdata.Destroy;
begin
  OTVD.free;
  OMD.free;
  OVS.free;
  OInc.free;
  OAlfa30.free;
  Osectiontype.free;
  headerlabel.free;
  Odeletebtn.Free;
  inherited;
end;

procedure tdragdata.onchangevalue(sender: tobject);

  function getvalue(st: tedit): single;
  begin
    try
      result: = strtofloat(st.Text);
    except
      result: = -1;
      st.Text: = '-1';
    end;
  end;

  function gettype(st: tcombobox): tsectiontype;
  begin
    if st.Selected.Text = 'STvertical' then result: = ST_vertical
    else if st.Selected.Text = 'STcurved' then result: = ST_vertical
    else if st.Selected.Text = 'STstraight' then result: = ST_vertical
    else begin result: = ST_vertical;  end;
  end;

begin
  if sender = OTVD then
  begin
    Fdata[ord(df_TVD)]: = getvalue(OTVD);
  end
  else
  begin
    if sender = OMD then
    begin
      Fdata[ord(df_MD)]: = getvalue(OMD);
    end
    else
    begin
      if sender = OVS then
      begin
        Fdata[ord(df_VS)]: = getvalue(OVS);
      end
      else
      begin
        if sender = OInc then
        begin
          Fdata[ord(df_Inc)]: = getvalue(OInc);
        end
        else
        begin
          if sender = OAlfa30 then
          begin
              Fdata[ord(df_Alfa30)]: = getvalue(OAlfa30);
          end
          else
          begin
            if sender = Osectiontype then
            begin
              Fsectiontype: = gettype(Osectiontype);
            end
            else
              Exception.Create('sender unknown');
            end;
          end;
        end;
      end;
    end;
  end;

function tdragdata.getdata: tdragdatafields;
begin
  result.TVD: = Fdata[ord(df_TVD)];
  result.MD: = Fdata[ord(df_MD)];
  result.VS: = Fdata[ord(df_VS)];
  result.Inc: = Fdata[ord(df_Inc)];
  result.Alfa30: = Fdata[ord(df_Alfa30)];
  result.sectiontype: = Fsectiontype;
end;

procedure tdragdata.ondeletebtnclick(sender: tobject);
begin
  self.Release;
end;

end.

回答1:

I found something interesting about FreeNotification() method here.

Use FreeNotification to register AComponent as a component that should be notified when the component is about to be destroyed. It is only necessary to register components this way when they are in a different form or have a different owner. For example, if AComponent is in another form and uses the component to implement a property, it must call FreeNotification so that its Notification method is called when the component is destroyed.

For components with the same owner, the Notification method is called automatically when an application explicitly frees the component. This notification is not sent out when components are freed implicitly, because the Owner is already being freed.

And then when I removed the line

a.FreeNotification(self);

In the method (the first component)

procedure Tncrdragdata.additem(Aname:string);

And the problem was gone.

I think the problem is that I was calling the FreeNotification() method with Tdragdata, not having a different owner. Clearly I was breaking a rule.

Thanks to @victoria and @CraigYoung for their help.