[Android]TFrame create and delete

This is the forum for miscellaneous technical/programming questions.

Moderator: 2ffat

Lena
BCBJ Master
BCBJ Master
Posts: 688
Joined: Sun Feb 06, 2011 1:28 pm

[Android]TFrame create and delete

Post by Lena »

Hi.
In main form I want press button and create my TFrame. In this TFrame I want press button and remove this TFrame.
How to dynamically create and then delete TFrame in FMX C++?

P.S.
I found In Delphi how delete TFrame:

Code: Select all

Frame1.Parent := nil;
Frame1.DisposeOf;
Frame1 := nil; //it is necessary or not???
rlebeau
BCBJ Author
BCBJ Author
Posts: 1708
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA
Contact:

Re: [Android]TFrame create and delete

Post by rlebeau »

Lena wrote: In main form I want press button and create my TFrame. In this TFrame I want press button and remove this TFrame.
I would probably do something like this:

Code: Select all

void __fastcall TMyFrame::ButtonClick(TObject *Sender)
{
    TThread::ForceQueue(nullptr, [this](){ this->DisposeOf(); });
}
Lena wrote: How to dynamically create and then delete TFrame in FMX C++?

P.S.
I found In Delphi how delete TFrame:

Code: Select all

Frame1.Parent := nil;
Frame1.DisposeOf;
Frame1 := nil; //it is necessary or not???
Yes, you should null out any references you have to the dead Frame object (this is especially imporrtant if you target ARC platforms). In which case, I would probably do something more like this instead:

Code: Select all

class TMyFrame : public TFrame
{
public:
    ...
    TNotifyEvent OnButtonClick;
    ...
};

void __fastcall TMyFrame::ButtonClick(TObject *Sender)
{
    if (OnButtonClick)
        OnButtonClick(this);
}

...

void __fastcall TMainForm::CreateFrame()
{
    Frame = new TMyFrame(this);
    Frame->Parent = this;
    Frame->OnButtonClick = &FrameButtonClicked;
}

void __fastcall TMainForm::FrameButtonClicked(TObject *Sender)
{
    TThread::ForceQueue(nullptr, [Frame](){ Frame->DisposeOf(); });
    Frame = nullptr;
}
[code]

Or:

[code]
class TMyFrame : public TFrame
{
public:
    ...
    TNotifyEvent OnButtonClick;
    ...
};

void __fastcall TMyFrame::ButtonClick(TObject *Sender)
{
    if (OnButtonClick)
        OnButtonClick(this);
    TThread::ForceQueue(nullptr, [this](){ this->DisposeOf(); });
}

...

void __fastcall TMainForm::CreateFrame()
{
    Frame = new TMyFrame(this);
    Frame->Parent = this;
    Frame->OnButtonClick = &FrameButtonClicked;
}

void __fastcall TMainForm::FrameButtonClicked(TObject *Sender)
{
    Frame = nullptr;
}
Last edited by rlebeau on Tue Feb 13, 2018 12:19 pm, edited 1 time in total.
Remy Lebeau (TeamB)
Lebeau Software
Lena
BCBJ Master
BCBJ Master
Posts: 688
Joined: Sun Feb 06, 2011 1:28 pm

Re: [Android]TFrame create and delete

Post by Lena »

Thank You very much! I will try Your code.
Lena
BCBJ Master
BCBJ Master
Posts: 688
Joined: Sun Feb 06, 2011 1:28 pm

Re: [Android]TFrame create and delete

Post by Lena »

Sorry I can not apply your code. Where is my mistake?
I try in main Form:

Code: Select all

void __fastcall TForm1::FrameButtonClicked(TObject *Sender)
{
  TThread::ForceQueue(nullptr, [Frame1](){ Frame1->DisposeOf(); });
  Frame1 = nullptr;
}
[bccaarm Error] UnitDibocca.cpp(38): no member named 'ForceQueue' in 'System::Classes::TThread'
[bccaarm Error] UnitDibocca.cpp(38): 'Frame1' cannot be captured because it does not have automatic storage duration
UnitFrameMessage.h(31): 'Frame1' declared here
rlebeau
BCBJ Author
BCBJ Author
Posts: 1708
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA
Contact:

