1
votes

I have a very simple Firemonkey project (RadStudio 10.3.3) that I am building to test certain layout options for a future project. In the past with VCL, I used modal forms. The project I am testing uses panels (Panel1 and Panel2) on the main form (Form1) to embed two additional forms (Form2 and Form3). The two embedded forms consist of a single listbox (ListBox1) on each form. The panels on the main form overlay, so I use the Visibility property to show the embedded form that I want. All the code is on the main form.

The issue I have is that when I switch between Form2 and Form3, the strings loaded into the listbox on Form3 never appear. I have tried Repaint on the listbox and panel, InvalidateRect on the listbox, SetFocus on the panel, etc., all followed by Application.ProcessMessages. Nothing works successfully.

The main code is:

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.StdCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Button1: TButton;
    Panel2: TPanel;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
    procedure EmbedForm(AParent:TControl; AForm:TCustomForm);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

uses Unit2, Unit3;

procedure TForm1.FormCreate(Sender: TObject);
begin
  // Embed Form2 in Panel1
  Application.CreateForm(TForm2, Form2);
  EmbedForm(Panel1, Form2);
  Panel1.Visible := true;

  // Embed Form3 in Panel2
  Application.CreateForm(TForm3, Form3);
  EmbedForm(Panel2, Form3);
  Panel2.Visible := false;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  // Populate ListBox1 on Form2 - the LOAD button
  Form2.ListBox1.Items.Add('Hello');
  Form2.ListBox1.Items.Add('World');
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  // Hide Panel1 (Form2) and show Panel2 (Form3)
  Panel1.Visible := false;
  Panel2.Visible := true;
  // Populate ListBox1 on Form3
  Form3.ListBox1.Items.Add('Goodbye');
  Form3.ListBox1.Items.Add('World');
  // Repaint (Here's why I have tried various things to get the listbox strings to show up)
  //Panel2.Repaint;
  //Form3.ListBox1.Repaint;
  //Application.ProcessMessages;
end;

procedure TForm1.EmbedForm(AParent: TControl; AForm: TCustomForm);
begin
  while AForm.ChildrenCount>0 do
    AForm.Children[0].Parent:=AParent;
end;

end.

Form2 is as follows:

unit Unit2;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Layouts,
  FMX.ListBox;

type
  TForm2 = class(TForm)
    ListBox1: TListBox;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.fmx}

end.

Form3 is as follows:

unit Unit3;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Layouts,
  FMX.ListBox, FMX.Controls.Presentation, FMX.StdCtrls;

type
  TForm3 = class(TForm)
    ListBox1: TListBox;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form3: TForm3;

implementation

{$R *.fmx}

end.

The .fmx files are below, as requested.

Unit1.fmx (Form1):

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 480
  ClientWidth = 640
  FormFactor.Width = 320
  FormFactor.Height = 480
  FormFactor.Devices = [Desktop]
  OnCreate = FormCreate
  DesignerMasterStyle = 0
  object Button1: TButton
    Position.X = 232.000000000000000000
    Position.Y = 448.000000000000000000
    TabOrder = 1
    Text = 'Load'
    OnClick = Button1Click
  end
  object Button2: TButton
    Position.X = 328.000000000000000000
    Position.Y = 448.000000000000000000
    TabOrder = 2
    Text = 'Next'
    OnClick = Button2Click
  end
  object Panel1: TPanel
    Align = Center
    Size.Width = 640.000000000000000000
    Size.Height = 393.000000000000000000
    Size.PlatformDefault = False
    TabOrder = 0
  end
  object Panel2: TPanel
    Position.Y = 43.000000000000000000
    Size.Width = 640.000000000000000000
    Size.Height = 393.000000000000000000
    Size.PlatformDefault = False
    TabOrder = 4
  end
end

Unit2.fmx (Form2):

object Form2: TForm2
  Left = 0
  Top = 0
  Caption = 'Form2'
  ClientHeight = 480
  ClientWidth = 640
  FormFactor.Width = 320
  FormFactor.Height = 480
  FormFactor.Devices = [Desktop]
  DesignerMasterStyle = 0
  object ListBox1: TListBox
    Align = Center
    TabOrder = 0
    DisableFocusEffect = True
    DefaultItemStyles.ItemStyle = ''
    DefaultItemStyles.GroupHeaderStyle = ''
    DefaultItemStyles.GroupFooterStyle = ''
    Viewport.Width = 200.000000000000000000
    Viewport.Height = 200.000000000000000000
  end
end

Unit3.fmx (Form3):

