📄 cb199911nf_f.asp.htm
字号:
<HTML>
<HEAD>
<TITLE>Creating Custom Components</TITLE>
</HEAD>
<BODY>
<TABLE border=0 width="100%" cellpadding=0 cellspacing=0>
<TR valign=top>
<TD width="100%">
<p class=ColumnTitle><font size="2">From the
Palette</font></p>
<p class=ColumnSubtitle>Components
/ Properties / Buttons</p>
<p class=BodyText> </p>
<p class=Byline>By Neal Ford</p>
<p class=BodyText> </p>
<p class=StoryTitle><font size="2"><b>Creating
Custom Components</b></font></p>
<p class=StorySubtitle><font size="2">Part
II: Properties and Button Behavior</font> </p>
<p class=BodyText> </p>
<p class=BodyText> In Part
I of this series on component creation in C++Builder, I introduced the basics
of component development, as well as some peripheral issues, such as how to
create component bitmaps. Part II delves deeper into the internal workings of
components and discusses two issues: </p>
<p class=BodyText> 1)
How
properties are defined and used. </p>
<p class=BodyText> 2)
How
the <i>Click</i> behavior of buttons works. </p>
<p class=BodyText> </p>
<p class=Subheads>Get/Set
Routines</p>
<p class=BodyText> You're
already familiar with properties from having to set their values from the
Object Inspector. However, they are more broadly useful. A property is
essentially a level of indirection away from members of classes - typically
private members. Properties also provide a terrific information-hiding
mechanism. First, we'll look at the syntax for declaring a property; then we'll
illustrate some common uses for properties. </p>
<p class=BodyText> </p>
<p class=BodyText> Properties
implement what is known in the software engineering world as Get/Set routines.
For every private class member you want to access, you can write a routine to
get the value, and another routine to set the value. This provides good
information hiding because you're not directly accessing the private part of
the class. Therefore, you're free to change the internal data representation in
the future. The property can be accessed as if it were a simple variable. </p>
<p class=BodyText> </p>
<p class=BodyText> Here is
an example of a simple class with a property to act as Get/Set routines for the
private member variable: </p>
<p class=BodyText> </p>
<p class=Code><span class=Code><b>class</b>
MyClass : <b>public</b> TComponent</span></p>
<p class=Code><span class=Code>{</span></p>
<p class=Code><span class=Code> <b> private</b>:</span></p>
<p class=Code><span class=Code> TColor FColor; </span></p>
<p class=Code><span class=Code><b> __published</b>:</span></p>
<p class=Code><span class=Code> <b> __property</b>
TColor Color = { read = FColor, write = FColor };</span></p>
<p class=Code><span class=Code>};</span></p>
<p class=BodyText> </p>
<p class=BodyText> The
naming convention for private variables in classes whose values will be set
with properties prefixes the variable name with an "F" (for "Field"). It's more
common in the C++ world that private member variables be prefixed with an "m_"
instead of an "F". However, when dealing with the VCL, it's wise to follow its
conventions. </p>
<p class=BodyText> </p>
<p class=BodyText> When you
declare a property, you can declare a read and write clause for it. In the
previous example, the read and write modifiers allow the property to be changed
directly, but don't provide much obvious benefit. Consider this version: </p>
<p class=BodyText> </p>
<p class=Code><span class=Code><b>class</b>
MyClass : <b>public</b> TComponent</span></p>
<p class=Code><span class=Code>{</span></p>
<p class=Code><span class=Code> <b> private</b>:</span></p>
<p class=Code><span class=Code> TColor FColor; </span></p>
<p class=Code><span class=Code> <b> void</b>
<b>__fastcall</b> SetColor(TColor value); </span></p>
<p class=Code><span class=Code> TColor <b>__fastcall</b> GetColor();</span></p>
<p class=Code><span class=Code><b> __published</b>:</span></p>
<p class=Code><span class=Code> <b> __property</b>
TColor Color = { read = GetColor, write = SetColor };</span></p>
<p class=Code><span class=Code>};</span></p>
<p class=BodyText> </p>
<p class=BodyText> Notice
that the read and write modifiers can accept as parameters either a variable
reference or a method. In this example, all access to <i>TColor</i> is done through the Get/Set routines: Nowhere is the value
directly accessed. This is beneficial because you're now free to change how <i
style='mso-bidi-font-style:normal'>TColor</i> is kept internally. For example,
you may change the internal color representations to use direct RGB values
instead of the enumerated <i>TColor</i>
type. To affect this change, you can change the Get/Set routines to map the new
representation, but you don't have to make any changes to the property. </p>
<p class=BodyText> </p>
<p class=BodyText> The
property provides a well-published interface to the internals of the class,
which may change over time. However, any code that depends on this class needs
only to be re-compiled when this class is changed. This effectively hides how
you have implemented color inside the class, and leaves you free to change it
whenever you want, as long as you don't change the published interface. </p>
<p class=BodyText> </p>
<p class=BodyText> Remember,
the property could be accessed as if it were a simple variable. This is true
even if the Get/Set routines are present. C++Builder takes care of mapping the
Get/Set routines appropriately. So, when you assign a property, it looks as if
you're simply assigning a value to a variable: </p>
<p class=BodyText> </p>
<p class=Code><span class=Code>MyObj->Color
= clRed; </span></p>
<p class=BodyText> </p>
<p class=BodyText> C++Builder
takes care of firing the Get/Set routines for you. Incidentally, this is how
C++Builder manages to act so much like an interpreter in design mode. When you
change a property with an assignment statement, you're not only changing the
internal value of the property; the Set routine is executing the code necessary
in Windows to make the change come about. For example, when you assign a color
to a label's <i>Color</i> property, this
assignment is changing the label's internal <i>FColor</i>
property to <i>clRed</i>, but the Set routine is executing the necessary
Windows calls to make the color of the label actually change to red on the
form. </p>
<p class=BodyText> </p>
<p class=BodyText> You
should rely on properties for all the member variables in the classes you
create. Even if you don't need this level of indirection now, you can never
predict when you will need it in the future. You should implement properties
that directly read and write the internal values until you find yourself
needing to create a side effect as a result of an assignment. Then you can
create the Get and/or Set routine to provide that functionality. By creating
them as properties, you can ensure the published interface to your class
doesn't change. </p>
<p class=BodyText> </p>
<p class=Subheads>Default
Values</p>
<p class=BodyText> Component
properties frequently have default values associated with them. This is
accomplished in two steps. First, use the <b>default</b> directive when
declaring the property. Here is an example: </p>
<p class=BodyText> </p>
<p class=Code><span class=Code><b>class</b>
MyClass : <b>public</b> TComponent</span></p>
<p class=Code><span class=Code>{</span></p>
<p class=Code><span class=Code> <b> private</b>:</span></p>
<p class=Code><span class=Code> TColor FColor; </span></p>
<p class=Code><span class=Code><b> __published</b>:</span></p>
<p class=Code><span class=Code> <b> __property</b>
TColor Color =</span></p>
<p class=Code><span class=Code> { read = FColor, write = FColor, <b
style='mso-bidi-font-weight:normal'>default</b> = clSilver };</span></p>
<p class=Code><span class=Code>};</span></p>
<p class=BodyText> </p>
<p class=BodyText> In the
class definition, the <b>default</b> directive doesn't actually set a default
value for the control. Instead, it controls whether the value for this control
gets streamed out to the DFM file at design time. Remember, the only values
that get streamed to the DFM file are those that have changed from their default
values. The <b>default</b> directive determines that default value. </p>
<p class=BodyText> </p>
<p class=BodyText> To
actually make the value the default value for a control, you must also set the
value in the component's constructor: </p>
<p class=BodyText> </p>
<p class=Code><span class=Code><b>__fastcall</b>
MyClass::MyClass(TComponent* Owner) : TComponent(Owner) </span></p>
<p class=Code><span class=Code>{</span></p>
<p class=Code><span class=Code> Color = clSilver; </span></p>
<p class=Code><span class=Code>}</span></p>
<p class=BodyText> </p>
<p class=BodyText> Although
you can type all the code necessary to create a property by hand, C++Builder
(starting with version 4) has a nice dialog box that allows you to easily add
properties and all their characteristics. The Add Property dialog box is
invoked from the class explorer by right-clicking and choosing to create a new
property (see Figure 1). </p>
<p class=BodyText> </p>
<p class=Captions><img width=333 height=383
src="images/cb199911nf_f_image002.gif" tppabs="http://www.cbuilderzine.com/features/1999/11/cb199911nf_f/cb199911nf_f_image002.gif" align=left> <br clear=all>
<b>Figure 1:</b> The Add Property dialog box. </p>
<p class=BodyText> </p>
<p class=Subheads><i>TRandomNumber</i> </p>
<p class=BodyText> Not all
objects (or components) are created to handle features or characteristics not
supported by the library, i.e. the standard C/C++ libraries, or the VCL. It's
sometimes useful to create components that encapsulate some operation that's
"messy" for some reason. This is the rationale for the <i>TRandomNumber</i> component. The <i>TRandomNumber</i>
component hides the messy details of acquiring a random number from C++Builder.
To get a random number, you must: </p>
<p class=BodyText> 1)
Call
the <i>randomize</i> function to seed the random number generator, and</p>
<p class=BodyText> 2)
Call
the <i>random</i> function, which gives you a random number between 0 and the
number passed. </p>
<p class=BodyText> </p>
<p class=BodyText> If you
need a random number within a range, you have to deal with adding and
subtracting the number you get back, which is prone to off-by-one errors. Also,
you have to remember to call the <i>randomize</i> function. This is a perfectly
messy operation to encapsulate into a component. The complete source for the <i
style='mso-bidi-font-style:normal'>TRandomNumber</i> component (both header and
source) can be found in <a href="#ListingOne">Listing
One</a>.</p>
<p class=BodyText> </p>
<p class=BodyText> The
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -