Introduction
One of the most difficult challenges for the component developer lies in creating
components that, like TPanel, allow other components to be dropped on them.
The reason this is difficult isn't because it is hard, but because it is a)
undocumented and b) relies on the largely undocumented VCL component streaming
system. This article will take care of (a) and some of (b); for more on (b)
see the article on Aggregate Streamable
Components.
The component described in this article is a simple one, which allows you to
drop other controls onto a scrollable panel. Almost all of the code supports
the ability of the component to save which controls belong in it. It is based
on the techniques shown in the now out of print "Developing Custom Delphi
3 Components" by Konopka.

Technique
The key thing to understand is how the streaming system needs to deal with
the dropped components.
The VCL system itself normally parents a component to the component on which
it is dropped. However, the owner of the component remains the form not the
parent. When the form is saved, it is converted to a DFM file format through
the process of streaming. A number of complex events occur during streaming,
but the most important thing to remember is that only those components owned
by the form are streamed into the DFM.
Unfortunately, with regard to the control we are developing, we are parenting
dropped components to a child of our component (the drop site). Keep in mind
that anything owned by the form can be deleted and can also be modified in the
object inspector. To prevent the drop site from being available for deletion
(which would be a catastrophe) we can't allow it to be owned by the form - it
has to be owned by our component. But since only components owned by the form
are streamed, the drop site will not be streamed, its children will, and the
resulting DFM will be corrupted.
Getting around the problem requires implementing the following functions...
-
DYNAMIC void __fastcall GetChildren(TGetChildProc theChildDecisionFunction,TComponent
*theRoot);
-
void __fastcall DefineProperties(TFiler *theFiler);
-
void __fastcall WriteChildComponentList(TWriter *theWriter);
-
void __fastcall ReadChildComponentList(TReader *theReader);
-
void __fastcall Loaded(void);
These functions are all overrides of functions provided by the streaming system
interface of TPersistent.
-
GetChildren() provides an opportunity for a component to identify components
which are its children byh any means desired. We use it to pretend that
the dropped controls are children of this component so that the streaming
system knows they should be "inside" it. We have to do that because
the drop site isn't visible to the streaming system.
-
DefineProperties() lets us stream out properties that are not published
- in this case, a list of the names of the components which are children
of the drop site. We can use that later when the component is streamed in,
to fix up the parentage of the child components to the drop site.
-
The WriteChildComponentList() is called by the DefineProperty() call in
DefineProperties(). It copies out the content of the list of dropped component
names to the stream.
-
The WriteChildComponentList() is called by the DefineProperty() call in
DefineProperties(). It copies in the content of the list of dropped component
names from the stream.
- Loaded() is invoked after everything is streamed in and it looks at the
list of component names, finds the named components, and reparents them to
the drop site.
Let's look at the implementation in detail....
The Header
//---------------------------------------------------------------------------
#ifndef MCScrollPanelH
#define MCScrollPanelH
//---------------------------------------------------------------------------
#include <SysUtils.hpp>
#include <Controls.hpp>
#include <Classes.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>
//---------------------------------------------------------------------------
class PACKAGE TMCScrollPanel : public TPanel
{
private:
TStringList *myChildComponentList;
protected:
TPanel *myDropSite;
TScrollBar *myVerticalScrollBar;
void __fastcall VerticalScrollBarChange(TObject
*Sender);
int __fastcall GetPanelHeight(void){return
myDropSite->Height;};
void __fastcall SetPanelHeight(int thePanelHeight)
{
myVerticalScrollBar->Max
= thePanelHeight;
myVerticalScrollBar->PageSize
= Height;
myDropSite->Height
= thePanelHeight;
};
DYNAMIC void __fastcall GetChildren(TGetChildProc
theChildDecisionFunction,TComponent *theRoot);
void __fastcall DefineProperties(TFiler
*theFiler);
void __fastcall WriteChildComponentList(TWriter
*theWriter);
void __fastcall ReadChildComponentList(TReader
*theReader);
void __fastcall Loaded(void);
public:
__fastcall TMCScrollPanel(TComponent* Owner);
__fastcall ~TMCScrollPanel(void);
__published:
__property int PanelHeight = {read=GetPanelHeight,write=SetPanelHeight};
};
//---------------------------------------------------------------------------
#endif
The Implementation
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "MCScrollPanel.h"
#pragma package(smart_init)
//---------------------------------------------------------------------------
// ValidCtrCheck is used to assure that the components created do not have
// any pure virtual functions.
//
static inline void ValidCtrCheck(TMCScrollPanel *)
{
new TMCScrollPanel(NULL);
}
//---------------------------------------------------------------------------
void __fastcall TMCScrollPanel::VerticalScrollBarChange(TObject *Sender)
{
myDropSite->Top = -myVerticalScrollBar->Position;
}
//---------------------------------------------------------------------------
__fastcall TMCScrollPanel::TMCScrollPanel(TComponent* Owner)
: TPanel(Owner)
{
myChildComponentList = new TStringList;
Parent = dynamic_cast<TWinControl *>(Owner); // Dimensions
for subcomponents don't work correctly unless this is done
Left = 0;
Top = 0;
Width = 320;
Height = 240;
TabOrder = 0;
myVerticalScrollBar = new TScrollBar(this);
myVerticalScrollBar->Parent = this;
myVerticalScrollBar->Kind = sbVertical;
myVerticalScrollBar->Align = alRight;
myVerticalScrollBar->OnChange = VerticalScrollBarChange;
myVerticalScrollBar->PageSize = Height;
myDropSite = new TPanel(this);
myDropSite->Parent = this;
myDropSite->Width = ClientWidth - myVerticalScrollBar->Width
- 1;
myDropSite->Height = 240;
myDropSite->Caption = "";
}
//---------------------------------------------------------------------------
__fastcall TMCScrollPanel::~TMCScrollPanel(void)
{
delete myChildComponentList;
}
//---------------------------------------------------------------------------
void __fastcall TMCScrollPanel::Loaded(void)
{
for (int Index = 0; Index < myChildComponentList->Count;
Index++)
{
TComponent *Child = GetParentForm(this)->FindComponent(myChildComponentList->Strings[Index]);
if (Child != NULL) ((TControl *) Child)->Parent
= myDropSite;
};
}
//---------------------------------------------------------------------------
void __fastcall TMCScrollPanel::GetChildren(TGetChildProc theChildDecisionFunction,
TComponent *theRoot)
{
TPanel::GetChildren(theChildDecisionFunction,theRoot);
TCustomForm *Form = GetParentForm(this);
for (int Index = 0; Index < myDropSite->ControlCount;
Index++)
{
TControl *Control = myDropSite->Controls[Index];
if (Control->Owner == Form) theChildDecisionFunction(Control);
};
}
//---------------------------------------------------------------------------
void __fastcall TMCScrollPanel::DefineProperties(TFiler *theFiler)
{
theFiler->DefineProperty("ChildComponentList",ReadChildComponentList,WriteChildComponentList,true);
}
//---------------------------------------------------------------------------
void __fastcall TMCScrollPanel::ReadChildComponentList(TReader *theReader)
{
myChildComponentList->Clear();
theReader->ReadListBegin();
while (!theReader->EndOfList())
{
myChildComponentList->Add(theReader->ReadIdent());
};
theReader->ReadListEnd();
}
//---------------------------------------------------------------------------
void __fastcall TMCScrollPanel::WriteChildComponentList(TWriter *theWriter)
{
theWriter->WriteListBegin();
for (int Index = 0; Index < myDropSite->ControlCount;
Index++)
{
theWriter->WriteIdent(myDropSite->Controls[Index]->Name);
};
theWriter->WriteListEnd();
}
//---------------------------------------------------------------------------
namespace Mcscrollpanel
{
void __fastcall PACKAGE Register()
{
TComponentClass classes[1] = {__classid(TMCScrollPanel)};
RegisterComponents("MCTestComponent", classes,
0);
}
}
//---------------------------------------------------------------------------
Conclusion
Follow the model above, and it's not that hard to make a "panel-like"
component. Enjoy it! Extend it!
|
|