📄 cb199911nf_f.asp.htm
字号:
component defines a couple of published properties that will appear in the
Object Inspector: <i>MaxValue</i> and <i
style='mso-bidi-font-style:normal'>MinValue</i>. Neither of these properties
does anything special when they are set, so they both directly access the
private member variables. There is also one public property, <i>Value</i>, that
is defined to be read-only, because it only has a Get method and doesn't have
an internal member variable. Whenever the component user accesses the <i>Value</i>
property, the <i>GetValue</i> method is
called to supply the random number. <i>GetValue</i>
hides the messy details of calling the random number function. By allowing the
user to select a range of values, we can translate them to the format required
by the function. Then, after getting the random number in the format preferred
by the function, we can internally convert it back to the range the user
wanted. Also notice that the randomize function is called in the constructor of
the component. This relieves the user from having to remember to seed the
random number generator. </p>
<p class=BodyText> </p>
<p class=Subheads>Default
Behavior for Buttons</p>
<p class=BodyText> In
C++Builder, every event handler is a pointer to a method, either written by the
component writer or, more commonly, by the programmer using the component. You
can think of event handlers as places where the user can "plug in" a routine. </p>
<p class=BodyText> </p>
<p class=BodyText> This is
implemented in C++Builder by using method pointers. Each event handler
typically has a private member variable that points to an event handler of a
particular type: </p>
<p class=BodyText> </p>
<p class=Code><span class=Code><b>class</b>
PACKAGE TControl : <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> TNotifyEvent *FOnClick; </span></p>
<p class=Code><span class=Code><b> protected</b>:</span></p>
<p class=Code><span class=Code> <b> virtual</b>
<b>void</b> <b>__fastcall</b> Click();</span></p>
<p class=Code><span class=Code> <b> __property</b>
OnClick = { read=FOnClick, write=FOnClick };</span></p>
<p class=Code><span class=Code> ... </span></p>
<p class=BodyText> </p>
<p class=BodyText> The
private member variable holds the pointer to the routine that the programmer,
who is using this component, will write. The <i>Click</i> method is defined as
follows: </p>
<p class=BodyText> </p>
<p class=Code><span class=Code><b>void</b> <b
style='mso-bidi-font-weight:normal'>__fastcall</b> TControl::Click()</span></p>
<p class=Code><span class=Code>{</span></p>
<p class=Code><span class=Code> <b> if</b>
(Assigned(FOnClick)) </span></p>
<p class=Code><span class=Code> { </span></p>
<p class=Code><span class=Code> FOnClick(<b>this</b>);</span></p>
<p class=Code><span class=Code> } </span></p>
<p class=Code><span class=Code>}</span></p>
<p class=BodyText> </p>
<p class=BodyText> The <i
style='mso-bidi-font-style:normal'>Assigned</i> function returns <b>true</b> if
the argument points to a routine, and <b>false</b> if the argument is null.
This routine checks to see if the user has attached any code to the event
handler, and executes it if the code has been defined. </p>
<p class=BodyText> </p>
<p class=BodyText> Events
are implemented this way in C++Builder to make them as flexible as possible. A
rule of thumb for component writing in C++Builder is to make the components as
robust as possible. In other words, you don't want to write a component that
requires the user to attach some code to it for it to work. Components should
ignore event handlers that are empty, and the previous mechanism allows this. </p>
<p class=BodyText> </p>
<p class=Subheads><i>CustomButton</i> </p>
<p class=BodyText> We're
going to create a button that has some default behavior attached to it. When
the user clicks the button, the default code will execute. The component class
is shown in <a href="#ListingTwo">Listing
Two</a>. This class overrides the protected <i>Click</i> routine from
the parent (<i>TButton</i>), and provides a
component writer-defined routine. When this button is dropped on a form and the
user clicks on the button at run time, the user gets the <i>ShowMessage</i> dialog box (see Figure 2). </p>
<p class=BodyText> </p>
<p class=Captions><img width=231 height=103
src="images/cb199911nf_f_image003.gif" tppabs="http://www.cbuilderzine.com/features/1999/11/cb199911nf_f/cb199911nf_f_image003.gif" align=left> <br clear=all>
<b>Figure 2:</b> Custom-defined button. </p>
<p class=BodyText> </p>
<p class=BodyText> This
demonstrates how to attach some default behavior to a control by manipulating
the protected method that actually dispatches the component user's code.
However, look what happens if users add their own code to the <i>OnClick</i>
event of the button: </p>
<p class=BodyText> </p>
<p class=Code><span class=Code><b>void</b> <b
style='mso-bidi-font-weight:normal'>__fastcall</b>
TForm1::CustomButton1Click(TObject *Sender) </span></p>
<p class=Code><span class=Code>{</span></p>
<p class=Code><span class=Code> ShowMessage("From the component user's
code"); </span></p>
<p class=Code><span class=Code>}</span></p>
<p class=BodyText> </p>
<p class=BodyText> When the
application is run, the user gets both message dialog boxes. So, the component
writer's code and the component user's code are being executed. In fact, the
location in the component's <i>Click</i>
method determines which code is executed first. Notice that the inherited
class's <i>Click</i> method appears first in
the <i>Click</i> routine for the custom
button, but the user's code is executed first. It's more common to call the
inherited routine first, then supply your own code. However, it doesn't matter
in which order you perform these actions, as long as you call the inherited
routine to accommodate any code that the component user has added. </p>
<p class=BodyText> </p>
<p class=Subheads>An Example
Unfolds </p>
<p class=BodyText> A common
Windows interface technique is to create dialog boxes that "unfold." This way,
simple options can be placed on the main part of the form and more complex or
seldom-used operations can be placed "out of the way" on the folded portion of
the dialog box. Of course, it would be easy enough to create this type of form
in C++Builder, placing a button on the form to automatically resize the form
and disable itself when pressed. With a component, however, you can create it
once and never have to worry about it again. We're going to create such a
control next. This illustrates how to create a button that already has some
default behavior associated with it. </p>
<p class=BodyText> </p>
<p class=BodyText> The <i
style='mso-bidi-font-style:normal'>UnfoldButton</i> control is a button that
automatically implements this unfolding behavior. When you drop the button on
the form, it notes the current size of the form. When the application runs, the
form resizes itself (before it is shown), to extend to the bottom of the unfold
button plus a little extra distance. When the button is clicked, the dialog box
unfolds to show the rest of the form. The "folded" version of the dialog box is
shown in Figure 3, and the "unfolded" version is shown in Figure 4. </p>
<p class=BodyText> </p>
<p class=Captions><img width=333 height=156
src="images/cb199911nf_f_image005.gif" tppabs="http://www.cbuilderzine.com/features/1999/11/cb199911nf_f/cb199911nf_f_image005.gif" align=left> <br clear=all>
<b>Figure 3: </b>The "folded" version of the dialog
box. </p>
<p class=BodyText> </p>
<p class=Captions><img width=333 height=248
src="images/cb199911nf_f_image007.gif" tppabs="http://www.cbuilderzine.com/features/1999/11/cb199911nf_f/cb199911nf_f_image007.gif" align=left> <br clear=all>
<b>Figure 4:</b> The "unfolded" version of the
dialog box. </p>
<p class=BodyText> </p>
<p class=BodyText> The
button automatically unfolds the dialog box and disables itself (because you
obviously won't be expanding the same form twice). However, this control
doesn't affect your application in the Designer. Essentially, everything under
the button will be part of the "folding" portion, and everything above the
button will always appear. The source for <i>UnfoldButton</i> is shown in <a
href="#ListingThree">Listing
Three</a>.</p>
<p class=BodyText> </p>
<p class=BodyText> We're
creating some private fields to hold the additional space (i.e. the space
between the bottom of the button and the bottom of the form) and the initial
form height. We're also overriding the constructor and the <i>Click</i> method. Notice that we are also overriding the <i
style='mso-bidi-font-style:normal'>Loaded</i> method. The <i>Loaded</i> method is called for each component after the form has
loaded its values from the DFM file. We then set the default values. Remember
that the <b>default</b> directive only signifies whether the value is streamed
to the DFM file. You must still provide the default values in the constructor.
We're also blindly setting the form's <i>AutoScroll</i>
property to <b>false</b>. This is generally not a good idea from an
encapsulation standpoint to blindly assign a value like this, but in this case,
the button can't possibly work if this property is set to <b>true</b>. The <i
style='mso-bidi-font-style:normal'>Click</i> method calls the <i>inherited</i>
method and resets the form to the original size captured in the <i
style='mso-bidi-font-style:normal'>Loaded</i> method. </p>
<p class=BodyText> </p>
<p class=BodyText> The most
complex method for this component is the <i>Loaded</i>
method. There are several points to note here. The first order of business is
to call the inherited <i>Loaded</i> method.
You should always do this any time you override a method, not just for the
constructor and destructor. Next, we'll save the initial height of the form in
a property, so we can restore it in the <i>Click</i> routine. Next, we'll
obtain the <i>ClientOrigin</i> value for the
button. <i>ClientOrigin</i> is a wrapper for
a Windows API call that tells you the absolute position on the screen of the
component. We'll then set the form's height (after casting <i>Owner</i>, which
is of type <i>TComponent</i>) to the base
class (which includes the <i>height</i> property), to the position of the
button on the screen minus the position of the form on the screen. This
calculation will tell how much to resize the form to be just a little taller
than the button position. </p>
<p class=BodyText> </p>
<p class=BodyText> Why go
to the trouble of getting screen coordinates from Windows? Why not just look at
the height property of the button? That will work as long as there are no
containers on the form (e.g. panels, groupboxes, etc.) underneath the <i
style='mso-bidi-font-style:normal'>UnfoldButton</i> control. A component's position on the form is relative to the
container, not the form itself. So, if you have a panel on the form, and a
button on the panel, the <i>height</i> property of the button is relative to
the panel, not the form. It's possible to loop through all the containers on
the form, adding the heights as you go (in fact, a previous version of this
control did just that), but it's much cleaner and more efficient to let Windows
tell you the relative position. This is a common theme in Windows: Don't build
it if it already exists. </p>
<p class=BodyText> </p>
<p class=BodyText> Why not
do this in the constructor, rather than overriding the <i>Loaded </i>method?
Notice that the form creates all the controls that it owns during its
constructor. The problem lies in the fact that we must refer to, and change,
one of the form's properties as the button is created. However, the form's
properties aren't yet available in its constructor. In other words, you can't
refer to the form's height, and change it, in a constructor for one of the
controls that the form owns. The form doesn't exist yet! Thus, the <i
style='mso-bidi-font-style:normal'>Loaded</i> method is perfect in this
situation. In fact, the rule of thumb is that any time you must refer to the
form or one of its controls, you must do so in <i>Loaded</i>, rather than the constructor. </p>
<p class=BodyText> </p>
<p class=Subheads>Conclusion</p>
<p class=BodyText> This
article showed two more of the architectural underpinnings of components in
C++Builder: properties, and how the <i>Click</i>
methods of buttons work. The event model will be examined more closely in a
future article, including how to create custom events and event handlers. In
the next installment, we'll look at component containership and creating dialog
box components. </p>
<p class=BodyText> </p>
<p class=BodyText> <i>The
files referenced in this article are available for <a href="download/cb199911nf_f.zip" tppabs="http://www.cbuilderzine.com/features/1999/11/cb199911nf_f/cb199911nf_d.asp">download</a>. </i></p>
<p class=BodyText> </p>
<p class=Biotext>Neal is the
Vice President of Technology at The DSW Group. He is also the designer and developer
of applications, instructional materials, magazine articles, video
presentations, and author of the book<i> JBuilder 3 Unleashed</i>. He can be
reached at <a href="mailto:nford@thedswgroup.com">mailto:nford@thedswgroup.com</a>,
or by calling The DSW Group at (800) 356-9644. </p>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -