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

(Ebook - Pdf) Kick Ass Delphi Programming

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

TObject(Self).ClassName + '.KAD',fmOpenWrite or fmCreate);

Next, we filter out the types that can’t be saved, taking no action when those types are encountered.

for i := 0 to ComponentCount-1 do begin

if ((Components[i] is TSizingRect) or (Components[i] is TMenu) or (Components[i] is TMenuItem) or (Components[i] is TPopupMenu) or (not(Components[i] is TControl))) then Continue;

Each time we discover a component that can be saved, we save it out to the stream.

Writer := TWriter.Create(FileStream, SizeOf(Components[i]));

Writer.WriteRootComponent(Components[i]);

Writer.Free;

After iterating through the form’s components and saving any pertinent ones, we save any of the form’s own properties that are important to the application.

TempRect.Top := Self.Top;

TempRect.Left := Self.Left;

TempRect.Bottom := TempRect.Top + Self.Height;

TempRect.Right := TempRect.Left + Self.Width;

FileStream.Write(TempRect, SizeOf(TRect));

FileStream.Write(Self.Color, SizeOf(TColor));

FileStream.Free;

Finally, we set the flag allowing the form to be closed.

CanClose := True;

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!

-----------

Alternate Paths to a Stream

You may have noticed that the TFileStream class also appears to have methods that will save and restore a component’s properties. While TFileStream has, in fact, two different sets of methods for saving and loading components to and from files, these routines do some additional processing that makes them somewhat less efficient than the TReader/TWriter implementation we’ve chosen.

The WriteComponentRes and ReadComponentRes methods read and write the component as a standard windows resource. This adds some processing overhead to the equation. In addition, these routines save data that is simply of no use to us, making our properties files larger than they really need to be.

The WriteComponent and ReadComponent methods also achieve the same end result as we do, but with an additional function call or two. Our method is more efficient and slightly faster.

Toward More Flexible User Interfaces

Runtime-definable user interfaces really deserve more than a single book chapter to treat them in full detail. We’ve looked at giving users control over the most common UI elements, including control position and size, fonts, and tab order, as well as other properties that may be displayed in a commercial property editor component. That’s only scratching the surface, especially when you consider that a really good user interface customization system should itself have a careful UI, as well as guidelines and checks to ensure that the user does not somehow render the application unusable. Consider this chapter as a jumping-off point from which you can explore the matter as far as you (and your users) need to take it.

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 11

Hierarchical Data in Relational

Databases

RICHARD HAVEN

The Nature Of Hierarchical Data

Simple Recursive Hierarchical Data

Nested Recursive Hierarchical Data

Hierarchy Navigation And Display

Hierarchical Data In Queries

SQL And The Problem Of Arbitrary Nesting

Stored Procedures For Handling Arbitrary Nesting

A Family Of Hierarchical Data Handling Components

Database data isn’t always expressed as rows and columns. Here are some pointers on handling hierarchical data within Delphi databases, with some VCL components to shoulder the bulk of the load.

The real world contains lots of hierarchical data. This broad category includes

companies, and within companies, divisions, departments, and teams; parts which make up parts which make up parts; specialties, sub-specialties, and skills; employees and supervisors; and on and on. A group of items where one item can own or be the “parent” of any number of other items is organized in a tree hierarchy. The VCL object hierarchy is an immediate example: A TEdit is a TControl because TEdit descends from TControl. TEdit is also a

TWinControl and a TCustomControl because it descends from those as well.

Such relationships are not intuitively represented by the relational database model. Often, these hierarchical relationships are recursive (in that any row can own any other row) and arbitrarily nested, which means that any row can own another row no matter who owns it, the prohibitions against circular links notwithstanding. Simple navigation and display of a tree hierarchy can be difficult in the two-dimensional landscape of rows and columns, and querying is especially confusing. You often want to extract an item’s lineage (the list of its owner and its owner’s owner, and so on) or progeny (the list of all its descendants and its descendants’ descendants) to use as criteria for a query. This chapter will provide some mechanisms for dealing with such hierarchical relationships within the relational database idea familiar to Delphi programmers.

One-to-Many Hierarchies

Delphi deals well with traditional relational databases: These are tables (sometimes called relations) with rows (records) and columns (fields) that link to one another by matching common values in columns of different rows. (See Figure 11.1.) To link rows this way is also called relating the rows in question. The relational model is not the only model used in database management. The hierarchical and network database models were standard before the relational model, and the object-oriented database model is a modern alternative.

FIGURE 11.1 Master/detail tables.

The value of any of these models lies in how they help the database designer to create databases to fit the data. The relational model fits many kinds of real-world data structures: many invoices to each customer; many parts to many vendors; many members each having many characteristics; and so on. Delphi’s TTable.MasterSource and TQuery.DataSource properties let you show related subsets of a table or query based on linking values in another table. This is one way to establish a master/detail relationship within Delphi, where “master” is the “one” in “one-to-many” and “detail” is the “many.”

Simple Recursive Hierarchical Data

The relational model works well for master/detail data in the same table as long as there is only a single level of ownership: that is, when any single row is either owned by one other row or owns another row. The boss and staff rows shown in Table 11.1 might as well be in two separate tables at this single level of recursion.

TABLE 11.1 Simple recursive hierarchical data.

Emp_ID

Boss_ID

Emp_Name

Boss 1

<nil>

Frank Eng

Boss 2

<nil>

Sharon Oakstein

Boss 3

<nil>

Charles Willings

Staff 1

Boss 1

Roger Otkin

Staff 2

Boss 1

Marilyn Fionne

Staff 3

Boss 1

Judy Czeglarek

Staff 4

Boss 2

Sean O’Donhail

Staff 5

Boss 3

Karol Klauss

Staff 6

Boss 3

James Riordan

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!

-----------

Table 11.2 summarizes the property values necessary to have two sets of Table, DataSource and DBGrid components, where both sets point to the same physical table. One set will show parent rows while the other set will show the child rows that belong to the currently selected parent row. The MasterSource and MasterFields properties of the child Table automatically restrict it to the rows belonging to the current row in the parent Table.

TABLE 11.2 Property setup for parent or child row display.

Parent component set

Child Component Set

Table1.TableName = ‘employees’

Table2.TableName = ‘employees’

Table1.IndexFieldName = ‘Boss_ID;Emp_ID’

Table2.MasterSource = ‘DataSource1’

DataSource1.DataSet = ‘Table1’

Table2.MasterFields = ‘Emp_ID’

DBGrid1.DataSource = ‘DataSource1’

Table2.IndexFieldName = ‘Boss_ID;Emp_ID’

Table1.SetRange([‘’],[‘’]);

DataSource2.DataSet = ‘Table2’

 

DBGrid2.DataSource = ‘DataSource2’

To restrict the parent Table so that it does not show any child rows, place an explicit filter on it so it only shows rows that have nothing in their Boss_ID column. These are parent rows.

All properties called out in Table 11.2 are design-time properties, except for Table1.SetRange. This is a method that you call to make Table1 display only boss rows (that is, those where Boss_ID = Nil). A good place to put the call to Table1.SetRange is in the Table1.AfterOpen handler, so it executes whether the table is left open at design-time or opened at runtime.

Figure 11.2 shows a Delphi form containing two dbGrid components, set up according to the properties summarized in Table 11.2. The boss rows are shown in one grid, and the staff rows in the other. All rows are, in fact, present in one physical table.

FIGURE 11.2 A Master/Detail relationship between rows of the same table.

Whenever DataSource1 (connected to Table1) changes, Table2 will automatically set its range just as though you had executed the code in Listing 11.1.

Listing 11.1 Equivalent code for automatic range updates

procedure TForm1.DataSource1DataChange(Sender : TObject; Field : TField);

begin

if (Field = nil) or (Field.FieldName = 'Emp_ID') then Table2.SetRange([Table1.FieldByName('Emp_ID').AsString],

[Table1.FieldByName('Emp_ID').AsString]);

end;

NOTE:

It can only do this if there is an index on Table2 for Boss_ID so it can filter all the rows where Table1.Boss_ID for one grid contains the null string and Table2.Boss_ID = Table1.Emp_ID for the other. This index can have extra columns that are not part of the range, which determine the sequence of the rows in the filtered set. In this case, only staff for one boss will appear in Table2, and those rows will be in Emp_ID sequence. If the table is on a SQL Server, all non-BLOB (Binary Large Objects) columns are considered indexed, although performance will suffer if you use columns that don’t actually have physical indexes.

Class TQuery as a Detail DataSet

A TQuery can be a detail DataSet as well by letting the master data set (of class TTable or TQuery) send its values to the TQuery as parameters to a dynamic query. In the example above, the SQL property of the detail TQuery would look something like this:

SELECT *

FROM Employee T1

WHERE T1.Boss_ID = :Emp_ID

The TQuery.DataSource property indicates where the parameter value comes from. In the SQL query shown above, the parameter named Emp_ID, gets its value from TQuery.DataSource.DataSet. FieldByName(‘Emp_ID’). Anytime the master field changes, the query re-executes with its new parameter value.

Detail TQuery datasets have to use dynamic SQL and the DataSource property together. The query is dynamic because it uses a parameter instead of recreating the full SQL text each time. However, if there are several possible statement structures for the query and you wish to use more than one, you will have to create the entire SQL statement textually. Parameters will only take you so far.

You can use code instead of the MasterSource property if you want more control over how ranges appear or you want to send customized SQL through a TQuery instead of letting TQuery.DataSource do it for you. For example, you might want to include certain rows besides the ones specified by the range. This is best done when an OnDataChange event handler is attached to the master DataSource, as shown in Listing 11.2.

Listing 11.2 Special handling in range updates for values outside the range

procedure TForm1.DataSource1DataChange(Sender : TObject; Field : TField); begin

if (Field = nil) or (Field.FieldName = 'Emp_ID') then begin

Query2.DisableControls;

Query2.Close; with Query2.SQL do

begin Clear;

Add('SELECT *'); Add('FROM employees T1');

Add('WHERE T1.Boss_ID = ' + Table1.Field ByName('Emp_ID').AsString);

Add('OR T1.Boss_ID IS NULL'); {// extra code } end;

Query2.Open;

Query2.EnableControls;

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!

-----------

Nested Recursive Hierarchical Data

Recursively hierarchical data means that the owner and the owned rows are all in the same table: One non-key column of a row contains the key value of another row, indicating that that row is owned by the other row. This non-key column is called a foreign key, even if it links the row with another row in the same table. The examples described earlier reflect one level of ownership, in that each row is either a boss or a staffer. To allow a staffer to also be a boss, the table becomes fully recursive: Any row can be both be a boss and have a boss. Note that the table’s key is Emp_ID. You do not have to add the Boss_ID column as part of the key in order to manage the relationship; a secondary index starting with Boss_ID will work fine. See Table 11.3.

 

TABLE 11.3 A recursive table.

Emp_ID

Boss_ID

Emp_Name

 

Boss 1

<nil>

 

Boss 2

<nil>

 

Boss 3

<nil>

 

Manager 1

Boss 1

 

Manager 2

Boss 1

 

Manager 3

Boss 2

 

Staff 1

Boss 1

 

Staff 2

Manager 1

 

Staff 3

Manager 1

 

Staff 4

Manager 3

 

Staff 5

Boss 3

 

Staff 6

Boss 3

This data has three levels: Boss, Manager, and Staff. Rather than adding another DBGrid for