Board index » cppbuilder » streaming on the fly child components

streaming on the fly child components


2003-07-18 11:09:07 PM
cppbuilder104
Hi All,
Does anyone know how to make a form remember the creation of a child
component in the constructor, or any other function for that matter? The
only hook to the child would be through
TForm->Components[Index];
There are no members storing a pointer. I have used the following code to
remember form states, but it will not work to remember components created on
the fly.
Thanks, Brady
int __fastcall SaveComponent(AnsiString filename, TComponent* Component)
{
assert(Component != NULL);
assert(Component->Owner != NULL);
std::auto_ptr<TFileStream>fs(
new TFileStream(filename, fmCreate)
);
std::auto_ptr<TWriter>Writer(
new TWriter(fs.get(), 4096)
);
// specify the Root component
Writer->Root = Component->Owner;
// write the component
Writer->WriteComponent(Component);
// return the number of bytes written
return Writer->Position;
}
//--------------------------------------------------------------------------
-
int __fastcall LoadComponent(AnsiString filename, TComponent*& Component)
{
assert(Component != NULL);
assert(Component->Owner != NULL);
std::auto_ptr<TFileStream>fs(
new TFileStream(filename, fmOpenRead)
);
std::auto_ptr<TReader>Reader(
new TReader(fs.get(), 4096)
);
// set Root, Owner, and Parent
Reader->Root = Component->Owner;
Reader->Owner = Component->Owner;
TControl* Control =
dynamic_cast<TControl*>(Component);
if (Control) {
Reader->Parent = Control->Parent;
}
// remove the existing component
delete Component; Component = NULL;
// load the stored component
Reader->BeginReferences();
try {
Component =
Reader->ReadComponent(NULL);
}
__finally {
Reader->FixupReferences();
Reader->EndReferences();
}
// return the number of bytes read
return Reader->Position;
}
 
 

Re:streaming on the fly child components