Re: [Android]TFrame create and delete

Post by rlebeau »

Lena wrote: [bccaarm Error] UnitDibocca.cpp(38): no member named 'ForceQueue' in 'System::Classes::TThread'
TThread::ForceQueue() was added in 10.2 Tokyo. If you are using an older version, you will have to use a worker thread to call TThread::Queue() instead:

Code: Select all

void __fastcall TForm1::FrameButtonClicked(TObject *Sender)
{
    //TThread::ForceQueue(nullptr, [Frame1](){ Frame1->DisposeOf(); });
    TThread::CreateAnonymousThread(
        [Frame1](){
            TThread::Queue(nullptr, [Frame1](){ Frame1->DisposeOf(); });
        }
    )->Start();
    Frame1 = nullptr;
}
Lena wrote: [bccaarm Error] UnitDibocca.cpp(38): 'Frame1' cannot be captured because it does not have automatic storage duration
Hmm... I'm not sure what to make about that one. My lambda kung-fu is not so good.

Frame1 is an object pointer, so a lambda should be able to capture it by value. On the other hand, object pointers in Android are ARC-enabled, so that is probably interfering with the capture. Or, it could just be the fact that Frame1 is not a local variable to FrameButtonClicked() to begin with, so it doesn't need to be captured at all.

Try this:

Code: Select all

void __fastcall TForm1::FrameButtonClicked(TObject *Sender)
{
    TFrame *frm = Frame1;
    //TThread::ForceQueue(nullptr, [frm](){ frm->DisposeOf(); });
    TThread::CreateAnonymousThread(
        [frm](){
            TThread::Queue(nullptr, [frm](){ frm->DisposeOf(); });
        }
    )->Start();
    Frame1 = nullptr;
}
Or this:

Code: Select all

void __fastcall TForm1::FrameButtonClicked(TObject *Sender)
{
    //TThread::ForceQueue(nullptr, [](){ Frame1->DisposeOf(); });
    TThread::CreateAnonymousThread(
        [](){
            TThread::Queue(nullptr, [](){ Frame1->DisposeOf(); });
        }
    )->Start();
    Frame1 = nullptr;
}
Last edited by rlebeau on Thu Feb 15, 2018 8:11 pm, edited 2 times in total.
Remy Lebeau (TeamB)
Lebeau Software
Lena
BCBJ Master
BCBJ Master
Posts: 688
Joined: Sun Feb 06, 2011 1:28 pm

Re: [Android]TFrame create and delete

Post by Lena »

Thank Yoy rlebeau for your help!
Both versions compile successfully:

Code: Select all

void __fastcall TForm1::FrameButtonClicked(TObject *Sender)
{
    TFrame *frm = Frame1;
    //TThread::ForceQueue(nullptr, [frm](){ frm->DisposeOf(); });
    TThread::CreateAnonymousThread(
        [frm](){
            TThread::Queue(nullptr, [frm](){ frm->DisposeOf(); });
        }
    );
    Frame1 = nullptr;
}
And this:

Code: Select all

void __fastcall TForm1::FrameButtonClicked(TObject *Sender)
{
    //TThread::ForceQueue(nullptr, [](){ Frame1->DisposeOf(); });
    TThread::CreateAnonymousThread(
        [](){
            TThread::Queue(nullptr, [](){ Frame1->DisposeOf(); });
        }
    );
    Frame1 = nullptr;
}
I'm trying to move on. :)
Lena
BCBJ Master
BCBJ Master
Posts: 688
Joined: Sun Feb 06, 2011 1:28 pm

Re: [Android]TFrame create and delete

Post by Lena »

