📄 ch20.htm
字号:
<TT>TWidget</TT> shows programmers a good deal about how BCB implements
encapsulation:</P>
<PRE><FONT COLOR="#0066FF">class TWidget: public TCustomControl
{
private:
TListHierarchy *Hierarchy;
Currency FCost;
TDateTime FTimeCreated;
AnsiString FDescription;
void __fastcall SetTimeCreated(AnsiString S);
AnsiString __fastcall GetTimeCreated();
protected:
virtual void __fastcall Paint(void);
public:
__fastcall virtual TWidget(TComponent *AOwner): TCustomControl(AOwner)
{ Hierarchy = new TListHierarchy(); Width = 25; Height = 25; }
__fastcall virtual TWidget(TComponent *AOwner, int ACol, int ARow);
__fastcall virtual ~TWidget() { delete Hierarchy; }
virtual AnsiString GetName() { return "Widgets";
}
TStringList *GetHierarchy() { return Hierarchy->GetHierarchy(this); }
void __fastcall WidgetMouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
__published:
__property Currency Cost={read=FCost,
write=FCost};
__property AnsiString TimeCreated={read=GetTimeCreated, write=SetTimeCreated};
__property AnsiString Description={read=FDescription, write=FDescription};
};
</FONT></PRE>
<P>The private section of <TT>TWidget</TT> contains
several fields of data and two
methods:</P>
<PRE><FONT COLOR="#0066FF">private:
TListHierarchy *Hierarchy;
Currency FCost;
TDateTime FTimeCreated;
AnsiString FDescription;
void __fastcall SetTimeCreated(AnsiString S);
AnsiString
__fastcall GetTimeCreated();
</FONT></PRE>
<P>All of the private data in the program has variable names that begin with the
letter F. As stated before, this is a convention and not a syntactical necessity.
These variables are called internal storage,
or data stores.</P>
<P>Internal storage should always be declared <TT>private</TT>, and as such cannot
be accessed from outside of this unit. Other objects should never access any of this
data directly, but should manipulate it through a predefined
interface that appears
in the <TT>protected</TT>, <TT>published</TT>, or <TT>public</TT> sections. The F
in these variable names stands for field. If you want, however, you can think of
the F in these names as standing for forbidden, as in "it is
forbidden to directly
access this data!"</P>
<P>Don't step between a mother grizzly bear and her cubs. Don't ask who's buried
in Grant's tomb during the middle of a job interview. Don't swim with the sharks
if you are bleeding. Don't declare
public data in a production-quality program! The
problem is not that the error is embarrassing, but that it is going to cause you
grief!</P>
<P>The <TT>GetTimeCreated</TT> and <TT>SetTimeCreated</TT> functions are also declared
<TT>private</TT>, and
you will see that they are accessed through a property. Most
objects have many more private methods, but <TT>TWidget</TT> is relatively bare in
this department. The lack of private methods occurs because <TT>TWidget</TT> is such
a simple object that
there isn't much need to perform complex manipulations of its
data.</P>
<P>The <TT>protected</TT> section is simple and contains a single virtual method
called <TT>Paint</TT>. This portion of the object can be accessed by descendants
of
<TT>TWidget</TT>, but not by an instance of the class. For example, you will have
trouble if you write the following code:</P>
<PRE><FONT COLOR="#0066FF">{
TWidget *Widget = new TWidget(this);
Widget->Paint(); // this line won't compile
delete Widget();
}
</FONT></PRE>
<P>Only a descendant of <TT>TWidget</TT> can explicitly call the <TT>Paint</TT> method.</P>
<P>The methods in the <TT>public</TT> section of the object make it possible to manipulate
the widgets that you declare:</P>
<PRE><FONT COLOR="#0066FF">public:
__fastcall virtual TWidget(TComponent *AOwner): TCustomControl(AOwner)
{ Hierarchy = new TListHierarchy(); Width = 25; Height = 25; }
__fastcall virtual TWidget(TComponent *AOwner, int ACol, int ARow);
__fastcall virtual ~TWidget() { delete Hierarchy; }
TStringList *GetHierarchy() { return Hierarchy->GetHierarchy(this); }
void __fastcall WidgetMouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y);
</FONT></PRE>
<P>Here, you can see several constructors, a destructor, a routine that lets you
iterate through the hierarchy of the object, and a routine that handles mouse clicks
on the object. All of these are common activities and need to be
declared <TT>public</TT>.</P>
<P>The first constructor for the object sets up the aggregated <TT>THierarchy</TT>
object and then sets the object's width and height. The second constructor allows
you to establish the <TT>Left</TT> and <TT>Top</TT>
properties for the object.</P>
<P>You have now had an overview of all the code in the Widget program, except for
its published properties, which will be discussed in the next section. The discussion
so far has concentrated on the BCB object-scoping
directives. You have learned about
the <TT>private</TT>, <TT>protected</TT>, <TT>public</TT>, and <TT>published</TT>
sections of a program, and have seen why each is necessary.
<H3><A NAME="Heading21"></A><FONT COLOR="#000077">Properties</FONT></H3>
<P>Properties provide several advantages:
<UL>
<LI>Properties enable you to hide data and implementations.
<P>
<LI>If you write a component and place it in the Component Palette, its published
properties appear in the Object Inspector.
<P>
<LI>Some properties can be made available at design-time, and variables are available
at runtime only.
<P>
<LI>Properties can have side effects such as not only setting the value of the <TT>FWidth</TT>
variable, but also physically changing the
width of the object that appears on the
screen.
<P>
<LI>Property access methods can be declared <TT>virtual</TT>, which gives them more
flexibility than simple variables.
</UL>
<P>The Widget2 program contains three properties, as shown here:</P>
<PRE><FONT COLOR="#0066FF">__published:
__property Currency Cost={read=FCost, write=FCost};
__property AnsiString TimeCreated={read=GetTimeCreated, write=SetTimeCreated};
__property AnsiString Description={read=FDescription,
write=FDescription};
</FONT></PRE>
<P>Because the <TT>TWidget</TT> class is a descendant of <TT>TComponent</TT>, all
these properties can be put in the <TT>published</TT> section, and therefore could
be seen from inside the Object Inspector if the
object were compiled into BCB's library.
It usually does not make sense to create published sections in objects that do not
have <TT>TComponent</TT> in their ancestry.</P>
<P>Remember that properties in the published section have the advantages, and
the
overhead, associated with a heavy dose of runtime type information. In particular,
properties placed in the public section will automatically be streamed to disk!</P>
<P>There is no rule that says which properties should be declared in the
<TT>published</TT>
or <TT>public</TT> sections. In fact, properties often appear in public sections,
although there is little reason for them to be in <TT>private</TT> or <TT>protected</TT>
sections.</P>
<P>The cost and description properties shown
here are simple tools that do nothing
more than hide data and lay the groundwork for their use inside the Object Inspector.</P>
<PRE><FONT COLOR="#0066FF">property Currency Cost={read=FCost, write=FCost};
</FONT></PRE>
<P>The declaration starts with
the keyword <TT>property</TT>, which performs the
same type of syntactical chore as class or struct. Every property must be declared
as having a certain type, which in this case is <TT>Currency</TT>.</P>
<P>Most properties can be both read and
written. The <TT>read</TT> directive for
the <TT>Cost</TT> property states that the value to be displayed is <TT>FCost</TT>
and the value to write is <TT>FCost</TT>. In short, writing</P>
<PRE><FONT COLOR="#0066FF">{
Widget->Cost = 2;
int i =
Widget.Cost
}
</FONT></PRE>
<P>sets <TT>FCost</TT> to the value <TT>2</TT> and sets <TT>i</TT> to the value of
<TT>FCost</TT> (again, <TT>2</TT>).</P>
<P>The reasons for doing this are twofold:
<UL>
<LI>To hide data so that it is protected.
<P>
<LI>To create a syntax that allows properties to be shown in the Object Inspector.
Of course, you won't see these values in the Object Inspector until you metamorphose
the object into a component, which is a subject that will be covered in Chapter
22,
"Creating Descendants of Existing Components."
</UL>
<P>The <TT>Cost</TT> and <TT>Description</TT> properties provide what is called direct
access; they map directly to the internal storage field. The runtime performance
of accessing
data through a direct-access property is exactly the same as accessing
the private field directly.</P>
<P>The <TT>Cost</TT> and <TT>Description</TT> examples represent the simplest possible
case for a property declaration. The <TT>TimeCreated</TT>
property presents a few
variations on these themes:</P>
<PRE><FONT COLOR="#0066FF">property AnsiString TimeCreated={read=GetTimeCreated, write=SetTimeCreated};
</FONT></PRE>
<P>Rather than reading a variable directly, <TT>TimeCreated</TT> returns the
result
of a private function:</P>
<PRE><FONT COLOR="#0066FF">AnsiString __fastcall TWidget::GetTimeCreated()
{
return FTimeCreated.DateTimeString();
}
</FONT></PRE>
<P><TT>SetQuantity</TT>, on the other hand, enables you to change the value of
the
<TT>FQuantity</TT> variable:</P>
<PRE><FONT COLOR="#0066FF">void __fastcall TWidget::SetTimeCreated(AnsiString S)
{
FTimeCreated = TDateTime(S);
}
</FONT></PRE>
<P><TT>GetTimeCreated</TT> and <TT>SetTimeCreated</TT> are examples of access
methods.
Just as the internal storage for direct access variables begins by convention with
the letter F, access methods usually begin with either <TT>Set</TT> or <TT>Get</TT>.</P>
<P>Take a moment to consider what is happening here. To use the
<TT>Quantity</TT>
property, you need to use the following syntax:</P>
<PRE><FONT COLOR="#0066FF">{
AnsiString S;
W->TimeCreated = "1/1/56 02:53:35 AM";
S = W->TimeCreated;
}
</FONT></PRE>
<P>Note that when you are writing to
the <TT>FTimeCreated</TT> variable, you don't
write</P>
<PRE><FONT COLOR="#0066FF">W->TimeCreated(Now());
</FONT></PRE>
<P>Instead, you use the simple, explicit syntax of a direct assignment:</P>
<PRE><FONT COLOR="#0066FF">W->TimeCreated =
Now();
</FONT></PRE>
<P>BCB automatically translates the assignment into a function call that takes a
parameter. C++ buffs will recognize this as a limited form of operator overloading.</P>
<P>If there were no properties, the previous code would look
like this:</P>
<PRE><FONT COLOR="#0066FF">{
AnsiString S;
W->SetTimeCreated(Now());
S = W->GetTimeCreated;
}
</FONT></PRE>
<P>Instead of remembering one property name, this second technique requires you to
remember two, and instead of
the simple assignment syntax, you must remember to pass
a parameter. Although it is not the main purpose of properties, it should now be
obvious that one of their benefits is that they provide a clean, easy-to-use syntax.
Furthermore, they allow you
to completely hide the implementation of your <TT>Get</TT>
and <TT>Set</TT> methods if you so desire.
<H3><A NAME="Heading22"></A><FONT COLOR="#000077">Streaming Classes</FONT></H3>
<P>Published properties allow you to automatically stream the data of
your program.
In particular, most published properties will be automatically written to your DFM
files and restored when they are reloaded.</P>
<P>The following code shows how to explicitly write a component to disk:</P>
<PRE><FONT
COLOR="#0066FF">void WriteWidgetToStream(AnsiString StreamName, TWidget *Widget)
{
TFileStream *Stream = new TFileStream(StreamName, fmCreate | fmOpenWrite);
Stream->WriteComponent(Widget);
delete Stream;
}
</FONT></PRE>
<P>To call this
method, you might write code that looks like this:</P>
<PRE><FONT COLOR="#0066FF">TWidget * Widget= new TWidget(this);
Widget->Parent = this;
Widget->Left = 10;
Widget->Top = 10;
Widget->Cost = 3.3;
Widget->Description = "This
is a widget";
Widget->TimeCreated = Now();
WriteWidgetToStream("Afile.dat", Widget);
</FONT></PRE>
<P>This code creates an instance of the <TT>Widget</TT> component, assigns values
to its data, and then writes it to disk in the
last line of the code quoted here.
This creates a persistent version of the object and explicitly preserves each property
value.</P>
<P>The <TT>TFileStream</TT> component can be used to stream anything to disk; however,
it has a very useful
<TT>WriteComponent</TT> method that will stream an object automatically,
taking care to store the current values of most published properties. In some cases,
you might find a property that the VCL does not know how to stream. You can usually
convert
the property to an AnsiString, which the VCL component will know how to stream.
This is what I did in this example, when I found that the VCL didn't want to write
a variable of type <TT>TDateTime</TT>.</P>
<P>To construct an instance of
<TT>TFileStream</TT>, you pass in the name of the
file you want to work with and one or more flags specifying the rights you want when
you open the file. These flags are listed in the online help and declared in <TT>SysUtils.hpp</TT>:</P>
<PRE><FONT
COLOR="#0066FF">#define fmOpenRead (Byte)(0)
#define fmOpenWrite (Byte)(1)
#define fmOpenReadWrite (Byte)(2)
#define fmShareCompat (Byte)(0)
#define fmShareExclusive (Byte)(16)
#define fmShareDenyWrite (Byte)(32)
#define fmShareDenyRead
(Byte)(48)
#define fmShareDenyNone (Byte)(64)
#define fmClosed (int)(55216)
#define fmInput (int)(55217)
#define fmOutput (int)(55218)
#define fmInOut (int)(55219)
</FONT></PRE>
<P>FileStreams have a <TT>Handle</TT> property that you can use if
you need it for
special file operations or if you want to pass it to a handle-based C library file
IO routine.</P>
<P>This is not the place to go into a detailed description of how streaming works
in the VCL. However, you might want to open up
<TT>Classes.hpp</TT> and take a look
at the <TT>TReader</TT> and <TT>TWriter</TT> classes, which are helper objects that
the VCL uses when it is time to stream an object. These classes have methods such
as <TT>ReadInteger</TT>, <TT>WriteInteger</TT>,
<TT>ReadString</TT>, <TT>WriteString</TT>,
<TT>ReadFloat</TT>, and <TT>WriteFloat</TT>. <TT>TReader</TT> and <TT>TWriter</TT>
are for use by the VCL, but I have used these classes for my own purposes on several
occasions.</P>
<P>Here is how to read a
component from a stream:</P>
<PRE><FONT COLOR="#0066FF">namespace Widgets
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -