📄 ch03.htm
字号:
seen in the object inspector, and to be automatically streamed out to and read from
disk without intervention from the programmer. The automatic streaming of objects
is one of the great features of the VCL.
</DL>
<P>Properties can be added to either the <TT>public</TT> or <TT>published</TT> section
of a class declaration. It makes no sense to put them in either the <TT>protected</TT>
or <TT>private</TT> sections, because their primary purpose is to provide a
public
interface to your object.</P>
<P>Here is a declaration for a property:</P>
<PRE><FONT COLOR="#0066FF">class TMyObject : public TComponent
{
private:
int FSomeInteger;
__published:
__property int SomeInteger={read=FSomeInteger,
write=FSomeInteger};
};
</FONT></PRE>
<P>In this class, <TT>SomeInteger</TT> is a property of type <TT>int</TT>. It serves
as the public interface for the private <TT>FSomeInteger</TT> variable.</P>
<P>To declare a property:
<UL>
<LI>Use the
<TT>__property</TT> keyword.
<P>
<LI>Declare the type of the property.
<P>
<LI>Add the name of the property.
<P>
<LI>Add an equal sign and an open and close curly brace followed by a semicolon.
<P>
<LI>Between the curly braces, properties can
have read and write sections. These
sections are used to designate how to set or get the value of a property. To declare
the read section, write the word read followed by an equal sign, and do the same
thing with the write section.
</UL>
<P>It is
very common in VCL classes to declare <TT>private</TT> data with the letter
<TT>F</TT> prefixed to it. The letter <TT>F</TT> stands for field. It serves as a
reminder that this is a <TT>private</TT> variable and should not be accessed from
another
class.</P>
<P>You can declare a property to be read only or write only:</P>
<PRE><FONT COLOR="#0066FF">__property int SomeInteger={read=FSomeInteger};
__property int SomeInteger={write=FSomeInteger};
</FONT></PRE>
<P>The first of these declarations
is read only, the second is write only.</P>
<P>Because <TT>FSomeInteger</TT> is private, you might need to provide some means
of accessing it from another object. You can and should sometimes use the <TT>friend</TT>
notation, but that really violates
the whole concept of the <TT>private</TT> directive.
If you want to give someone access to your data, but continue to hide the specific
implementation of that data, use properties. The preceding code provides one means
of giving someone access to your
data while still protecting it. It goes without
saying that if your property does nothing else but call directly to a variable of
the same type in the <TT>private</TT> section, the compiler generates code that gives
you direct access to the type. In
other words, it doesn't take any longer to directly
use a property like <TT>SomeInteger</TT> than it would to use <TT>FSomeInteger</TT>
directly. The compiler takes care of the details for you, and uses less code than
it would with an inline access
function.
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE: </B></FONT>C++ programmers have long used get and
set methods to provide access to <TT>private</TT> data. There is a certain amount
of controversy as to whether or not properties add
something to the object model
outside of their capability to be seen in the Object Inspector.</P>
<P>Here is a second way to state the matter. Given the presence of the Object Inspector,
properties clearly play an important role in BCB programming.
A second debate, however,
would involve the question of whether or not--absent the issue of the Object Inspector--properties
add something new to the mix that would not be there if we had only access functions
and get and set methods.<BR>
<BR>
One thing I like a great deal about properties is the clarity of the syntax they
present to the user. They make it easier to write clear, maintainable code. They
also do a very good job of helping you protect the low-level implementation of your
object. I recognize, however, that it could be argued that access functions provided
sufficient power to accomplish these goals without the aid of properties.<BR>
<BR>
I could safely hide behind the fact that the BCB programming model demands the
presence
of properties. However, I will stick my neck out on this one and say that I feel
properties are an important contribution to C++, and should become part of the language.
I recognize, however, that this is the kind of subject that fosters
intense controversy,
and concede that the debate is not entirely one-sided.<BR>
<HR>
</BLOCKQUOTE>
<P>Here is another use of properties that differs from the one just shown:</P>
<PRE><FONT COLOR="#0066FF">class MyObject : public TObject
{
private:
int FSomeInteger;
int __fastcall GetSomeInteger() { return FSomeInteger; }
void __fastcall SetSomeInteger(int i) { FSomeInteger = i; }
__published:
__property int SomeInteger={read=GetSomeInteger, write=SetSomeInteger};
</FONT></PRE>
<PRE><FONT COLOR="#0066FF">};
</FONT></PRE>
<P>In this class, <TT>SomeInteger</TT> has get and set methods. These get and set
methods are associated with a property and should always be declared with the <TT>__fastcall</TT>
calling
convention.</P>
<P>Get and set methods provide a means of performing calculations or other actions
when getting and setting the value of a property. For instance, when you change the
<TT>Width</TT> property in a <TT>TShape</TT> object, not only does
some internal
variable get set, but the whole object redraws itself. This is possible because there
is a set method for this property that both sets the internal <TT>FWidth</TT> property
to a new value and redraws the object to reflect these
changes.</P>
<P>You could perform calculations from inside a get method. For instance, you could
calculate the current time in a property designed to return the current time.</P>
<P>The existence of get and set methods represents another important
reason for keeping
the data of your object <TT>private</TT>, and for not giving anyone else access to
it except through properties. In particular, you may change a value in a set or get
method or have side effects that you want to be sure are
executed. If someone accesses
the data directly, the side effects or other changes will not occur. Therefore, you
should keep the data <TT>private</TT> and let them access it through a function,
and let the function itself be accessed through a
property.</P>
<P>Properties can be of a wide variety of types. For instance, they can be declared
as <TT>AnsiString</TT>s, arrays, sets, objects, or enumerated types. Events, which
are a kind of method pointer, are really a type of property, but they
behave according
to their own peculiar rules and are usually treated as a separate subject from properties.</P>
<P>Properties can be declared to have a default value:</P>
<PRE><FONT COLOR="#0066FF">__property int SomeInteger={read=GetSomeInteger,
write=SetSomeInteger, default=1};
</FONT></PRE>
<P>This syntax is related only tangentially to the concept of setting a property
automatically to a particular value. If you want to give a property a predefined
value, you should do so in the object's
constructor.</P>
<P>Default is used to tell the VCL whether or not a value should be written out to
a stream. The issue here is that streaming can result in producing large files, because
so many objects have such large numbers of properties. To cut
down on the size of
your form files, and on the time spent writing the files to disk, the VCL enables
you to declare a default value for a property. When it comes time to stream properties
to disk, they will not be streamed if they are currently set
to the default value.
The assumption is that you will initialize them to that value in the object's constructor,
so there is no need to save the value to disk. Many properties are declared to be
nodefault, which means they should be streamed to
disk.</P>
<P>A property can also have the stored directive. Confusingly enough, this is another
means of deciding whether or not a property should be streamed. In this case, it
gives you the option of changing whether or not a property can be
streamed. For instance:</P>
<PRE><FONT COLOR="#0066FF">property TColor Color={read Fcolor, write SetColor,
stored=IsColorStored, default=clWindow};
</FONT></PRE>
<P>This property calls a method named <TT>IsColorStored</TT> to determine whether
the
color should be stored at this time. For instance, if the property is set to
have the same value as its parent, there is no need to store it, and <TT>IsColorStored</TT>
returns <TT>False</TT>. This property will therefore only be stored if
<TT>IsColorStored</TT>
returns <TT>True</TT> and the color is not set to <TT>clWindow</TT>.</P>
<P>Say what?</P>
<P>Don't worry, most of the time you don't have to get involved with these tricky
storage specifiers and their disarming schemes to save
disk space. However, there
are times when the matter comes to the front, and now you have a place to look to
remember what they mean.</P>
<P>Properties play a big role in VCL programming, and furthermore, they can be used
in objects that have nothing
to do with the VCL. This subject has tremendous potential
scope and will affect the way the C++ language as a whole is handled by BCB programmers.</P>
<P>That is all I'm going to say about properties for now. There is an example of
creating and using
an array property later in this chapter in the section called
"Arrays of AnsiStrings."
<H3><FONT COLOR="#000077">Events: Understanding Delegation</FONT></H3>
<P>Most of the time I will refer to closures as events. Technically, a closure is
the type of method pointer used in event properties, but I will generally refer to
the whole syntactical structure as an event that supports the delegation model.</P>
<P>As you learned in the last section, an event is really a kind of property. The
primary difference between a standard property and an event is that the type of an
event is a method pointer.
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE: </B></FONT>Some people refer to events as closures,
but it could be argued that the
word has a rather stuffy, academic overtone to it.
Certainly it is used in some circles in a very strict manner that does not necessarily
conform in all its particulars to the way BCB handles events.
<HR>
</BLOCKQUOTE>
<P>Consider the
<TT>OnMouseUp</TT> event that is supported by many components and
declared in <TT>CONTROLS.HPP</TT>:</P>
<PRE><FONT COLOR="#0066FF">__property TMouseEvent OnMouseUp = {read=FOnMouseUp, write=FOnMouseUp};
</FONT></PRE>
<P>Like the <TT>OnMouseUp</TT>
property, <TT>FOnMouseUp</TT> is, naturally, also
declared to be of type <TT>TMouseEvent</TT>:</P>
<PRE><FONT COLOR="#0066FF">TMouseEvent FOnMouseUp.
</FONT></PRE>
<P>So far the syntax used for events seems pretty much identical to that used for
all
properties. The new code that is specific to the delegation model is the actual
declaration for the method pointer used by an event:</P>
<PRE><FONT COLOR="#0066FF">typedef void __fastcall (__closure *TMouseEvent)(System::TObject* Sender,
TMouseButton Button, Classes::TShiftState Shift, int X, int Y);
</FONT></PRE>
<P>All <TT>OnMouseUp</TT> events are of this type. In other words, if an <TT>OnMouseUp</TT>
event is not set to <TT>null</TT>, it is set to a method pointer of this type.
You
can then call the event by writing code of this type:</P>
<PRE><FONT COLOR="#0066FF">if (FOnMouseUp)
FOnMouseUp(this, Button, Shift, X, Y);
</FONT></PRE>
<P>If the event is not set to <TT>null</TT>, call the method associated with the
event
and pass parameters of a type that conform with the signature of the relevant
method pointer. This is called delegating the event.
<BLOCKQUOTE>
<P>
<HR>
<FONT COLOR="#000077"><B>NOTE: </B></FONT>It is generally considered very bad form
to return a
value from an event. Trying to do so could cause a compiler error, and
you should avoid including code of this type in your own programs. The VCL has gone
through several iterations now, and returning values from events has worked in some
versions
and not in others. The team that wrote the VCL, however, asked the DOC team
to state explicitly that events should not return values. It usually turns out badly
when you get stuck with legacy code that worked in one version, but directly contradicts
the desires of the folks who keep the reins in their own hands.
<HR>
</BLOCKQUOTE>
<P>The method associated with the <TT>OnMouseUp</TT> event will look something like
this:</P>
<PRE><FONT COLOR="#0066FF">void __fastcall TForm1::Button1MouseUp(
TObject *Sender,
TMouseButton Button,
TShiftState Shift,
int X,
int Y)
{
}
</FONT></PRE>
<P>I generally call a method of this type an event handler. It handles an event when
it is delegated by a component or object.</P>
<P>Most of the
time an event is assigned to a method automatically by the compiler.
However, you can do so explicitly if you desire, and in fact I do this in several
places in my own code. If you want an example, see the Music program from Chapter
16, "Advanced
InterBase Concepts." Here is how the assignment would be
made from inside the Controls unit:</P>
<PRE><FONT COLOR="#0066FF">FOnMouseEvent = Button1MouseUp;
</FONT></PRE>
<P>Code of this type appears everywhere in the VCL, and indeed it is one of
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -