Delphi3000 Articles
Delphi3000 Articles
Delphi3000 Articles
delphi3000.com Article
Question: How do I show tooltips/hints for menu items and any visual control
Answer: There are several methods to show hints in a statusbar (or similar control), each of these methods
requires more then one line of code!
There are more important things to be spending your time on, drop the following code into a file and save
it as TheStatusbar.pas, now install it in the Delphi IDE menu
File->Components->Install Component
Once installed drop the statusbar onto a form, set the hint property of one or more controls (do some
menu items too for the test). Compile/run the project and while running place the mouse cursor over any
of the items which you set the hint property. You should see the hint in the statusbar.
Note: I have not tested this in D6 but be forewarned that "DsgnIntf" in the USES clause will most likely be
a problem, if so then remove it and all code that does the About dialog.
unit TheStatusBar;
{*****************************************************************}
{ Description }
{ Makes it easy to show hints for components and menu items in the}
{ statusbar. All the programmer needs to do is write the text for }
{ the desired item to show hints. You do not have to set ShowHints}
{ to True on a form unless you want ballon style hints also. }
{ }
{ Tested with D3 to D5 }
{=================================================================}
{ USE TSmartStatusBar AT YOUR OWN RISK. }
{ I AM NOT RESPONSIBLE FOR ANY HARM THIS COMPONENT MIGHT CAUSE!! }
{ }
{ Kevin S. Gallagher }
{ E-Mail : Gallaghe@teleport.com Home }
{ kevin.s.gallagher@state.or.us Work }
{*****************************************************************}
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ComCtrls, DsgnIntf;
type
TAbout = class(TPropertyEditor)
public
procedure Edit; override ;
function GetAttributes: TPropertyAttributes; override ;
function GetValue: string; override ;
end;
TSmartStatusBar = class(TStatusBar)
private
FSizeGrip: Boolean ;
FAbout: TAbout ;
FOldOnHint: TNotifyEvent ;
FSysOnHint: TNotifyEvent ;
FShowAppHint: boolean ;
procedure SetShowAppHint(b : boolean) ;
protected
procedure DispAppHint(Sender : TObject) ;
public
constructor Create(AOwner: TComponent); override ;
published
property About: TAbout read FAbout write FAbout ;
property ShowAppHint : boolean read FShowAppHint write SetShowAppHint default True ;
end;
procedure Register;
implementation
uses Commctrl ;
procedure TAbout.Edit ;
begin
Application.MessageBox('TSmartStatusBar component' + #13 +
'1998-2001 Kevin S. Gallagher - This component is freeware.',
'About TSmartStatusBar Component', MB_OK + MB_ICONINFORMATION) ;
end;
inherited Create(AOwner) ;
SimplePanel := True ;
FSizeGrip := True ;
FShowAppHint := True ;
ShowAppHint := True ;
FSysOnHint := Application.OnHint ;
procedure Register;
begin
RegisterComponents('Win32', [TSmartStatusBar]);
RegisterPropertyEditor(TypeInfo(TAbout), TSmartStatusBar, 'ABOUT', TAbout);
end;
end.
Example code for working with a main form and a child form:
{ Here we stop the hints from being displayed in this the main form
and show them in the child form. }
procedure TForm1.cmdTestOneClick(Sender: TObject);
var
f:TfrmChild;
begin
SmartStatusBar1.ShowAppHint := False ;
f:= TfrmChild.Create(Self) ;
try
f.ShowModal ;
SmartStatusBar1.ShowAppHint := True ;
finally
f.release ;
end ;
end;
{ Here we stop the hints from being displayed in the child form and
show them in the main form. Please note that if the child form is
not disabled then both forms will show the same hint which would
be confusing to some users. }
procedure TForm1.cmdTestTwoClick(Sender: TObject);
var
f:TfrmChild;
begin
f:= TfrmChild.Create(Self) ;
try
f.SmartStatusBar1.ShowAppHint := False ;
f.ShowModal ;
finally
f.release ;
end ;
end;
statusbar's autohint
Antoine (Dec 27 2001 3:48PM)
with borland's standard statusbar, if you set the "autohint" property to true, it will display automatically
all the text following a "|" in the hint property of any component. Is it what you tried to implement ?
Respond
Yep, but older versions of Delphi didn't have this property. By creating this component w/o using
the underlying method used in more recent versions of Delphi a programmer can use D3 to D6 w/o
any problems. I tend to work with D3 and D5 thus no conflicts when moving between versions.
Also it is easier to alter the logic for showing hints in my component if you want to tweak the code.
For instance in my personal copy the hint is shown in Panel[1].
Cheers
Kevin
Respond
delphi3000.com Article
Easy and Simple way Using ADO with any image in MS Access with EDBImage
Undertitle:
URL: http://www.delphi3000.com/article.asp?ID=4426
Category: Database-VCL
Uploader: mind programmer
Question: I got an error message “bitmap image is not valid”. I try several tips out there, but none of
them works. From several tips I found, this is my simple solution:
Answer: Why this happen? watch this blob file viewed in hex editor :
1 151C2F00020000000D000E0014002100 ../...........!.
2 FFFFFFFF4269746D617020496D616765 ....Bitmap Image
3 005061696E742E506963747572650001 .Paint.Picture..
4 05000002000000070000005042727573 ...........PBrus
5 6800000000000000000020540000424 D h......... T..BM
6 16540000000000007600000028000000 .T......v...(...
7 C0000000DF0000000100040000000000 ................
8 A0530000CE0E0000D80E000000000000 .S..............
MS Access stores some information (for JPEG or other type it also include the path of a linked
OLE) in object as part of the object's definition in the OLE object field. Because the definition
of OLE object storage is not documented (!? this is straight from MS) there is no way to
know what gets written before the actual image data.
Obviously line 2 says the data stream is a "Bitmap Image." I assume in Lines 4-5 that PBrush
("Paintbrush") is the program that should be used to open the file.
The first two bytes of BMP files normally are "BM" or 42 4D in hex, so that it appears that the
BMP file starts in line 5, at byte 14 above -- byte 78 within the file.
Because Bitmap is always start in byte 78 within the BLOB, you can use the
OnLoadCustomImage in this way:
---------------------------------------------------------------------------
JPEG start with FF D8. But not always be at the same position in the file. We’ll need to seek
to the 'FFD8' and read the image from there. You can use this function:
That is ALL.!!! - do not call B.Free. You can use any image, since you have the library that
support the image. For many graphic support you can use GraphicEx library from Mike
Lischke (www.soft-gems.net) . And use hex editor to see the header. e.g. :
42 4D : Bitmap
4D 4D : TIFF
47 49 : GIF
D7 CD : Metafile
FF D8 : JPEG
I try to create function that support JPEG and Bitmap, the JPEG is ok but its only support
Bitmap in 16 bit. So if anyone knows how to fix that or to optimize the code, please post
here.
Copyright 2000 delphi3000.com
Contact: delphi3000@bluestep.com'
delphi3000.com Article
Iterators
Undertitle:
URL: http://www.delphi3000.com/article.asp?ID=592
Category: OO-related
Uploader: Peter Friese
TMyItem = class
private
FValue: boolean;
FName: string;
public
property Name: string read FName write FName;
property Value: boolean read FValue write FValue;
end;
TMyListIterator = class
private
FList: TMyList;
FIndex: integer;
protected
constructor Create(List: TMyList);
public
procedure First;
procedure Next;
function IsDone: boolean;
function CurrentItem: TMyItem;
end;
TMyList = class
private
FList: TList;
function GetItem(Index: integer): TMyItem;
procedure SetItem(Index: integer; const Value: TMyItem);
function GetCount: integer;
public
constructor Create;
destructor Destroy; override;
procedure Add(Item: TMyItem);
procedure Delete(const Index: integer);
procedure Clear;
procedure Iterator(var Iter: TMyListIterator);
property Count: integer read GetCount;
property Items[Index: integer]: TMyItem read GetItem write SetItem; default;
end;
As you can see, the TMyList class is a simple type-safe list wrapper that has the capability of
handing out an iterator to the client (the code which uses the list).
But how do we use the iterator? Letäs take a look at the way we normally would traverse
this list:
procedure OldFashioned;
var
i: integer;
item: TMyItem;
begin
for i := 0 to
item := FMyList[i];
Listbxo1.Items.Add(item.Name);
end;
end;
The problem with the above code is that we cannot see what is really happening. A for loop
can have many meanings, for example adding some numbers. But we cannot judge from the
mere existance of a for loop that the code traverses a list.
Enter iterator!
The same code fragment using an iterator looks like this:
procedure TheRightWay;
var
iter: TMyIterator;
item: TMyItem;
begin
FMyList.Iterator(Iter);
end;
We can judge from the presence of the iterator that this must be some code that traverses a
list or some other structure. Those of you who already have used the FindFirst/FindNext
functions will see the resemblance of this code fragment with the one used commonly to list
the contents of a directory.
In my opinion, iterators are much easier to use than for loops once you understand them.
One of their advantages is that you can write for example a reverse iterator that traverses
the list in reverse order. If you do so, you won't have to chaneg the client code, since all the
traversation logic is handled by the iterator.
Sample code:
///////////////////////////////
unit ListIterator;
///////////////////////////////
interface
uses Classes;
type
TMyItem = class;
TMyList = class;
TMyListIterator = class;
TMyItem = class
private
FValue: boolean;
FName: string;
public
property Name: string read FName write FName;
property Value: boolean read FValue write FValue;
end;
TMyListIterator = class
private
FList: TMyList;
FIndex: integer;
protected
constructor Create(List: TMyList);
public
procedure First;
procedure Next;
function IsDone: boolean;
function CurrentItem: TMyItem;
end;
TMyList = class
private
FList: TList;
function GetItem(Index: integer): TMyItem;
procedure SetItem(Index: integer; const Value: TMyItem);
function GetCount: integer;
public
constructor Create;
destructor Destroy; override;
procedure Add(Item: TMyItem);
procedure Delete(const Index: integer);
procedure Clear;
procedure Iterator(var Iter: TMyListIterator);
property Count: integer read GetCount;
property Items[Index: integer]: TMyItem read GetItem write SetItem; default;
end;
implementation
{ TMyListIterator }
procedure TMyListIterator.First;
begin
FIndex := 0;
end;
procedure TMyListIterator.Next;
begin
inc(FIndex);
end;
{ TMyList }
constructor TMyList.Create;
begin
inherited Create;
FList := TList.Create;
end;
destructor TMyList.Destroy;
begin
FList.Free;
inherited;
end;
procedure TMyList.Clear;
begin
FList.Clear;
end;
FList[Index] := Value;
end;
end.
///////////////////////////////
unit TestMain;
///////////////////////////////
interface
uses
Windows, Messages, SysUtils,
Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls,
ListIterator;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
ListBox1: TListBox;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
FMyList: TMyList;
public
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
for i := 0 to 10 do begin
item := TMyItem.Create;
item.Name := 'Test' + inttostr(i);
item.Value := true;
FMyList.Add(item);
end;
end;
FMyList.Iterator(Iter);
end;
end.
References:
[GAM95] Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John: Design Patterns -
Elements of Reusable Object-Oriented Software. Reading, MA: Addison-Wesley, 1995
Copyright 2000 delphi3000.com
Contact: delphi3000@bluestep.com'
To keep this technique, change Iterator to a function returning type IIterator - also allows anything to be
passed through that implements IIterator.
The instance will be freed as soon as the variable goes out of scope.
Respond
FMyList.Iterator(Iter);
Respond
delphi3000.com Article
Question: These kind of great articles are somewhere on the web... and nobody knows where !!
Therefore i found that its good to post it here...
SuperComponents
How to Build Aggregate/Composite Components in Delphi™
Advantages
SuperComponents take all of the advantages of normal components and build on them. Rapid
application development, object reuse, and user interface consistency are all benefits. Code
shrinkage is another. If you have two or more forms containing similar component relationships
(either inside an application or among several), that component relationship is tied to each form
with - you guessed it - code. That binding Object Pascal code is duplicated with each form the
component group lies on. By fusing the component collections into a single component, the code to
manage the sub-component relationships disappears from the user interface code. The code for each
form more truly represents its functionality; it's easier to read and maintain.
Another benefit achieved through the use of SuperComponents is multiple form inheritance. You
can take parts of different forms (SuperComponents) and group them together into a new form. This
leads to even faster prototyping and user interface design than normally experienced with Delphi.
SuperComponents can simplify the properties needed to control the collection. Instead of dealing
with properties and events for each component in the collection, the SuperComponent only exposes
the properties and events it needs, thereby reducing the design-time complexity of the component.
As mentioned earlier, SuperComponents can embody the rules of the relationship among the
subcomponents. This relationship might represent an algorithm or solution to a problem, or it might
support state changes not present in any of the individual parts. Algorithm parameters and/or hooks
to state changes can be easily exposed through new properties and events.
Finally, SuperComponents can be thought of as mini-apps. They are components themselves, and as
such, lend themselves to a well-defined segregation from the rest of the project they are contained
in. In team development environments, this means that developers most familiar with the problem
and solution (whatever they be) can design the SuperComponents so that less-experienced
developers can piece together the application with these building blocks.
Disadvantages
There are two disadvantages to making SuperComponents. First, visual SuperComponents require
an additional Windows handle. Second, there is a small amount of overhead in the container parent
component that holds all the sub-components.
Before We Start
It is useful to distinguish among the different roles developers and users take when dealing with
components. There are three that we are concerned with:
•Application Users (a k.a. "users") will use applications built with the components we make.
•Application Developers (a k.a. "developers") will build applications using our components.
•Component Writers (that's us!) will create components that will make is easier for developers to
build applications and easier for users to use. It's important to note that as a component writer,
you're designing for two customers.
Visual Containment
1. Design the layout of your components inside a form in Delphi, placing all the components
inside a TPanel (or a descendant thereof).
2. Select and copy the panel and paste it into a text file.
3. Replace all instances of " = " with " := ", and add a semi-colon to the end of each line.
4. Convert all DFM "object" declaration lines to appropriate object constructor code, setting the
parent of all visual controls to the container panel.
5. Clean up any remaining code. Bitmaps will need to be placed in resource files.
6. Place this new pascal code inside a create constructor for your component. Within the
constructor , group object sections under the appropriate sub-component creator.
Let's use an example to illustrate these steps. We'll make an OK/Cancel/Help button combination.
Inside Delphi, the layout looks like this (Note: the TPanel's border is set to none):
Selecting, copying and pasting the above collection into a text file yields the following:
Left = 114
Top = 10
http://www.delphi3000.com/printarticle.asp?ArticleID=3468 04/12/2007 21:29:53
delphi3000.com - Printing Articles Page 4
Width = 75
Height = 95
BevelOuter = bvNone
TabOrder = 0
Left = 0
Top = 0
Width = 75
Height = 25
Caption = 'OK'
Default = True
ModalResult = 1
TabOrder = 0
end
Left = 0
Top = 35
Width = 75
Height = 25
Cancel = True
Caption = 'Cancel'
ModalResult = 2
TabOrder = 1
end
Left = 0
Top = 70
Width = 75
Height = 25
Caption = 'Help'
TabOrder = 2
end
end
This is the text representation of our SuperComponent group. Next, we need to convert this text to
something that looks a little more like Object Pascal:
Left := 114;
Top := 10;
Width := 75;
Height := 95;
BevelOuter := bvNone;
TabOrder := 0;
Left := 0;
Top := 0;
Width := 75;
Height := 25;
Caption := 'OK';
Default := True;
ModalResult := 1;
TabOrder := 0;
end
Left := 0;
Top := 35;
Width := 75;
Height := 25;
Cancel := True;
Caption := 'Cancel';
ModalResult := 2;
TabOrder := 1;
end
Left := 0;
Top := 70;
Width := 75;
Height := 25;
Caption := 'Help';
TabOrder := 2;
end
end
Now we're getting closer to what we want. The next step is to transfer the panel initialization to the
component's constructor. We'll create the embedded controls here, too:
begin
inherited Create(AOwner);
Width := 75;
Height := 95;
BevelOuter := bvNone;
TabOrder := 0;
OKButton := TButton.Create(Self);
OKButton.Parent := Self;
CancelButton := TButton.Create(Self);
CancelButton.Parent := Self;
HelpButton := TButton.Create(Self);
HelpButton.Parent := Self;
end; { Create }
The three buttons, OKButton, CancelButton, and HelpButton need to be declared as fields of our
new component. Our component's declaration looks like this:
type
TOkCancelHelp = class(TPanel)
OKButton: TButton;
CancelButton: TButton;
HelpButton: TButton;
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
end; { TOkCancelHelp }
Now let's take that converted DFM text and initialize the three buttons. Although you can do this
inside our component's constructor, some VCL sub-component initialization code depends on a
windows handle existing in its parent. At the time when our new SuperComponent is created (inside
the Create constructor), this handle does not yet exist. So we need to find a method we can override
in the TPanel that is called before the component is displayed and before its loaded method is
called, but after a windows handle is assigned to the TPanel. The CreateWindowHandle method is
the best place to do this. An override of this method, with the DFM initialization code inserted,
looks like this:
begin
inherited CreateWindowHandle(Params);
with OKButton do
begin
Left := 0;
Top := 0;
Width := 75;
Height := 25;
Caption := 'OK';
Default := True;
ModalResult := 1;
TabOrder := 0;
end; { OKButton }
with CancelButton do
begin
Left := 0;
Top := 35;
Width := 75;
Height := 25;
Cancel := True;
Caption := 'Cancel';
ModalResult := 2;
TabOrder := 1;
end; { CancelButton }
with HelpButton do
begin
Left := 0;
Top := 70;
Width := 75;
Height := 25;
Caption := 'Help';
TabOrder := 2;
end; { HelpButton }
end; { CreateWindowHandle }
type
TOkCancelHelp = class(TPanel)
OKButton: TButton;
CancelButton: TButton;
HelpButton: TButton;
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
end; { TOkCancelHelp }
Finally, we need to add a register method so we can place our new component onto Delphi's
component palette:
procedure Register;
begin
RegisterComponents('CDK', [TOkCancelHelp]);
end; { Register }
Grouping components together to create new SuperComponents is a pretty neat trick. One
advantage is that it allows you to isolate many of the grouped components' properties from
application developers. In fact unless you explicitly state otherwise, all of the grouped components
properties will be hidden from developers!
So how do you expose a sub-component property? You need to create two transfer methods that
transfer the sub-component's properties to the outside world.
For example, in our TOkCancelHelp component, it might be useful to expose the caption property
so application writers can change it (e.g., for application development in a language other than
English). Our transfer methods look a lot like the standard property Get and Set methods we're
already familiar with. Here's the declaration for the OK button's caption property:
type
TOkCancelHelp = class(TPanel)
private
{ Private declarations }
published
end;
These transfer methods pass the property values to and from the subcomponents. Their
implementation looks like this:
begin
result := OKButton.Caption;
end; { GetCaption_OKButton }
begin
OKButton.Caption := newValue;
end; { SetCaption_OKButton }
You may notice that there is no field variable for this property. All sub-component properties rely
on the sub-components themselves for storage. Also notice that unlike most Set methods, this one
doesn't check to see if the internal value is different from the passed-in newValue. We let the sub-
component handle this check if necessary.
type
TOkCancelHelp = class(TPanel)
private
{ Private declarations }
FOnClick_OKButton: TNotifyEvent;
published
end;
Here, Click_OKButtonTransfer acts as the event handler. Notice that its type, TNotifyEvent,
matches the expected type for the OnClick event (TNotifyEvent). The implementation for the
transfer method looks like this:
begin
if assigned(FOnClick_OKButton) then
end; { Click_OKButtonTransfer }
If you've triggered events before, you probably recognize this code. The if-clause checks to see if
the event is assigned (typically performed via the Object Inspector at design-time), and if so calls it,
passing a reference to itself (the SuperComponent) to the component user's event handler. So, the
sub-component's event is handled by our transfer method, which in turn passes the event to a
component user's event handler (that's right -- two event handlers for each event!). To hook up this
chain of events, all we do is dynamically assign the event transfer method to the sub-component's
event. We do this in the overridden CreateWindowHandle method:
begin
inherited CreateWindowHandle(Params);
with OKButton do
begin
OnClick := Click_OKButtonTransfer;
end; { OKButton }
end; { CreateWindowHandle }
Sometimes you want to respond to a sub-component event without exposing it. The steps involved
here are similar to those you'd follow to expose a sub-component event, except you don't declare
the event (and you don't need the corresponding event field variable).
type
TOkCancelHelp = class(TPanel)
private
{ Private declarations }
published
end;
procedure TOkCancelHelp.Click_CancelButtonHandler(Sender:
TObject);
begin
end; { Click_CancelButtonHandler }
We glue it all together by dynamically assigning our handler to the sub-component event, just as we
do when we want to expose a sub-component event:
begin
inherited CreateWindowHandle(Params);
with CancelButton do
begin
OnClick := Click_CancelButtonHandler;
end; { CancelButton }
end; { CreateWindowHandle }
Summary
Recommended Reading
The following books were current at the time of this writing. Make sure you check for the most
recent edition.
http://www.coriolis.com
585 pages
ISBN 1-883577-47-0
This book is highly recommended if you need additional information on component building or
building business components. It is filled with useful information and excellent examples. The
explanations are clear and easy to understand. In addition, Ray Konopka’s
TRzBusinessComponent, included with the CDK, is presented and detailed in his book.
831 pages
ISBN 1-57169-026-3
This is an amazing book, packed with valuable information you can’t get anywhere else. Also
contains excellent coverage of property editors, component editors, and other advanced component-
building topics.
Email: 70602.1215@compuserve.com
This magazine consistently contains excellent technical articles on Delphi-related subjects. Also
contains a monthly column by Bob Swart (a k.a. Dr. Bob) on component building in Delphi.
395 pages
ISBN 0-201-63361-2
This book organizes and presents a catalog of proven object-oriented solutions for architecting
complex systems that you can apply to your own specific applications. These design constructs are
labeled, allowing your development team to share a common vocabulary. The CDK help file
references two of the design patterns in this book: the Proxy pattern and the Template Method
pattern. The examples are in C++, but the patterns apply to all programming languages.
(408) 431-1000
156 pages
The Component Writers Guide is a lean but important resource for learning about how to create
working components and to ensure that components you write are well-behaved parts of the Delphi
environment. The book guides you to writing components that fit in well with any Delphi
application.
Pattern
Max Kleiner (Dec 9 2002 2:24PM)
Next year a book will be published «Delphi Design Patterns», so a Super Component is like a Composite
Pattern ;)
Respond
delphi3000.com Article
Question: How do I create a balanced round robin tournament schedule which ensures that every team
plays every other team once with the minimum amount of idle time (a/k/a byes) and ensures
that teams have an equal number of home and away games.
Answer:
The creation of a balanced round robin schedule is more complex than it first appears. There
are several delphi round-robin components that are available on the web ... for example the one
from engerning objects international at http://www.engineeringobjects.com/RndRobin.htm which
costs $495.00. I wrote this algorithm to quickly and cheaply calculate a round robin
schedule. Currently, the program has a hard-coded maximum number of 500 teams; however,
this can easily be increased. If you have no need for such an algorithm then so be it, but I hope
that anyone who needs to create such a schedule will give this algorithm a try. The
CreateRoundRobin procedure will easily create a schedule for you.
For example assume that there are 5 teams, a valid schedule would be.
Round 1
Home : 1 Away: 4
Home : 2 Away: 3
Home : 5 Away: -1
Round 2
Home : 5 Away: 3
Home : 1 Away: 2
Home : 4 Away: -1
Round 3
Home : 4 Away: 2
Home : 5 Away: 1
Home : 3 Away: -1
Round 4
Home : 3 Away: 1
Home : 4 Away: 5
Home : 2 Away: -1
Round 5
Home : 2 Away: 5
Home : 3 Away: 4
Home : 1 Away: -1
Most traditional round robin schedules look like the following, but there is no easy way to
calculate which team is the home team (or has the home field) and which team is away team (or
plays on the away field).
team
\ 1 2 3 4 5 6 7 8 9 10
round \............................................................
1: 10 9 8 7 6 5 4 3 2 1
2: 6 10 9 8 7 1 5 4 3 2
3: 2 1 10 9 8 7 6 5 4 3
4: 7 3 2 10 9 8 1 6 5 4
5: 3 4 1 2 10 9 8 7 6 5
6: 8 5 4 3 2 10 9 1 7 6
7: 4 6 5 1 3 2 10 9 8 7
8: 9 7 6 5 4 3 2 10 1 8
9: 5 8 7 6 1 4 3 2 10 9
const
MaxTeams = 500;
MaxRounds = MaxTeams;
http://www.delphi3000.com/printarticle.asp?ArticleID=1790 12/11/2007
delphi3000.com - Printing Articles Page 2 sur 9
....
http://www.delphi3000.com/printarticle.asp?ArticleID=1790 12/11/2007
delphi3000.com - Printing Articles Page 3 sur 9
end;
end;
You can easily print the result to a richedit with a push of a button.
begin
RichEdit1.Clear;
CreateRoundRobin(RoundRobinAry,NoOfTeams);
PrintFullChart(RoundRobinAry); //see below
end;
round robin
Jean Robbins (Jan 22 2007 5:25PM)
Can someone help me? I have 8 teams with only 3 courts. I need to make a schedule where
everyone plays everyone.
Thanks
Respond
http://www.delphi3000.com/printarticle.asp?ArticleID=1790 12/11/2007
delphi3000.com - Printing Articles Page 4 sur 9
I have the exact same problem. Did you get an answer on a round robin on 3 courts with 8
teams? I hope you can help me.
Thanks,
Stan
Respond
round robin
sam (Jan 8 2005 4:51PM)
I'm trying to set up a coed singles pool league at my club. I'm not sure of the number
of people yet but we will play 12 weeks. Is there a formula to set this up?
Thanks jack
Respond
this is fucking gay... i like to make tournaments.. jesus christ get a life
Respond
#include "stdinc.h"
#include "list.h"
#include "dlist.h"
#include "partition.h"
#include "llheaps.h"
#include "wgraph.h"
http://www.delphi3000.com/printarticle.asp?ArticleID=1790 12/11/2007
delphi3000.com - Printing Articles Page 5 sur 9
Respond
I would like a schedule of 20 teams playing each other 1 time with no duplicates.
Respond
tournament
Bill (Nov 9 2001 8:40PM)
i am attempting to divide a 24 team golf league in a schedule that has either tri or quad meets ...
everyone plays each other once in ONE round
help
Respond
RE: tournament
John Denaro (Jan 6 2007 3:43PM)
Excellent Code
Dave (Feb 20 2001 2:15PM)
I ported it to Visual Basic and it worked great except the home / away logic needed some
tweaking. The code as is, for example, will put team #1 in all homes games for the first half of the
http://www.delphi3000.com/printarticle.asp?ArticleID=1790 12/11/2007
delphi3000.com - Printing Articles Page 6 sur 9
rounds and all away games for the next half. To alternate the home and away games I propose
that you change this:
for ii := 1 to Half do
begin
RoundRobinAry[i][ii][Home] := GameAry[ii];
RoundRobinAry[i][ii][Away] := GameAry[Bottom-ii+1];
end;
To this:
======
for ii := 1 to Half do
begin
IF ((i Mod 2)=0)
Then Begin
RoundRobinAry[i][ii][Home] := GameAry[ii];
RoundRobinAry[i][ii][Away] := GameAry[Bottom-ii+1];
end
else Begin
RoundRobinAry[i][ii][Away] := GameAry[ii];
RoundRobinAry[i][ii][Home] := GameAry[Bottom-ii+1];
end;
end;
Dave Johnston
Respond
Dave,
While your modified code does have some merit, it does not "solve" the problem. If you
want to switch home and away aspects for all teams, I would use the following code…
for i := 1 to Rounds do
begin
For iv := 1 to Rounds do
Begin
If (iv <= (half+1))
Then i := iv * 2 – 1
Else i := (iv – (half+1)) * 2;
Best regards,
Charles
Respond
I have 5 teams playing in a pool tournament , I will like to know how to set up a round robin
tournament.
Respond
How about a 'double' round robin. Where everybody plays each other twice(once home
and once away).
http://www.delphi3000.com/printarticle.asp?ArticleID=1790 12/11/2007
delphi3000.com - Printing Articles Page 7 sur 9
Respond
Great code
Chris Allsop (Jan 24 2001 6:37PM)
This code is superb. Its exactly what I was looking for, and as far as I am concerned, who cares
what category its supposed to be in or what hassles people have with it. I think its a great bit of
coding. Full credits to the author.
Respond
WINNT
Outlook (Feb 3 2005 3:56PM)
Respond
#include "stdinc.h"
#include "list.h"
#include "dlist.h"
#include "partition.h"
#include "llheaps.h"
#include "wgraph.h"
http://www.delphi3000.com/printarticle.asp?ArticleID=1790 12/11/2007
delphi3000.com - Printing Articles Page 8 sur 9
Respond
A Good One!!!
S S B Magesh Puvananthiran (Jan 24 2001 4:46PM)
It's really a good algorithm that can be implemented in any language... Good work!!!
Thanx.
Magesh.
Respond
Permutations
Mike Heydon (Jan 23 2001 1:59AM)
Nice example of resolving permutations and combinations. It is very valid under the Algorythm
section and it can be adapted for various functions.
Respond
Respond
well if Algorithm is going to be about "Round Robin Tournament Schedule" then I VOTE to
get rid of it....
Respond
Several delphi round-robin components are available on the web ... for example the
one from engerning objects international at
http://www.engineeringobjects.com/RndRobin.htm which costs $495.00. I wrote this
algorithm to quickly and cheaply calculate a round robin schedule. Currently, the
program has a hard-coded maximum number of 500 teams; however, this can easily
be increased. If you have no need for such an algorithm then so be it, but I hope that
any one who needs to create such a scheduel will give this algorithm a try.
Respond
I'm sure anyone who needs to write a schedule program in Delphi will be GLAD to have
http://www.delphi3000.com/printarticle.asp?ArticleID=1790 12/11/2007
delphi3000.com - Printing Articles Page 9 sur 9
Who cares about your vote. Don't read it and shut up.
Respond
WHAT ??
Aqab Bin Talal (Jan 17 2001 8:08AM)
shouldn't this artical be on www.algorithms3000.com ?? what does Robin Tournament Schedule
(whatever that is) got to do with Delphi ??
Respond
Lighten up...
B Nice (Jan 17 2001 9:52PM)
Hey, lighten up a bit! We want to encourage people to donate whatever code they can to
make this site the best around. Stop flaming people just because YOU can't find a reason for
every little thing that's posted. Personally, I think this article could be of use in the right
situation. If it helps ONE person out there, it's worth having.
Respond
Dude... get a life and stop flaming those of us who are just trying to help. If you don't like
the article, DON'T READ IT! There's a lot of people who need all sorts of different things in
life (algorithms included!).
Respond
http://www.delphi3000.com/printarticle.asp?ArticleID=1790 12/11/2007
delphi3000.com - Printing Articles Page 1
delphi3000.com Article
tCalculator = class
private
ValueA,ValueB,Interval : extended;
Strings : TStringList;
public
...
end;
tCalculatorRecall = class
private
RefObject : tCalculator;
ValueA,ValueB,Interval : extended;
Strings : TStringList;
public
constructor Create(Calculator : tCalculator);
destructor Destroy; override;
end;
Strings := TStringList.Create;
Strings.Assign(RefObject.Strings);
end;
destructor tCalculatorRecall.Destroy;
begin
RefObject.ValueA := ValueA;
RefObject.ValueB := ValueB;
RefObject.Interval := Interval;
RefObject.Strings.Assign(Strings);
Strings.Free;
inherited Destroy;
end;
TCalculator = class(TPersistent)
private
ValueA,ValueB,Interval : extended;
Strings : TStringList;
...
public
...
procedure Assign(Source: TPersistent); override;
...
end;
TCalculatorRecall = class(TRecall)
public
constructor Create(ACalculator : TCalculator);
end;
delphi3000.com Article
procedure TForm.FormMouseDown/FormMouseMove/FormMouseUp
begin
..
if fZoomFlag then
begin
...
end else
if fPaintFlag then
begin
...
end;
..
end;
tTool = class
private
fForm : tForm;
public
constructor Create(Form : TForm);
destructor Destroy; override;
tZoomTool = class(tTool)
public
procedure SetCursor; override;
procedure HandleMouseDown(x,y : integer); override;
procedure HandleMouseMove(x,y : integer); override;
procedure HandleMouseUp(x,y : integer); override;
end;
tPaintTool = class(tTool)
public
procedure SetCursor; override;
procedure HandleMouseDown(x,y : integer); override;
procedure HandleMouseMove(x,y : integer); override;
procedure HandleMouseUp(x,y : integer); override;
end;
procedure TForm.SetZoomModus;
begin
fTool.Destroy;
fTool := tZoomTool.Create(Self);
end;
procedure TForm.SetPaintModus;
begin
fTool.Destroy;
fTool := tPaintTool.Create(Self);
end;
procedure TForm.SetZoomModus;
begin
fActiveTool := fZoomTool;
end;
procedure TForm.SetPaintModus;
begin
fActiveTool := fPaintTool;
end;
Regards Jacco
P.S: I think calling destroy is not the standard way to go. Always call Free (this will prevent exceptions
when freeing nil).
Respond
delphi3000.com Article
Question: When you fill a DBGrid with Data from a Query you can search for each column of the Grid, with a TEdit.
Answer: Here is a sample project:
//
MWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMW/
/ Makes incremental search in a DBGrid with a TEdit
//
MWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMW
unit U_Main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Db, DBTables, StdCtrls, Grids, DBGrids, ExtCtrls, DBCtrls;
type
TFm_Main = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
qry_Data: TQuery;
Ds_Data: TDataSource;
dbg_Data: TDBGrid;
Label1: TLabel;
Ed_Search: TEdit;
Database1: TDatabase;
qry_DataNUM_FACTURA: TStringField;
qry_DataF_FACTURA: TDateTimeField;
qry_DataM_DEVENGADO: TFloatField;
DBNavigator1: TDBNavigator;
procedure dbg_DataTitleClick(Column: TColumn);
procedure FormCreate(Sender: TObject);
procedure Ed_SearchChange(Sender: TObject);
private
FQueryStatement: string;
//I will only validate for Fields of type ftFloat, but you easily
//customize the code for your own needs..
var
Fm_Main: TFm_Main;
implementation
{$R *.DFM}
DisableControls;
Close;
SQL.Clear;
SQL.Text := QueryStatement + 'ORDER BY '+ vs_Field;
Open;
EnableControls;
end;
//Get the DataType of the selected Field and change the Edit´s event
//OnKeyPress to the proper method Pointer
case Column.Field.DataType of
ftFloat: Ed_Search.OnKeyPress := FloatOnKeyPress;
else
Ed_Search.OnKeyPress := FALphaNumericKeyPress;
end;
end;
end;//End of TFm_Main.dbg_DataTitleClick
//
MWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMWMW
with dbg_Data do
begin
//First determine wich is the Selected Column
for vi_Counter:=0 to Columns.Count-1 do
if Columns[vi_Counter].Color = clTeal then
begin
vs_Field := Columns[vi_Counter].FieldName;
Break;
end;
So, you can customize the code to manage another DataTypes of TFields.
delphi3000.com Article
Question: Have you ever written an application where things have to know when things happen, such
as when an object gets freed then you need to update some UI screen or remove some
depency. Or in the case of a paint program where when a mode change requires a cursor
change, buttons to enable or disable or push down... if something gets deleted then you
have to do this and that etc... I have a solution that will keep your code clean of linking
code.
Answer: This uses Delphi's built in Messaging in TObject.
Whats in here:
Explaining on the concept.
Example implementation.
Source code of the MessageCenter is listed at the end.
Download full demo.
There are times when you write an application that turns into a linking nightmare when your
system needs to react to certain conditions. Examples are Mode changing in a paint program
requires cursor changes, an object being updated needs to update some UI element or
disable and enable controls, when an object gets freed you need to remove
dependencies. In other words there are side effects that you need to happen as a result of
something changing in your application. Coding these side effects can produce some nasty
code that is like a big spider web.
The solution to the problem is to use a "Message Center". I have created a easy to use
MessageCenter class that uses the built in messaging capablity already built into
TObject. Source code is at the end of this artical.
2. Example Implementation
This app is an image editor where you can have multiple images opened at once.
Each Image is opened in a Form class of TForm_ImageEdit.
A graphical list of buttons are listed at the top of the main form, there is one button per
opened image and a picture of the image is drawn on the surface of the button. Users can
click the button and active the form for that image.
First define the MessageID and the record for the message.
const
MID_ImageEdit = 14936;
type
TMID_ImageEdit = packed record
MessageID: Cardinal; // This is required field for Dispatching
Action: (aDestroyed, aActivated);
ImageEdit: TForm_ImageEdit;
end;
At some point in your main form when you create the Image Editor, add this code
after creation:
F:= TForm_ImageEdit.Create(Self);
// Listen to messages
GetMessageCenter.AttachListner(Self, F);
// Next few lines will add the button for the new form at the top of the main window.
.
.
.
This way the Main form will receive messages from the ImageEditor window.
ImageEditorClosed method will remove the button from the main form
EditorFocused will push down the button associated with the ImageEditor.
-------------------------
Thats all, you have low coupling and you may attach as many listners as you like.
This concept has a lot of potential and it will make your complex apps very simple and
maintainable.
=================================
unit MessageCenter;
{
William Egge public@eggcentric.com
Created Feb - 28, 2002
You can modify this code however you wish and use it in commercial apps. But
it would be cool if you told me if you decided to use this code in an app.
Doc:
procedure BroadcastMessage(MessageSource: TObject; var Message);
The message "Message" will be sent to all objects who called AttachListner
for the MessageSource.
If no objects have ever called AttachListner then nothing will happen and
the code will not blow up :-). Notice that there is no registration for
a MessageSource, this is because the MessageSource registration happens
automatically when a listner registers itself for a sender.
(keeping external code simpler)
Info:
Performance and speed were #1 so...
MessageSources are sorted and are searched using a binary search so that
a higher number of MessageSources should not really effect runtime performance.
The only performance penalty for this is on adding a new MessageSource because
it has to do an insert rather than an add, this causes all memory to be shifted
to make room for the new element. The benifit is fast message dispatching.
There is no check for duplicate MesssageListners per Sender, this would have
slowed things down and this coding is usefull only when you have bugs. And
hoping you prevent bugs, you do not have to pay for this penalty when your
code has no bugs.
}
interface
uses
Classes, SysUtils;
type
TMessageCenter = class
private
FSenders: TList;
FBroadcastBuffers: TList;
implementation
var
GMessageCenter: TMessageCenter;
ShuttingDown: Boolean = False;
Result:= GMessageCenter;
end;
{ TMessageCenter }
constructor TMessageCenter.Create;
begin
inherited;
FSenders:= TList.Create;
FBroadcastBuffers:= TList.Create;
end;
destructor TMessageCenter.Destroy;
var
I: Integer;
begin
for I:= 0 to FSenders.Count-1 do
TList(FSenders[I]).Free;
FSenders.Free;
FBroadcastBuffers.Free;
inherited;
end;
Index: Integer;
begin
L:= FindSenderList(MessageSource, Index);
if L <> nil then
begin
for I:= L.Count-1 downto 1 do
if L[I] = Listner then
L.Delete(I);
if L.Count = 1 then
begin
FSenders.Remove(L);
L.Free;
end;
initialization
finalization
ShuttingDown:= True;
FreeAndNil(GMessageCenter);
end.
Copyright 2000 delphi3000.com
Contact: delphi3000@bluestep.com'
good idea
Jorge Abel Ayala Marentes (Mar 4 2002 9:00PM)
you could also use the Observer pattern
Respond
Actually this is an implementation of an observer pattern except that it uses Delphi's built in
message dispatching and also maintains the relationships. This means your objects can be free of
maintaining observers and free of looping through them. Also any object can be "observed" without
altering or needing them to decend from some base class.
Respond
Yes, I would be happy to make one. What features would you like it to have. Are there any problems
you would want me to approach and solve?
Respond
Hi William,
I would like to have the demo on how to use MessageCenter in our applications. A simple
demo ( preferably non-database )
which shows all the features, methods and use of Message center will be be good enough.
Best Regards,
Jayan Chandrasekhar
Respond
I created a demo that shows all features. You can download it from the component link. If
you have further questions, I can change the demo to include more examples of usage.
Respond
why not...
EberSys (Mar 3 2002 7:30PM)
mmm... why not simply use Delphi TActionList, that's a very efficient way of keeping code centralized
and has some other cool features (as enabling/disabling the control that they're associated to, assign the
bitmap image to the associated control(s), etc)
I've noticed that many delphi programmers don't use this component, and is actually one that really helps
to organize all the source code
salu2
EberSys
Respond
The problem with using TActionList is that you have to take everything underneath TActionList with
it. Sometimes that isn't practical for systems which require a messaging with a small footprint.
For example, if you have business objects which use TActionList instead of the TMessageCenter
component, you suddenly have to worry about linking or deploying half of the VCL with your code.
Respond
delphi3000.com Article
Question: Should you avoid using functions that create and return objects as there is no direct
'ownership' of the object?
Answer: Freeing Result Objects from functions
This represents my own opinion as a full time Delphi developer (four years experience) with a
couple of years of C++ experience. My first reaction was that returning an Object (like a
stringlist) from a function was a bad thing because it introduces an object that is potentially
unowned and may not get freed. In managing objects you must always consider who owns
the object and is responsible for freeing it up. How you create them and use them is less
important than ensuring they are freed up.
And considering that all objects are created by calling a Constructor function, you can see
that returning objects from a function that creates them is acceptable, so long as you
maintain a reference to the object to let it be freed up after use.
You might consider developing an object management framework where every objects is
created indirectly through calls to the framework and the framework tracks each object as it
is created, manages explicit deletions (your code tells the framework that it no longer needs
the object) and deletes any undeleted objects when the framework is destroyed. This
approach can be a little inflexible though.
Creating & Freeing a lot of objects repeatedly can cause other problems (see my earlier
article http://www.delphi3000.com/article.asp?ID=1414) so you need to be well equipped to
detect memory leak bugs. If you are lucky they will cause an access violation when the
application terminates. If they don’t show up now then they will show up at some time in the
future, usually at the worst possible time. I recommend Snoop an excellent free utility for
trapping memory errors- you can find it at http://www.rmarsh.com/snoop.html
function makelist:tstringlist;
begin
result := tstringlist.create;
result.add('Address 1');
result.add('Address 2');
end;
Button1 is the correct way- the list is owned by the list variable, and listbox1 manages its
own copy. Button2 is naughty – the list is unowned by anyone so the stringlist still exists
when the application terminates. Running Snoop on these two confirms that Button1 works
fine and button 2 leaves the list (and its strings) hanging around.
Possible Solution
Stephen Deetz (Mar 22 2003 5:40AM)
There are several different ways to solve this problem.
This is the one our company has adopted and it has worked well.
A function with the potential to create an object uses the keyword Create somewhere in the name of that
function.
Along with that, we allow object of a similar type to be passed in and used. Only if nil is passed as an arg
to the function will an object be created.
Otherwise:
Respond
I'm not sure if the old Pascal objects will be around for ever. Compared to Object classes - they
suffer from at least one major disadvantage- not descending from Tobject but I can see the
attraction.
Respond
VCL way
Peter Morris (Oct 1 2000 4:01AM)
If you look at the VCL Inprise always do the following
Coding guidelines
Todd Lang (Sep 29 2000 9:14AM)
At our company, we simply avoided this issue by adopting guidelines for memory management. This
comes from our transition from C, and these have served us well. Here's a brief comment on how we
view memory ownership.
If a routine to be called requires a buffer (or an object in this case) the routine being called should be
supplied with the buffer. The reasoning goes along the lines that the caller knows more about how the
application functions than the callee. If the callee were responsible for the memory management, then
they would also the need the ability to signal an error condition and a reason for that error condition. (i.e.
- Could not allocate the memory due to insufficient memory.)
We use
procedure GetStringList( StringList : TStringList );
begin
StringList.Add( ...
StringList.Add( ...
end;
procedure Foo;
var
SL : TStringList;
begin
SL := TStringList.Create;
GetStringList( SL )
DoSomethingWithSL( SL );
SL.Free;
end;
This way all of the control is in the caller, who quite probably knows how to handle errors in a more
graceful way.
This is our company guideline and we count on this behaviour in all routines.
Respond
To make this even more clear, use the keywords 'out', or 'const' on your object parameters. It can
be confusing when looking at a prototype for a method that takes an object parameter. Do you
create the object before passing it in, or will the method create it internally?
For example, in your example you would use 'const' to communicate that you are expecting a
created object to be passed in. A const parameter can not be "created" inside a method as it would
change the address of that pointer (the compiler will not let you even try it). Since most
programmers understand this they would realize that they need to pass in a "created" object to you
method, since you have promised that you will not be creating it yourself.
On the flip side, if you want a caller to just pass in "place" to put an object that will be created
inside the method, use 'out'. Out is a newer reserved word you may see in COM programming that
works great for non-COM programming too. It means that whatever the value of the parameter is
when passed in irrelevant (and inaccessible I believe), the method is merely using the parameter to
pass out some results. A 'var' parameter on the other hand could be used but the value passed in
could be significant and is accessible.
Of course you could also pass out the object as a function result, as the original article
suggests. But when you can't or choose not to, I suggest using 'const' or 'out' to be imminently
clear what you are planning to do with the parameter.
Respond
IAddressList is an interface, and it's implementing object will be freed automatically when it's no longer
needed. But be careful with that method, because a new instance of TAddressList is created each time the
function is used, and since you don't have to free it by yourself, it's easy to use it in a loop, which causes
memory fragmentation like Mr. Bolton pointed out in a previous article.
Respond
Comments
Jérôme Tremblay (Sep 28 2000 8:15AM)
Functions that returns an Object are sometimes very useful, but that can be confusing for the users. Such
functions should be clearly defined as returning a new object that must be handled by the caller.
There is a Design Pattern that fits this description, it's a Factory. So by naming it something like
AddressListFactory, it's clear that it's task is to create something, and the caller is less prone to forget
it.
Respond
Safer way
John Cargill (Sep 28 2000 7:45AM)
instead of this:
have this:
Respond
Ah yes I did overlook that. Tch and heres me trying to increase robustness. Thanks.
Respond
delphi3000.com Article
Question: Implementing a different method of getting the line number of a selected row in a DBGrid. Using
dataset.RecNo is not the best practice with a master-detail relationships.
Answer:
This is a different method for getting the line number of a selected row in a DBGrid and the
total number of lines using master-detail relationships. The table.recno doesn't respect the
order of the items in the detail dataset.
So in the AfterScroll event of the master dataset you load a TStringList with the recno property
of all the items. Then you can use the index property of that TStringList to search the exact
rownumber.
The source uses two dataset (master-detail), a dbgrid and two label to put the selected row
number and the total row number.
...
Database: TDatabase;
Details: TTable;
Master: TTable;
...
nRowLbl: TLabel;
totRowsLbl: TLabel;
...
procedure LoadRowList();
function getRowNum: integer;
function getTotRowsNum: integer;
...
var
Form1: TForm1;
rowList: TStringList;
inserting: Boolean;
implementation
{$R *.dfm}
http://www.delphi3000.com/printarticle.asp?ArticleID=4704 18/11/2007
delphi3000.com - Printing Articles Page 2 sur 3
inserting:= false;
nRowLbl.Caption:=IntToStr(getRowNum);
end;
procedure TForm1.LoadRowList;
begin
rowList.Clear;
Details.DisableControls; //for better performance
Details.First;
while not Details.Eof do
begin
rowList.Add(inttostr(Details.RecNo));
Details.Next;
end;
Details.EnableControls;
end;
Copyright 2000 delphi3000.com
Contact: delphi3000@bluestep.com'
http://www.delphi3000.com/printarticle.asp?ArticleID=4704 18/11/2007
delphi3000.com - Printing Articles Page 3 sur 3
http://www.delphi3000.com/printarticle.asp?ArticleID=4704 18/11/2007
delphi3000.com - Printing Articles Page 1 sur 3
delphi3000.com Article
Saving and loading binary data to/from an MSSQL Image (Blob) field.
How to get binary data in a workable format into or out of an MSSQL Image (Blob) field using
Undertitle:
ADO components.
URL: http://www.delphi3000.com/article.asp?ID=1267
Category: ADO/OLE-DB
Uploader: andrew venmore
Question: How to get binary data in a workable format into or out of an MSSQL Image (Blob) field using
ADO components.
Answer:
The main problem I faced when trying to do this was to deal with the fact that TField.Value
returns a varOleStr no matter what was written into it, so the data needed to be converted into a
more usable format.
Note that there is no checking here that the TField is in fact of the correct type, and that the
stream must be created and free-ed elsewhere manually. Also, additional memory equal to the
size of the stream/blob is required, so be cautious if large amounts of data are involved.
For ease of use in my own application, I incorporated this functionality into my descendent of
TADOQuery.
-------------------------------------------------------
Examples:
If you have an ADO query "qryBlobTest" with the following fields: nFileIcon: Image; nFileData:
Image;
http://www.delphi3000.com/printarticle.asp?ArticleID=1267 12/11/2007
delphi3000.com - Printing Articles Page 2 sur 3
var
AFileIcon: TIcon;
MS: TMemoryStream;
begin
Result := false;
AFileIcon := TIcon.Create;
MS := TMemoryStream.Create;
try
AFileIcon.handle := ExtractAssociatedIcon('c:\temp\Test.doc'); // Pseudocode !!
AFileIcon.SaveToStream(MS);
Result := SaveToBlob(MS, qryBlobTest.FieldByName('nFileIcon'));
finally
AFileIcon.Free;
MS.Free;
end;
end;
http://www.delphi3000.com/printarticle.asp?ArticleID=1267 12/11/2007
delphi3000.com - Printing Articles Page 3 sur 3
Only if the PChar was going to be passed to something that was expecting a PChar (a Win32
API for example). In this case it is just used as a mechanism to get a pointer to the
beginning of the data.
Respond
Alliviated...
Oswaldo Ricardo Rocha Jr (May 15 2002 7:28PM)
Vine ha some days tendando to record documents in tables of the Ms-SQL Server, and this article
assisted me very.
Respond
http://www.delphi3000.com/printarticle.asp?ArticleID=1267 12/11/2007
delphi3000.com - Printing Articles Page 1
delphi3000.com Article
Question: What is the Composite Design Pattern ? How do you copy a directory with all subdirectories
in a simple way ?
Answer:
The COMPOSITE Design Pattern lets clients treat individual
objects and compositions of objects uniformly
(according to Gamma, Helm, Johnson and Vlissides
"Design Patterns", Addision-Wesley, 1995. Allthough
this book now is nearly 8 years old, it is even in
the new DOT NET World still important.)
aComponentClass = class
..
procedure DoSomething; virtual;
end;
aCompositeClass = class(aComponentClass)
aList: tList; // List of "aComponentClass" Objects
procedure DoSomething; override;
...
end;
aComponentClass.DoSomething;
uses Classes,SysUtils,..;
tFile = class
public
fName : string;
public
constructor Create(Name : string);
procedure Copy(DstDir : string); virtual;
tDirectory = class(tFile)
private
FileList : tList;
public
constructor Create(Name : string);
destructor Destroy;
procedure Copy(DstDir : string); override;
property Name;
end;
{ tFile }
{ tDirectory }
RelPath:=ExtractRelativePath(IncludeTrailingPathDelimiter(Name),
tDirectory(FileList[i]).Name);
tDirectory(FileList[i]).Copy(DstDir+'\'+RelPath)
end else
tFile(FileList[i]).Copy(DstDir)
end;
FileList := tList.Create;
Root:=IncludeTrailingPathDelimiter(Name);
s:=Root+'*.*';
if FindFirst(s, faAnyFile , sr) = 0 then
begin
repeat
if (sr.Name = '.') or (sr.Name = '..') then continue;
destructor tDirectory.Destroy;
var i : integer;
begin
for i:=0 to FileList.Count-1 do
tFile(FileList[i]).Destroy;
FileList.Free;
end;
Respond
Respond
delphi3000.com Article
Question: How to make objects aware of changes in the state of another object, without coupling the
changing object with the observing object.
Answer: To make, for example a form, avare of changes in another object we can
add a notify event to that object and implement some method to it. But
in that case only one observer can exist for that class. If more than
one class has to get notified of changes in the class, we have to implement some kind of list
with events. This is best implemented with an Observer-pattern.
For clarity we call the classes thats going to use these for observation
TObserver : The class that want to get notified of changes
TChanger : The class that changes
To use this you start with the class you want others to be able to
observe (TChanger), and implement a private field for TObserverMgr.
Then to avoid need for coupling between observer and TObserverMgr you
implement two methods, AddObserver and RemoveObserver in the changer
which just forwards the methods to TObserverMgr.
Now if you want to observe the field FMyString in changer you implement
a property MyString with a set method
Ok its esier for you to understand the source so look below or in Observer.zip
which has an example also.
unit Observers_un;
interface
uses
contnrs, classes;
type
{{
Item for holding an observer and one of its TNotifyEvents.
}
TObserverItem = class
private
FObserved: TObject;
FObserver: TObject;
FOnChange: TNotifyEvent;
protected
constructor Create(theObserved: TObject);
public
procedure Change;
property Observer: TObject read FObserver write FObserver;
property OnChange: TNotifyEvent read FOnChange write FOnChange;
end;
{{
Class for managing ObserverItems.
implementation
****************************************** TObserverItem
*******************************************
}
constructor TObserverItem.Create(theObserved: TObject);
begin
inherited Create ;
FObserved := theObserved ;
end;
procedure TObserverItem.Change;
begin
if Assigned(FOnChange) then
FOnChange( FObserved ) ;
end;
{
******************************************* TObserverMgr
*******************************************
}
constructor TObserverMgr.Create(Owner : TObject);
begin
inherited Create ;
FObserved := Owner ;
FObserverList := TObjectList.Create;
FObserverList.OwnsObjects := true;
end;
begin
result := -1 ;
For I := 0 to FObserverList.count-1 do
begin
anItem := TObserverItem( FObserverList.Items[i] ) ;
procedure TObserverMgr.RunObservers;
var
I: Integer;
anObserver: TObserverItem;
begin
For I := 0 to FObserverList.Count - 1 do
begin
anObserver := TObserverItem( FObserverList.items[i] );
anObserver.Change ;
end ;
end;
end.
It's dangerous and most often leads to code which can be understood only by stepping through it with a
debugger, because the flow of execution is obscured.
If you've ever had weird bugs pop up in unrelated places with data access components and data aware
controls, they were most likely caused by the fact that the VCL uses a form of multicasting (allowing the
same event to be handled in multiple places.)
An alternative is to use a hierachical event model where one component propagates messages to several
components that it knows. The idea is that one component handles the event, and calls public methods on
other objects which need to know when the event occurs. These components can in turn pass the event
on to other components that they know.
Respond
Any use of events (multi or not) obscures the flow of execution practically by the very nature of
'events'.
Just try tracing any user interface process through the vcl once the windows and component
messaging kicks in.
The observer model solves the design problem of separating the observer from the observed.
If anyone is interested in some theory just search for "observer design pattern" on the internet, e.g.
http://compsci.about.com/science/compsci/cs/observerpattern/
(which has a delphi link on it).
-Mat
Respond
Interface
Peter Morris (May 22 2001 10:24AM)
Personally I would use a TInterfaceList (cntnrs.pas)
type
INotifyEvent = Interface
procedure Notify(Sender: TObject);
end;
SomeList.Add(Watcher);
This is not compiled code, I just typed it directly into the browser
Respond
delphi3000.com Article
begin
QuerySQL := 'SELECT * FROM Customer.DB';
Query1.SQL.Add(QuerySQL);
Query1.Open;
end;
In the DBGrid event OnTitleClick, we just add an ORDER-BY clause to the sql and refresh the
query.
http://www.delphi3000.com/printarticle.asp?ArticleID=905 12/11/2007
delphi3000.com - Printing Articles Page 2 sur 3
Respond
Does anyone know how to modify the column heading of a TDBGrid so as to display an arrow
indicating which column is sorted ? (see Microsoft Outlook for an example of this).
Respond
Hi !
I found this code snippet in a project where I modified the TDBGrid to allow for painting
arrows. I see now that one routine paints it with PolyLine, the other with LineTo...:
Respond
http://www.delphi3000.com/printarticle.asp?ArticleID=905 12/11/2007
delphi3000.com - Printing Articles Page 3 sur 3
PaintArrowDown). Thanks
Respond
But you can come into some trouble depending on what database your are using
(i.e with access one field name can be in two part)
Respond
Great !
Thanks, man !
Respond
DBGrid
M Lopez (Apr 26 2000 11:37PM)
That's actually very good, but what happens if you want to sort by a lookup field when using a
client dataset?
tia
Respond
http://www.delphi3000.com/printarticle.asp?ArticleID=905 12/11/2007
delphi3000.com - Printing Articles Page 1 sur 5
delphi3000.com Article
Sorting a TListView
Undertitle: Sorting a TListView by the first or any arbitrary column
URL: http://www.delphi3000.com/article.asp?ID=1582
Category: VCL-General
Uploader: Ernesto De Spirito
ListView1.SortType := stText;
ListView1.SortType := stNone;
Now that we have an OnCompare event, to sort the list and having
sorted, we should set SortType to stBoth (instead of stText, that
sorts by the first column without using the OnCompare event):
ListView1.SortType := stBoth;
http://www.delphi3000.com/printarticle.asp?ArticleID=1582 11/12/2007
delphi3000.com - Printing Articles Page 2 sur 5
ListView1.SortType := stBoth;
ListView1.SortType := stNone;
or else:
ListView1.CustomSort(nil, 0);
Then, every time you want to sort the list, you call CustomSort
passing the address of the ordering function. For example:
ListView1.CustomSort(@ByFourth, 0);
The code below is an extend to the sorting code on this page. It has a trick for the ascending
global (unneeded) variable. And can deal with columns containing numerical data.
http://www.delphi3000.com/printarticle.asp?ArticleID=1582 11/12/2007
delphi3000.com - Printing Articles Page 3 sur 5
end;
try
// Check out if the column contains numerical data
f1 := StrToFloat(s1);
f2 := StrToFloat(s2);
// if code execution get's to this point, we have to deal with numerical values, and return -1, 0,
1 according to
if f1 > f2 then
result := 1
else if f1 < f2 then
result := -1
else
result := 0;
except
// Else the result is based upon string comparison
Result := AnsiCompareText(s1, s2)
end;
VB Equivalent ?
Roni Havas (Aug 5 2001 11:38AM)
The following procedure will sort a TListView while you click the Header Column. How can I do this
with Delphi ?. Doesn't Delphi declare the same Method/Property as VB ?
*************************************************************************
Private Sub ListView1_ColumnClick(ByVal ColumnHeader As MSComctlLib.ColumnHeader)
If ListView1.SortOrder = lvwAscending Then
ListView1.SortOrder = lvwDescending
Else
ListView1.SortOrder = lvwAscending
End If
ListView1.SortKey = ColumnHeader.Index - 1
ListView1.Sorted = True
End Sub
************************************************************
Greetings,
Roni Havas
Respond
RE: VB Equivalent ?
Ernesto De Spirito (Aug 6 2001 7:26AM)
http://www.delphi3000.com/printarticle.asp?ArticleID=1582 11/12/2007
delphi3000.com - Printing Articles Page 4 sur 5
The controls aren't 100% equivalent. However, what you want to do can be achieved using
some of the elements I presented in the article:
var
Ascending: boolean;
Ernesto
Respond
Thank's a lot.
It works smoothly. Excellent Job
Best Regards,
Roni Havas
Respond
A little improvement
Ernesto De Spirito (Aug 6 2001 9:16AM)
Ernesto
Respond
http://www.delphi3000.com/printarticle.asp?ArticleID=1582 11/12/2007
delphi3000.com - Printing Articles Page 5 sur 5
http://www.delphi3000.com/printarticle.asp?ArticleID=1582 11/12/2007
delphi3000.com - Printing Articles Page 1
delphi3000.com Article
type
TChangeEvent = procedure(Sender: TObject) of object;
You may pass any parameters to your event methods, so the following code is perfectly
ok:
type
TDateChanged = procedure(MyBirthDay, YourBirthDay: TDateTime; ABoolean: boolean)
of object;
2) Augment your class with event support code. Classes support events via properties. Yes,
events are properties! You need a private field for storing a reference to the event handler
your user assigns, and a property definition. If you like, you can have a plain property
definition (writing directly to your private field) or introduce a pair of set/get methods:
type
TMyTestObject = class
private
FChange: TChangeEvent;
FChange2: TChangeEvent;
procedure SetChange(Value: TChangeEvent);
public
property OnChanged1: TChangeEvent read FChange write FChange; //plain
property definition
property OnChanged2: TChangeEvent read FChange2 write SetChange2; // property
definition with set method
end;
[...]
procedure TMyTestObject.SetChange(Value: TChangeEvent);
begin
if FChange2 <> Value then
FChange2 := Value;
end;
3) Now, you should call the event procedure everywhere in your class where appropriate.
Let's asume a method "Work" that does some computations:
procedure TMyTestObject.Work;
begin
// (compute something important)
FChange(self);
end;
4) STOP! Don't forget to check for NIL! Since your users are not obliged to use your event,
you will quite probably face the situation of having a nil pointer in your private event field. In
this case, you will produce a wonderful AV with the above code fragment. So, before calling
any event routine, you should check wether the event is assigned or not:
procedure TMyTestObject.Work;
begin
// (compute something important)
if Assigned(FChange) then
FChange(self);
end;
5) It is a good practice to factor out the code that checks for events pointing to nil:
procedure TMyTestObject.DoChange;
begin
if Assigned(FChange) then
FChange(Self);
end;
procedure TMyTestObject.Work;
begin
// (compute something important)
DoChange;
end;
6) Now, you can create an object of your class and assign an event procedure. See the
sample code how to do this.
Sample code
unit Unit1;
interface
uses
type
(* type of event gets defined here.
you don't need to specify the "Sender: TObject", if you don't need to know who the
sender is. however, supplying this parameter is a good habit.
if you want to provide information about the changed value, put as many additional
parameters "on top". Here, I have only supplied an integer parameter, since I wasn't
sure which params would be interesting to you *)
TChangeEvent = procedure(Sender: TObject) of object;
TMyTestObject = class
private
(* you need a local store for your event in your class. this is normally done within
the private section, so to keep classes that derive from your class away from
tampering with your events *)
FChange: TChangeEvent;
FMyBoolean: boolean;
protected
(* this procedure should be called whenever you want to signal a change to your
"subscribers", as we like to call it. if you are into design patterns: this is a
"template method". *)
procedure DoChange;
procedure SetMyBoolean(AValue: boolean);
public
published
(* this is where you make your event public to the rest of the world. you could also
put it into the "public" section, if you like, but this would hide your event from
mr. object inspector.
with the "read" and "write" keywords, you define how access to your local
store is achieved. you could also write an extra "accessor" function, but this
is rather uncommon for events.*)
property OnChanged: TChangeEvent read FChange write FChange;
property MyBoolean: boolean read FMyBoolean write SetMyBoolean;
end;
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
procedure ItHasChanged(Sender: Tobject);
public
{ Public declarations }
end;
var
Form1: TForm1;
_myTest: TMyTestObject;
implementation
{$R *.DFM}
procedure TMyTestObject.DOChange;
begin
(* if your event isn't assigned in the client, the FChange field will be nil.
it is a prerequisite in events programming to check wether something is
nil before calling on it. *)
if Assigned(FChange) then
FChange(Self);
end;
end.
delphi3000.com Article
And this is where we encounter our first problem. How does the Subject notify it's
Observers? By calling one of the Observer's method. But this means the Subject has to know
the Observer somehow, not very flexible. We could use an abstract Observer class, in that
way our Subject would always be able to call a known method, but this would force us to
always descend observers from the same hiearchy, which is not very practical (and
sometimes completely impossible).
Fortunately, for each problem there is a solution, and in our case there are at least TWO!
The first solution is to use Interfaces. Our Subject would accept an IObserver interface. The
neat thing about interfaces is that any class can implement any interface, and as long as an
object would implement IObserver, it would be able to connect to any subject. (If anyone is
interested in this implementation, let me know and I'll post another article, but there are
gazillions of examples on the net if you search a little). This works perfectly, but after
working with interfaces a little, I decided not to use them. The main reason for this is that
code navigation in the IDE is harder, Ctrl-Click brings you to the interface definition, not the
implementation. No big deal. But I wanted something better.
The second solution, the one I'm describing in this article, is a little different. The Observer is
no longer an object, it's a method. Just like standard VCL events. This means that a single
event handler could be used with a component and a Subject at the same time.
Let say we have a TSubject with an event called OnChange. This event is of type
TMultiNotifyEvent. Here is how a user would connect to this event. ChangeHandler is a
procedure matching a TNotifyEvent.
MySubject.OnChange.Attach(ChangeHandler);
From that point on, every time MySubject changes, it will call ChangeHandler, just like a
normal OnChange event. The difference is that there might be many observers of that event.
When the object no longer wish to receive updates from MySubject, it detaches:
MySubject.OnChange.Detach(ChangeHandler);
type
TMySubject = class
private
FOnChange: TMultiNotifyEvent;
protected
procedure DoChange; virtual;
public
constructor Create;
destructor Destroy; override;
property OnChange: TMultiNotifyEvent read FOnChange;
end;
implementation
constructor TMySubject.Create;
begin
inherited;
FOnChange := TMultiNotifyEvent.Create;
end;
destructor TMySubject.Destroy;
begin
FOnChange.Free;
inherited;
end;
procedure TMySubject.DoChange;
begin
{ Signal is the method that notify every observers }
OnChange.Signal(Self);
end;
In order to use a new type of event, one must declare a new class inheriting from
TMultiEvent. Why am I doing this? Because TMultiEvent only stores and knows about
TMethod records, it does not know what type of event is used, what are it's parameters, etc.
By having the SignalObserver method Abstract, each concrete class can typecast it to the
type of events it handles and pass all the required parameters. Also, creating a new class
provices a certain level of type by making sure you register a compatible method with the
subject. See TMultiNotifyEvent at the end of this article to see how to create customized
events, it's pretty straight forward.
That's it for the explication, if you have any questions just leave a comment and I'll try to
update the article accordingly.
unit uMultiEvent;
interface
uses
Classes, SysUtils;
type
TMultiEvent = class
private
FObservers: TList;
protected
function FindObserver(Observer: TMethod): integer;
function GetObserver(Index: integer): TMethod;
procedure SignalObserver(Observer: TMethod); virtual;
public
constructor Create;
destructor Destroy; override;
procedure Signal;
end;
TMultiNotifyEvent = class(TMultiEvent)
private
FSender: TObject;
protected
procedure SignalObserver(Observer: TMethod); override;
public
procedure Attach(Observer: TNotifyEvent);
procedure Detach(Observer: TNotifyEvent);
implementation
{ TEvent }
constructor TMultiEvent.Create;
begin
inherited;
FObservers := TList.Create;
end;
destructor TMultiEvent.Destroy;
begin
{ This assertion is facultative, but I prefer when all my objects }
{ are "clean" when they are destroyed. }
Assert(FObservers.Count = 0, 'Not all observers were detached');
FreeAndNil(FObservers);
inherited;
end;
i: integer;
begin
{ Search fails by default, if there is a match, result will be updated. }
Result := -1;
for i := (FObservers.Count div 2)-1 downto 0 do
begin
{ We have a match only if both the Code and Data pointers are the same. }
if (Observer.Code = FObservers[i * 2 ]) and (Observer.Data = FObservers[i
* 2 + 1]) then
begin
Result := i;
break;
end;
end;
end;
Assert(Assigned(@Observer));
{ We could make this method Abstract and force descendants, but }
{ I prefer to do a run-time check to validate the passe methods }
end;
procedure TMultiEvent.Signal;
var
i: integer;
begin
{ Call SignalObserver for each stored observers in reverse order. }
{ TMultiNotifyEvent }
end.
Change history
2001.06.21
- Fixed the example usage code (TMySubject.DoChange)
- Added a check to prevent an observer to link with an event twice.
- Minor changes in the text
2001.06.22
- Fixed a bug in Detach (Thanks to Flurin Honegger)
2001.10.01
- Changed the SignalObserver method to be simply virtual instead of Abstract. It allows to
validate the parameter at run-time in the ancestor code instead of duplicate it in all
descendants.
2001.11.29
- Updated the example TMultiNotifyEvent.SignalObserver to call inherited (thus validating
the callback method).
Design Time
Yoav Abrahami (Oct 7 2001 12:54PM)
The one thing bugging me in you're implementation, is the lack of Design time support.
It would be nice to have the property editor show the list of events.
You can accomplish this with the use of a TCollection and TCollectionItem as base classes for TMultiEvent
and the list.
Perhaps make a descendant of this class with a TNotifyEvent property that you could assign to an existing
vcl notify event (e.g. TButton.OnClick) (hoping it doesn't get reset later at runtime) so you could add
listeners (observers) to that class to listen to the button. Like a souped up action class.
thanks,
-Mat
PS: Maybe, for the example, modify the class to keep a TList to store TNotifyEvent pointers directly to
make it easier to understand at a first read.
Respond
Interesting.
I made an ImageLink class. It passed itself to the ImageList which held a list of them. When I
wanted a new component to point to an image I would expose the "Index" property of my
ImageLink, and set the OnChange event of my ImageLink so my component would be notified. The
ImageList would call each ImageLink which would in turn fire an OnChange event to the owning
component.
Pete
Respond
delphi3000.com Article
Question: When asked about my favourite dataaccess component, I always answer: TClientDataset. And
this is not only for its well-known features: its being simple to use, but reliable foundation stone
to build multitiered applications. There is also some kind of magic in the local applicability of this
component: you can always discover something new about it. It is a great field for
experimenting, if you enjoy playing rather than using the most obvious ways. It can be an
inspiration while remaining still easy, developer-friendly and reliable. This is a true miracle, ins''t
it?
Answer:
LOOKUPING
To start, I would like to remind you of one function we already know the Clientdataset can carry
out: to be a dataset for lookup fields in another dataset. Receiving its Data from the provider
only the first time when a lookup value is needed, the Clientdataset keeps all the information, so
that there is no need to call the server for these values again in this session. Of course, you can
use an usual Dataset and cached updates for this purpose, but it is well worth doing some simple
preliminary tests. Compare the time results and you will be astonished by the difference.
Moreover, there could be a situation in which the usual Dataset will be truly powerless: what if
you don't need the lookup data in the database at all! There are plenty of examples of data
which is never, or hardly ever, changed. In this case, the standard way to save and economize
transactions and database space is to include this information in something like the good old
inifiles on the client machine (NAME=VALUE) and to develop some tools to read and translate
these values before the data presentation. Using Tclientdataset instead is much more logical and
consistent: your datasources will be clearly indicated on your datamodule and the approach to all
data can be the same no matter where the data is situated. The Clientdataset decodes and
encodes the files by itself and transmitts the data just as if the source was a real table.Moreover,
you can easily add a tool which will change this data automatically on all the client machines -
using that the clientdataset supports the briefcase model, rarely transacting with the server and
applying updates or just replacing the files. It's up to you to choose the concrete approach, but
they all are simple, logical and safe.
------
procedure TfrmTypes.FormClose(Sender: TObject; var Action: TcloseAction);
begin
If DataMod.cdsActions.State in [dsEdit, dsInsert] then
DataMod.cdsActions.Post;
If DataMod.cdsPayments.State in [dsEdit, dsInsert] then
DataMod.cdsPayments.Post;
DataMod.cdsActions.MergeChangeLog;
DataMod.cdsPayments.MergeChangeLog ;
DataMod.cdsActions.SaveToFile (ExtractFilePath(Application.Exename)+'Actions.cds’);
DataMod.cdsPayments.SaveToFile (ExtractFilePath(Application.Exename)+'Payments.cds’);
Listing 1: In this example, two Tclientdatasets are used to give values for lookupfields.The
values can be different for different clients and can be, altough rarely, changed by the user. If
this occurs, Clientdatasets will save the changes in local files.
------
BACKUPING
Yet another advantage: for what else can we use the wonderful 'bisexuality' of the Clientdataset
to respond equally to tables and files? The answer is obvious: for everything previously done by
the ASCII files. It is not that I don't rely on the BDE ASCII driver, but how often have you
received messages as 'Data structure corruption!, etc., even if you have used it after diligent
preparation. There are other market products which do this task efficiently, but our hero beats
them all: it is enough to connect a provider and a clientdataset together to transfer all the
information from a table to a file. And, if necessary you can also easily send it back to a table,
again and again, and there will be no mistakes because of a missed comma or a misscount of a
string. In this contest, the ClientDataset may loose in speed, especially for very big tables, but
http://www.delphi3000.com/printarticle.asp?ArticleID=1100 12/11/2007
delphi3000.com - Printing Articles Page 2 sur 6
unit UnBackup;
interface
----------
implementation
Listing 2: In this example, a Clientdataset and a provider are used to save a file copy of the most
important for the application Oracle table on the user machine every time when the user is
exiting the application.
--------------
DATEPICKING
The next step in our investigation is: the Clientdataset can produce data for your application.
Sounds strange? Look at the following examples.
A client of mine asked to add additional 'Day of week' field to the database application. He told
me that when filling data for the previous few days (something often done in his organization) it
is much easier to orientate oneself by the days, not by the data and wanted to enter days, not
dates on the screen up to seven days back. This task can be performed in Delphi in many
different ways, but using a Clientdataset is one the most economical ways. There is one more
miracle here: the clientdataset can use a database table or a flat file to work, but in fact it does
not need them- it can be a live, selfsupporting dataset even without any external data carrier.
All you need are persistent fields, a dataset creating (right clicking on the component in design
time) and an idea how this can be useful to you. I don't know if the result is a 'real' dataset or a
brilliant imitation, but it works. I have created dataset with two persistent fields. Every time
when the user starts the application, they fill: the first with the date, and the second with the
name of the corresponding day of week, starting from the present day and going up to seven
days back. So no days repetition will occur in the dataset and you can use it for a lookup field
just the same way you would use a 'regular' database-based dataset. When you are choosing a
day of the week on the grid, the appropriate date is written in the database and this cannot be
done for more than a week back .
-----
procedure TDataMod.DataModuleCreate(Sender: Tobject);
.......
.......
try
cdsweek.Open ;
for iWeek:= 0 to 6 do
begin
cdsweek.Insert;
cdsweekdatenow.AsDateTime := date-iWeek;
cdsweekdayofweek.AsString := FormatDateTime('dddd' ,Date-iWeek);
end;
cdsweek.post;
except
end;
Listing 3:TclientDataset works even without a table or a flatfile and still produces a lookup field.
Notice the use of the same 'Insert' and 'Post' as if a real table were connected to the Dataset.
----------------
http://www.delphi3000.com/printarticle.asp?ArticleID=1100 12/11/2007
delphi3000.com - Printing Articles Page 3 sur 6
You dispose of TdateTimePicker to enter a date in your database. But what to do if your client is
a web one, created by using the Internet Express technology. I haven't come across an
equivalent of the datetimepicker for internet Express yet. You can, of course, write one (send it
to me, I will be hapy to have it) or you can use instead...
Yes, you've guessed correctly: a local use of our beloved Tclientdataset. You will have the start
(‘From’) and the end (‘Until’) date parameter list for your query by just putting them both on the
Web module and filling them on OnCreateWebModule procedure.The process is exactly the same
as described above, the only difference is that you may prefer filling up all the dates for a year,
or all the months, or whatever works for you. And even one date field in your clientdatasets is
enough. Enter the two parameters for the datefield in your XMLBroker; connect them with
TquerySelectOptions, compile the project, don't forget to write a query with these parameters in
your remote datamodule and enjoy the results: you do have operating datepicking, maybe not
as 'pretty' as the Windows one but fully functional on your Web browser! Thanks again to the
amazing clientdataset.
TClientDataset.UpdateStatus
Diogenis Fardellas (Jan 17 2003 3:25PM)
I would like to ask anyone that knows any solution to the following:
I have a clientdataset and I insert a new record. For a specific reason, I need to change its
updatestatus from usInserted to usModified. How can I do it?
Respond
Thanks
Respond
Sorry, I don't have an answer for your situation, but I would be REALLY interested to know
http://www.delphi3000.com/printarticle.asp?ArticleID=1100 12/11/2007
delphi3000.com - Printing Articles Page 4 sur 6
what situation would required a nesting of more than 15 datasets. I pity the user who has to
maintain this kind of master/detail/detail/detail/detail/detail/detail/(you get the idea)
relationship.
Cheers,
Raymond Barlow
Respond
well basically it takes up whole lots of memory (dont know exactly how, but i know it does,
since it has far more features than a normal table), so you probably are better not stuffing
it.
anyways the beauty of it is that you can handle only a certain amount of data, so if gives
you more flexibility.
now, this sounds like a charm in theory but once you get your hands on doing it, but i tried
with 60,000 records and it works fairly fast :)
one quick solution: use two datasets: a query for using in a grid, read only,and a
clientdataset for modifying that info that is shown in edits and combos... maybe sounds
crappy but it worked like a charm for me..
and a client dataset
Respond
ClientDataSet BUG!!!
AlGol (Jan 4 2001 5:11AM)
try following steps:
cdsTable.LoadFromFile('subitem.cds');
cdsTable.AddIndex('IXNAME','parentitemID;Name',[ixUnique]);
then modify your ClientDataSet data so, that IXNAME inique index is unvalid. Try to post it. First
time the exception (Key violation) is raised. Good.
BUT NEXT TIME IT POSTED!!!
Respond
http://www.delphi3000.com/printarticle.asp?ArticleID=1100 12/11/2007
delphi3000.com - Printing Articles Page 5 sur 6
Trouble in Win NT
Ismir Kamili (Jun 28 2000 4:47AM)
I`ve tried the solution proposed in the article (The backup system). It run successfully in Win95 /
Win98 Environment, but it crashed when run under Windows NT 4.0.
The message was `Cannot load IDAPI Service Library`. It happened when I assigned the Data
property of Provider to client dataset.
Regards,
IK
Respond
Have you install the BDE properly on the machine? Have you import the Midas.dll also?
Respond
Related article
anonymus (Jun 5 2000 6:12PM)
Dear anonymous,
these are two absolutely different articles.Even the topics are different.Only the field is
common, but this field is wide enough and there is even some place for you. I think, before
writing comments it could be helpful for you to write these articles!
Respond
yeah duh
Respond
http://www.delphi3000.com/printarticle.asp?ArticleID=1100 12/11/2007
delphi3000.com - Printing Articles Page 6 sur 6
http://www.delphi3000.com/printarticle.asp?ArticleID=1100 12/11/2007
delphi3000.com - Printing Articles Page 1
delphi3000.com Article
tListBox Tricks
Undertitle:
URL: http://www.delphi3000.com/article.asp?ID=3986
Category: VCL-General
Uploader: Jochen Fromm
or simply
ListBox.ScrollWidth := MaxWidth;
* How can you determine the Listbox index of the topmost entry or item,
which is displayed at the top of the Listbox ?
or simply
ListBox.TopIndex;
* How can you move items in a ListBox with Drag and Drop ?
* How can you move items in a ListBox with Drag and Drop,
which scrolls if you reach the top or the bottom ?
Box.ItemIndex := TargetPos;
r:=Box.ItemRect(TargetPos);
if r.top < Box.ItemHeight then
begin
if TargetPos > 0 then
Box.ItemIndex:=TargetPos-1;
end else
if r.bottom > (Box.Height-Box.ItemHeight) then
begin
if TargetPos < (Box.Items.Count-1) then
Box.ItemIndex:=Box.ItemIndex+1;
end;
if (TargetPos = -1)
then Accept := False
else Accept := True;
end;
delphi3000.com Article
{-----------------------------------------------------------------------------
Unit Name: Unit1
Description:
TMammal = record
TType: string;
Hair: string;
speak: string;
end;
At the end of this is the source for the DFM file used to run this application.
(It just needs to be bound into a project)
Notes:
If there are any terms or concepts in this demo you don't understand please
ask me.
Dependancies:
Compiler version:
History:
unit Unit1;
interface
uses
Windows, Messages, sysutils, Variants, classes, Graphics, Controls, forms,
Dialogs, StdCtrls;
type
PMammal = ^TMammal;
TMammal = record
TType: string;
Hair: string;
speak: string;
end;
TForm1 = class(TForm)
btnAdd: TButton;
btnDelete: TButton;
ListBox1: TListBox;
Button1: TButton;
GroupBox1: TGroupBox;
Label1: TLabel;
Label2: TLabel;
lblType: TLabel;
Label4: TLabel;
lblSpeak: TLabel;
lblHair: TLabel;
Label7: TLabel;
Button2: TButton;
Label3: TLabel;
lblIndex: TLabel;
Button3: TButton;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure btnAddClick(Sender: TObject);
procedure btnDeleteClick(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure ListBox1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
{ Private declarations }
MammalList: TList;
public
function GetRandomHair: string;
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FreeList;
{-----------------------------------------------------------------------------
Procedure: TForm1.FreeList
Author: Stewart Moss
Date: 20-Jan-2003
** Very important you can't just free the TList object and expect **
** everything else to disappear **
end;
Description:
Creates a new record and stores it in the Pointer Reference "APMammal".
One of the benifits of using objects is you don't have to know how
big the object is to create it.
// Create and assign memory to a new TMammal record and store it's pointer
new(APMammal);
with APMammal^ do
begin
case random(2) of
0: // Human
begin
// if it wasn't for "with APMammal^ do" then these
// lines would read.
// APMammal^.TType := 'Human';
// APMammal^.Hair := 'Blonde';
// APMammal^.speak := 'Hello!';
//
// "APMammal^." is called "dereferencing the pointer"
//
// You are basically saying:
// set (or get) the value of the item (eg Hair) at the pointer
// address held in APMammal.
TType := 'Human';
Hair := GetRandomHair;
speak := 'Hello!';
end;
1: // Dog
begin
TType := 'Dog';
Hair := GetRandomHair;
speak := 'Woof Woof!';
end;
end; // case
end; // with
showlist;
end;
procedure TForm1.showlist;
{-----------------------------------------------------------------------------
Procedure: TForm1.showlist
Author: Stewart Moss
Date: 20-Jan-2003
Note:
This time I have "type-casted" the generic pointer. I have
prefered not to use a temporary variable called "APMammal" again.
This saves on memory management when accessing more than one property.
var
loop: integer;
APMammal:PMammal;
begin
ListBox1.Items.Clear;
for loop := 0 to MammalList.Count - 1 do
begin
APMammal := PMammal(MammalList.Items[loop])^;
ListBox1.Items.add(IntTostr(loop) + ' -> ' + PMammal(APMamal.TType);
end;
end;
-----------------------------------------------------------------------------}
var
loop: integer;
begin
try // cheap trick to swallow un-wanted exceptions
ListBox1.Items.Clear;
for loop := 0 to MammalList.Count - 1 do
begin
ListBox1.Items.add(IntTostr(loop) + ' -> ' +
PMammal(MammalList.Items[loop])^.TType);
end; // for
// Call the Listbox1 click event, to update the groupbox by retreiving the record
// from the list
ListBox1Click(Self);
// User has to enter the index of the item to delete (ie remove from the list)
tmpstr := Inputbox('Delete an item out of the list',
'Which item do you want to delete (0=first) ?', '');
try
tmpint := StrToInt(Trim(tmpstr));
except
raise exception.create(tmpstr + ' is not an integer!');
end;
// Now delete it
DeleteMammalAtIndex(tmpint);
showlist;
Procedure: TForm1.FindMammalByIndex
Arguments: Index: integer
Description:
This returns the TMammal record stored at position Index
in the TList.
with APMammal^ do
begin
lblType.caption := TType;
lblHair.caption := Hair;
lblSpeak.caption := speak;
lblIndex.caption := IntTostr(ListBox1.ItemIndex);
end; // with
end;
begin
case random(5) of
0: result := 'Blonde';
1: result := 'Brown';
2: result := 'Blue';
3: result := 'Black';
4: result := 'Red';
end; // case
end;
procedure TForm1.ClearGroupBox;
begin
lblType.caption := '<none>';
lblHair.caption := '<none>';
lblSpeak.caption := '<none>';
lblIndex.caption := '<none>';
end;
end.
{
{
Unit1.DFM
----------
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
OnClose = FormClose
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object Label1: TLabel
Left = 24
Top = 12
Width = 193
Height = 13
Caption = 'Nice and dirty record TList demonstration'
end
object btnAdd: TButton
Left = 24
Top = 61
Width = 75
Height = 25
Caption = 'Add'
TabOrder = 0
OnClick = btnAddClick
end
object btnDelete: TButton
Left = 104
Top = 31
Width = 75
Height = 25
Caption = 'Delete Prompt'
TabOrder = 1
OnClick = btnDeleteClick
end
object ListBox1: TListBox
Left = 28
Top = 92
Width = 217
Height = 109
ItemHeight = 13
TabOrder = 2
OnClick = ListBox1Click
end
object Button1: TButton
Left = 184
Top = 61
Width = 75
Height = 25
Caption = 'Refresh'
TabOrder = 3
OnClick = Button1Click
end
object GroupBox1: TGroupBox
Left = 24
Top = 208
Width = 221
Height = 137
Caption = ' Object Properties '
TabOrder = 4
object Label2: TLabel
Left = 53
Top = 50
Width = 24
Height = 13
Caption = 'Type'
end
object lblType: TLabel
Left = 105
Top = 50
Width = 36
Height = 13
Caption = '<none>'
end
object Label4: TLabel
Left = 50
Top = 74
Width = 31
Height = 13
Caption = 'Speak'
end
object lblSpeak: TLabel
Left = 105
Top = 74
Width = 36
Height = 13
Caption = '<none>'
end
object lblHair: TLabel
Left = 105
Top = 98
Width = 36
Height = 13
Caption = '<none>'
end
object Label7: TLabel
Left = 39
Top = 98
Width = 52
Height = 13
Caption = 'Hair Colour'
end
object Label3: TLabel
Left = 52
Top = 26
Width = 26
Height = 13
Caption = 'Index'
end
object lblIndex: TLabel
Left = 105
Top = 26
Width = 36
Height = 13
Caption = '<none>'
end
end
object Button2: TButton
Left = 196
Top = 352
Width = 75
Height = 25
Caption = 'E&xit'
TabOrder = 5
OnClick = Button2Click
end
object Button3: TButton
Left = 105
Top = 61
Width = 75
Height = 25
Caption = 'Delete'
TabOrder = 6
OnClick = Button3Click
end
end
delphi3000.com Article
Question: This was a small demonstration to teach a friend how to use the TList helper object.
It creates a list of Pointers to Integers but they could be a list of pointers to any record or class type.
Answer:
{-----------------------------------------------------------------------------
Unit Name: Unit1
Author: StewartM (Stewart Moss)
Version 1.0
-----------------------------------------------------------------------------
Compiler Directives:
Purpose:
Dependancies:
Description:
At the end of this is the source for the DFM file used to run this application.
(It just needs to be bound into a project)
Notes:
History:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
PInteger = ^Integer;
TForm1 = class(TForm)
btnAdd: TButton;
btnDelete: TButton;
ListBox1: TListBox;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure btnAddClick(Sender: TObject);
procedure btnDeleteClick(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure CustomizeDlg1Close(Sender: TObject);
procedure acnTestExecute(Sender: TObject);
private
{ Private declarations }
TempList: TList;
procedure FreeList;
procedure showlist;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
FreeList;
TempList.Free;
end;
procedure TForm1.FreeList;
{-----------------------------------------------------------------------------
Procedure: TForm1.FreeList
Author: Stewart Moss
Date: 20-Jan-2003
Remember by Deleting the first element in the list, you are shifting the
list up and you are also reducing the TempList.count..
** Very important you can't just free the TList object and expect **
** everything else to disappear **
-----------------------------------------------------------------------------}
var
loop: Integer;
tmpcount : Integer;
begin
tmpcount := templist.Count -1;
end;
// Create and assign memory to a new Integer and store it's pointer
// in AInteger
new(AInteger);
// Now take tmpint and store it at the memory address referenced by the pointer
// AInteger
AInteger^ := tmpint;
showlist;
end;
procedure TForm1.showlist;
{-----------------------------------------------------------------------------
Procedure: TForm1.showlist
Author: Stewart Moss
Date: 20-Jan-2003
// User has to enter the index of the item to delete (ie remove from the list)
tmpstr := Inputbox('Delete an item out of the list', 'Which item do you want to delete (0=first) ?', '');
try
tmpint := StrToInt(trim(tmpstr));
except
raise Exception.Create(tmpstr + ' is not an integer!');
end;
tmpint2 := Integer(TempList.Items[tmpint]^);
dispose(TempList.Items[tmpint]);
TempList.Delete(tmpint);
showlist;
end;
end.
end.
end.
(*
UNIT11.DFM
----------
*)
procedure TForm1.FreeList;
var
loop: Integer;
begin
for loop := templist.Count -1 downto 0 do
begin
dispose(TempList.Items[0]);
end;
TempList.Clear
end;
Respond
hehehe
Respond
PInteger
Martin Strand (Jun 22 2003 8:02PM)
You doesn't need to declare PInteger, since it already is declared within some Delphi unit. The declaration
is the same, though.
Respond
RE: PInteger
Stewart Moss (Jun 23 2003 6:30PM)
The declaration of PInteger was to show that you can use a pointer to anything. I will be sumitting a
more complicated example shortly to demonstrate how to use pointers of other types (ie Records)
Respond
Evolution of the web industry leads developers through sophisticated technologies waving
and balancing between client-oriented and server-oriented approaches. New standards for
browsers, protocols, and scripting languages replace each other. The next step is XML -
extended markup language, which suppose to replace HTML and dominate in web
applications as main communication format. XML has been designed to transfer data to
clients separately from data's format. Web server or Internet client should combine XML data
with a style sheet and generate well-known HTML code to display the data.
On the other hand you may use XML language to extend functionality of your programs.
Consider XML as a universal data storage format, which as a matter of fact much more
flexible than DBF or any other relational database format.
Why should you use XML as the only advanced way to create web pages? XML is more
universal and more flexible than just only markup language. You may even create your own
data format or network protocol by inventing your own tags and interface.
Below you will find one example of such non-standard XML usage, designed for use in
Borland Delphi applications.
Analysis
Your program needs to store some data in a file or transfer the data to another computer.
You can save your data in text or any proprietary format. You will need to write code to
support such format. Should you change data structure, you will face necessity to modify the
code. In any case you should not consider this format as a standard one, and other
developers probably will not support the format.
Use XML as base for your data storage and transfer and you will gain the following
advantages:
* Unlike usual database, XML text does not have fixed structure.
In most cases whole XML text stored in memory, so it should
be relatively small files;
The most optimal and convenient way to implement this protocol in Delphi is to design an
interface between Pascal objects and XML code. The goal is to store instances of Pascal
classes in XML format with ability to restore the instances from the XML text.
It is a good idea to split "Pascal Object - XML" interface into two interfaces: "Pascal Object -
Data object" and "Data object - XML" (see figure 1).
I do not see a dainty solution to extend any Pascal class with XML interface. Instead, let's
create a new class to store and manage properties and data fields of an object. A developer
will need to write additional code to move data between Pascal object and data object.
Design
Our task is to implement two independent interfaces: "Pascal Object - Data Object" and "Data
Object - XML".
First interface will enable a developer to replicate data structure of an instance of any class
and instance of the SPO class. "SPO" stands for Standard Pascal Object. SPO class will
provide data container and set of methods to store and retrieve data.
Second interface will be implemented as a set of routines to convert SPO object to XML text
and visa versa.
The main purpose of the SPO class is to store and provide access to a data fields and
properties. Because of that, it is much more important to create a convenient data access
interface, than to optimize data storage method. After all, we will be able to change data
storage method later without affecting the interface.
First of all let's define the data types, supported by our interface. We will consider only five
data types in our model:
* String
* Numeric
* Date
* Boolean
* Object
The data type "object" allows us to build complex data structure by nesting one object to
another. On figure 2 you may see an example of data object structure:
var
O: TspoObject;
P: TspoProperty;
S: string;
begin
P := AnObject.PropByName['Career position'];
O := P.TheObject;
S := P.PropByName['Title'].Value;
end;
The same long sequence will be necessary to add new property to an object. To simplify
access to nested objects and properties we will use path-style resource locator string. For
example to get value of the "Start date" field you will use the following syntax:
To create the object on figure 2 we can use only six lines of code:
Using collections
Sometimes you need to store several objects inside one property. You may index this
collection of objects by a number or by keyword. To create a collection in object property
you have to create an object and then add as many properties as you wish:
<object></object>
<object><br>
<property></property><br>
<property></property><br>
<method></method><br>
</object>
Each property must contain a name, type and value. It can also include default value, and
scope:
<property><br>
<type>string</type><br>
<name>First name</name><br>
<value>Smith</value><br<
</property>
* Object
* Property
* Scope
* Type
* Name
* DefaultValue
* Value
* Param
* Method
Construction
There are two units. First unit - CPOM.PAS contains classes to create in-memory data
objects: TspoObject, TspoProperty, and TspoMethod. Second unit - CxmlInterface.pas
contains routines to convert TspoObject to XML text and vice versa.
The development is still in process and I will be happy to hear any comments.
Copyright 2000 delphi3000.com
Contact: delphi3000@bluestep.com'
Website gone?
Peter Snoek (Oct 25 2001 11:49PM)
Hi there...
thanks for the article, it's very good. I only can't go to the website of the author anymore... Is it gone
forever?
Peter
Respond
Delphi Bible includes a serials of application sources code that from general to advance
applications,also contains some developers develop experiences and tips.
http://www.qwerks.com/product.asp?ProductID=6874
Respond
missing file
Johannes Grabsch (Dec 20 2000 7:04AM)
In Article
http://www.delphi3000.com/articles/article_515.asp
thanks
Johannes
Respond
Sincerely
Respond
Superb article
Thierry Coq (Nov 16 2000 5:16AM)
I've tried many ways to store objects painlessly.
One very good way is to use the persistent mechanism of Delphi
.However, there is a drawback : it isn't a recognised standard.
When will Inprise use XML to store objects and forms ?
Respond
Borland already has started using XML in support of their IDE... for example in C++ Builder 5, the
project options (e.g. compiler settings etc) file is now stored in XML format. (I believe the equivalent
Delphi file is the .DOF file).
Respond
Your Article
Subbu Balakrishnan (Sep 22 2000 7:05PM)
We are using a similar approach with the VB-ASP-IIS-MTS/COM+ reengineering of our product. Since VB
does not provide multithreading, we had to develop our 'ObjectAsXML' helper DLL in C++. Would have
liked to use Delphi but then we are a MS shop :(
Very good article and I hope to use the concept in my own private Delphi projects when appropriate.
Keep 'em coming :)
Respond
Sorry but wrong URL New in Delphi, can you give some short example how to use your 2 units, try
but something is going wrong Thanks a lot
Respond
a couple of improvements...
saj (Jun 30 2000 6:55PM)
[1] In XML empty tags need special syntax (e.g. <br> should be <br/>).
[2] the <object> element could use a sub-element like <class> to indicate object type
Respond
It is looks good
Vlad (Jun 2 2000 4:15PM)
I hope you will continue the posting following article in this topic
Thanks
Respond
Very interesting...
anonymus (May 14 2000 12:07AM)
This would be very interesting to look deeper into. Any more who can shed more light on this subject?
Respond
Excellent Article
(Mar 4 2000 5:18PM)
This is really an excellent article, hope that there will be posted further articles about XML on delphi3000
soon!
Respond
delphi3000.com Article
Inheritance :
reuse by subclassing, lets you define the
implementation of one class in terms of another's
Aggregation (or Composition):
reuse by assembling or composing objects to get
more complex functionality
Delegation :
reuse by connecting methods : a object that
receives a request, delegates operations to
another object
(special kind of composition, two or more objects
are involved to produce a behavior)
SendMessage(ProgressForm.Handle,WM_ProgressMsg,Progress,0);
...
TMethod = record
Code, Data: Pointer;
end;
Method.Code := Class.MethodAddress(MethodName);