Hi Brady,
Quote
I have used the following code to remember form states, but it
will not work to remember components created on the fly.
[snip]
Are you calling the SaveComponent() and LoadComponent() functions to
save an entire form? I'm not sure this will work as expected because
the Owner of a design-time form is traditonally the Application,
whereas the controls on that form are owned by the form itself. I
mentioned in the Saving and loading components article
(tinyurl.com/hggd) that the component and its sub-components
must share the same Owner. If you're creating these sub-components at
run-time, you'll therefore have to pass to their constructors the same
Owner as the form; e.g.,
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner), MyButton = new TButton(Owner) // <-- here
{
}
This should work for run-time-created controls, but I don't think the
published properties of the form's design-time-created controls will
be stored correctly. Specifically, design-time-created are owned by
their parent form, whereas the form is owned by the Application.
You might consider placing a TPanel object on your form (no border,
aligned alClient), and then make all controls children of the panel.
This way, you can simply call LoadComponent() and SaveComponent() on
the panel. The panel and all of its design-time child controls will,
by default, share the same Owner (their parent form), and you can
simply specify this same parent form in the constructor when creating
controls at run-time, which you'd do like so...
MyButton = new TButton(Form1);
MyButton->Parent = Panel1;
You'd then use LoadComponent() and SaveComponent() with Panel1.
Granted, you'll have to manually store the form's settings (because
you're streaming the panel, not the form), but there usually aren't
too many properties that need to be persistent.
Good luck,
--
Damon (TeamB)
BCBCAQ - bcbcaq.bytamin-c.com
 

Re:streaming on the fly child components

Quote
>How it can be done on the fly with creating new TTabSheets with
>the same components on them was demonstrated here:
It shouldn't be too hard to modify the functions for use in copying a
component...
Here's some rough code which seems to work for me (very minimally tested;
needs more error-handling)...
TWinControl* CopyWinControl(
TWinControl& WinControl
)
{
if (WinControl.Owner == NULL)
{
throw Exception("CopyWinControl()--Invalid WinControl::Owner.");
}
TWinControl* WinControlCopy = NULL;
std::auto_ptr<TStringList>Names(new TStringList());
RemoveNames(WinControl, *Names.get());
try
{
// register the owned components with the streaming system
RegisterOwnedComponents(*WinControl.Owner);
// create a memory stream to hold the stored data
std::auto_ptr<TMemoryStream>ms(new TMemoryStream());
// write the component to the stream...
{
std::auto_ptr<TWriter>Writer(new TWriter(ms.get(), 4096));
Writer->Root = WinControl.Owner;
Writer->WriteComponent(&WinControl);
}
// rewind the stream
ms->Seek(0, soFromBeginning);
// read the component from the stream...
{
std::auto_ptr<TReader>Reader(new TReader(ms.get(), 4096));
Reader->Root = WinControl.Owner;
Reader->Owner = WinControl.Owner;
Reader->Parent = WinControl.Parent;
Reader->BeginReferences();
try {
WinControlCopy = static_cast<TWinControl*>(
Reader->ReadComponent(NULL)
);
}
__finally {
Reader->FixupReferences();
Reader->EndReferences();
}
}
}
catch (...)
{
// restore the names
RestoreNames(WinControl, *Names.get(), true);
throw;
}
// restore the names
RestoreNames(WinControl, *Names.get(), true);
// return a pointer to the copy
return WinControlCopy;
}
The RemoveNames() and RestoreNames() functions are defined as follows...
void RemoveNames(
TWinControl& WinControl,
TStringList& OldNames
)
{
OldNames.Add(WinControl.Name);
WinControl.Name = "";
int const count = WinControl.ControlCount;
for (int index = 0; index < count; ++index)
{
TControl* ChildControl = WinControl.Controls[index];
if (ChildControl != NULL)
{
TWinControl* ChildWinControl =
dynamic_cast<TWinControl*>(ChildControl);
if (ChildWinControl != NULL)
{
RemoveNames(*ChildWinControl, OldNames);
}
else
{
OldNames.Add(ChildControl->Name);
ChildControl->Name = "";
}
}
}
}
void RestoreNames(
TWinControl& WinControl,
TStringList& Names,
bool reset = false
)
{
static int name_index = 0;
if (reset) {
name_index = 0;
}
WinControl.Name = Names.Strings[name_index++];
int const count = WinControl.ControlCount;
for (int index = 0; index < count; ++index)
{
TControl* ChildControl = WinControl.Controls[index];
if (ChildControl != NULL)
{
TWinControl* ChildWinControl =
dynamic_cast<TWinControl*>(ChildControl);
if (ChildWinControl != NULL)
{
RestoreNames(*ChildWinControl, Names);
}
else
{
ChildControl->Name = Names.Strings[name_index++];
}
}
}
}
To copy a TTabSheet object (e.g., TabSheet1), you'd do...
TTabSheet* TabSheetCopy =
static_cast<TTabSheet*>(CopyWinControl(*TabSheet1));
if (TabSheetCopy != NULL)
{
TabSheetCopy->PageControl = TabSheet1->PageControl;
TabSheetCopy->Caption = "TabSheetCopy";
}
For me, this technique works for both design-time- and run-time-created
controls. Well, at least for a TButton...
TButton* MyButton = new TButton(TabSheet1->Owner);
MyButton->Parent = TabSheet1;
MyButton->Caption = "MyButton";
MyButton->Name = "MyButton";
If you're still having trouble, I'd be happy to take a look at your code.
Alternatively, if you have the VCL sources, the TReader and TWriter
definitions in Classes.pas may shed some light.
Regards,
--
Damon (TeamB)
BCBCAQ - bcbcaq.bytamin-c.com
 

{smallsort}

Re:streaming on the fly child components

Thanks to both of you, Damon and Hans.
I have tried both methods -- Damon, I corrected the Owner property, and your
code worked for components created at runtime.
What I would really like, though, is to drag and drop components from a
"source" form to a "container" form, and for the container form to remember
what components were on it.
I modified Hans' code (from yoto yotov) -- and was able to do just that.
However, if the panels were the parent of another component, for example a
TCheckBox or TLabel, those child components would not get saved to the
stream.
Damon's code will not stream dragged components, perhaps because the owner
doesn't change, but the parent does. Hans' code, if recursive in nature
would do the trick, because it doesn't care about the owner. Since
Reader->ReadComponent is recursive and will stream in the last child
component, using this funciton would be ideal.
So, how do you set the read-only Owner property to the proper value?
Thanks, Brady
 

Re:streaming on the fly child components