Does not work for me. :(

Code: Select all

//h frame
class TFrame1 : public TFrame
{
__published:	// IDE-managed Components
	TRectangle *Rectangle1;
	TLayout *Layout1;
	TRectangle *Rectangle2;
	TRectangle *Rectangle3;
	TLabel *Label1;
	TButton *Button1;
	void __fastcall Button1Click(TObject *Sender);
private:	// User declarations
public:		// User declarations
	__fastcall TFrame1(TComponent* Owner);
    TNotifyEvent OnButtonClick;
};
//---------------------------------------------------------------------------
extern PACKAGE TFrame1 *Frame1;

Code: Select all

//cpp frame
#include <fmx.h>
#pragma hdrstop

#include "UnitFrameMessage.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.fmx"
TFrame1 *Frame1;
//---------------------------------------------------------------------------
__fastcall TFrame1::TFrame1(TComponent* Owner)
	: TFrame(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TFrame1::Button1Click(TObject *Sender)
{
 if (OnButtonClick)
	OnButtonClick(this);
}
Main.

Code: Select all

//h main
class TForm1 : public TForm
{
//***
public:		// User declarations
	__fastcall TForm1(TComponent* Owner);
	void __fastcall FrameButtonClicked(TObject *Sender);

};

Code: Select all

//cpp main
void __fastcall TForm1::Button2Click(TObject *Sender)
{

	Frame1 = new TFrame1(this);
	Frame1->Parent = this;
	Frame1->Label1->Text = L"My first frame";
	Frame1->OnButtonClick = &FrameButtonClicked;
}

void __fastcall TForm1::FrameButtonClicked(TObject *Sender)
{
	//TThread::ForceQueue(nullptr, [](){ Frame1->DisposeOf(); });
    TThread::CreateAnonymousThread(
        [](){
            TThread::Queue(nullptr, [](){ Frame1->DisposeOf(); });
        }
    );
	Frame1 = nullptr;
}

I see my Frame. When I press Button1Click on my Frame nothing happens. The frame does not disappear. :(
rlebeau
BCBJ Author
BCBJ Author
Posts: 1708
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA
Contact:

Re: [Android]TFrame create and delete

Post by rlebeau »

Lena wrote: I see my Frame. When I press Button1Click on my Frame nothing happens. The frame does not disappear. :(
Make sure to call Start() on the TThread object that TThread::CreateAnonymousThread() returns. The thread is suspended by default. I forgot that in my earlier examples, but I have fixed it now.
Remy Lebeau (TeamB)
Lebeau Software
Lena
BCBJ Master
BCBJ Master
Posts: 688
Joined: Sun Feb 06, 2011 1:28 pm

Re: [Android]TFrame create and delete

Post by Lena »

Make sure to call Start() on the TThread object that TThread::CreateAnonymousThread() returns.
Thank You rlebeau!
Problem still exists: I see my Frame. When I press Button1Click on my Frame nothing happens. The frame does not disappear. Were my mistake?
h frame:

Code: Select all

class TFrame1 : public TFrame
{
__published:	// IDE-managed Components
//***
	void __fastcall Button1Click(TObject *Sender);
private:	// User declarations
public:		// User declarations
	__fastcall TFrame1(TComponent* Owner);
    TNotifyEvent OnButtonClick;
};
cpp frame:

Code: Select all

TFrame1 *Frame1;
//---------------------------------------------------------------------------
__fastcall TFrame1::TFrame1(TComponent* Owner)
	: TFrame(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TFrame1::Button1Click(TObject *Sender)
{
 if (OnButtonClick)
	OnButtonClick(this);
}
h main:

Code: Select all

class TForm1 : public TForm
{
//***
private:	// User declarations
public:		// User declarations
	__fastcall TForm1(TComponent* Owner);
	void __fastcall FrameButtonClicked(TObject *Sender);
};
cpp main:

Code: Select all

void __fastcall TForm1::Button2Click(TObject *Sender)
{

	Frame1 = new TFrame1(this);
	Frame1->Parent = this;
	Frame1->Label1->Text = L"My first frame";
    Frame1->OnButtonClick = &FrameButtonClicked;
}

void __fastcall TForm1::FrameButtonClicked(TObject *Sender)
{
	//TThread::ForceQueue(nullptr, [](){ Frame1->DisposeOf(); });
    TThread::CreateAnonymousThread(
        [](){
            TThread::Queue(nullptr, [](){ Frame1->DisposeOf(); });
        }
    )->Start();
    Frame1 = nullptr;
}
More questions :)
1. Why we use TThread for delete Frame?
2. Do we need FreeOnTerminate = true?
3. Frame1->DisposeOf() I read what could be better Frame1->Release()???
rlebeau
BCBJ Author
BCBJ Author
Posts: 1708
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA
Contact:

Re: [Android]TFrame create and delete

Post by rlebeau »

Lena wrote: Problem still exists: I see my Frame. When I press Button1Click on my Frame nothing happens. The frame does not disappear. Were my mistake?
Well, did you verify that the code is even TRYING to do something? Did you verify that Button1Click() is being called, and thus FrameButtonClicked() is being called? That TThread::Queue() is calling DisposeOf()? This is basic debugging stuff.
Lena wrote: 1. Why we use TThread for delete Frame?
You need to free the TFrame object in a delayed manner after its Button1Click() handler has exited first. To ensure that no code is still trying to access the TFrame object when/after it is destroyed. Since you are using a version of C++Builder that doesn't support TThread::ForceQueue(), we have a TThread::Queue() alternative, except that TThread::Queue() is not delayed when called in the main UI thread, so we call it in a worker thread instead. TThread::ForceQueue() would have avoided the need for that worker thread.
Lena wrote: 2. Do we need FreeOnTerminate = true?
TThread::CreateAnonymousThread() creates a TThread object that has FreeOnTerminate=true set.
Lena wrote: 3. Frame1->DisposeOf() I read what could be better Frame1->Release()???
Oh yeah, I keep forgetting that all TFmxObject-derived objects (like TFrame) have a Release() method in FMX (whereas only TForm has it in VCL). Well, in that case:

Code: Select all

void __fastcall TForm1::FrameButtonClicked(TObject *Sender)
{
    Frame1->Release();
    Frame1 = nullptr;
}
On the other hand, TFmxObject::Release() has been officially deprecated in 10.2 Tokyo, with no official word on what it should be replaced with. So, best to just design your code to account for that now, in case you decide to ever upgrade to Tokyo+ later. Of course, if you did upgrade, then you would have access to TThread::ForceQueue() and wouldn't have to worry about this threading issue anymore.
Remy Lebeau (TeamB)
Lebeau Software
Lena
BCBJ Master
BCBJ Master
Posts: 688
Joined: Sun Feb 06, 2011 1:28 pm

Re: [Android]TFrame create and delete

Post by Lena »

Thank You veru much! Now everything works well.

Code: Select all

void __fastcall TForm1::FrameButtonClicked(TObject *Sender)
{
    Frame1->Release();
    Frame1 = nullptr;
}
I want to replace all ShowMessage to show Frame because ShowMessage it does not work in FMX for FullScreen = true.
https://forums.embarcadero.com/thread.j ... 0&tstart=0

I use Indy. Can I creation TFrame in an event ThreadTerminated?

Code: Select all

void __fastcall TFormMenu::ThreadTerminated(TObject *Sender)
{

    Button2->Enabled = true;
	AniIndicator1->Visible = false;
	Close();

	TMyConnect *Thread = static_cast<TMyConnect*>(Sender);
	if(Thread->FatalException)//if setver OFF = Connect timed out.
	 {
	  chek = 1;

		  Frame1 = new TFrame1(this);
		  Frame1->Parent = this;
		  Frame1->Label1->Text = L"Sorry, there is no connection to the server.";
		  Frame1->OnButtonClick = &(Form1->FrameButtonClicked); //&FrameButtonClicked;
		  //ShowMessage(L"Sorry, there is no connection to the server.");
	 }

}
rlebeau
BCBJ Author
BCBJ Author
Posts: 1708
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA
Contact:

Re: [Android]TFrame create and delete

Post by rlebeau »

Lena wrote: I use Indy. Can I creation TFrame in an event ThreadTerminated?
The TThread::OnTerminate event is synchronized with the main UI thread by default. So yes, you can do whatever you want with your UI (you are already doing it with Button2 and AniIndicator1). That has nothing to do with Indy.

On the other hand, your OnTerminate handler is Close()'ing the same Form that you are setting as the Parent for your TFrame, so the user won't get a chance to see your error message. Maybe you meant to use Form1 as the Parent instead?
Remy Lebeau (TeamB)
Lebeau Software
Lena
BCBJ Master
BCBJ Master
Posts: 688
Joined: Sun Feb 06, 2011 1:28 pm

Re: [Android]TFrame create and delete

Post by Lena »

Maybe you meant to use Form1 as the Parent instead?
Yes you are right. It works now. Thank you very much.

Code: Select all

void __fastcall TFrame2::Button1Click(TObject *Sender)
{
 //Frame2->Release();
 //Frame2 = nullptr;
  if (OnButtonClick)
   OnButtonClick(this);
}
//---------------------------------------------------------------------------
void __fastcall TFrame2::ThreadTerminated(TObject *Sender)
{

    Button2->Enabled = true;
	AniIndicator1->Visible = false;

	//close Frame2 with Memo1
	Frame2->Release();
	Frame2 = nullptr;

	TMyConnect *Thread = static_cast<TMyConnect*>(Sender);
	if(Thread->FatalException)//if setver OFF=Connect timed out.
	 {
	  //show Frame1 with message
		  Frame1 = new TFrame1(Form1);
		  Frame1->Parent = Form1;
		  Frame1->Label1->Text = L"Sorry, there is no connection to the server.";
		  Frame1->OnButtonClick = &(Form1->FrameButtonClicked); 
		  //ShowMessage(L"Sorry, there is no connection to the server.");
	 }
    else
	   {
		Frame1 = new TFrame1(Form1);
		Frame1->Parent = Form1;
		Frame1->Label1->Text = L"Thank you.";
		Frame1->OnButtonClick = &(Form1->FrameButtonClicked);
        Form1->Button2Click(0);
	   }


}
ingalime
Top Poster
Top Poster
Posts: 29
Joined: Wed Apr 12, 2017 3:22 am

Re: [Android]TFrame create and delete

Post by ingalime »

I read this topic. Why can not you just delete TFrame?
It will be a critical mistake for android?

Code: Select all

void __fastcall TFrame1::Button1Click(TObject *Sender)
{
	Frame1->Release();
	Frame1 = nullptr;
}
Or should I must use always TNotifyEvent OnButtonClick for delete TFrame in FMX?

Code: Select all

void __fastcall TFrame1::Button1Click(TObject *Sender)
{
 if (OnButtonClick)
   OnButtonClick(this);
}
Another question. Do I need to do extra checks when delete TFrame?

Code: Select all

void __fastcall TForm1::FrameButtonClicked(TObject *Sender)
{
 if(Frame1 != NULL)//<--- ?
  {
	Frame1->Parent = NULL; //<--- ?
        Frame1->Release();
	Frame1 = nullptr;
  }
}
С++ Builder Berlin.
Thank you for your advice.
rlebeau
BCBJ Author
BCBJ Author
Posts: 1708
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA
Contact:

Re: [Android]TFrame create and delete

Post by rlebeau »

ingalime wrote:I read this topic. Why can not you just delete TFrame?
Because it is not safe to immediately 'delete' an object while you are inside an event handler that is owned by that same object. The RTL still needs access to the object after the event handler exits. So the 'delete' needs to be deferred to a later time when the object is not being accessed anymore.
ingalime wrote:

Code: Select all

void __fastcall TFrame1::Button1Click(TObject *Sender)
{
	Frame1->Release();
	Frame1 = nullptr;
}
If you read the discussion more carefully, you will see that 1) calling Release() is one of the proposed solutions, since Release() is a deferred 'delete', and 2) Release() is deprecated in 10.2 Tokyo.
ingalime wrote:Or should I must use always TNotifyEvent OnButtonClick for delete TFrame in FMX?
If you read the discussion more carefully, you will see that the OnButtonClick event was being used because the Frame1 object pointer is owned by another object (the MainForm), so while the TFrame1 object *could* Release() itself, the reference to the released TFrame1 object still needs to be nulled out. Letting the MainForm decide how to manage its TFrame1 object pointer is a better choice from a design perspective.
ingalime wrote:Another question. Do I need to do extra checks when delete TFrame?
Not in the example in this discussion, since OnButtonClick is fired by the TFrame1 object, so the MainForm's Frame1 object pointer should never be null when the OnButtonClick event is fired. And you don't need to set the Parent to null before calling Release().
Remy Lebeau (TeamB)
Lebeau Software
Post Reply