(Ebook - Pdf) Kick Ass Delphi Programming
.pdf{implementation}
constructor DemoComponent.Create(Owner: TComponent); begin
inherited Create(Owner); fGlyph := TBitmap.Create;
{Be sure to fill in the field with an empty object} end;
procedure DemoComponent.SetGlyph(Glyph: TBitmap); |
|
|
||
begin |
|
|
|
|
if fGlyph <> Glyph then |
{ fGlyph = Glyph when SetGlyph is |
} |
||
begin |
|
{ called by the Loaded procedure |
} |
|
fGlyph.Free; |
{Assign |
can fail if the target is not |
Empty:} |
|
fGlyph := TBitmap.Create; {Free/Create/Assign is a lot |
safer} |
|||
fGlyph.Assign(Glyph); |
|
|
|
|
end; |
|
|
|
|
{Extract any necessary data ... set the PropertyWritten flag} fGlyphWritten := True;
end;
procedure DemoComponent.Loaded; begin
inherited Loaded; {Don't forget to do this!}
if (not fGlyphWritten) and (not fGlyph.Empty) then SetGlyph(fGlyph); {Extract any necessary data from the bitmap}
end;
Partial-loading is a little more complex. Fortunately, Delphi components have a Loaded method which you can override to perform any necessary post-processing. You can overcome partial-loading by taking advantage of the Loaded method, and by making a few small changes to your code.
The first thing to do is to add an fPropertyWritten boolean flag for every TPersistent property that might be stored. (See Listing 7.1.) The flag will be set to False when the object is created, and should be set to True only by the write method.
Second, you must override (using the override directive) your component’s Loaded method, and add a line like this
if not fPropertyWritten then SetProperty(fProperty);
Products | Contact Us | About Us | Privacy | Ad Info | Home
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
Kick Ass Delphi Programming
Go!
Keyword
(Publisher: The Coriolis Group)
Author(s): Don Taylor, Jim Mischel, John Penman, Terence Goggin
ISBN: 1576100448
Publication Date: 09/01/96
Search this book:
Go!
-----------
so that Loaded calls your write method if (and only if) the component-loading code didn’t call it.
Third, you generally don’t want to Assign a TPersistent to itself, and you certainly don’t want to Free it, Create a new instance, then Assign the old (freed) instance to the new instance. It’s best to use code like that shown in Listing 7.2. This way, you only reset the private field variable if the new value does not equal the existing field. Adding this test ensures that SetProperty(fProperty) will not cause a GPF now, and won’t constitute much overhead if read-to-write does go away.
Listing 7.2 PERSIST2.SRC
if fProperty <> NewPropertyValue then begin
fProperty.Free; {Assigning 'over' a } fProperty := TPropertyType.Create; {TPersistent can fail:} fProperty.Assign(NewPropertyValue) {Free/Create/Assign's safer}
end;
{Extract any necessary data from NewPropertyValue} fPropertyWritten := True;
A Little Perspective
My personal suspicion is that read-to-write is the result of a bit of overzealous optimization by the Delphi team. While it seems like it would be pretty hard to make a case for it’s not being a bug, whenever I run into a bug or bit of poor design in Delphi, I like to ask myself how many apps I’ve ever used or written that are more stable than Delphi, or have a higher ratio of good to bad design decisions. The answer is always “Few, if any.”
It’s also worth bearing in mind that the write method is called at load time for simple types like integers, enums, and strings—and that read-to-write for TPersistent objects and descendents is pretty easy to deal with.
Using RDTSC for Pentium Benchmarking
Back in ancient times, writing fast code wasn’t just a matter of choosing the right algorithm; you had to know your instruction times, and you had to benchmark different sequences. Of course, because the system timer only ticked every 55 milliseconds, benchmarking meant either repeating your code hundreds of thousands of times, or pulling hackerish tricks like reading the timer chip’s internal registers to get the time in 838 nanosecond increments.
Nowadays, of course, compilers are so good and processors are so fast that it’s gotten pretty hard to come up with a piece of ‘brain-damaged’ brute force code that’s going to noticeably slow your programs. It seems more than a little ironic, then, that Intel waited for the Pentium to introduce a benchmarking instruction. RDTSC—Read Time Stamp Counter—returns the number of clock cycles since the CPU was powered up or reset. Where was RDTSC back when we really needed it?
Still, better late than never. RDTSC is a two byte instruction: $0F 31. It returns a 64-bit count in EDX:EAX. Since the 8087 comp datatype is a 64-bit integer, we can use the Delphi code in Listing 7.3 to read the current value.
Listing 7.3 RDTSC.SRC
const
D32 = $66;
function RDTSC: comp; var
TimeStamp: record
case byte of
1:(Whole: comp);
2:(Lo, Hi: LongInt);
end;
begin asm
db $0F; db $31;// BASM doesn't suppprt RDTSC
Pentium RDTSC - Read Time Stamp Counter - instruction $ifdef Cpu386
mov[TimeStamp.Lo],eax// the low dword mov[TimeStamp.Hi],edx// the high dword
$else db D32
movword ptr TimeStamp.Lo,AX
mov [TimeStamp.Lo],eax - the low dword
db D32
movword ptr TimeStamp.Hi,DX
mov [TimeStamp.Hi],edx - the high dword
$endif end;
Result := TimeStamp.Whole; end;
One problem you may run into when you use RDTSC is that both IntToStr and Format('%d') can only handle LongInt values, not comp values. While you can pass a comp value to one of these functions, it cannot be any larger than High(LongInt), which is 2,147,483,647. While that would be a mighty satisfying number of dollars to see on your brokerage statement, it’s only a little over 16 seconds of clock ticks to a 133 MHz Pentium. If you need to compare two long-running processes, the difference between the start ticks and the stop ticks can easily exceed High(LongInt). In these cases, you can use the CompToStr function shown in Listing 7.4.
Listing 7.4 COMP2STR.SRC
function CompToStr(N: comp): CompStr; var
Low3: string[3]; N1: extended;
begin
if N < 0
then Result := '-' + CompToStr(-N)
else begin
N1 := N / 1000;
Str(Round(Frac(N1) * 1000), Low3); N := Int(N1);
if N > 0 then begin
while Length(Low3) < 3 do Low3 := '0 ' + Low3; Result := CompToStr(N) + ThousandSeparator + Low3;
end
else Result := Low3 end;
end;
In the end, I guess you could say this is just another case of “The rich get richer”—we need to do a lot less benchmarking than we ever did before, while at the same time, the RDTSC instruction makes benchmarking easy and reliable.
And on that note, I turn control of this chapter over to my collaborator, Ed Jordan. Take it, Ed.
Products | Contact Us | About Us | Privacy | Ad Info | Home
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
Kick Ass Delphi Programming
Go!
Keyword
(Publisher: The Coriolis Group)
Author(s): Don Taylor, Jim Mischel, John Penman, Terence Goggin
ISBN: 1576100448
Publication Date: 09/01/96
Search this book:
Go!
-----------
Drag-and-Drop Rectangles for a Delphi Listbox
Thanks, Jon. When a user drags an item in Delphi, the mouse cursor changes; by default, it becomes an arrow with a small box clinging to its tail. Signifying drag and drop like this works with no fuss—after all, the mouse was going to be there anyway.
It is possible, however, to give users more assurance that something is happening. For example, when dragging an item from a list box, the mouse could carry around the outline of a rectangle the same size and shape as one line of list box text. As it turns out, dragging this rectangle is easy. Listing 7.5 presents the complete source code for a ListBox component that adds the feature.
The first question is where to draw. A rectangle drawn on the list box canvas will disappear if the mouse moves off the edge of the list box—not ideal. And on the parent canvas of the list box, the rectangle we draw will be clipped by any sibling windowed controls.
The one place where the rectangle will always show is the “canvas” of the screen. Actually, the screen doesn’t come with a built-in canvas property, but you can gain access to the screen’s device context with a call to GetDC and a parameter of 0. The rectangle can then be drawn using the Windows API function DrawFocusRect. DrawOutline in Listing 7.5 illustrates getting the DC, drawing on it, and releasing it.
Listing 7.5 OUTLNLBX.PAS
unit OutlnLBx;
interface uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TOutlineListBox = class( TListBox ) private
DrawX, DrawY: Integer; |
{ Mouse position |
} |
||
XOfs, YOfs: Integer; |
{ |
Drawing |
offset |
from mouse pos.} |
OutlineDrawn: Boolean; { |
TRUE if |
outline drawn } |
||
procedure DrawOutline; |
|
|
|
|
protected
procedure MouseDown( Button: TMouseButton; Shift: TShiftState; X, Y: Integer ); override;
procedure MouseMove( Shift: TShiftState; X, Y: Integer ); override;
procedure MouseUp( Button: TMouseButton; Shift: TShiftState; X, Y: Integer ); override;
end;
implementation
procedure TOutlineListBox.DrawOutline; var
ScreenDC: HDC;
Outline: TRect; begin
ScreenDC := GetDC( 0 ); try
with ClientToScreen( Point( DrawX - XOfs, DrawY - YOfs )) do begin
Outline := Bounds( X, Y, Width, ItemHeight ); DrawFocusRect( ScreenDC, Outline );
end; finally
ReleaseDC( 0, ScreenDC ); end;
end;
procedure TOutlineListBox.MouseDown; begin
inherited MouseDown( Button, Shift, X, Y ); if ( Button = mbLeft ) then
begin
XOfs := X;
YOfs := Y - ( Y div ItemHeight ) * ItemHeight; end;
end;
procedure TOutlineListBox.MouseMove; begin
inherited MouseMove( Shift, X, Y ); if Dragging then
begin
if OutlineDrawn then DrawOutline; { Erases rectangle } DrawX := X;
DrawY := Y; |
|
DrawOutline; |
{ Draws rectangle } |
OutlineDrawn := True; |
|
end; |
|
end;
procedure TOutlineListBox.MouseUp; begin
inherited MouseUp( Button, Shift, X, Y );
if ( Button = mbLeft ) and OutlineDrawn then
begin |
|
DrawOutline; |
{ Erases rectangle for last time } |
OutlineDrawn := False; end;
end;
end.
Three other methods finish off the draggable outline code. MouseDown makes a note of the offset of the mouse from the top-left corner of the rectangle. Remembering the offset ensures that if the mouse “grabbed” the rectangle by its middle, it keeps hold of the middle as it moves around.
MouseMove erases the rectangle if it has already been drawn at least once, then draws it in the new position. In the process, it remembers the top-left corner for erasing next time.
MouseUp erases the rectangle one last time.
One note about the drawing mechanism: DrawFocusRect draws rectangles via XOR. Drawn this way, a rectangle shows up against almost any background; and drawn twice in the same place, it disappears.
Making String Collections More List-Like
When I moved to Delphi from Borland Pascal, I wished that string lists were a little more like string collections—where were my ForEach iterators?
Now, though, as I port a Delphi application back to Turbo Vision, I’m wishing that string collections were a lot more like string lists.
Moving strings into and out of collections is like mixing concrete compared to the same smooth operations with lists—mainly because the new Object Pascal syntax is so much cleaner.
Compare this code, for adding an item to Turbo Vision collection
AStringColl^.AtInsert(AStringColl^.Count, NewStr(S));
S := PString(AStringColl^.At(Index))^;
to this code, which does the same thing for a Delphi string list
StringList.Add(S);
S := StringList[Index];
and you’ll see what I mean. Not only are the string collection operations practically unreadable, the second line of code above is simply wrong. If the PString being indexed is NIL, signifying that we put an empty string into the collection, we’ll read garbage into our string variable S.
Fortunately, a descendant of TStringCollection can make strings and collections mix more gracefully. The new object type shown in Listing 7.6 creates a pointer-safe StrAt method and a simple Add method. Now we can cleanly code things like this:
StrList^.Add(S);
S := StrList^.StrAt(Index);
And this similar syntax will ease our trips back and forth between the new and old worlds—as long as that old DOS world still matters.
Listing 7.6 STRLIST.PAS
{ Create a friendlier, TStringList-like string colleciton.} unit StrList;
interface uses Objects;
type
PStrListCollection = ^TStrListCollection; TStrListCollection = object(TStringCollection)
function StrAt(Index: Integer): string; procedure Add(const S: string);
end;
implementation
{Translate a pointer into a string, handling the nil case. } function PtrToStr(P: Pointer): string;
begin
if P = nil then PtrToStr := '' else PtrToStr := PString(P)^; end;
{Safely return a string from the string collection. } function TStrListCollection.StrAt(Index: Integer): string; begin
StrAt := PtrToStr(At(Index)); end;
{Add a string to the end of the string collection. } procedure TStrListCollection.Add(const S: string); begin
AtInsert(Count, NewStr(S)); end;
end.
Products | Contact Us | About Us | Privacy | Ad Info | Home
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.
To access the contents, click the chapter and section titles.
Kick Ass Delphi Programming
Go!
Keyword
(Publisher: The Coriolis Group)
Author(s): Don Taylor, Jim Mischel, John Penman, Terence Goggin
ISBN: 1576100448
Publication Date: 09/01/96
Search this book:
Go!
-----------
Letting Delphi Applications Set Up Themselves
Since I develop shareware in Delphi, I wanted to use it to create a simple setup program for people who download my software from online services or bulletin boards. Unfortunately, because of the considerable resources Delphi provides nearly automatically, even a simple setup program can run nearly 200k (of course, the increase in program size levels off very quickly after that). Now, this size is unremarkable for a normal Windows application, but setup programs should be as small as possible—especially when the user is paying for every second of download time, or when I am paying to email a registered version to a subscriber.
Happily, though, I have hit on a way to use the full resources of Delphi for my setup program, with a minimal increase in download size: I use my main application as its own setup program. The application is initially named SETUP.EXE, and when run under that name, it installs itself.
As far as users can tell, they are running a separate setup program. After installation, the program renames itself, and ceases to be an installer.
Here’s how it works. Listing 7.7 shows the main block of a typical unmodified Delphi application’s project (.DPR) file. Listing 7.8 shows the same main block with additions that make it function as a setup program. Note that we test to see whether the application’s .EXE name is SETUP.EXE—if it is, we run a form, or a series of forms, to let the user choose the program directory, program group, and other setup options.
Listing 7.7 BEFORE.SRC
{The main block of an application's .DPR file, before being changed to function as a setup program. }
begin
Application.Title := 'Program Title'; Application.CreateForm(TMainForm, MainForm); Application.Run;
end.
Listing 7.8 AFTER.SRC
{ The main block of an application's .DPR file, after being
changed to function as a setup program. }
{ Note that SYSUTILS.PAS must be added this unit's USES clause. }
begin
Application.Title := 'Program Title';
if UpperCase(ExtractFileName(Application.ExeName)) = 'SETUP.EXE' then Application.CreateForm(TSetupForm, SetupForm)
else
Application.CreateForm(TMainForm, MainForm); Application.Run;
end.
Before I zip up my software (.EXE file, help file, read-me file, and so on) to upload it, I rename my .EXE file SETUP.EXE. After the user downloads the software, unzips it, and runs SETUP.EXE, the application copies itself and the auxilliary files to the final directory, renaming itself to its proper name. The next time it is run, it will find that it is not named SETUP.EXE, and so will behave normally.
In exchange for an inconsequentially small increase in program size and download time, users get a helpful setup program, and I get (I hope) a few more sales.
Using INHERITED with Redeclared Properties in Delphi
Suppose you’re developing a Delphi VCL component—a descendant of TDrawGrid, let’s say—and you need to take some special action when the component user (in this case, a programmer) changes the ColCount property. There are two ways to do this; the best way for you depends on whether you want mere notification of the change, or whether you want to control what the value of ColCount can be.
The ColCount property determines the number of columns in a grid. Like most properties, its value is stored in a private field (FColCount, in this case), and it is changed by way of a private access method (SetColCount). Thus, when a programmer writes
ColCount := AValue;
in his code, or when he changes ColCount in the Object Inspector at design time, SetColCount is called, and with the help of other private methods, changes the value of the FColCount field, and makes adjustments in the grid as needed. All this is encapsulated, out of reach.
But the original developers of TDrawGrid anticipated that developers of TDrawGrid descendants might want notification of a change in the number of columns—so after the change is made, but before it is shown, the SizeChanged method is called. SizeChanged is a dynamic method, which means we can override it, and our own SizeChanged will be called every time the number of columns (or the number of rows) changes. See Listing 7.9.
Listing 7.9 SIZECHAN.SRC
{A descendant of TDrawGrid that overrides the SizeChanged method. This lets the descendant component be aware of when its number of columns or rows has changed. }
{In unit's interface... }
type