Damon Chandler (TeamB) wrote:
Quote
Did you remember to assign the same
Owner to the run-time-created controls (i.e., so that NewControl->Owner ==
Panel1->Owner)?
Yes, I did. They were not saved as Brady had already reported.
Quote
... I specifically mentioned this in the
article. (The technique was primarily meant for use as demonstrated in the
"toolbar customization" article in the same issue.)
Brady had not told that the functions LoadComponent and SaveCOmponent
came from your article. I saw the article only later.
Quote
Did you remember to call the RegisterOwnedComponents() function as
mentioned in the article?
Then I had not read it, but I saw the solution with Yoto Yotov.
Quote
It shouldn't be too hard to modify the functions for use in copying a
component. Have you tried temporarily wiping/restoring the Name property
of the to-be-copied component
Think that it is easy now to use all this code. But that is
for Brady to be done.
Hans.
 

Re:streaming on the fly child components

Damon Chandler (TeamB) wrote:
Quote
Here's some rough code which seems to work for me (very minimally tested;
needs more error-handling)...
...
If you're still having trouble, I'd be happy to take a look at your code.
Alternatively, if you have the VCL sources, the TReader and TWriter
definitions in Classes.pas may shed some light.
Well, I was deeped a bit in it to help Brady. Now he can resolve
his problems I think.
Thanks for the code. If I am going to use it myself and encounter
problems I will be back.
Hans.
 

Re:streaming on the fly child components

Hans Galema wrote:
Quote
Think that it is easy now to use all this code. But that is
for Brady to be done.
Sorry, I thought you also had a question. I misread the line "How it can
be done on the fly..." as "How can it be done on the fly..."
--
Damon (TeamB)
BCBCAQ - bcbcaq.bytamin-c.com
 

Re:streaming on the fly child components

Brady,
Quote
Damon's code will not stream dragged components, perhaps because the
owner doesn't change, but the parent does. [...]
So, how do you set the read-only Owner property to the proper value?
As Fishface mentioned, you can use the TComponent::InsertComponent() and
RemoveComponent() methods to change a component's Owner. However, if
you're dragging-and-dropping a control that contains other child controls,
you may have to call these methods recursively. In the end, it might just
be easier to modify Yoto's code to operate in a recursive fashion.
Good luck,
--
Damon (TeamB)
BCBCAQ - bcbcaq.bytamin-c.com
 

Re:streaming on the fly child components

Hans Galema wrote:
Quote
Well, I was deeped a bit in it to help Brady. Now he can resolve
his problems I think.
Sorry, as I mentioned in my other reply, I mistook your reply as a
question.
Quote
Thanks for the code. If I am going to use it myself and encounter
problems I will be back.
Well, I'm not sure if I can be of much help. I'm currently running into
EAbstractErrors myself.
--
Damon (TeamB)
BCBCAQ - bcbcaq.bytamin-c.com
 

Re:streaming on the fly child components

Thanks to Damon, Hans and Fishface.
I'm still working on this -- recursion is the only way out, I think, as
Damon mentioned.
I'll post the code if I get it to work.
Brady
"Damon Chandler (TeamB)" < XXXX@XXXXX.COM >wrote in message
Quote
Hans Galema wrote:
>Think that it is easy now to use all this code. But that is
>for Brady to be done.

Sorry, I thought you also had a question. I misread the line "How it can
be done on the fly..." as "How can it be done on the fly..."

--
Damon (TeamB)
BCBCAQ - bcbcaq.bytamin-c.com
 

Re:streaming on the fly child components

Damon -- WOW!
That example is exactly what I was working toward. I've got to tell you, I
am so unfamiliar with the whole component streaming system, that even if I
could have gotten this to work, it would have taken me a month. I had
resigned myself to a cheezy workaround, and that too, was giving me trouble.
Thank you -- and best of luck with your research.
Brady
"Damon Chandler (TeamB)" < XXXX@XXXXX.COM >wrote in message
Quote
Brady,
>I'm still working on this -- recursion is the only way out,
>I think, as Damon mentioned.

If you're interested, I've created a project that demonstrates
dragging-and-dropping components from one form onto a TScrollBox of
another
form; the latter form can save and restore the TScrollBox...

people.psych.cornell.edu/~dmc27/CopyComponent.zip

The meat of the work is done in a unit called "comp_utils," which has
functions for saving, loading, copying, and changing the owner of a
component (and all of its children, for TWinControls). It also contains a
"cracker" class that provides access to common properties of TControl.

HTH,
--
Damon (TeamB)
BCBCAQ - bcbcaq.bytamin-c.com