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

(Ebook - Pdf) Kick Ass Delphi Programming

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

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!

-----------

The Project File

In most normal application projects, the Delphi project (.DPR) file is rarely modified. However, for a screen saver, there are several modifications that need to be performed in order for the screen saver to work properly. Listing 8.4 shows the source code for the DLLaser main program project file. The first modification involves adding a description to the program. This is accomplished by using the {$D} directive. For screen savers, the description string must start with SCRNSAVE and be followed by a colon and the name of the screen saver. This string is used to identify the screen saver within the Desktop settings of the control panel.

The second modification prevents more than one instance of the screen saver from being executed. This is accomplished by checking the HPrevInst global variable. If this value is not zero, then another instance of the screen saver is currently active.

The final modification to the main program file involves processing command line arguments to determine which form to load. When Windows invokes a screen saver, a “/s” (or “-s”) parameter is passed to the screen saver program. When Windows needs to display the configuration dialog box, no command line parameters are passed. The ParamStr function can be used in a Delphi program to extract the command line arguments. In this example, the FrmLaser form is created if the screen saver is to be started, while the FrmConfig form is created if the configuration dialog box is to be displayed.

Listing 8.4 DLLASER.DPR

program DLLaser; {===============================================================

The following description must start with the {$D SCRNSAVE and follow with the name to show up in control panel. Compile this program to an .EXE and copy it to your windows directory with the extension .SCR.

===============================================================}

{$D SCRNSAVE : Def Leppard Laser}

uses Forms,

SysUtils,

Laserfrm in 'LASERFRM.PAS' {FrmLaser},

Config in 'CONFIG.PAS' {FrmConfig},

Setpwfrm in 'SETPWFRM.PAS' {FrmSetPassword},

Svrutils in 'SVRUTILS.PAS',

Getpwfrm in 'GETPWFRM.PAS' {FrmGetPassword},

Dlpoints in 'DLPOINTS.PAS';

{$R *.RES}

 

var

 

Params : string;

 

begin

 

if HPrevInst <> 0 then

 

Exit;

{ Only allow one instance of app }

{ Build a different "Main" form depending on Command Line } Params := UpperCase( ParamStr( 1 ) );

if ( Params = '/S' ) or ( Params = '-S' ) then begin

Application.Title := 'Def Leppard Laser Screen Saver'; Application.CreateForm( TFrmLaser, FrmLaser );

end else begin

Application.CreateForm( TFrmConfig, FrmConfig ); end;

Application.Run;

end.

One final note: Once the DLLaser program is compiled to an executable file, you must rename the file, giving it an .SCR extension, and then move it to the Windows directory. Once it’s there, you can select it using Control Panel.

NOTE:

Under Windows 95, a screen saver is selected and configured by accessing the Display Properties dialog through the Control Panel, or by right clicking on the desktop. The Screen Saver property sheet lists all installed screen savers using the description strings. However, the description string for a particular screen saver will only appear in the list if the actual file name contains all upper case characters.

Yes, you read that last sentence correctly. To make matters worse, when you try to rename a file in Windows 95 to upper case, the Explorer only displays the first character in upper case. Fortunately, the file does indeed get renamed correctly, even though it doesn’t appear that way.

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!

-----------

CHAPTER 9

The Shadowy Math Unit

TERENCE GOGGIN

Dynamic Data For Static Declarations

A Data-Aware Statistical Reporting Component

Bugs In The Math Unit

The Poly Function For Polynomial Equations

The Power Function

Math Unit Function Summary

Explore the new Delphi unit no one knows about...and harness it to do all sorts of statistical dirty work on your data.

Delphi 2 contains a first-class utility unit that, so far, has received only

second-class attention. It’s in the documentation (sort of), and people seem to know it’s there (some people, at least), but most of them don’t have the first notion how to use it. It’s called the Math unit, and it’s an excellent collection of financial, statistical, and general arithmetic and trigonometric functions.

Using some of these functions, this chapter will show you how to build—and use—a data-aware statistical reporting component, TDBStatistics. This component will allow you to give your users a complete data profile of up to 13 different statistics.

Three Good Reasons to Use the Math Unit

There are actually three good reasons to use the Math unit. First and foremost

is speed. The Math unit’s routines are fast. Many of them are coded in assembly language specifically optimized for the Pentium’s Floating Point Unit. Unless you’ve got a P6 chip and a lot of free time, it probably doesn’t get much faster than that!

Second, the alternative of using SQL’s statistical routines is unacceptable. SQL only offers four or five statistical functions—far too few to provide a complete statistical picture.

Third, choosing the Math unit over SQL or BDE-based solutions ensures that the TDBStatistics component should work with replacement database engines such as Apollo, Titan, or Direct Access.

Dynamic Data and Static Declarations

The Math unit is fast and comprehensive, but there’s a catch. You’ll need a trick or two to make the most of the statistical functions. You see, many of the Math unit’s routines take a parameter declared as:

const Data: array of Double

Functions like this can be difficult to use because arrays passed via these parameters must be statically declared. At first glance, then, there doesn’t appear to be a way to pass dynamic data to these functions. Most programmers assume that they’ve got to use one of two partial solutions to solve this dynamic data dilemma:

1.They can hard-code values into the program; or

2.Make the array as large as possible and hope no user ever exceeds that limit.

Hard-coding values into a program is occasionally necessary, but most of the time it turns out to be a very bad idea. In fact, that’s exactly what happens here. Consider this call to the Mean function:

Mean([3, 2, 1, 5, 6]);

Essentially, what we have in this line of code is a calculator that gives the same result each time, no matter what values are entered. That’s not exactly a useful result, is it?

Obviously, the idea of hard-coding data just won’t cut it. This leaves the option of “over-declaring” the size of the array. While this technique is typically very useful—if not downright necessary at times—in some situations, it can introduce unseen complications.

This is especially true in the case of the Math unit. Again, consider the Mean function. The “mean” is defined as the Sum of N terms / N. Let’s assume we have a 10,000 element array that we’re going to pass to the Mean function. If the user were to enter only 50 values, the denominator—that’s N—would be off by 9,950! Whoops!

Over-declaring doesn’t appear to be such a good idea either. So, how do we

pass dynamic data to these otherwise excellent routines? The answer lies in an all but unknown function buried in the System unit called Slice:

function Slice(var A: array; Count: Integer): array;

Slice takes an array of any size and type and returns Count elements as though they were a separate and distinct array. With Slice, we can declare a very large array and use as many or as few elements as we want.

The Slice function, then, allows us to revive the idea of “over-declaring” such that we no longer need to be concerned about the problem of the inaccurate denominator. This, in turn, makes it possible for us to pass dynamic data to these functions.

Armed with our newfound solution, we can now proceed with the creation of our quick and easy statistical reporting component.

Creating TDBStatistics

We’ve just discovered—thanks to Slice—how to pass dynamic data to the functions of the Math unit. So now, what we need is a way to make these routines work efficiently for database analysis. The simplest and friendliest way to do this is wrapping the routines up as a component; a component called, appropriately enough, DBStatistics.

Defining the Component’s Tasks

When you’re building components, it’s usually a good idea to begin by defining the component’s tasks. As you might expect, that’s just what we’ll do for DBStatistics.

DBStatistics’ main task is to provide us with easy access to one, several, or all of the 13 possible statistics, given a field name and a DataSource. In order to do this, the component will need the following inner workings:

1.Access to data, preferably via a DataSource;

2.A place to store large amounts of data locally;

3.A way to extract the data from a DataSource; and

4.A way to make the 13 statistics easily available.

In the next four sections, we’ll discuss these inner workings in detail.

Getting Access to the Data

To extract the data TDBStatistics is to analyze, it must first have a way to link to some TTable or TQuery. The easiest and simplest way to do this is to provide our component with a DataSource property. And that’s exactly what we do. The private section contains the following declaration

fDataSource : TDataSource;

which, of course, is made available at design time via a published property.

We also need a way for DBStatistics to know which field it is to analyze. This can be easily provided via a DataField property. There’s nothing new to this; you can see it in any component on the “Data Controls” palette tab. Because these two properties are so common, including them in DBStatistics helps to create a familiar design-time feel.

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.

Go!

Keyword

To access the contents, click the chapter and section titles.

Kick Ass Delphi Programming

(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!

-----------

Storing Data Locally

Now that we have access to data, we need a place to store this data once we extract it. The Math unit’s routines require a statically declared array; therefore, we must have just such an array. Our array is called, appropriately enough, Data.

The question that remains, then, is what the maximum number of elements in Data should be. When choosing this value, there are two factors to consider. We must first consider the number of records in our average table. If our tables generally don’t exceed 4000 records, then 4500 would be a good maximum elements value.

The other factor to take into account is the amount of available memory. If memory is not an issue, then we can make the array as large as we like. If memory is an issue, however, trial and error is the way to find the optimum size for the array.

For the purposes of illustration, though, let’s declare our Data array to have 10,000 elements. (Naturally, the 10,000 value will be declared as a constant, MaxValues.)This should give us plenty of space for most everyday applications.

Extracting data

The next two pieces of TDBStatistics, are very tightly interrelated, so we’ll try to develop both of them at the same time. The first of these two areas of interest is the error checking routine, GetRange. When the component extracts data, the error checking routine must ensure that everything is done “legally.” In most cases, “legally” means nothing more than preventing the component from, say, reading beyond the last record.

However, in the case of TDBStatistics, it’s a little more subtle than that. Because users might want to analyze a set of records that is larger than our array, we should allow them to select a subset of their data. This is done through two properties: UpperBound and LowerBound. They provide the component with starting and ending record numbers. The job of the error checking routine then, is to monitor these two values. The simplest way to accomplish this is through a function that:

1.Checks the values for possible errors;

2.Makes any necessary adjustments; and

3.Returns the (adjusted) difference between the two values.

The first two tests ensure that LowerBound is greater than zero, and that UpperBound is at least

LowerBound + 1:

if (LowerBound < 1) then LowerBound := 1;

if (UpperBound < 1) then UpperBound := LowerBound + 1;

The next test ensures that UpperBound is greater than LowerBound. If it’s found that LowerBound is greater than UpperBound, the two values are swapped:

if (LowerBound > UpperBound) then begin

TempInt := UpperBound; UpperBound := LowerBound; LowerBound := TempInt;

end;

Then, we test and adjust the values of UpperBound and LowerBound to make sure that they do not exceed the number of records in the DataSource. (The DataSource.DataSet.RecordCount value has already been retrieved and stored in the Records variable):

if (LowerBound > Records) then LowerBound := 1;

if (UpperBound > Records) then UpperBound := Records;

The last test makes sure that the difference between UpperBound and LowerBound doesn’t exceed the number of elements in the Data array. In other words, we can’t store more values than we have elements in the array:

if (UpperBound - LowerBound > MaxValues) then UpperBound := LowerBound + MaxValues;

Finally, the GetRange function returns the difference between the now adjusted UpperBound and

LowerBound:

Result := UpperBound - LowerBound;

And that’s our error checking routine.

Now that we have our error checking done, we can proceed to the task of extracting the data from the DataSource and storing it in our Data array. This is done in a procedure called FillArray.

The real work of FillArray begins with a call to GetRange. Then, once the bounds have been adjusted (as described above), we can extract the data and store it locally. First, we open the DataSource and go to the record number specified by LowerBound:

fDataSource.DataSet.Open;

fDataSource.DataSet.MoveBy(LowerBound);

Next, we test the type of fDataField. If the field contains numerical values, then we just extract the data, one record at a time, placing each value into the Data array:

if ((fDataSource.DataSet.FieldByName(fDataField) is TCurrencyField) or (fDataSource.DataSet.FieldByName(fDataField) is TFloatField) or (fDataSource.DataSet.FieldByName(fDataField) is TIntegerField) or (fDataSource.DataSet.FieldByName(fDataField) is TSmallIntField))then begin

for I := LowerBound to UpperBound do begin

if not (fDataSource.DataSet.FieldByName(fDataField).IsNull) then

Data[Index] := fDataSource.DataSet.FieldByName(fDataField).Value else

Data[Index] := 0; Inc(Index); fDataSource.DataSet.Next;

end;

end;

If the field is a character field, though, we should extract the data in a slightly different manner. The only character field data that the component expects to deal with are ZIP codes. There are two possible types of ZIP codes: the older five digit kind, and the newer five “plus four” kind.

As far as TDBStatistics is concerned, if the field contains five digit ZIP codes, the value can be converted to a numeric type without additional handling. If the value is a nine-digit, hyphenated ZIP code, though, the hyphen must first be changed to a ‘.’ character so that the value can be converted to a numeric type:

else if (fDataSource.DataSet.FieldByName(fDataField) is TStringField) then

begin

for I := LowerBound to UpperBound do begin

TempString := fDataSource.DataSet.FieldByName(fDataField).Value;

if (Pos('-', TempString) > 0) then TempString[Pos('-', TempString)] := '.';

Data[Index] := StrToFloat(TempString); Inc(Index);

fDataSource.DataSet.Next;

end;

end;

Finally, we close the DataSource and reset two boolean flags:

fDataSource.DataSet.Close; IsArrayFilled := True; DidGetAll := False;

The IsArrayFilled variable lets the other methods of the component know if the data has been extracted from the DataSource. If it is set to False, the other routines can call FillArray before completing their work. The DidGetAll variable is another boolean flag used by access methods. (Its purpose will become clear momentarily.)

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.

Go!

Keyword

To access the contents, click the chapter and section titles.

Kick Ass Delphi Programming

(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!

-----------

Making the Data Available

Now that we’ve done all the background work, the only thing left is to provide a way for us to retrieve the statistics. There are two ways to do this:

1.A method that retrieves all of the 13 results at once;

2.Individual access methods for each of the 13 results, made available via properties.

In this component we use both ideas.

To retrieve all of the statistics at once, we have the GetAllStats procedure. GetAllStats passes the Data array to all 13 statistical functions and stores the results in variables defined in the private section of our component. In addition, it sets the boolean variable DidGetAll to True, indicating to other methods that all of the statistics have already been retrieved.

The individual access methods, of course, can then check the value of the DidGetAll variable. If it is set to True, the access method can simply return the already stored result. On the other hand, if DidGetAll is set to False, the access method can call its related Math function directly and return the result from the Math unit. As an example of a typical access method, let’s look at GetMean, which returns the mean of the selected DataField’s values.

First, we make sure that the data has been extracted from the DataSource and stored in the Data array:

if not (IsArrayFilled) then FillArray;

Now, another test—this time to make sure that the statistical result we’re looking for hasn’t already been retrieved. The idea here is that if the result has already been obtained and stored, there’s no reason to get it again; the access method should simply return the already stored value to save time.

On the other hand, if it hasn’t yet been retrieved, we call the relevant Math unit function, using Slice and the GetRange function. Lastly, we return the statistic given to us by the Math unit:

if not (DidGetAll) then

fMean := Math.Mean(Slice(Data,GetRange)); Result := fMean;

Now that we’ve provided a quick and easy way to access the statistical results, TDBStatistics is ready to be plugged into any project.

Test Driving the DBStatistics Component