object Form3: TForm3
  Left = 0
  Top = 0
  Caption = 'Form3'
  ClientHeight = 480
  ClientWidth = 640
  FormFactor.Width = 320
  FormFactor.Height = 480
  FormFactor.Devices = [Desktop]
  DesignerMasterStyle = 0
  object ListBox1: TListBox
    Position.X = 8.000000000000000000
    Position.Y = 8.000000000000000000
    TabOrder = 1
    DisableFocusEffect = True
    DefaultItemStyles.ItemStyle = ''
    DefaultItemStyles.GroupHeaderStyle = ''
    DefaultItemStyles.GroupFooterStyle = ''
    Viewport.Width = 196.000000000000000000
    Viewport.Height = 196.000000000000000000
  end
end

Again, Form2 and Form3 each only contain a listbox (Listbox1 on both) and no additional code. I simply run the executable, click Button1 to display Hello World, then click Button2 to switch panels and display the second form and its listbox. As I am new to Firemonkey, I am sure I am missing something simple. Thanks for any and all help!


The solution was very simple. I had to remove the CreateForm events for Form2 and Form3 from the project's initialization settings--a dumb mistake on my part. It was losing the reference to those forms during execution.

2
FWIW, it's much easier to put the embedded content inside of a TLayout on the source form, then just assign the parent of that layout.Jerry Dodge
@Jerry Thanks for the information. I will begin looking into the TLayout container. I used a TPanel because I was familiar with them in VCL.Ric Crooks
I deleted my answer for now, as you claim it can't be the case. However, since the child parent relations can not be seen from the code you have posted, I ask you to add the three .fmx files to your question. I also need to emphasize, that I cannot reproduce your problem if the panels are direct childs of Form1. It just works the way you intended.Tom Brunberg
@Tom - I have done as you've requested. As you can see, there is nothing involved with Unit2 (Form2) or Unit3 (Form3). Since you can't duplicate the problem, I will just build another test case from scratch and see if the problem exists. I have my main piece of software that also has this issue, which is why I built the simple test code to verify my issue. As I mentioned, I just updated to RadStudio 10.3.3, though I had the same issue in 10.3.2. Thank you for all your assistance!Ric Crooks
Thanks for the update. But, now you have, in Unit1.fmx a TPanel and as a child to that panel, a TLayout, which you don't have in Unit1.pas. Otoh, you don't have Panel2 at all in Unit1.fmx! So Unit1.fmx doesn't match Unit1.pas. If you have changed the second panel to a TLayout (based on Jerry's suggestion), and that panel was in the place of the current layout, then that is exactly what I said in my answer. That Panel2 was a child of Panel1 and not a child of Form1. Which gives exactly the behaviour you described. Do you agree?Tom Brunberg

2 Answers

1
votes

The only way I can reproduce the error is by setting the parent - child relation between the form and the two panels wrong.

E.g. I can reproduce the erroneous behaviour you describe, if Panel2 is a child of Panel1, but if they are both childs of the form, I can not reproduce the problem. Perhaps you dropped the second panel on Panel1 by mistake? Check the structure window in the IDE.


Edit after .fmx files were added to the question

Looking at the supplied Unit1.fmx which ends like this:

  object Panel1: TPanel
    Align = Center
    Size.Width = 640.000000000000000000
    Size.Height = 393.000000000000000000
    Size.PlatformDefault = False
    TabOrder = 0
    object Layout1: TLayout
      Align = Contents
      Size.Width = 640.000000000000000000
      Size.Height = 393.000000000000000000
      Size.PlatformDefault = False
      TabOrder = 0
    end
  end

There is no Panel2 as there should be according to Unit1.pas. Instead there is a Layout1 as child to Panel1. So, the now posted .fmx file doesn't match the .pas file you supplied yesterday. But it might still confirm what I already said.

If Panel2 has been substituted with this layout, and in other words Panel2 was a child to Panel1, then this would exactly explain the behaviour you asked about originally.

1
votes

Ok, thanks to Tom's frustration (again, I am sorry Tom!) and my own, I rebuilt the test application once again. This time, neither listbox would show the strings. I then began thinking about Form2 and Form3 losing their connection to Form1, so I looked into the .fmx files that Tom had asked about. That led me to looking into the Project initialization file, and voila!

program Project1;

uses
  System.StartUpCopy,
  FMX.Forms,
  Unit1 in 'Unit1.pas' {Form1},
  Unit2 in 'Unit2.pas' {Form2},
  Unit3 in 'Unit3.pas' {Form3};

{$R *.res}

begin
  Application.Initialize;
  Application.CreateForm(TForm1, Form1);
  //Application.CreateForm(TForm2, Form2);  <-- NOW COMMENTED OUT
  //Application.CreateForm(TForm3, Form3);  <-- NOW COMMENTED OUT
  Application.Run;
end.

The application was creating Form2 and Form2 on startup, and then I would create duplicates within the code, thus leading to a loss of reference. I simply commented out the Form2 and Form3 lines in startup to keep the application from creating them and it works as intended. Thanks, Tom!