📄 compnent.dph
字号:
Components
If you抳e had any exposure to Borland抯 amazing new product, Delphi, you have no doubt noticed
the minimal amount of code required to write a fully functional application. This is thanks to
the Visual Component Library (VCL), which contains the components necessary to create an
application containing buttons, list boxes, and so on simply by 'drawing' them on a form at
design time. VCL components are a significant improvement for three reasons:
The component code is linked right into your application, which means that you do not have
to include seemingly endless custom control files when you ship. However, this does not mean
that VBX users are left in the cold -- Delphi contains full support for VBX抯 that meet VB 1.0
specifications.
Delphi has the ability to create custom components as well by simply creating a descendant
object of a template component (TComponent) or one of its descendants. This is actually a
powerful concept if you think about it for a moment; it means that you can derive your component
from a fully functional one in a clean, no nonsense fashion. For example, you could derive a
component from TStringGrid and add in-cell editing, then save it as a separate component.
If all this weren抰 enough, you抣l be pleased to learn that you can debug a component, while
an application that uses that component is running, by using Delphi抯 integrated debugger.
In Delphi, there are two types of components: visual and non-visual. Visual components do
something that you can see and often interact with. Non-visual components, on the other hand,
encapsulate a task that has no necessary involvement with the screen at runtime, like run a
timer, interface with a database, or in our case, implement a simple communications interface.
You might wonder what the point of using a visual programming language with a non-visual control
is, and why not just create a unit that contains the same code and place it in your USES
statement. At runtime, the difference would be zero, but during your design phase, you wouldn抰
get the ability to visually change default values, automatically create procedures for events by
double clicking on their names, or see at a glance exactly what is contained in your application
-- that is the beauty of a fully visual design environment.
Creating a Component
To begin the process of creating a component, a unit must be created, a parent component must be
decided upon, and the component must have a register procedure. Every custom component created
in Delphi must be derived from TComponent or a descendant of it. Since we do not need graphical
or user interface abilities, we can stick with deriving our object from TComponent. All of this
is done like so:
unit Comm;
interface
uses Classes;
type
TComm=class(TComponent)
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents ('Additional',[TComm]);
end;
end.
If you were to compile the above example and add it to the component library, you might be
surprised when you found out that it is fully functional. For example, it can be added to a
form, its position can be changed, and it has properties that can be changed and will stay
changed when you reload the form. True, it can抰 do anything useful, but you have to start
somewhere.
Now that we have created the skeleton for the component, we can begin to flesh it out.
One of the first things to be decided upon is properties. Properties for components are usually
added to the published section of the class declaration. This allows the property to be
accessed at design time. A typical property declaration looks like this:
property Port:Byte
read FPort
write SetPort
default 1;
This indicates that Port is of type Byte. But, what is all that funny stuff after the
declaration? That is a clever way to allow some procedure to be called when you set the value
of a variable. The read word indicates that a variable called FPort is to be used when reading
the value of Port. The write word indicates that the procedure SetPort is to be called when
setting the value of Port. The write procedure for a property always has exactly one variable
of the same type as the property itself, and usually, the variable that the corresponding
property represents is set within it. If it had not been necessary to call a procedure when the
value of Port was set, the declaration could have been rewritten as follows:
property Port:Byte
read FPort
write FPort
default tptNone;
The write section now indicates that the variable FPort will be accessed for writes as
well as reads. This brings up an important point -- properties are not variables -- they are
access mechanisms for getting and setting values.
Notice the default word in the above statements. The value after it is the same one that
appears in the object inspector initially at design time, before you change anything. Even
though this default value scheme exists, it is still very important to always initialize the
variable or call the access function for the property with the same default value during your
Create constructor. If you fail to do so, the values you set may not be the values that are
actually used because the compiler checks to see if the value that is entered in the object
inspector and stored with the form is the same as the default value. If it is the actual value,
it is not set because it was assumed to have been set in the Create constructor. If this reason
is not compelling enough, you should be aware that if you create a component at run time, the
values indicated by the default directive are not used at all! The only initialization that
will be done is that which occurred in the Create constructor. One final hazard concerning the
Create constructor should be noted; always declare it with the override word, otherwise it will
never be called at all. The reason you should be on alert for this is if you have been
programming in Borland Pascal for any length of time, it is probably etched within your head
that constructors can never be virtual, so you automatically hit Enter at the end of the line
without a second thought.
Events and Their Handlers
Now that properties are taken care of, we can go on to the events. Events are properties that
access pointers to functions. Consider the following declaration:
property OnReceive:TNotifyReceiveEvent
read FOnReceive
write FOnReceive;
This is similar to what we have seen before, except the return type is TNotifyReceiveEvent.
One of the changes to Delphi抯 implementation of Pascal is that a function can now return nearly
any type. The type declaration for TNotifyEvent is shown below:
TNotifyReceiveEvent = procedure (Sender:TObject;Count:Word) of object;
You probably recognize this as a standard function template. What you may not recognize is the
of object part. All this does is indicate to the compiler that this function will be a member
of an object rather than a far function.
When the OnReceive property is read from or written to, what is really happening is that the
pointer to a procedure is being accessed. When a form is created during run time, the setting
of this pointer happens automatically. Calling the event function is now a piece of cake; all
you have to do is call the underlying member of the property, FOnReceive, in our example.
Implementing Communications
The specification for the communications component is rather simple. We need to be able to
assign the properties at both run-time and design-time, read and write to the comm port, and
respond to events, such as when the receive buffer has a certain amount of data and when the
amount of data in the transmit buffer drops below a certain point, making it ready to receive
more. The code for the communication component is shown in Listing 1.
New properties include: baud rate, number of data bits, parity, communications port,
read buffer size, a value to indicate how full the receive buffer has to be before an event is
triggered, number of stop bits, a value to indicate how empty the transmit buffer has to be
before an event is triggered, and the write buffer size. All of these properties can be set at
run time by clicking on the appropriate cell in the object inspector and selecting the
appropriate value.
Perhaps the most important property is Port, which defaults to tptNone because generally
you do not know what port you will be using at design time. However, if you抮e doing a quick
test program, you can set the port value using the object inspector and proceed immediately in
your program to use the communications component without any runtime setup whatsoever. By
setting the value of Port, what you are really doing is calling the method SetPort, which is
ultimately responsible for opening a port and closing a previously open one if an open port
exists.
Once the port has been opened, SetPort configures it to the proper state by setting all of the
appropriate properties such as baud rate and parity. The code that sets these properties
is not the most efficient in the world because it calls GetCommState and SetCommState for each
property instead of calling GetCommState, setting all of the properties and then calling
SetCommState once. But because this is done only once during the initialization of the port, it
was not a major concern, and I decided to keep it the way it is. However, with a few upgrades
to the code, you can reduce the intitialization time considerably.
The final step in the call to SetPort is to enable communications event posting by calling
EnableCommNot-ification. This results in the posting of three distinct events to a window:
CN_EVENT, CN_RECEIVE, and CN_TRANSMIT. This, of course, means that we need a window handle to
post these events to. AllocateHWnd is simple function for creating a hidden window. It takes
a window procedure as a parameter and returns a window handle. It is the window procedure that
receives the communications notifications and, in turn, calls the appropriate event handler.
The OnReceive and OnTransmit event handlers are straightforward. They are called when
CN_RECEIVE and CN_TRANSMIT are posted to the window respectively. The only other processing
that occurs before the actual event is triggered is to determine how many bytes are in the
appropriate buffer so the count can be included as a parameter.
The OnEvent event is not quite so straightforward. When the property Events is set, thereby
calling the procedure SetEvents, a bit mask of events is created. It is this bit mask that is
used in the call to SetCommEventMask to enable the desired events. During the receipt of a
CN_EVENT, the events that occurred are retrieved and reset by calling GetCommEventMask. These
events are added to a set which is then passed to the OnEvent event handler.
To write data to a port, you must call the Write method:
procedure TComm.Write(Data:PChar;Len:Word);
Here, Data is a pointer to a block of data, and Len is the length of the block. Reading data is
done similarly:
procedure TComm.Read(Data:PChar;Len:Word);
The parameters are the same as before. Remember that the value that you use for Len can be
determined from the Count parameter provided by the OnReceive event.
Error handling is minimal at best. Exceptions are not used, but rather a member function
-- IsError -- returns True when an error state occurrs. Calling IsError also resets the error
state. There are only three cases when IsError will return True: In trying to open a port, in
reading from the port, and in writing to the port.
Initializing By the Numbers
When designing a component, the initialization sequence is important to keep in mind. At first
I had the initial call to SetPort in the Create method, but a problem quickly showed up: Only
the property values set in the Create method would be used, rather than the expected values that
were set using the Object Inspector during design time. This is because design time properties
are set after the Create method.
The Loaded method, on the other hand, provided what I was really looking for because it is not
called until all Object Inspector properties and events are set. This, unfortunately, created
another minor problem: SetPort would be called twice, once during the automatic setting of all
the properties when the form was loaded, and again during my call in Loaded. The problem is not
strictly that the same function would be called twice without just cause, but that the first
call to SetPort would be called at some unknown (at least to me) time. This meant that none,
some, or all of the other properties would have been set and their write procedures called
after SetPort was called. This can get messy if you consider that properties like
WriteBufferSize and ReadBufferSize work by actually closing the port and reopening it. This fix
was simple: A flag, HasBeenLoaded, is set to False during the Create method and set to True
during the Loaded method. When SetPort is called, it checks the flag to make sure it is True
before allowing the port to open.
Adding the Component
To add the communications component to the Component Palette, use the Install Components dialog
box. A palette bitmap (see Figure 1) was designed for the component and will be displayed on
the Component Palette(see Figure 2) in the 揂dditional
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -