|
 
- 13 -
Beyond the
Basics
Today you
learn ways to turn a good Windows application into a great Windows
application. Specifically, I discuss the following:
This
discussion continues tomorrow as you look at implementing advanced
Windows programming features in your Delphi applications.
Creating
Window Decorations
No, I'm not
talking about pretty lights around the front window of your house. What
I am talking about are features such as toolbars and status bars. These
features are often called window decorations. This section deals with
these types of decorations and how to implement the m in your
application.
Toolbars
A toolbar
(also called a control bar or a speedbar) is almost standard equipment
for Windows programs today. Users expect certain amenities, and a
toolbar is one of them. A top quality toolbar should have the following
features and capabilities:
-
Buttons
representing tasks that are also available on the application's
menus.
-
Enabling
and disabling of buttons when appropriate.
-
Tooltips
describing a button's function.
-
Additional
hint text that is displayed in the application's status bar.
-
Docking
capability.
-
Other
controls such as combo boxes or drop-down menu buttons.
Some of the
features in this list are optional features that not every toolbar needs
to have. With Delphi, implementing these toolbar features is easy to
accomplish. A little later in the chapter I talk about command enabling
in the section "Adding Functionality with Command Enabling."
I'll save discussion of command enabling for toolbar buttons for that
time.
NOTE: It
is considered good practice to place on the toolbar only buttons that
have corresponding menu items. The toolbar is an alternative to using
the menu. It should not contain items found nowhere else in the
program.
On Day 8,
"Creating Applications in Delphi," I said that the easiest way
to construct a toolbar is to use the Application Wizard. Even if you
already have a partially written application, you can still use the
Application Wizard to create a toolbar. Just generate an application
with the Application Wizard, copy the panel with the toolbar to the
Clipboard, reopen your original application (don't bother saving the
Application Wizard application), and paste the toolbar into your
application from the Clipboard. Slick and easy.
However, the
Application Wizard doesn't give you everything you could possibly need
in a toolbar. Most notably, the Application Wizard uses the old method
of creating a toolbar--with a panel and speed buttons. The preferred way
of creating a toolbar is to use the ToolBar and CoolBar components
(found on the Win32 tab of the Component palette). Let's take a look at
those components next.
NOTE: The
CoolBar and ToolBar components require version 4.72.2106.4 or later of
COMCTL32.DLL, which should have been installed as part of the Delphi
installation. If you don't have the latest version of this DLL, you
can find it at Microsoft's Web site (http://www.microsoft.com). When
you deploy your applications using these components, you should
install COMCTL32.DLL version 4.70 or later. Be sure you are using a
good installation program so that you don't overwrite a new version of
this DLL when you install your application.
The CoolBar
Component
The CoolBar
component is an encapsulation of the Win32 cool bar (sometimes called a
rebar). This component is a specialized container control. Most of the
time the CoolBar is used as a container for toolbars, but its use isn't
limited strictly to toolbars.
Cool Bar
Bands
A cool bar
has bands that can be moved and resized at runtime. The bands show a
sizing grip on the left side, giving the user a visual cue that the band
can be moved or sized. Cool bar bands are represented by the TCoolBand
class. A cool bar band can contain only one component. Usually that
component is a toolbar, but it can be a combo box or any other
component. Let's do an exercise to better understand how the CoolBar
component works:
-
-
1. Start
with a new application and drop a CoolBar component on the form.
-
-
2. Drop
a ComboBox component on the cool bar. Delphi creates a new band to
hold the combo box. Notice that the combo box fills the width of the
cool bar.
-
-
3. Drop
another ComboBox component on the coo l bar. Another band is created
and is placed below the first band.
-
-
4. Place
your mouse cursor between the sizing grip and the combo box on the
second band. The cursor will change to a pointing hand, indicating
that you can move the band. (You can also use the sizing grip to
drag the band.) Drag the band up into the band above it. As you do,
the first band will shrink to make room for the band you are
dragging. Drop the band near the middle of the cool bar. You can now
use the sizing grips to resize either band.
-
-
5. Place
a Panel component on the cool bar. A new band is created to hold the
panel.
-
-
6.
Select the cool bar and change its AutoSize property to True.
-
-
7. Place
a Memo component on the form below the cool bar. Set its Align
property to alClient.
Now your
form looks like Figure 13.1.
FIGURE
13.1. The form with a cool bar
and three bands.
Now run the
program. Experiment with the cool bar bands. Drag them up or down or
resize them. Notice that as you drag the bands up or down, the cool bar
resizes as needed and the memo always fills the remaining client area.
Cool bar
bands are accessed through the Bands property. This property is a
TCoolBands, which is a list of TCoolBand components. If you want to hide
the second band, you can do this:
CoolBar.Bands[1].Visible
:= False;
You can add
bands in two ways. As you have already seen, you can create a band by
dropping any component on the cool bar, but you can also use the Bands
Editor. To invoke the Bands Editor, double-click the cool bar or click
the ellipsis button next to the Bands property in the Object Inspector.
You add bands by clicking the Add button; you delete bands by clicking
the Delete button. The Move Up and Move Down buttons enable you to
change the order of the bands.
NOTE : If
the AutoSize property is set to True, you will have to turn it off
temporarily if you want to add new bands by dropping components on the
cool bar. Set the AutoSize property to False, make the cool bar
higher, drop a component on the cool bar, and set the AutoSize
property to True again.
NOTE: When
you select a band in the Bands Editor, the Object Inspector displays
the band's properties. Figure 13.2 shows the Bands Editor and the
Object Inspector when a band is selected.
FIGURE
13.2. The cool bar Bands Editor.
The Bitmap
property enables you to set a background bitmap for a band. To select an
image that will appear to the left of the band, you use ImageIndex.
ImageIndex requires the ImageList property of the cool bar to be set to
a valid TImageList. You can set a band's minimum height and width
through the MinHeight and MinWidth properties. To make a band immovable,
set the FixedSize property to True.
Other
CoolBar Properties
A cool bar
can be either vertical or horizontal. By default the Align property is
set to alTop. To make a vertical cool bar, change the Align property to
alRight or alLeft. Some components, when placed on a cool bar,
automatically orient themselves based on whether the cool bar is
vertical or horizontal. Another way to change the orientation of the
cool bar is by setting the Vertical property.
The Bitmap
property enables you to set a background bitmap for the cool bar. The
bitmap you choose will be tiled to fill the cool bar's background. Note
that this sets the background bitmap for the cool bar itself, not for
any individual bands on the cool bar (as discussed in the previous
section). You use the ImageList property to set the image list that the
bands will use to display an image to the left of any band that has its
ImageIndex property set.
The AutoSize
property determines whether the cool bar w ill resize itself when bands
are moved. You saw the effect of the AutoSize property in the preceding
exercise.
NOTE:
Check out the TControlBar component on the Additional tab of the
Component palette. TControlBar is a native VCL component that works
very much like a cool bar. This component does not rely on
COMCTL32.DLL as does TCoolBar, so it is less susceptible to the whims
of Microsoft.
The ToolBar
Component
The ToolBar
component encapsulates the Win32 toolbar control. The toolbar will
automatically arrange and size the controls placed on the toolbar so
that they all have a consistent height. You can use the ToolBar
component with or without a cool bar. If you have only a single toolbar,
use the toolbar without a cool bar. If you have multiple toolbars that
you want to enable the user to move, place two or more toolbars on a
cool bar.
Creating a
toolbar and adding buttons to it is very easy. If your toolbar buttons
will have glyphs (and most do), you have to use an ImageList component
for the glyphs. To illustrate how to build a toolbar with the ToolBar
component, let's again go back to the ScratchPad program. You'll tear it
apart and put it back together.
Removing the
Old Toolbar
If you
recall, the toolbar you created for ScratchPad originally was just a
placeholder. The first thing you need to do is get rid of the old
toolbar by performing these steps:
-
-
1. Click
the Memo component and change its Align property to alNone. Drag
down the top of the Memo to make room for the new toolbar.
-
-
2. Click
the toolbar component and delete it.
Adding a New
Toolbar
Now you can
start adding components back again. The first thing to do is add a cool
bar and a toolbar. You don't really need a cool bar at this stage
because you have only one toolbar, but you might want to add another
toolbar later, so it's best t o plan ahead. Perform these steps:
-
-
1. Drop
a CoolBar component on the form. It automatically aligns itself to
the top of the form. Change its Name property to CoolBar.
-
-
2. Drop
a ToolBar component on the cool bar. Change its Name property to
MainToolBar.
-
-
3.
Double-click the EdgeBorders property in the Object Inspector to
show all the edge border items. Change the ebTop style to False (all
EdgeBorders styles should now be False).
-
-
4.
Change the Flat property to True. This gives the toolbar buttons a
flat appearance until the cursor passes over them.
-
-
5. Click
the cool bar and change the AutoSize property to True. The cool bar
sizes itself to the size of the toolbar.
-
-
6. Click
the Memo component and change its Align property back to alClient.
Adding
Buttons to the Toolbar
Now you
begin adding buttons to the toolbar; you will add several buttons and a
few spacers. At first the buttons won't have glyphs on them, but you'll
take care of that later. For now, follow these steps:
-
-
1.
Right-click the toolbar and choose New Button. A button is placed on
the toolbar. Change the button's Name property to FileNewBtn. Set
the Hint property to New|Create A New File and the ShowHint property
to True. (Remember when you wrote the hint code on Day 8? That code
is still in the program, so the new hints will work immediately.)
-
-
2.
Right-click on the toolbar and again choose New Button. A second
button is placed on the toolbar to the right of the first button.
Change its Name property to FileOpenBtn. Set the Hint property to
Open|Open An Existing File and the ShowHint property to True.
-
-
3. Add
another button. Change this button's Name property to FileSaveBtn.
Set the Hint property to Save|Save A File and the ShowHint property
to True.
TIP:
Buttons and spacers added to the toolbar always appear to the right of
the toolbar's last control. You can't insert a button at a specific
location in the toolbar, but after a button or spacer is added, you
can drag it to a different location on the toolbar. The existing
buttons will make room for the new button.
That
finishes the first set of buttons (except for the glyphs). You are about
to add a second set of buttons, but before you do, there needs to be a
little separation between the first set of buttons and the second. Back
to work:
-
-
1.
Right-click on the toolbar again, but this time choose New
Separator. A separator is added to the toolbar.
-
-
2. Add
another button. This time change the Name property to EditCutBtn and
the Hint property to Cut|Cut To Clipboard.
-
-
3. Add
buttons for Copy and Paste. Change their Name and Hint properties as
appropriate.
-
-
4. Add
another separator.
-
-
5. Add a
button called HelpAboutBtn. Change its Hint property to About|About
ScratchPad.
-
-
6.
Select the Cut, Copy, Paste, and Help buttons (use Shift+-Click to
select each button). Change the ShowHint property to True. It will
be changed for all buttons selected.
Your form
now looks like the one shown in Figure 13.3.
-
Figure
13.3. The ScratchPad main
form after adding a toolbar.
Making the
Toolbar Buttons Functional
You now have
a good start on the toolbar, but the toolbar buttons don't do anything
because you haven't assigned any event handlers to their OnClick events.
Let's do that next.
-
-
1. Click
the FileNewBtn (the first button) and select the Events page in the
Object Inspector. Click the drop-down arrow next to the OnClick
event and choose FileNewClick. The button is now hooked up to the
FileNewClick event handler.
-
-
2.
Repeat step 1 for each remaining button, being careful to select the
appropriate OnClick handler for each button (FileOpenClick,
FileSaveClick, EditCutClick, and so on).
-
-
3. If
you haven't yet created an About Box for ScratchPad, create one.
When you are done, create an event handler for the Help|About menu
item. Hook the OnClick event of the HelpAboutBtn to the event
handler for the Help|About menu item.
Adding
Bitmaps to the Toolbar Buttons
Obviously
this toolbar is missing something. You need to add glyphs to the toolbar
buttons. To do so, you must add an ImageList component to the form by
following these steps:
-
-
1. Place
an ImageList component on the form. (You find it on the Win32 tab of
the Component palette.) Change the Name property to ImageList.
-
-
2.
Right-click the ImageList component's icon on your form and choose
ImageList Editor. The ImageList Editor is displayed. (You can also
double-click the ImageList icon on your form to display the
ImageList Editor.)
-
-
3. Click
the Add button. Navigate to the Common Files\Borland Shared\Images\
Buttons directory. Select the FILENEW.BMP file and click Open.
-
-
A
message box appears and asks whether you want to separate the bitmap
into two images. What is happening here is that the image list's
Width and Height properties are both set to 16. The bitmap you have
selected is wider than 16 pixels, so it has to be split into two
images or shrunk to fit. If you recall, the button bitmaps that come
with Delphi are a single bitmap with two images. The first image is
the normal button bitmap, and the second image is for the disabled
button. You will have the ImageList Editor split the bitmap into two
images, and then you will delete the second part of the image.
-
-
4. Click
Yes to have the ImageList Editor split the bitmap into two images.
The ImageList Editor now shows two images. You need only the first
of these images, so click on the second image (the disabled button
image) and click the Delete button.
-
-
5. Click
the Add button again. This time choose the FILEOPEN.BMP file. Click
Yes again when prompted to split the bitmap into two images. Click
on the disabled image for this bitmap and delete it. Figure 13.4
shows the image editor as it looks just before deleting the second
image.
FIGURE
13.4. The
ImageList Editor after adding three images.
-
-
6.
Repeat step 5 for each remaining button (File Save, Cut, Copy,
Paste, and About). Use any bitmaps you like, but make certain you
delete the extra bitmap each time you add an image to the list. Also
make sure that the images in the ImageList editor follow the order
of the buttons on the toolbar. When you are done, you will have
seven images in the image list, numbering from 0 to 6.
-
-
7. Click
OK to close the ImageList Editor.
TIP: You
can select multiple images in the ImageList Editor's Add Images dialog
box and add them all to the image list at one time.
Now you are
ready to hook the image list to the toolbar. Click on the toolbar.
Locate the Images property in the Object Inspector and choose ImageList
from the drop-down list. If you did everything right, your buttons now
have glyphs. You probably didn't notice, but each time you added a
toolbar button, Delphi automatically incremented the ImageIndex property
for the button. Because you created the buttons and images in the same
order, the glyphs on the buttons should be correct. If a button is
wrong, you can either change the button's ImageIndex property or go back
to the ImageList Editor and change the order of the images in the image
list.
TIP: To
rearrange the images in an image list, drag them to a new position in
the ImageList Editor.
Disabled
Button Glyphs
Right now
you have glyphs only for the buttons in the enabled state. You also need
glyphs that will be displayed when the buttons are disabled. You aren't
disabling the toolbar buttons yet, but you will be before the day is
done. There are two ways to implement the disabled button glyphs:
Certainly
the easier of these two methods is to let the toolbar create the
disabled state buttons automatically. Most of the time this is
sufficient. Sometimes, however, the algorithm for creating the disabled
buttons glyphs doesn't work well. (The glyphs end up losing definition
and don't look quite right.) This happens with buttons that don't have
enough contrasting colors. In that case, you can create a second image
list that contains the disabled button glyphs. Set the DisabledImages
property of the toolbar to the image list containing the disabled
glyphs, and the rest is automatic. For ScratchPad you're going to go
with the automatic disabling of toolbar buttons, so nothing further is
required.
Poor old
ScratchPad is back together again. This is a good time to save the
project. After saving the project, click the Run button and give the
program a workout. Click the buttons and see whether they do what they
are supposed to do. If all went well, you have a working program again.
If your program won't compile, review the steps and try to fix the
problem. If all else fails, you can refer to the ScratchPad project of
the book's code, available at http://www.mcp.com/info (Day 13).
Toolbar Tips
and Hints
I covered
nearly everything there is to be said about tooltips and hints in the
discussion of components on Day 7, "VCL Components"; again on
Day 8, "Creating Applications in Delphi," when adding hint
text support for the ScratchPad program; and again today when rebuilding
the toolbar.
There is one
issue I didn't talk about, and that is changing the tooltip properties.
The TApplication class has four properties that control how the tooltips
behave. Table 13.1 lists these properties and their descriptions.
TABLE 13.1.
TApplication PROPERTIES PERTAINING TO TOOLTIPS.
|
Property
|
Description
|
|
Hintcolor
|
Sets
the background color of the Tooltip window. Default: CLINFOBK
|
|
HintHidePause
|
Controls
how long to wait (in milliseconds) before hiding the tooltip if
the mouse cursor remains stationary over the component. Default:
2500 milliseconds
|
|
HintPause
|
Controls
the interval between the time the mouse cursor is paused over a
component and the time the tooltip appears (in milliseconds).
Default: 500 milliseconds
|
|
HintShortPause
|
Controls
how long to wait before showing tooltips after they have already
been shown--for example, when the user is roaming over a group of
toolbar buttons. Default: 50 milliseconds
|
The default
values for these properties are sufficient for most applications. Still,
if you need to change any hint properties, you have that option.
HOUSE
RULES: TOOLBARS AND HINTS
-
Don't
use tooltips on controls where they will be in the way when the
user wants to read the text in the control. In particular, don't
use tooltips for edit c ontrols and combo boxes. At a minimum,
give the user the option to turn off tooltips for those types of
controls.
-
Keep
your tooltip hint (the short hint) brief and to the point.
-
Make
your status bar hint (the long hint) more descriptive and
meaningful.
-
Consider
giving your users the option of turning off hints altogether.
Adding Other
Controls to Toolbars
Because the
ToolBar component is so versatile, nothing special needs to be done to
add other types of controls to your toolbar. The most common type of
control to add to a toolbar is a combo box. You can use a toolbar combo
box to select a font, a configuration option, a zoom setting. . . the
possibilities are endless.
To add a
component to the toolbar, select the component from the Component
palette and drop it on the toolbar. The toolbar will take care of
aligning the component. Add spacers as necessary to separate components
visually. When the component is on the toolbar, you deal with it just as
you would a component on a form. I could try to complicate this for you,
but the truth is, it's just that simple. If you've never tried to
implement a combo box on a toolbar using the Windows API, you cannot
appreciate how much work Delphi saves you. Take my word for it--it's
significant.
Toolbars
come in many shapes and sizes, and Delphi makes creating and
implementing toolbars very easy. With Delphi, you no longer have the
excuse, "That's too hard!" In fact, you might even enjoy
creating toolbars with Delphi.
Dockable
Toolbars
Dockable
toolbars are common in many Windows programs. Dockable toolbars are a
paradox of sorts. On the one hand, they are a cool feature and most
power users expect a good application to have dockable toolbars. On the
other hand, I doubt anyone actually uses the docking capability of most
dockable toolbars. Still, dockable toolbars are easy enough to implement
in Delphi, so you might as well provide them.
NOTE: The
docking features discussed in this section apply to any windowed
control, not just to toolbars.
Creating a
Dockable Toolbar
Making a
toolbar dockable requires two steps:
After you
set these two properties, you can drag your toolbar around the screen.
Dragging the toolbar around the screen doesn't get you very much,
though. In order for dockable toolbars to make sense, you have to have a
target for the drop part of the drag-and-drop equation.
Dock Sites
A dockable
toolbar needs to have a place to dock. As Roger Waters said, "Any
fool knows a dog needs a home." Home for a dockable toolbar is a
dock site. A dock site is any windowed component that has its DockSite
property set to True. Components that are typically used as dock sites
are TCoolBar, TControlBar, TPageScroller, TPanel, and TPageControl.
There are other controls that have a DockSite property, but those
controls are less likely to be used as dock sites.
An exercise
helps illustrate the use of dock sites. Follow these steps:
-
-
1. Drop
a CoolBar component on a blank form. Set its DockSite property to
True.
-
-
2. Drop
a ToolBar on the CoolBar. Set the toolbar's DragKind property to
dkDock and its DragMode property to dmAutomatic. Create a few
buttons on the toolbar so that you can better see the toolbar.
-
-
3. Place
a second CoolBar on the form. Change its Align property to alBottom
and its DockSite property to True.
-
-
4. Place
a third CoolBar on the form. Change its Align property to alLeft and
its DockSite property to True. Size the CoolBar so that its width is
40 pixels or so.
Now run the
program. Drag the toolbar from dock site to dock site. Notice how the
toolbar cha nges its orientation when you drag it to the cool bar on the
left side of the form.
Let's
experiment some more. Set each of the CoolBar components' AutoSize
properties to True. This will cause each CoolBar to change its size
based on the controls it contains. Now run the program again and move
the toolbar to the individual dock sites. Notice that each cool bar is
nearly invisible until the toolbar is docked to the site. Then the cool
bar expands to contain the toolbar.
Floating
Toolbars
A toolbar
can be made into a floating toolbox by simply dragging the toolbar off
its dock site and dropping it anywhere (anywhere other than another dock
site, that is). The toolbar becomes a floating window. You can specify
the type of window that should host the floating dock site by setting
the FloatingDockSiteClass property to the name of the class you want to
act as the parent to the floating toolbar. For example, suppose that you
design a form that has all the characteristics you want for a custom
floating toolbox, and that the form is called MyToolBox. In that case,
you could cause this form to be the host for a floating toolbar with
this code:
ToolBar.FloatingDockSiteClass
:= TMyToolBox;
When the
toolbar is undocked and the mouse button released, Delphi will
automatically create an instance of the TMyToolBox class and place the
toolbar in the form for that class. To dock the floating toolbox again,
simply drop it on any dock site. To make a dock site accept a floating
toolbox, you must respond to the OnDockOver and OnDockDrop events for
the dock site. In the OnDockDrop event handler, call the ManualDock
method of the toolbar to cause the toolbar to dock.
Status Bars
A status bar
is another feature that makes an application more marketable. Not all
applications benefit from a status bar, but many can. The VCL StatusBar
component, which encapsulates the Win32 status bar control, makes
creating status bars a breeze. First, take a quic k look at the major
properties of the StatusBar component, listed in Table 13.2.
TABLE 13.2.
StatusBar PROPERTIES.
|
Property
|
Description
|
|
AutoHint
|
Automatically
displays hints in the status bar when the mouse cursor passes over
any component whose Hint property has been set.
|
|
Panels
|
For
status bars with multiple panels. This property defines the
individual panels.
|
|
SimplePanel
|
Determines
whether the status bar shows a simple panel or multiple panels.
|
|
SimpleText
|
The
text for the status bar's simple panel.
|
|
Property
|
Description
|
|
SizeGrip
|
Determines
whether the status bar displays the sizing grip in the lower-right
corner. The sizing grip provides an area that the user can drag to
size the window. The absence of the sizing grip doesn't prevent
the window from being sized, but the presence of the size grip
makes sizing a window easier.
|
|
UseSystemFont
|
Always
uses the current system font, overriding the settings of the Font
property. This is particularly useful for users who use Plus pack
themes.
|
As you can
see from this table, a status bar can be a simple status bar or have
multiple panels. Let's discuss this choice next.
Simple or
Complex?
A status bar
can be either a simple status bar or a complex status bar. A simple s
tatus bar has a single panel that occupies the entire status bar. If you
want a simple status bar, set the SimplePanel property to True. The
SimplePanel property acts as a toggle. You can switch between a simple
and a complex status bar at runtime by setting SimplePanel to True or
False, accordingly.
A complex
status bar is one with multiple panels. If you elect to use a complex
status bar, you can use the StatusBar Panels Editor to set up the panels
you want to see on your status bar. To invoke the StatusBar Panels
Editor, double-click on the Value column of the Panels property. To add
a panel, click the Add New button on the StatusBar Panels Editor. To
delete a panel, click the Delete Selected button. To edit a panel,
select the panel and then make changes to the panel's properties in the
Object Inspector. Figure 13.5 shows the StatusBar Panels Editor and
Object Inspector when editing panels.
FIGURE
13.5. The StatusBar Panels
Editor.
NOTE: The
individual panels in a complex status bar are instances of the
TStatusPanel class.
Most of the
properties are self-explanatory, but a couple require further note. The
Text property contains the text that will be displayed in the panel. You
can also use the Text property at runtime to change the text in the
panel. Setting the status bar text is discussed a little later; you
don't need to supply text for the panel at design time if you are going
to change the text at runtime.
You can set
the Style property to either psText or psOwnerDraw. If the Style is set
to psText (the default), the panel behaves as you would expect. The text
is aligned in the panel according to the value of the Alignment
property. If the Style is set to psOwnerDraw, it is up to you to draw
any text or image that is displayed in the panel. Owner drawing of panel
items is discussed later in the section "Owner-Drawn Status Bar Pan
els."
The Width,
Bevel, and Alignment properties for the panel are straightforward.
Experiment with these properties to see how they affect your status
bar's appearance.
NOTE: In
the Form Designer you immediately see the results of changes made to
the status bar via the StatusBar Panels Editor. Position the StatusBar
Panels Editor so that you can view the status bar as you work with the
StatusBar Panels Editor. Each time you make a change, it will be
reflected in the Form Designer.
When you are
done adding panels to the status bar, close the StatusBar Panels Editor
and return to the Form Designer.
NOTE: When
you modify the Panels property of a StatusBar component, the Form
Designer automatically sets the SimplePanel property to False. The
assumption is that if you are using multiple panels, you don't want to
have a simple status bar.
Changing
Text in the Status Bar
There are
two ways to change text in a status bar:
-
Manually
modify the SimpleText property of the status bar (for simple status
bars) or the Text property of an individual panel (for complex
status bars).
-
Let VCL
automatically supply the status bar text by setting the AutoHint
property to True.
Manually
changing the text in the status bar is simple, particularly if you have
a simple status bar. When the SimplePanel property is True, you can set
the SimpleText property to the text you want displayed in the status
bar:
StatusBar.SimpleText
:= `This shows up in the status bar.';
In the case
of complex status bars, changing the text is only slightly more
complicated. If you want to change the text for the first panel of a
complex status bar, you would use something like this:
StatusBar.Panels[0].Text
:= `Status Bar Text';
The Panels
property of the StatusBar component has a property called Items that is
an array of panels in the status bar. Setting the Text property for an
element in the Items array changes the text for that panel (because
Items is the default array property for the Panels object, you don't
have to specifically reference Items). As you can see, the array is
0-based. The first panel in the status bar is array element 0.
Automatic
status bar hint text doesn't require much in the way of explanation. All
you have to do is set the AutoHint property to True. The rest is, as the
property name implies, automatic.
NOTE: You
can still modify the status bar's text manually even when using
AutoHint. There's nothing to stop you from changing the text manually,
but remember that the text will be replaced the next time the mouse
passes over a component with hint text.
Owner-Drawn
Status Bar Panels
Earlier I
said that a panel's Style property can be either psText or psOwnerDraw.
When you set a panel's style to psOwnerDraw, you must take the
responsibility of drawing anything in the panel that needs to be
displayed there. It is unlikely that you are going to go to the trouble
of using an owner-drawn panel just to display text. Usually it means you
are going to display some sort of icon or bitmap in the status bar.
Regardless of what is being drawn on the panel, the steps are the same:
-
1. Set
the panel's Style property to psOwnerDraw (usually via the StatusBar
Panels Editor).
-
-
2.
Respond to the OnDrawPanel event.
Obviously,
the real work here is going to take place in the event handler for the
OnDrawPanel event. The declaration for the OnDrawPanel event handler
looks like this:
procedure
TForm1.StatusBar1DrawPanel(StatusBar: TStatusBar;
Panel: TStatusPanel; const Rect: TRect);
The
StatusBar parameter is a pointer to the status bar. Usually you have a
pointer to the status bar anyway (the Name property of the StatusBar
component), so this param eter is not all that useful unless you are
using multiple owner-drawn status bars. The Panel property is a pointer
to the particular panel that currently needs drawing. You can use this
parameter to determine which panel needs drawing if you have more than
one owner-drawn panel in your status bar. The Rect parameter contains
the panel's size and position. The Rect parameter is important because
it tells you the exact dimensions of the drawing area.
The
OnDrawPanel event handler is called once for each panel that has its
Style property set to psOwnerDraw. If you have only one panel to draw,
you don't have to worry about much except the Rect parameter. If you
have multiple panels to draw, you must first determine which panel to
draw and then do your drawing. An illustration might help to explain
this. The book's code includes a program called StatBar that illustrates
some of the things you can do with status bars. Run the program and
examine its source for tips on implementing status bars in your
applications. Figure 13.6 shows the StatBar program running.
FIGURE
13.6. The StatBar program with
owner-drawn status bar panels.
As you can
see, the status bar in this program has multiple panels. The middle
three panels are owner-drawn. The panels marked OVR and EXT simulate the
status bar on a word processing program or code editor. In those types
of programs, the Overtype or Extended Selection modes might be on or
off. If the mode is on, the text in the status bar panel shows in black.
If the mode is off, the text has a 3D disabled-text appearance. The
third owner-drawn panel displays a stock Windows icon to illustrate the
use of a graphic on a status bar. Run the program and experiment with it
to learn how it works.
Listing 13.1
shows the OnDrawPanel event handler from the StatBar program. Examine it
and read the comments to understand what is going on in the code.
LISTING
13.1. THE StatusBarDrawPanel METHOD OF THE StatBar PROGRAM.
procedure
TMainForm.StatusBarDrawPanel(StatusBar: TStatusBar;
Panel: TStatusPanel; const Rect: TRect);
var
R : TRect;
Icon : HIcon;
begin
with StatusBar.Canvas do begin
{ Create a temporary TRect object. The Rect parameter
{ is const so we can't change it. }
R := Rect;
{ Check to see if panel 3 is the panel which needs
{ to be drawn. If so, draw an icon in the panel. }
if Panel.Index = 3 then begin
{ Load one of the stock
Windows icons. This time
{ using the API is easier
than using VCL. }
Icon := LoadIcon(0,
IDI_HAND);
{ Draw the icon and shrink it
down to 15 x 15 pixels. }
{ Center it in the panel,
too. }
DrawIconEx(Handle, Rect.Left
+ 6, 3,
Icon, 15, 15, 0,
0, DI_NORMAL);
{ Nothing more to do. }
Exit;
end;
{ This rather lengthy if statement checks to see if
{ either the Overtype Mode or Extended Selection
{ check boxes are checked. If so, then what we need
{ to do is to draw the text twice. First, we draw it
{ in white. Then we draw it again, offset by 1 pixel,
{ in gray. The effect is a 3D disabled-text look. }
if ((Panel.Index = 1) and (OvrMode.Checked = False))
or
((Panel.Index = 2) and
(ExtendedSel.Checked = False))
then begin
{ Move over and down one pixel for the
offset. }
Inc(R.Left);
Inc(R.Top, 2);
{ Change the text color to white. }
Font.Color := clWhite;
{ Set the backround mode to transparent
so the
{ text appears hollow and so that the
white
{ text can be seen under the gray. }
Brush.Style := bsClear;
{ Draw the text using the API function
DrawText. }
{ I use DrawText because it allows me to
center
{ the text both horizontally and
vertically within
{ the given rectangle. }
DrawText(Handle, PChar(Panel.Text), -1,
R, DT_CENTER or DT_VCENTER or
DT_SINGLELINE);
{ Set the color to gray because we're
going to
{ draw the t
ext in gray in a moment. }
Font.Color := clGray;
{ Set the rect back to the original size. }
Dec(R.Left);
Dec(R.Top, 2);
end;
{ Display the text. If the item is not disabled then
{ the default color (black) is used to draw the text.
}
{ If the item is disabled, then the text color has
{ been set to gray by the code above. }
DrawText(Handle, PChar(Panel.Text), -1,
R, DT_CENTER or DT_VCENTER or
DT_SINGLELINE);
end;
end;
This code
might seem intimidating, but most of it is comment lines. The code
itself is relatively simple. The comment lines explain what is happening
at each step: The 3D appearance for the disabled text is accomplished by
drawing the text once in white and then drawing it again in gray with a
slight offset. The result is that the text looks recessed. The icon is
displayed using the Windows API functions LoadIcon and DrawIconEx.
Owner
drawing of status bar panels is daunting at first, but you'll soon find
out that it's not all that bad. You might write Windows applications for
a long time and never need owner-drawn panels in your status bar. If you
ever need them, however, you'll know that's not impossible to
accomplish.
Adding
Functionality with Command Enabling
Command
enabling is the process of enabling
or disabling buttons depending on current conditions. For example,
there's not much point of having the Cut or Copy button or menu item
enabled for a text editor when no text is currently selected. Likewise,
if there is no text in the Clipboard, the Paste button should be
disabled.
Command
enabling isn't difficult, especially with Delphi's new TActionList
component. Still, it takes time to get right. It takes time because you
have to pay attention to detail. (Sometimes it is attention to detail
that separates the great applications from the mediocre applications.)
Command
Enabling with TActionList and TAction
The TAction
class provides a convenient way of performing command enabling.
TActionList, the nonvisual component that manages actions, is found on
the Additional tab of the Component palette. TActionList, as its name
implies, contains a list of TAction objects. You create an action and
then assign that action to any controls that need to be enabled or
disabled based on that action. By controls I mean menu items, toolbar
buttons, context menu items, and so on.
Let's take
the Edit|Cut menu item, for example. You could have at least three
objects associated with this particular task:
-
A main
menu item
-
A
toolbar button
-
A pop-up
menu item
You create
actions with the ActionList Editor. Given the Edit|Cut example, you
would create an action for Cut called, say, CutAction. Then, using the
Object Inspector, you assign CutAction to the Action property of each of
the objects that correspond to the cut operation (toolbar buttons and
menus, for example). At runtime, when you need to enable the Cut option,
you can do so with just one line of code:
CutAction.Enabled
:= True;
This will
enable all components with their Action properties set to CutAction.
Disabling the Cut items is as simple as assigning False to the Enabled
property of the action. The OnUpdate event of TAction and TActionList
provides a convenient place to put your command-enabling code.
Command
enabling with TAction is something that you have to experience to fully
appreciate. You add command enabling to ScratchPad in the next section.
Implementing
Command Enabling
In this
section you implement command enabling for the ScratchPad program. First
you set up the ActionList component, and then you hook up the various
components to the action list.
Creating an
ActionList
It all
starts with the ActionList component, which is the heart of the
command-enabling system in VCL. First you add the actions for the Edit
menu item s. After that, you add an action for the File menu's Save and
Save As menu items. The following steps will take you through the
ActionList setup process.
Creating the
Edit Menu Actions
The
following steps show you how to create actions for the Edit menu's Cut,
Copy, and Paste items. Perform these steps:
-
-
1. Place
an ActionList component on the form and change the Name property to
ActionList.
-
-
2.
Double-click the ActionList icon to invoke the ActionList Editor.
-
-
3.
Right-click on the ActionList Editor and choose New Standard Action
from the context menu. Choose the TEditCopy action and click OK.
Notice that the Object Inspector changes to show the properties of
the TEditCopy action class.
-
-
I want
to pause for a moment here and explain more about how actions work.
Examine the Object Inspector at this point. Notice that the
TEditCopy action has several familiar properties and that those
properties all have values. In particular, notice that the Caption,
Hint, ImageIndex, and ShortCut properties already have values that
correspond to an Edit menu's Copy item. These properties will be
transferred to any control that you assign this action to, which
means that you must set any properties of the action to the values
that you want the corresponding components to acquire. That won't
make much sense until you attach the action to components in the
next section, but it will be clear at that time. Let's continue with
the action creation process.
-
-
4.
Change the new action's Name property to CopyAction, the Hint
property to Copy|Copy to Clipboard, and the ImageIndex property to
3.
-
-
5.
Create another standard action, this time a TEditCut action. Change
the Name to CutAction, the Hint property to Cut|Cut to Clipboard,
and the ImageIndex property to 4.
-
-
6.
Create a third action, a TEditPaste action. Change the Name property
to PasteAction, the Hint property to Paste|Paste from Clipboard, and
the ImageIndex property to 5.
You have now
created the actions for the primary Edit menu items.
Creating the
File Menu Actions
Next you
need to create the actions for the File menu's Save and Save As menu
items. Perform these steps:
-
-
1.
Right-click on the ActionList Editor and this time choose New
Action. A new TAction is created and is shown in the Object
Inspector.
-
-
2.
Change the Name property to SaveAction, the Caption property to
&Save..., the Category property to File, the Hint property to
Save|Save a File, the ImageIndex property to 2, and the ShortCut
property to Ctrl+S.
-
-
3.
Create another new action. Change the Name property to SaveAsAction,
the Caption property to Save &As..., and the Category property
to File. You don't need to set the Hint or ImageIndex properties
because this action doesn't have an associated button on the
toolbar. Figure 13.7 shows the ActionList Editor at this point.
-
FIGURE
13.7. The
ActionList Editor after creating the action items.
-
-
4. Close
the ActionList Editor.
NOTE: You
can create categories of actions if you have several dozen actions to
keep track of. To create an action category, simply set the Category
property of one or more actions to any text you want. For example, to
create a category for the edit actions created earlier, set each
action's Category property to Edit. Action categories are simply for
organization and don't have any bearing on the way actions operate.
Attaching
Actions to Components
The next
step is to attach the actions you just created to the various menu items
and toolbar buttons to which those actions correspond. Perform these st
eps:
-
-
1.
Double-click on the MainMenu component to start the Menu Editor.
-
-
2.
Select the File|Save menu item and change its Action property to
SaveAction.
-
-
3.
Select the File|Save As menu item and change its Action property to
SaveAsAction.
-
-
4. Move
to the Edit menu and select the Cut menu item. Change the Action
property to CutAction.
-
-
5.
Repeat step 4 for the Copy and Paste items on the Edit menu using
CopyAction and PasteAction for the Action property, respectively.
Close the Menu Editor.
-
-
6. In
the Form Designer, click on the File Save button on the toolbar.
Change the Action property of the button to FileSaveAction.
-
-
7.
Repeat step 6 for the Cut, Copy, and Paste buttons on the toolbar,
setting the Action property to CutAction, CopyAction, and
PasteAction, respectively.
-
-
8.
Change the Action property of the MemoPopup menu items as needed.
You probably
didn't notice, but when you assigned SaveAction to the Action property
of a component, that component's Caption, Checked, Enabled, HelpContext,
Hint, ImageIndex, ShortCut, and Visible properties all changed to the
values of those properties in the SaveAction object. It is important to
understand that the action's properties will overwrite the properties of
any component that the action is assigned to. You must set up the action
with that in mind. That is why I had you change the Hint and ItemIndex
properties when you created the actions. Had you not done that, the hint
text and toolbar button glyphs would not be correct.
Now each of
the components just listed is hooked to an action. When a particular
action changes, any components hooked to that action will change as
well. Take the following code, for example:
SaveAction.Enabled
:= False;
When this
code executes, any components with their Action property set to
SaveAction will be disabled (the main menu Save item and the Save button
on the toolbar). To enable all items associated with this action, use
this code:
SaveAction.Enabled
:= True;
It's as
simple as that. Because the main menu and toolbar Save components have
their Action property set to SaveAction, the following two code snippets
are equivalent:
{ One-shot
using the Action. }
SaveAction.Enabled := False;
{ The hard way. }
FileSave.Enabled := False;
FileSaveBtn.Enabled := False;
Granted you
save only one line of code in this example, but if you have several
components that need to be enabled or disabled, actions can save you a
lot of time. After you create an action and associate that action with
one or more components, command enabling is as simple as one line of
code. The beauty of this system is that it doesn't matter how many
components you need to enable or disable. It's still just one line of
code.
Run the
ScratchPad program. Notice that the Cut and Copy buttons are disabled.
Type some text in the memo and highlight it. The Cut and Copy buttons on
the toolbar are magically enabled. Click anywhere in the memo to
deselect the selected text. The Cut and Copy buttons are disabled again.
Is the Paste button enabled? If so, type Alt+Print Screen on the
keyboard. (This copies the current window to the Clipboard as a bitmap.)
When you press Alt+Print Screen, the Paste button should become disabled
because you can't paste a bitmap into a memo. Select some text and click
either the Cut or the Copy button. The Paste button is now enabled
because the Clipboard contains text you can paste into a memo.
How does it
work? The TEditCopy, TEditCut, and TEditPaste standard actions
automatically know how to enable and disable their associated components
when any type of edit control has input focus. It's not magic, but it's
the next best thing to it! You only have to create the standard actions
and the rest is automatic. You didn't have to write any c ode to get the
Edit menu command enablers working. You can't beat that!
Command
Enabling for the Save and Save As Items
The Edit
menu items were easy because you didn't have to write any code. The File
menu items, Save and Save As, will take a little more work because there
are no standard actions for these menu items. Not to worry,
though--adding command enabling for those menu items doesn't take much
time. To implement command enabling for these menu items, you will make
use of the OnUpdate event. But first you need a little background
information to put the OnUpdate event in perspective.
The OnUpdate
event provides a convenient place to put your command-enabling code.
When your application runs out of messages to process, Windows sends it
a WM_ENTERIDLE message. Windows, in effect, tells your program, "I
don't have anything for you to do right now, so relax and take it easy
for a while." When a Delphi application receives a WM_ENTERIDLE
message, it triggers a TAction's OnUpdate event. All you have to do is
create an event handler for the OnUpdate event and do your command
enabling there. You can use the event handler to check the state of the
memo component and enable the Save and Save As items accordingly.
The only
step remaining, then, is to create an event handler for the action's
OnUpdate event. Perform these steps:
-
-
1.
Double-click the ActionList component to start the ActionList
Editor.
-
-
2.
Select the SaveAction action from the list of available actions.
Click on the File action category or (All Actions) if you don't see
the action in the action list.
-
-
3. In
the Object Inspector, double-click the Value column next to the
OnUpdate event. The Code Editor displays the OnUpdate event handler.
You add code to the event handler in just a bit.
-
-
4.
Locate the ActionList Editor (use View|Window List if you can't find
the ActionList Editor window). Select SaveAsAction from the list of
actions.
-
-
5. In
the Object Inspector, click the drop-down arrow next to the OnUpdate
event. Choose SaveActionUpdate from the list. This enables the Save
and Save As items to use the same OnUpdate event handler.
-
-
6. Close
the ActionList Editor.
Creating the
event handler is the simple part, of course. The more difficult aspect
is writing the code that goes between the begin and end statements.
Listing 13.2 shows the completed OnUpdate event handler. Switch to the
Code Editor and enter the code shown in Listing 13.2 into your OnUpdate
event handler.
LISTING
13.2. THE ScratchPad OnUpdate EVENT HANDLER.
procedure
TMainForm.SaveActionUpdate(Sender: TObject);
begin
{ Command enabler for Save and Save As. }
SaveAction.Enabled :=
Memo.Modified and (Length(Memo.Lines.Text) > 0);
SaveAsAction.Enabled := SaveAction.Enabled;
{ The
following two command enablers don't use actions. }
{
Instead the Enabled property of the two menu items }
{ is accessed directly. }
{ Command enabler for Select All. }
EditSelectAll.Enabled := Memo.Lines.Count > 0;
{ Command enabler for Undo. }
EditUndo.Enabled := Memo.Modified;
end;
The
SaveAction's Enabled property is set based on whether or not the memo
has been modified and whether or not the memo contains text. In a
nutshell, the Save action is enabled if the memo has been modified since
it was loaded and if it contains text. The same value is assigned to the
Enabled property of the SaveAsAction. This enables both Save and Save As
at the same time using the same criteria.
Notice that
I slipped a couple of extra commands into the OnUpdate event handler.
The command enablers for the Select All and Undo items of the Edit menu
do not use actions. Instead, the Enabled property of the menu items is
set directly. Using actions for these two items is overkill because
there is only one line of code in each case. As long as you have an
OnUpdate event handler, you can use it for any type of command enabling
you want. Put another way, this OnUpdate event handler is not
exclusively for the File menu items Save and Save As. Any command
enabling can be done in the OnUpdate event.
NOTE: The
OnUpdate event handler might be called thousands of times per second.
For that reason, you must keep code in this method as short as
possible.
NOTE:
Debugging code in the OnUpdate event handler is tricky. The problem is
that any breakpoints in the OnUpdate event handler will be hit as soon
as you run the program. You will have to use debugging methods other
than straight breakpoints when debugging code in the OnUpdate event
handler. Two such methods are conditional breakpoints and using
OutputDebugString to send messages to the Event Log.
A
TCommandList Bonus
There's an
added benefit to command lists that I haven't mentioned yet. To see this
hidden (up to now) benefit, perform these steps:
-
-
1.
Select the MainMenu icon on ScratchPad's main form.
-
-
2.
Change the Images property to ImageList.
-
-
3. Do
the same for the PopupMenu.
Now run the
program and look at the File and Edit menus. Wow! Instant menu bitmaps!
The menu items inherit the ImageIndex property of their associated
action. All you have to do to enable the bitmaps is assign the same
image list use for the actions to the menu's Images property. The rest
is automatic.
Printing in
Delphi Applications
Printing is
an everyday necessity for most Windows users. Whereas plenty of programs
do not have printing capability, the majority of Windows applications
have some form of printing support. I'll cover the basics of printing in
this section.
Providing
printing capabilities in a DOS application used to be a re al chore. A
DOS program had to provide and install printer drivers for every type of
printer that the program supported. That put a huge burden on software
developers, especially on small companies or shareware developers.
Windows changed all that. For the most part, Windows takes on the burden
of dealing with different printers, printer drivers, and so on. All you
have to do is send output to the printer just as you would send output
to a window. I'll get to that soon.
Printing in
Delphi applications comes in several flavors. You'll probably be
relieved to learn that, in many cases, printing is built into VCL and
comes nearly automatically. In other cases, though, you have to do some
specialized printing. Before you learn how to go about that, let's look
at the common dialog boxes that pertain to printing. After that I'll
discuss the different ways you can print from a Delphi application.
The Common
Printing Dialog Boxes
Windows
provides the common Print and Print Setup dialog boxes for use in your
applications. You use the Print dialog box just before printing begins
and the Print Setup dialog box to configure the printer. First, though,
you must add the components to your form.
The Print
Dialog Box
As I've
mentioned, the Print dialog box is displayed just before printing
begins, usually when the user chooses File|Print from the main menu. If
the user clicks OK, printing begins; if the user clicks Cancel, printing
is aborted. Figure 13.8 shows the Windows Print dialog box in its most
basic form.
FIGURE
13.8. The Windows Print dialog
box.
No doubt
this is not the first time you have seen this particular dialog box. The
combo box at the top of the dialog box enables you to choose the
particular printer to which you want to print. The Properties button
brings up a dialog box specific to the printer currently selected that
enables you to set the orientation, resol ution, and other properties
specific to that printer. The Print Range section enables the user to
print all pages, a page range, or any objects or text currently selected
in the application. The Copies section enables the user to specify the
number of copies to print as well as whether to collate the copies.
The Print
dialog box is encapsulated in VCL in the PrintDialog component. As with
the other common dialog boxes, you display the Print dialog box by
calling its Execute method. It shouldn't disappoint you to learn that
Windows carries out much of what the Print dialog box does. The printer
selection, number of copies, and collating options are all handled by
Windows, so you don't have to worry about them. Depending on your
application, you might need to enable the user to print a specified
range of pages or to print the current selection in the application. If
you are providing that kind of support, you need to examine some of the
PrintDialog properties before printing begins.
The
PrintDialog component has the Execute method only and no events. All the
functionality of the PrintDialog component takes place through its
properties, as listed in Table 13.3.
TABLE 13.3.
THE PrintDialog PROPERTIES.
|
Property
|
Description
|
|
Collate
|
Specifies
collated copies. If this is set to True, Windows will print so
that the copies are collated.
|
|
Copies
|
Specifies
the number of copies to print. You can set this property before
calling the Print dialog box if one of your application's options
is the number of copies to print. Windows takes care of printing
the correct number of copies.
|
|
FromPage
|
Specifies
the starting page when th e option of printing a range of pages is
enabled. Applications that support page-range printing should read
this property to determine which pages to print.
|
|
MaxPage
|
Specifies
the maximum page number that can be specified in the To field when
printing a range of pages. The Print dialog box takes care of
validating entry in the From and To fields.
|
|
MinPage
|
Specifies
the minimum page number that can be specified in the From field
when printing a range of pages.
|
|
Options
|
Contains
a set of options that control which features of the print dialog
box are enabled. You can elect to have a help button, to display
the print to file option, or to enable the page-range or print
selection options.
|
|
PrintRange
|
Controls
which of the Print Range radio buttons is selected when the Print
dialog box is initially displayed.
|
|
PrintToFile
|
Indicates
whether the user has chosen the Print to File option. It is up to
the application to write the output to a file.
|
|
ToPage
|
Specifies
the ending page number when printing a range of pages.
Applications that support page-range printing should read this
property to determine which pages to print.
|
The
application doesn't have much to do in response to the Print dialog box
closing unless the Print Range and Print to File options are enabled.
For example, if your application enables printing a range of pages, you
need to read the FromPage and ToPage properties to determine which pages
to print. Other than that, you begin printing if the user clicks OK.
The Print
Setup Dialog Box
The Print
Setup dialog box, shown in Figure 13.9, is used when the user wants to
change printers, page size, paper source, or orientation.
FIGURE
13.9. The Print Setup dialog box.
The Print
Setup dialog box isn't necessary in most applications because the user
can always press the Properties button on the Print dialog box to change
the setup options (refer to Figure 13.8). On the other hand,
implementing the Print Setup dialog box is so easy that you might as
well include it in your applications. How easy is it? Well, the
PrinterSetup component has no properties, methods, or events specific to
it. As with the PrintDialog component, the Execute method is the only
method in which you are interested. To further simplify things, Windows
handles everything that the Print Setup dialog box does. In fact, the
Execute method doesn't even return a value. This is because Windows
handles everything for you. If the user clicks Cancel, Windows does
nothing. If the user clicks OK, Windows makes the appropriate changes in
preparation for printing. All you have to do is display the Print Setup
dialog box and forget about it. A typical event handler for the
File|Printer Setup menu item would look like this:
procedure
TMainForm.FilePrintSetupClick(Sender: TObject);
begin
PrinterSetupDialog.Execute;
end;
That's all
there is to it. As I said, implementing the Print Setup dialog box is so
simple you might as well add it to your application.
Printing the
Easy Way
Printing is
an application-specific task. That might not sound profound, but it's
true. Depending on what kind of application you develop, printing can be
as simple as one line or it can entail hundreds of lines of code. Let me
first discuss the easiest forms of printing, and then I'll progress to
the more difficult printing operations.
The Print
Method for Forms
The TForm
class has a method called Print that can be used to print the contents
of a form. Only the client area of the form is printed; the form's frame
and menu bar are not. Although this method works well for a simple
screen dump, it is limited in its implementation. You can choose from
three print options, which are controlled through the PrintScale
property. Table 13.4 lists the print scaling choices and their
descriptions.
TABLE 13.4.
THE PrintScale PROPERTY OPTIONS.
|
Option
|
Description
|
|
poNone
|
No
special scaling is applied. The printed output of the form varies
from printer to printer.
|
|
PoProportional
|
This
option attempts to print the form in roughly the same size as it
appears on the screen.
|
|
poPrintToFit
|
This
increases or reduces the size of the image to fit the current
printer settings.
|
You can set
the PrintScaled property at runtime or at design time. The Print
method's use is limited to simple screen dumps and isn't likely to be
used for any serious printing.
The Print
Method for the RichEdit Component
The RichEdit
component is powerful primarily due to the amount of work done by the
underlying Windows memo control. Printing in the RichEdit component is
accomplished via a call to the Print method. This method takes a single
parameter called Caption that is used by the Print Manager when it
displays the print job. Printing the contents of a RichEdit component is
as simple as this:
RichEdit.Print(`MyApp.exe
- readme.txt');
Everything
is taken care of for you. Word wrapping and pagination are automatically
implemented. If yo u are using a multiline edit control that requires
printing, the RichEdit component is the way to go.
TIP: You
can use the Windows API function ShellExecute to print a text file.
ShellExecute is used, among other things, to run a program based on a
filename extension. For example, by default Windows registers the .txt
extension as belonging to Windows Notepad. If you double-click on a
file with a .txt extension in Explorer, Windows will look up the .txt
extension in the Registry, see that Notepad.exe is registered to
handle .txt files, and will run Notepad. The file you double-clicked
will be loaded automatically.
You can
use this behavior to your advantage. Take this line of code, for
example:
ShellExecute(Handle,
`print', `readme.txt', nil, nil, SW_HIDE);
This code
loads Notepad, prints the file called Readme.txt, and then exits
Notepad. In fact, you never see the Notepad program's main window
because the SW_HIDE style is specified for the Show parameter. Using
this technique assumes that the user has not modified the default
registration of the .txt extension and has not deleted Notepad from
his or her system. If you use ShellExecute, you have to add ShellApi
to your unit's uses list.
Printing via
QuickReport
Database
programs can use QuickReport to print reports. I mention QuickReport
here because it obviously pertains to printing, but because QuickReport
is one of the database components, I will postpone a detailed discussion
about its actual implementation until Day 18, "Building Database
Applications."
Printing the
Hard Way
Don't let
the title of this section put you off. Printing isn't all that
difficult; it just takes some time and organization. First, let's look
at some steps you need to know in order to implement printing in your
applications. After that you d elve into the actual code.
What's a
Device Context?
I talked
about device contexts and TCanvas in detail yesterday, but a recap can't
hurt. A device context (DC) is like a slate that Windows programs can
draw on. A better word would be canvas. On this canvas you can draw
text, lines, bitmaps, rectangles, ellipses, and so on. The type of line
used when drawing on a device context depends on the current pen
selected into the DC. The current fill color and fill pattern are taken
from the brush that is currently selected into the device context.
Device contexts must be carefully managed. There are a limited number of
DCs available to Windows, and you have to be careful to release the
device context as soon as you are finished with it. Also, if you don't
properly delete the objects you select into the device context, your
program will leak memory and perhaps even leave Windows itself in a
precarious state. As you can imagine, working with DCs can be
complicated.
The good
news is that VCL shields you from having to know every detail of device
contexts. VCL encapsulates Windows DCs in the TCanvas class. The Canvas
property frees you from worrying about all the little details that can
drive you nuts when dealing with Windows device contexts. VCL takes care
of obtaining the DC, selecting the appropriate objects into the DC, and
releasing the DC when it is no longer needed. All you have to do is draw
on the canvas and let VCL worry about the rest.
So what does
this have to do with printing? (Inquiring minds want to know.) Well,
it's like this: Windows enables you to obtain a printer device context
on which you can draw text, graphics, lines, and so on. In other words,
you draw on a printer canvas just as you draw on a screen canvas. This
concept represents quite a switch from the way printing was approached
back in the good old days of DOS. In this case, it is Windows that comes
to your rescue by enabling the use of a printer DC. VCL further aids you
by encapsulating device c ontexts in the Canvas property. The bottom
line is that printing is easier than it's ever been.
The TPrinter
Class and the Printer Function
VCL aids in
printing operations by providing the TPrinter class. This class
encapsulates the whole of printing in Windows. TPrinter has a Canvas
property that you can use to output lines, text, graphics, and other
drawing objects to the printer. I don't want to make it sound too easy,
but all you have to do to print in your Delphi programs is add Printers
to your uses list and then do something like the following:
Printer.BeginDoc;
Printer.Canvas.TextOut(20, 20, `Hello There!');
Printer.EndDoc;
In this
code, Printer is a VCL function. This function enables you access to a
TPrinter object that is set up and ready to go. All you have to do is
put it to work.
Now let's
take a quick look at TPrinter's properties and methods. Table 13.5 lists
the primary TPrinter properties, and Table 13.6 shows the primary
TPrinter methods.
|
|
TABLE
13.6. TPrinter METHODS.
|
Method
|
Description
|
|
Abort
|
Used
to abort the printing before normal completion.
|
|
BeginDoc
|
Begins
the printing process. Sets up the printer with Windows in
preparation for printing.
|
|
EndDoc
|
Ends
the printing process. Forces the current page to be
printed and performs printing cleanup with Windows.
|
|
GetPrinter
|
Retrieves
the current printer. Use the Printers property instead of
this method.
|
|
NewPage
|
Used
to force printing of the current page and start a new
page. Increments the PageNumber property.
|
|
SetPrinter
|
Sets
the current printer. Use the Printers property instead of
this method.
|
The
TPrinter class has no design-time interface. Everything is
accomplished at runtime.
Putting
It to Work
It's
time to put your newly acquired knowledge to work. Once again
it's time to dust off the ScratchPad program
and
spruce it up a bit. After all, what good is a text editor that
doesn't print?
First,
you need to modify the main form slightly. You already have menu
items set up for the Print and Print Setup menu items,
but
you need to enable them and add the Print and Printer Setup
dialog boxes to the form. Here goes:
-
1.
Double-click the MainMenu component to bring up the Menu
Designer.
-
2.
Choose File|Print from the ScratchPad menu in the Menu
Designer. Change the Enabled property to True.
-
-
3.
Do the same for the File|Print Setup menu item. Close the
Menu Designer.
-
-
4.
Place a PrintDialog component on the form and change its
Name property to PrintDialog.
-
-
5.
Place a PrinterSetupDialog on the form and change its Name
property to PrinterSetupDialog.
Okay,
now that you've completed the form, it's time to go to work
modifying the code.
To
start, you have to add a couple items to the main form's
declaration. Perform the following steps:
-
-
1.
Switch to the Code Editor and add the Printers unit to the
uses list of the main form's unit.
-
2.
Locate the TMainForm class declaration in the interface
section.
Add
this line to the private section of the class declaration
procedure
PrintFooter(var R : TRect; LineHeight : Integer);
-
-
This
is the declaration for a method that prints the footer at
the bottom of each page.
-
-
3.
Type Ctrl+Shift+C to have Delphi's Class Completion feature
create the
PrintFooter
procedure in the implementation section.You fill in the code
later.
-
-
4.
Switch to the Form Designer and choose File|Print from the
form's main menu.
The
FilePrintClick method is displayed. For now, leave the
method empty.
-
-
5.
Choose File|Print Setup from the main menu.
Enter
one line at the cursor so that the entire
FilePrintSetupClick method looks like this:
procedure
TMainForm.FilePrintSetupClick(Sender: TObject);
begin
PrinterSetupDialog.Execute;
end;
Okay,
now you're ready to fill in the FileP rintClick and PrintFooter
methods. Listing 13.3 shows the FilePrintClick method.
You
can enter the code in this method by hand, or you can load the
ScratchPad project
that
comes with the book's code and examine it in the Delphi IDE.
Listing
13.4 shows the PrintFooter method. Enter the code in these
methods in your SPMain.pas file.
You
don't have to type the comment lines, of course.
LISTING
13.3. THE FilePrintClick METHOD.
procedure
TMainForm.FilePrintClick(Sender: TObject);
var
I
: Integer;
LineHeight : Integer;
LinesPerPage : Integer;
LineCount : Integer;
R
: TRect;
S
: string;
begin
{ Display the Print dialog. }
if PrintDialog.Execute then begin
{ Set the title for the printer object. }
Printer.Title := `ScratchPad - ` +
OpenDialog.FileName;
{ Set the printer font to the same font as
the memo. }
Printer.Canvas.Font := Memo.Font;
{ Determine the line height. Take the Size of
the
{ font and and use the MulDiv function to
calculate
{ the line height taking into account the
current
{ printer resolution. Use the Abs function to
get
{ the absolute value because the result could
be a
{ negative number. After that add 40% for
leading. }
LineHeight := Abs(
MulDiv(Printer.Canvas.Font.Size,
GetDeviceCaps(Printer.Handle,
LOGPIXELSY), 72));
Inc(LineHeight, (LineHeight * 4) div 10);
{ Determine how many lines will fit on a
page. Trim
{ it back by three lines to leave some bottom
margin. }
LinesPerPage := (Printer.PageHeight div
lineHeight) - 4;
{ Start printing on line 4 rather than line 0
to leave
{ room for the header and to allow for some
top margin. }
LineCount := 4;
{ Tell Windows we're starting and print the
header. }
Printer.BeginDoc;
R.Top := LineHeight;
R.Left := 20;
R.Right := Printer.PageWidth;
R.Bottom := LineHeight * 2;
DrawText(Printer.Handle,
PChar(OpenDialog.FileName), -1,
R, DT_CENTER);
{ Loop through all o
f the lines and print each one. }
for I := 0 to Pred(Memo.Lines.Count) do begin
{ When we get to the bottom of
the page reset the
{ line counter, eject the page,
and start a new page. }
Inc(LineCount);
if LineCount = LinesPerPage then
begin
PrintFooter(R,
LineHeight);
LineCount := 4;
Printer.NewPage;
end;
{ Get the next string and print
it using TextOut }
S := Memo.Lines.Strings[I];
Printer.Canvas.TextOut(0,
LineCount * LineHeight, S);
end;
{ All done. }
PrintFooter(R, LineHeight);
Printer.EndDoc;
end;
end;
LISTING
13.4. THE PrintFooter METHOD.
procedure
TMainForm.PrintFooter(var R: TRect; LineHeight: Integer);
var
S : String;
begin
with Printer do begin
{ Build a string to display the page number.
}
S := Format(`Page %d', [PageNumber]);
{ Set up the rectangle where the footer will
be drawn. }
{ Find the bottom of the page and come up a
couple of
{ lines. }
R.Top := PageHeight - (lineHeight * 2);
R.Bottom := R.Top + lineHeight;
{ Display the text using DrawText so we can
center the
{ text with no fuss. }
DrawText(Handle, PChar(S), -1, R, DT_CENTER);
{ Draw a line across the page just above the
`Page X' text. }
Canvas.MoveTo(0, R.Top - 2);
Canvas.LineTo(R.Right, R.Top - 2);
end;
end;
This
code illustrates how you can print directly through Windows
rather than rely on the built-in printing that VCL provides.
Although
I always opt to do something the easy way when possible, there
are times when the easy way isn't flexible enough.
In
those times, it's good to have the knowledge to do the job
without trouble.
Printing
a Bitmap
Printing
a bitmap is simple. All you need to do is create an instance of
the TBitmap class,
load
a bitmap into the bitmap object, and send it to the printer
using the Draw method of TCanvas. Here's the entire code:
procedure
TForm1.Button1Click(Sender: TObjec
t);
var
Bitmap : TBitmap;
begin
Bitmap := TBitmap.Create;
Bitmap.LoadFromFile(`test.bmp');
Printer.BeginDoc;
Printer.Canvas.Draw(20, 20, Bitmap);
Printer.EndDoc;
Bitmap.Free;
end;
When
you print a bitmap, be aware that the bitmap might turn out very
small depending on the resolution of your printer.
The
bitmap might have to be stretched to look right. If you need to
stretch the bitmap,
use
the StretchDraw method instead of Draw.
Using
Cursors
The
use of cursors is not difficult, but I'll describe it here to
give you a basic understanding of how it works.
This
section deals with cursors that you change at runtime.
(To
change a cursor at design time, select a new value for the
component's Cursor property.)
After
a look at cursor basics, I will discuss how to load stock
cursors and custom cursors.
Cursor
Basics
First,
you can change the cursor for a particular component or form or
for the entire client area of your application.
If
you want to change the cursor for the entire application, you
need to change the Cursor property of the Screen object.
The
Screen object represents your application's screen. By changing
the Cursor property of the Screen object,
you
ensure that the cursor is the same regardless of which component
the cursor is over. Let's say, for example,
that
you want to change the cursor to the hourglass cursor. If you
change the Cursor property for the form only,
the
cursor will be an hourglass when it's over the form itself but
will revert back to the default cursor when it's
over
any other components on the form. Changing the Cursor for the
Screen object gives you the same cursor throughout.
Cursor
management is the responsibility of the Screen object.
All
the cursors available for your use are contained in the Cursors
property of the Screen object.
Note
that the name of this property is Cursors and is not the same as
the Cursor property discussed in the previous paragraph.
The
Curso rs property is an array that contains a list of available
cursors for the application,
and
the Cursor property is the property that is used to display a
particular cursor.
Although
this is confusing at first, you'll catch on quickly. All it
takes is a little experience working with cursors.
Windows
provides several built-in cursors for use in your applications.
In
addition to these built-in cursors, VCL adds a few cursors of
its own.
Together,
these cursors are called the stock cursors. Each stock cursor
has a named constant associated with it.
For
example, the arrow cursor is named crArrow, the hourglass cursor
is named crHourGlass,
and
the drag cursor is named crDrag.
All
these cursors are held in the Cursors array and occupy
positions -17 through 0 in the array; the
default cursor is at array index 0 (crDefault), no cursor is -1
(crNone), the arrow cursor is -2 (crArrow), and so on.
The
online help for the Cursors property lists all the cursors and
their constant names, so check Delphi help for a complete list
of
the available cursors.
To
use one of the cursors in the Cursors array,
assign
the name of the cursor you want to use to the Cursor property of
the Screen object:
Screen.Cursor
:= crHourGlass;
At
this point the VCL magic kicks in and the correct cursor is
loaded and displayed.
The
use of the Cursors property is transparent to you because you
don't directly access the Cursors
property
when you use a cursor. Instead, you make an assignment to the
Cursor property,
and
VCL takes care of looking up the proper cursor in the Cursors
array and displaying it.
The
Cursor property of a component and the Cursors property of the
Screen object work together to
display
different cursors in your application.
You
can change cursors in your applications for any number of
reasons--to display the wait cursor
(the
hourglass), if you have a drawing program that uses special
cursors, or to implement a help cursor in your application.
Loading
and Using Stock Cursors
Windows
provides several built-in cursors for use in your applications.
In
addition to those, VCL adds a few more cursors from which you
can choose.
You
can use these stock cursors anytime you like.
One
of the most obvious times to change the cursor is when you have
a lengthy process your application must perform.
It
is considered bad practice to tie up the program without giving
the user some indication that the program is busy.
Windows
provides the hourglass cursor (Windows calls it the wait cursor)
for exactly this purpose. Let's say, for example,
that
you have a processing loop in your application that might take a
long time. You would do something like this:
procedure
TMainForm.DoItClick(Sender: TObject);
var
I, Iterations : Integer;
OldCursor : TCursor;
begin
Iterations := StrToInt(Time.Text);
OldCursor := Screen.Cursor;
Screen.Cursor := crHourGlass;
for I := 0 to Iterations do begin
Application.ProcessMessages;
Time.Text := IntToStr(I);
end;
Screen.Cursor := OldCursor;
end;
Because
you don't always know which cursor your application is using at
any given time,
it
is a good idea to save the current cursor before changing the
Cursor property.
After
you have finished with a particular cursor, you can restore the
old cursor.
NOTE:
Sometimes you will change the Cursor property before a lengthy
processing loop and nothing seems to happen.
The
reason is that Windows didn't get a chance to change your
cursor before your program entered the loop.
When
in the loop, your application can't process any
messages--including the message to change the cursor.
The
fix is to pump the Windows message loop for waiting messages
while you are in your loop:
Application.ProcessMessages;
Now
your application enables messages to flow and the cursor
changes when your loop starts.
You
can, of c ourse, change the cursor for an individual component.
For
example, a drawing program might change the client area cursor
depending on the current drawing tool.
You
don't want to change the cursor for the Screen object in that
case because
you
want the arrow cursor to appear when the cursor is moved over
the toolbar, status bar, menu,
or
any other components that might be on the form.
In
that case, you set the cursor only for the component that
represents the client window of your application:
PaintBox.Cursor
:= crCross;
Now
the cursor is changed for just this one component and all other
components use their own predefined cursor.
Loading
and Using Custom Cursors
Loading
custom cursors requires a little more work. As I mentioned
earlier, the Cursors property of the
TScreen
class contains the list of cursors available to your
application. To use a custom cursor requires several steps:
-
-
1.
Create a cursor in the Image Editor or other resource editor
and save it as a .res file.
-
-
2.
Link the resource file to your program with the $R compiler
directive.
-
-
3.
Load the cursor into the Cursors array using the LoadCursor
function.
-
-
4.
Implement the cursor by assigning the index number to the
Cursor property for the form,
the
Cursor property for the Screen object, or the Cursor
property for any other component.
The
first two steps were covered on Day 11 when I talked about the
Image Editor and on Day 8 when I discussed resources.
After
you have the cursor bound to the .exe, you can load it with the
LoadCursor function.
Loading
a cursor into the Cursors array is easy:
Screen.Cursors[1]
:= LoadCursor(HInstance, `MYCURSOR');
This
code assumes that you have a cursor with a name of
MYCURSOR
and that you are assigning the cursor to position 1 in the
cursors list
(remember,
positions -17 through 0 are used for the stock cursors). Loading
the cursor has to be done only once,
so
you will probably do it in your main form's OnCreate event
handler.
NOTE:
All cursors are loaded into the Screen object's Cursors
property regardless of whether you use
the
cursor with the Screen object, with a form, or with a
component.
There
is only one Cursors array, and it belongs to the Screen
object.
To
use the cursor, then, just assign it to the Cursor property of
the Screen object or of any component:
PaintBox.Cursor
:= 1;
If
you have several cursors, you might want to create constants for
each cursor so that
you
have meaningful names to use instead of integer values that are
easy to mix up. Using that method,
the
preceding code would look like this:
const
MyCursor = 1;
{ later... }
Screen.Cursors[MyCursor]
:= LoadCursor(HInstance, `MYCURSOR');
{
later still... }
PaintBox.Cursor := MyCursor;
As
you can see, loading and implementing custom cursors isn't
difficult when you know how to do it.
The
book's code for today includes a sample program called CursTest
that demonstrates the theories discussed in this section.
Summary
Today
you learned about some features that make up a top-quality
Windows application and how to implement them in your own
programs.
I should warn you that it's tempting to overdo it with features
such as control bars, status bars, and cursors.
Implement
whatever decorations your application needs, but don't overuse
them.
You
also learned about printing today. In some cases, printing
support is built into a particular component,
and
in those cases printing is incredibly easy.
In
other cases you have to roll up your sleeves and go to work with
the TPrinter class.
Even
at those times, though, printing is nothing to fear.
Workshop
The
Workshop contains quiz questions to help you solidify your under
standing of the material covered and exercises to
provide
you with experience in using what you have learned.
You
can find the answers to the quiz questions in Appendix A,
"Answers to the Quiz Questions."
Q&A
-
-
Q
Can I disable all the components on my toolbar at one time?
-
-
A
Yes. Set the toolbar's Enabled property to False.
-
-
Q
I noticed that TCoolBar and TControlBar do about the same
thing. Which one should I use?
-
-
A
My advice would be to use TControlBar. There are many
versions of Microsoft's common control library,
COMCTL32.DLL.
TCoolBar is a wrapper around Microsoft's cool bar common
control and, as such,
is
affected by the version of COMCTL32.DLL the user has
installed on his or her machine.
TControlBar
isn't dependent on COMCTL32.DLL, so its behavior won't
change from system to system.
-
-
Q
How do I put a bitmap on my status bar?
-
-
A
Create a multipanel status bar. Change the style of the
panel that will contain the bitmap to psOwnerDraw.
Then,
in your OnDrawPanel event handler, use the Draw method of
TCanvas to draw your bitmap on the panel.
-
-
Q
Why should I bother with command enabling for my menu items
and toolbar buttons?
-
-
A
Because users expect a consistent interface. When certain
choices are not available
(whether
on menus or toolbars), they should be grayed out.
This
gives the user a visual cue that these commands are
sometimes available but not currently applicable.
-
-
Q
I'm a longtime Delphi user. I've got a system for command
enabling in place.
Why
should I switch to actions and action lists?
-
-
A
Actions give you a central location from which you can do
all your command enabling.
Rather
than having your command enabling code spread throughout
your code base, you can now ha ve it in one location.
-
-
Q
I just want basic output of a large, multiline edit control
in my application. What's the easiest way?
-
-
A
The easiest way is to use a RichEdit component and use its
Print method to print the contents of the component.
-
-
Q
I see that there is a Handle property for the Printer object
and also a Handle property for the Canvas property
of
the Printer object. What's the difference?
-
-
A
In this case there is no difference. If you are calling a
Windows API function that requires the printer device
context
handle,
you can use either Printer.Handle or Printer.Canvas.Handle.
-
-
Q
When I change the cursor for my main form, the cursor is
correct when it's over
the
form but reverts back to the arrow cursor when it's over any
of my buttons. Why?
-
-
A
You need to change the cursor for the Screen object, not for
the form.
Changing
the cursor for the Screen object ensures that the new cursor
will be used whenever the cursor is over
any
part of your application.
Quiz
-
-
1.
How do you attach an event handler to a toolbar button's
OnClick event?
-
-
2.
Can you put controls other than buttons on toolbars?
-
-
3.
What is the name of the TActionList event you respond to
when doing command enabling?
-
-
4.
What does the SimplePanel property of the StatusBar
component do?
-
-
5.
How do you change the status bar text manually?
-
-
6.
How do you enable and disable menu items and buttons?
-
-
7.
How do you access the printer in a Delphi application?
-
-
8.
What method do you call to begin printing with the TPrinter
class?
-
-
9.
What method of TPrinter do you call when you want to start a
new p age when printing?
-
-
10.
How do you change the cursor for a component at runtime?
Exercises
-
-
1.
Write a program from scratch. Add a toolbar and place five
buttons on it. Now add a status bar.
Enable
hints so that the toolbar buttons have both tooltips and
hint text in the status bar.
-
2.
Change the status bar for the ScratchPad program and add a
second panel. In this panel,
display
either Saved or Modified based on the value of
Memo.Modified.
-
3.
Change the About box of the ScratchPad program to read
Version 1.05.
Also,
change the Title property in the Project Options dialog box
and the Caption of the main form.
After
all, you added features, so you must let the users know!
-
-
4.
Create a custom cursor with the Image Editor.
Write
a program that displays the cursor when a button on the main
form is pressed.
-
-
5.
Extra Credit: Modify the ScratchPad program from exercise 3
to display different bitmaps
on
the status bar depending on whether the current file is
saved.
(Hint:
Look at the led1on.bmp and led1off.bmp bitmaps in the
Borland Shared Files\Images\Buttons directory.)
-
-
6.
Extra Credit: Modify the ScratchPad program so that the user
can specify a top margin,
-
bottom
margin, right margin, and left margin for printed output.

|
|