n the January 2004 issue, I wrote about
C++Builder property editors, and demonstrated how we can write our own property
editor in C++ (or Delphi) and then add them to the IDE. This time, I'll show how
we can enhance the design-time power of C++Builder even further by creating
component editors.
Component Editors
|
Figure A |
|

TDataSource component
editor. |
Component editors are like property
editors; they are used to enhance the integrated development environment of
Delphi and C++Builder. And, as with property editors, they are basically derived
from a single base class where some abstract methods need to be overridden and
redefined in order to give the component editor the desired behaviour. In
contrast to property editors however, component editors are component-specific,
not property-specific, entities. They are bound to a particular component type,
and they are generally executed by a click of the right mouse button on the
component (when dropped on a form). This way of activation is a bit different
than property editors, but other than that, the process of writing your own
component editor is essentially the same.
A component editor is created for
each component that is selected in the form designer based on the component's
type (see also
GetComponentEditor() and
RegisterComponentEditor() in
DesignIntf.pas).
When the component is double-clicked, the
Edit() method is called. When the
context menu for the component is invoked, the
GetVerbCount() and
GetVerb()
methods are called to build the menu. If one of the verbs is selected,
ExecuteVerb()
is called. Copy()
is called whenever the component is pasted to the clipboard. You only need to
create a component editor if you wish to add verbs to the context menu, change
the default double-click behaviour, or paste an additional clipboard format. The
interface definition for the
IComponentEditor interface
contains the six virtual
methods (Edit,
ExecuteVerb(),
GetVerb(),
GetVerbCount(),
PrepareItem(),
and Copy()),
and as with a property editor, we can override any of these six virtual methods
to build the special behavior inside our component editor.
Default Component Editor
Apart from the general
TComponentEditor type, there is a
default component editor—called
TDefaultEditor—that is
used by most components (unless another component editor is installed to
override the default one). The
TDefaultEditor class implements
Edit()
to search the properties of the component and to generate the (or navigate to an
already existing)
OnCreate,
OnChange or
OnClick
event (whichever it finds first), or the first alphabetic event handler that's
available.
Whenever the component editor
modifies the component it must call the
Modified() method of the IDE's designer
to inform the designer that (the component on) the form has been modified. If we
only use the component editor to display some information (like a general about
box, for example) there is no need to inform the designer.
Example:
TDataSourceComponentEditor
With this information at hand, we can build a useful
component editor. Consider the
TDataSource component, that lives
for one main purpose: to be connected to a
TDataSet (or
TDataSet-derived)
component and act as connector between this
TDataSet and other data-aware
components (including other
TDataSets).
Although we can use the Object
Inspector to assign a value to the
DataSet property, sometimes it's
quicker if you do not have to switch from your
form or data
module to the Object Inspector, but if you can simply assign a
value
|
Listing A:
DSCompEdit.h |
|
#ifndef DSCompEditH
#define DSCompEditH
#include "DesignIntf.hpp"
#include "VCLEditors.hpp"
class TDataSourceComponentEditor:
public TDefaultEditor
{
typedef TDefaultEditor inherited;
public:
virtual void __fastcall Edit(void);
virtual int __fastcall GetVerbCount(void);
virtual AnsiString __fastcall GetVerb(int
index);
virtual void __fastcall ExecuteVerb(int
index);
public:
#pragma option push -w-inl
inline __fastcall virtual
TDataSourceComponentEditor(
Classes::TComponent* AComponent,
_di_IDesigner ADesigner):
TDefaultEditor(AComponent, ADesigner) { }
#pragma option pop
public:
#pragma option push -w-inl
inline __fastcall virtual
~TDataSourceComponentEditor(void) { }
#pragma option pop
};
#endif // DSCompEditH |
to the
DataSet property by connecting it
to one of the available
TDataSets on the current
form or
data module.
This is the behaviour that I want to implement in my
TDataSource Component Editor
(called
TDataSourceComponentEditor).
First, we need to decide which
base class to use. For the
TDataSource component, it appears
that some (default) behavior is already implemented if you double-click on the
component, so the
TDefaultEditor is the best choice as parent class.
We then need to override and
implement a number of methods. Let's start with
GetVerbCount(), which returns the
number of menu entries that appear when we right-click on the
TDataSource
component. I want at to include one menu entry for an About box. And for every
TDataSet
(or derived) component I would like to have a dynamic menu entry that says
"Connect to ..." followed by the name of this
TDataSet component. This means
that we can simply right-click on a
TDataSource component and it will
show a "Connect to ..." option for all available datasets (which makes it quick
and easy to connect them to the
TDataSource). In order to find out how many datasets are available, we
need to look at the current form or data module. The component itself is owned
by this form or data module, so we only have to look at the owner of the current
component, and then walk through all components owned by the owner. This is
defined by the owner's
Components array, which holds
ComponentCount
components (counting from 0 to
ComponentCount minus 1).
Once we've defined how many menu
entries to show (returned by the
GetVerbCount()), it's not
difficult to return the individual menu entries (with
GetVerb()). Even the actual
ExecuteVerb()
is straightforward: just walk through the components of the owner, find the
right TDataSet
component (specified by the index number) and assign it to the
DataSet
property of the current
TDataSource component.
As final touch, I've also
overridden the Edit()
method in order to show an About box if you double-click on the
TDataSource
component (note that this would hide the default behaviour, so I've also called
the
TDefaultEditor::Edit() method to compensate for that). The
definition of the
TDataSourceComponentEditor, with the four virtual methods that
I've implemented, can be seen in Listing A.
The actual implementation of the
TDataSourceComponentEditor can be seen in
Listing B. Note that in order to
determine whether or not a component is a
TDataSet (or derived) component, I
use
dynamic_cast, which returns a valid pointer if the component
is a TDataSet
(or a derived type) but a
NULL pointer otherwise. This is an effective test that proves very
powerful, since it will, in one step, identify
TDataSet or derived classes (which
are the only classes that can be assigned to the
DataSet property of the
TDataSource
component).
|
Listing B:
DSCompEdit.cpp |
|
#include "DSCompEdit.h"
#include "DB.hpp"
#pragma package(smart_init)
void __fastcall
TDataSourceComponentEditor::Edit(void)
{
MessageDlg("TDataSourceComponentEditor",
mtInformation, TMsgDlgButtons() << mbOK, 0);
TDefaultEditor::Edit(); //
inherited
}
int __fastcall
TDataSourceComponentEditor::GetVerbCount(void)
{
TComponent* ComponentOwner = Component->Owner;
int DataSets = 1; //
first one for About...
if (ComponentOwner)
{
for (int i=0;
i < ComponentOwner->ComponentCount; i++)
if (dynamic_cast<TDataSet*>
(ComponentOwner->Components[i]))
DataSets++;
}
return DataSets;
}
AnsiString __fastcall
TDataSourceComponentEditor::GetVerb(int index)
{
if (index == 0) return
"&About
TDataSourceComponentEditor...";
else
{
TComponent* ComponentOwner = Component->Owner;
int DataSets = 0;
if (ComponentOwner)
{
for (int i=0;
i < ComponentOwner->ComponentCount; i++)
if (dynamic_cast<TDataSet*>
(ComponentOwner->Components[i]))
{
DataSets++;
if (DataSets == index)
return
"&Connect to " +
(ComponentOwner->Components[i])->Name;
}
}
}
}
void __fastcall TDataSourceComponentEditor::
ExecuteVerb(int index)
{
if (index == 0) Edit();
// about box
else
{
TComponent* ComponentOwner =
Component->Owner;
int DataSets = 0;
if (ComponentOwner)
{
for (int i=0; i < ComponentOwner->
ComponentCount; i++)
if (dynamic_cast<TDataSet*>
(ComponentOwner->Components[i]))
{
DataSets++;
if (DataSets == index)
{
(dynamic_cast<TDataSource*>(Component))
->DataSet = dynamic_cast<TDataSet*>
(ComponentOwner->Components[i]);
Designer->Modified();
}
}
}
}
}
namespace Dscompedit
{
void __fastcall PACKAGE Register()
{
RegisterComponentEditor(
__classid(TDataSource),
__classid(TDataSourceComponentEditor)
);
}
} |
Finally, notice the
Register()
method (which must be placed in the namespace that corresponds to the
file-name), where we call the
RegisterComponentEditor() method
to register the
TDataSourceComponentEditor for the
TDataSource component.
After you've installed this
component editor, for example in the
dclusr.dpk package (see the
article of January 2004 for more installation details), you can see the
component editor in action by right-clicking on the
TDataSource component. See, for
example, Figure A where I've used four
different dbExpress datasets to illustrate the behavior.
Although it doesn't save a whole
lot of time compared to moving to the Object Inspector, this component editor
enables you to extend the current functionality. Possible ideas that I've
considered with but never implemented are to append the name of the dataset with
additional information, like the number of records, whether the dataset is
currently open ("live") or closed, and—in case of the
TClientDataSet—if
the data are from a local or remote source. This would lead to functionality
that the Object Inspector can not easily offer, and is left as an exercise for
the reader (perhaps a topic for a follow-up article if you've enjoyed this
one—feel free to let me know).
Conclusion
In this article, I've demonstrated how we can build
component editors, by building a
TDataSource component editor that
can connect to any
TDataSet component on the same form or
data module. I hope to have shown that Property and Component
Editors make powerful additions to the C++Builder IDE, and they can increase the
RAD experience by automating or supporting tasks at design-time.
The full source for this article’s
example program can be downloaded from
http://www.bcbjournal.org.

|
Refer a
friend
and
receive 3 free months of the Journal! If your referral results in
a new 12-month subscription, we'll extend your subscription by three
additional months.
Contact
sales@bcbjournal.org for more information. |