📄 ch22.htm
字号:
TComponentClass classes[1]={__classid(TRadioButton)};
RegisterClasses(classes, 0);
}
void __fastcall TForm1::TestComponentsBtnClick(TObject *Sender)
{
TestComponentsBtn->Enabled = False;
TBigEdit *BigEdit = new TBigEdit(this);
BigEdit->Parent = this;
BigEdit->Show();
TEmptyPanel *E = new TEmptyPanel(this);
E->Parent = this;
E->Top = BigEdit->Height + 5;
E->Show();
TRadio2Panel *P = new TRadio2Panel(this);
P->Parent = this;
P->Top = E->Top + E->Height + 5;
P->Radio1->Caption = "As you from crimes would pardon'd be ";
P->Radio1->Width = Canvas->TextWidth(P->Radio1->Caption) + 20;
P->Radio2->Caption = "Let your indulgence set me free ";
P->Radio2->Width = Canvas->TextWidth(P->Radio2->Caption) + 20;
P->Width = max(P->Radio1->Width, P->Radio2->Width) + P->Radio1->Left + 20;
P->Show();
</FONT></PRE>
<P><FONT COLOR="#0066FF"><TT>}</TT></FONT>
<BR>
<BR>
You can find this program in the directory called <TT>TestUnleash2</TT> on the CD
that accompanies this book. Figure 22.4 shows the program in action.</P>
<P><A HREF="22ebu04.jpg" tppabs="http://pbs.mcp.com/ebooks/0672310228/art/22/22ebu04.jpg">Figure 22.4.</A><I>The TestUnleash2 program
demonstrates
how to test a series of components before placing them on the Component Palette.</I></P>
<P>After you've created a component that does something you like, you can easily
create children of it. Class <TT>TBigEdit</TT> descends from
<TT>TSmallEditTwo</TT>:</P>
<PRE><FONT COLOR="#0066FF">class TBigEdit : public TSmallEditTwo
{
public:
virtual __fastcall TBigEdit(TComponent* Owner)
:TSmallEditTwo(Owner) { Width = 250; Font->Size = 24; }
};
</FONT></PRE>
<P>It
inherits its font nearly unchanged from <TT>TSmallEditTwo</TT>, except that
it sets <TT>Font->Size</TT> to <TT>24</TT>, a hefty figure that helps the control
live up to its name. This elegant syntax is a good example of how OOP can save you
time
and trouble while still allowing you to write clear code.</P>
<P>The label controls shown in this code work in exactly the same way the edit controls
do, except that they descend from <TT>TLabel</TT> rather than from <TT>TEdit</TT>.</P>
<P>The
<TT>TEmptyPanel</TT> component rectifies one of the petty issues that sometimes
annoys me: Every time you put down a panel, it gets a caption. Most of the time,
the first thing you do is delete the caption so that you can place other controls
on it
without creating a mess!</P>
<P>At first, it would appear that you can change the caption of <TT>TPanel</TT> by
overriding its constructor. All you would need to do is set the <TT>Caption</TT>
property to an empty string:</P>
<PRE><FONT
COLOR="#0066FF">class TEmptyPanel : public TPanel
{
public:
virtual __fastcall TEmptyPanel(TComponent *Owner)
:TPanel(Owner) { Caption = ""; }
};
</FONT></PRE>
<P>This code should work correctly, but in the first version of
C++Builder the caption
does not get changed. There are two interesting workarounds for this problem that
illustrate interesting facts about the VCL. The first workaround is to override the
<TT>Loaded</TT> method:</P>
<PRE><FONT COLOR="#0066FF">void
__fastcall TEmptyPanel::Loaded(void)
{
TPanel::Loaded();
Caption = "";
}
</FONT></PRE>
<P>The code shown in this overridden <TT>Loaded</TT> method will change the caption
of the component at runtime, but not at design time. The
<TT>Loaded</TT> method is
called after a component has been loaded from a stream but before it is shown to
the user. The method exists so that you can set the value of properties that rely
on the value of other properties.</P>
<P>A second technique
involves overriding the <TT>SetParent</TT> method:</P>
<PRE><FONT COLOR="#0066FF">void __fastcall TEmptyPanel::SetParent(TWinControl *AParent)
{
TPanel::SetParent(AParent);
Caption = "";
}
</FONT></PRE>
<P>This will fix the problem
at both design time and at runtime.</P>
<P>In this example, I am not using <TT>SetParent</TT> for its intended purpose. To
understand the method's real purpose, you need to remember that a component is always
shown on the surface of its parent. The
primary purpose of the <TT>SetParent</TT>
method is to give you a chance to change the parent of a component just as it is
being made visible to the user. This allows you to store a component in a DFM file
with one parent, and to change that parent at
runtime. It's unlikely you will ever
need to change the parent of a component, but there are occasions when it is useful,
and so the VCL gives you that ability.</P>
<P>It is obviously useful to know that you can override the <TT>SetParent</TT> and
<TT>Loaded</TT> methods in order to change properties at two different stages in
the process of allocating memory for a component. You should note, however, that
in this case it is a bit strange that you have to do so, because merely overriding
the
constructor should have turned the trick.</P>
<P>The last new component in <TT>Unleash2.cpp</TT> enables you to drop down a panel
that comes equipped with two radio buttons. This way, you can make a single control
out of a set of components that are
often combined.</P>
<P>You could create other controls that contain three, four, or more radio buttons.
Or you could even create a panel that would populate itself with a specific number
of radio buttons.</P>
<P>The declaration for this new radio
button is fairly simple:</P>
<PRE><FONT COLOR="#0066FF">class TRadio2Panel : public TEmptyPanel
{
private:
TRadioButton *FRadio1;
TRadioButton *FRadio2;
public:
virtual __fastcall TRadio2Panel(TComponent *Owner);
__property TRadioButton
*Radio1={read=FRadio1 };
__property TRadioButton *Radio2={read=FRadio2 };
</FONT></PRE>
<PRE><FONT COLOR="#0066FF">};
</FONT></PRE>
<P>The actual radio buttons themselves are declared as private data, and access to
them is given by the
<TT>Radio1</TT> and <TT>Radio2</TT> properties.</P>
<P>Write access to the radio button properties is not needed because you can modify
without it. The following statement performs one read of <TT>RP->Radio1</TT> and
one write to the
<TT>Caption</TT> property of that radio button:</P>
<PRE><FONT COLOR="#0066FF">P->Radio1->Caption = "Control one";
</FONT></PRE>
<P>You don't want write access to the properties either because that would allow
the user to assign them
garbage (or <TT>NULL</TT>).</P>
<P>The constructor for the <TT>Radio2Panel</TT> begins by setting the width and height
of the panel:</P>
<PRE><FONT COLOR="#0066FF">__fastcall TRadio2Panel::TRadio2Panel(TComponent *Owner)
:TEmptyPanel(Owner)
{
Width = 175;
Height = 60;
FRadio1 = new TRadioButton(this);
FRadio1->Parent = this;
FRadio1->Caption = "Radio1";
FRadio1->Left = 20;
FRadio1->Top = 10;
FRadio1->Show();
FRadio2 = new TRadioButton(this);
FRadio2->Parent = this;
FRadio2->Caption = "Radio2";
FRadio2->Left = 20;
FRadio2->Top = 32;
FRadio2->Show();
}
</FONT></PRE>
<P>The next step is to create the first radio button. Notice that the code passes
<TT>this</TT> as the owner and sets the parent to the panel itself. The rest of the
code in the <TT>Create</TT> method is too trivial to merit comment.</P>
<P>Under some circumstances, you may need to register <TT>TRadioButton:</TT></P>
<PRE><FONT
COLOR="#0066FF">TComponentClass classes[1]={__classid(TRadioButton)};
RegisterClasses(classes, 0);
</FONT></PRE>
<P>This event would normally occur when you drop a component on a form. However,
in this case a <TT>TRadioButton</TT> is not necessarily
ever dropped explicitly on
a form. As a result, the safe thing to do is register the component, possibly in
the constructor for the class.</P>
<P>When you're ready to test the <TT>TRadio2Panel</TT> object, you can write the
following code in the
test-bed program to take it through its paces:</P>
<PRE><FONT COLOR="#0066FF">TRadio2Panel *P = new TRadio2Panel(this);
P->Parent = this;
P->Top = E->Top + E->Height + 5;
P->Radio1->Caption = "As you from crimes would
pardon'd be ";
P->Radio1->Width = Canvas->TextWidth(P->Radio1->Caption) + 20;
P->Radio2->Caption = "Let your indulgence set me free ";
P->Radio2->Width = Canvas->TextWidth(P->Radio2->Caption) + 20;
P->Width = max(P->Radio1->Width, P->Radio2->Width) + P->Radio1->Left + 20;
P->Show();
</FONT></PRE>
<P>Note that each radio button that belongs to the panel acts exactly as you would
expect a normal radio button to act,
except you have to qualify it differently before
you access it. I use the <TT>TextWidth</TT> property of <TT>Canvas</TT> to discover
the width needed for the string, and then add 20 to take into account the button
itself.
<DL>
<DD>
<HR>
<FONT
COLOR="#000077"><B>NOTE: </B></FONT>If you want, you can surface <TT>Radio1</TT>
and <TT>Radio2</TT> in the Object Inspecter as published properties of <TT>TRadio2Panel</TT>.
However, when you first do so, they will have no property editors
available because
BCB has no built-in property editors for <TT>TRadioButtons</TT>. To build your own,
you can refer to the <TT>DsgnIntf.pas</TT> unit that ships with BCB, as well as the
upcoming discussion of the <TT>Clock</TT> component and the
Tools API.
<HR>
</DL>
<P>The following code is used to register the objects in the <TT>Unleash2</TT> unit
so they can be placed on the Component Palette:</P>
<PRE><FONT COLOR="#0066FF">namespace Unleash2
{
void __fastcall Register()
{
TComponentClass classes[6]={__classid(TSmallEditTwo),
__classid(TBigEdit), __classid(TSmallLabel), __classid(TBigLabel),
__classid(TEmptyPanel), __classid(TRadio2Panel)};
RegisterComponents("Unleash", classes, 5);
}
}
</FONT></PRE>
<P>Notice how I register multiple components in this example at once by creating
an array of type <TT>TComponentClass</TT>. After registering the objects, you can
place them on the Component Palette and use them in a program, as shown in
Figures
22.5 and 22.6.</P>
<P><A HREF="22ebu05.jpg" tppabs="http://pbs.mcp.com/ebooks/0672310228/art/22/22ebu05.jpg">Figure 22.5.</A><I>The main form of a VCL application
that uses some of the components from the <TT>Unleash2</TT> unit.</I></P>
<P><A HREF="22ebu06.jpg" tppabs="http://pbs.mcp.com/ebooks/0672310228/art/22/22ebu06.jpg">Figure 22.6.</A><I>The new
components on the Component
Palette, along with some other controls created in later chapters.</I></P>
<DL>
<DD>
<HR>
<FONT COLOR="#000077"><B>NOTE: </B></FONT>To create custom bitmaps for components
shown on the Component Palette, you need to
create a standard resource with the extension
<TT>.dcr</TT>. The Image Editor component that ships with BCB is designed to handle
this chore.<BR>
<BR>
I ship a few DCR files in the <TT>Utils</TT> directory on the CD that accompanies
this book.
You can study them to see how to proceed, or you can find the DCR files
for other components that ship with BCB. To look at a DCR file, open it with the
Image Editor.<BR>
<BR>
The DCR file should have the name of the unit that contains the
controls, and each
small 24x24 bitmap that you create in the file should be named after the component
to which it belongs. For example, the DCR file for this unit would be called <TT>Unleash2.dcr</TT>,
and the bitmaps inside it would have names
like <TT>TBIGEDIT</TT> and <TT>TEMPTYPANEL</TT>.<BR>
I will discuss this subject in more depth later in this chapter.
<HR>
</DL>
<P>Before closing this section, I'd like to add some notes about how BCB handles
streaming chores. The good news is
that most of the time you don't have to concern
yourself with streaming at all. BCB handles most streaming chores automatically.
In particular, it will automatically stream <TT>published</TT> properties that are
simple types. Only under limited
circumstances must you explicitly stream the fields
of your object.</P>
<P>If a property type is a <TT>TComponent</TT> or descendant, the streaming system
assumes it must create an instance of that type when reading it in. If a property
type is
<TT>TPersistent</TT> but not <TT>TComponent</TT>, the streaming system assumes
it is supposed to use the existing instance available through the property and read
values into that instance's properties.</P>
<P>The Object Inspector knows to expand the
properties of <TT>TPersistent</TT> but
not <TT>TComponent</TT> descendants. This expansion is not done for <TT>TComponent</TT>
descendants because they are likely to have a lot more properties, which would make
navigating the Object Inspector
difficult.
<H2><FONT COLOR="#000077">Building Components from Scratch</FONT></H2>
<P>In the previous examples, you created descendants of existing components. Now
you're ready to see how to create entirely new components. The main idea to grasp
here
is that you can make a new component descend from three abstract objects. The
term "abstract" can have a specific technical meaning, but here I'm using
it to refer to any object that exists only so that you can create descendants of
it. In
short, the following three objects have built-in functionality that all components
need to access, but you would never want to instantiate an instance of any of them:
<UL>
<LI><TT>TWinControl</TT> and <TT>TCustomControl</TT> are base classes that
can be
used to produce a Windows control that can receive input focus and that has a standard
Windows handle that can be passed to API calls. <TT>TWinControl</TT> descendants
exist inside their own window. <TT>TEdit</TT>, <TT>TListBox</TT>,
<TT>TTabbedNotebook</TT>,
<TT>TNotebook</TT>, and <TT>TPanel</TT> are all examples of this type of control.
Most components of this type actually descend from <TT>TCustomControl</TT>, which
is in turn a descendant of <TT>TWinControl</TT>. The
distinction between the two
classes is that <TT>TCustomControl</TT> has a <TT>Paint</TT> method, and <TT>TWinControl</TT>
does not. If you want to draw the display of your new component, you should make
it inherit from <TT>TCustomControl</TT>. If
the object already knows how to draw
itself, inherit from <TT>TWinControl</TT>.
<P>
<LI><TT>TGraphicControl</TT> is for components that don't need to receive input focus,
don't need to contain other components, and don't need a handle. These
controls draw
themselves directly on their parent's surface, thereby saving Windows resources.
Not having a window handle eliminates a lot of Windows management overhead, and that
translates into faster display updates. In short,
<TT>TGraphicControl</TT>s exist
inside their parent's window. They use their parent's handle and their parent's device
context. They still have <TT>Handle</TT> and <TT>Canvas</TT> fields that you can
access, but they actually belong to their
parent. <TT>TLabel</TT> and <TT>TShape</TT>
objects are examples of this type of component. The drawback with this system is
that the component can never
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -