Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

(Ebook - Pdf) Kick Ass Delphi Programming

.pdf
Скачиваний:
284
Добавлен:
17.08.2013
Размер:
5.02 Mб
Скачать

{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. RDTSCRead 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