📄 tiofp documentation - a worked example of using the tiopf.htm
字号:
test data by hard coding. The question is, where shall the instance be created?
Who shall be responsible for creating it? And who shall be responsible for
freeing it?</P>
<P>I usually choose from one of three strategies:</P>
<OL>
<LI>Create is as an application wide, globally visible Singleton. This is a
useful technique when you are working with background data that can be shared
between objects, or when you are creating a single form application like the
one we are working on here.
<LI>Create it as an owned property of some sort of manager class. This
technique is good if there will be 0..n instances of the class, and you want
to create them on demand and share them between client classes once they have
been created.
<LI>Create it and free it in a form Create and free it in the constructor and
destructor of the class that it will interact with (often an application’s
form, or main form). This is good for MDI applications when you might want
several forms all editing the same class of object, but populated with
different data. </LI></OL>
<P>We shall create the TContactMgr class as a Singleton because we are working
with a single form application and it makes it easier to abstract persistence
code away from the GUI.</P>
<P>Create a unit called ContactMgr_Cli.pas, and add the following code:</P><PRE>unit ContactMgr_Cli;
interface
uses
ContactMgr_BOM
;
// We hide a variable with unit wide scope behind a function.
function gContactMgr : TContactMgr ;
implementation
var
uContactMgr : TContactMgr ;
// Fakes some of the behaviour expected of a Singleton
function gContactMgr : TContactMgr ;
begin
// If uContactMgr has not been created, then create one.
if uContactMgr = nil then
uContactMgr := TContactMgr.Create ;
result := uContactMgr ;
end ;
initialization
finalization
uContactMgr.Free ;
end.</PRE>
<P>This fakes some of the behaviour expected from a Singleton. This technique
will allow the singleton to be deleted, which is technically not permitted. It
will also allow more than one instance of TContactMgr to be created which also
violates the rules of a pure Singleton. The technique is, however, quick to code
and easy to understand which is not the case for a pure GoF Singleton when
implemented in Delphi.</P>
<P>Now, add the unit tiPtnVisPerObj_Cli to the uses clause of the Implementation
section of the main unit then drop a button on the application’s main form. Add
the following code to the button’s OnClick event handler:</P><PRE>procedure TFormMain.Button1Click(Sender: TObject);
begin
tiShowPerObjAbs( gContactMgr ) ;
end;</PRE>
<P>This will output the contents of the empty to TContactMgr to a popup dialog
like this:</P>
<P><IMG height=96
src="tiOFP Documentation - A worked example of using the tiOPF_files/6_AWorkedExampleOfUsingTheTIOPF_clip_image001_0003.gif"
width=412> </P>
<P>We will now populate TContactMgr with some hard coded data so create another
unit called ContactMgr_TST.pas (TST stands for test) and add the following
code:</P><PRE>unit ContactMgr_TST;
interface
uses
ContactMgr_BOM
;
procedure PopulateContactMgr( pContactMgr : TContactMgr ) ;
implementation
procedure PopulateContactMgr( pContactMgr : TContactMgr ) ;
var
lPerson : TPerson ;
begin
lPerson := TPerson.Create ;
lPerson.LastName := 'Hinrichsen' ;
lPerson.FirstName := 'Peter' ;
lPerson.Title := 'Mr' ;
lPerson.Initials := 'P.W.' ;
lPerson.Notes := 'Founder of the tiOPF project' ;
pContactMgr.People.Add( lPerson ) ;
end ;</PRE>
<P>This will populate the contact manager with yours truly.</P>
<P>We have to add a call to PopulateContactMgr somewhere in application and as
we are going down the singleton route for the creation of the TContactMgr, we
can add the code there:</P><PRE>function gContactMgr : TContactMgr ;
begin
// If uContactMgr has not been created, then create one.
if uContactMgr = nil then
begin
uContactMgr := TContactMgr.Create ;
PopulateContactMgr( uContactMgr ) ;
end ;
result := uContactMgr ;
end ;</PRE>
<P>This is what we are wanting so we can go on and create the remaining four
classes.</P>
<H2>Coding TAdrsList, TEAdrsList, TAdrs and TEAdrs</H2>
<P>We shall not code the four classes TAdrsList, TEAdrsList, TAdrs and TEAdrs.
There is a common property shared between TAdrs and TEAdrs called AdrsType, so
we will create an abstract address class to provide this common behaviour, and
descend both TAdrs and TEAdrs form this abstract class.</P>
<P>First, the interface of TPerson is extended with an owned instance of
TAdrsList and TEAdrsList. These list classes are surfaced as published
properties so they will be automatically detected by the iteration routine in
the TVisited class. The interface extended interface of TPerson is shown
below:</P><PRE>TPerson = class( TPerObjAbs )
private
FAdrsList : TAdrsList ;
FEAdrsList : TEAdrsList ;
protected
function GetCaption : string ; override ;
constructor Create ; override ;
destructor Destroy ; override ;
published
property AdrsList : TAdrsList read FAdrsList ;
end ;</PRE>
<P>The implementation of TPerson is as you would expect with an instance of
TAdrsList and TEAdrsList being created and destroyed in the constructor and
destructor. There are four extra lines of code however where the owner
relationship between TAdrsList, TEAdrsList, their list elements and the TPerson
are set. In the implementation of GetCaption, we will return the string
FirstName + ‘ ’ + LastName.</P>
<P>Now, from deep in the hierarchy, at say the TAdrs level, we want to be able
to chain up the object hierarchy like this to get the owning person like
this:</P><PRE>lPerson := lAdrs.Owner.Owner ;</PRE>
<P>But with the TAdrs and TEAdrsList classes the way we would build them by
default, the owner of a TAdrs is its TAdrsList. We really want the owner
property of a TAdrs to reference the TPerson object like this:</P><PRE>lPerson := lAdrs.Owner ;</PRE>
<P>This is achieved by setting the ItemOwner property of the TAdrsList and
TEAdrsList objects like this:</P><PRE>constructor TPerson.Create;
begin
inherited;
FAdrsList := TAdrsList.Create ;
FAdrsList.Owner := self ;
FAdrsList.ItemOwner := self ;
FEAdrsList := TEAdrsList.Create ;
FEAdrsList.Owner := Self ;
FAdrsList.ItemOwner := Self ;
end;</PRE>
<P>This can be a little confusing. The owner of a TAdrs, from the point of view
of the class being responsible for freeing the TAdrs is the TAdrsList. The owner
of the TAdrs when TAdrs.Owner is called is the TPerson class, which is much more
useful when traversing the object hierarchy. This is all taken care of in the
TPerObjAbs.Add method.</P>
<P>Next, we create the interface and implementation of TAdrsListAbs, TAdrsList
and TEAdrsList. These consist of new implementations of the methods GetItems,
SetItems, GetOwner, SetOwner and Add with the compiler warnings being suppressed
by using the reintroduce key word. The type of the property Items has also been
changed. By changing the type of these properties, along with their get and set
methods we firm up the relationship between the collection class and the objects
that it holds. This takes a bit of extra work up front, but we will be more than
compensated in saved development, debugging and maintenance time. The interface
of TAdrsList is shown below:</P><PRE>TAdrsListAbs = class( TPerObjList )
protected
function GetOwner: TPerson; reintroduce ;
procedure SetOwner(const Value: TPerson); reintroduce ;
public
property Owner : TPerson read GetOwner write SetOwner ;
end ;
TAdrsList = class( TAdrsListAbs )
private
protected
function GetItems(i: integer): TAdrs ; reintroduce ;
procedure SetItems(i: integer; const Value: TAdrs); reintroduce ;
public
property Items[i:integer] : TAdrs read GetItems write SetItems ;
procedure Add( pObject : TAdrs ; pDefDispOrdr : boolean = true ) ; reintroduce ;
published
end ;</PRE>
<P>The interface of TAdrsList is not shown, but as you would expect, it follows
the same pattern as TPeople with each of the Get and Set methods simply calling
inherited with some type casting as necessary. The interface and implementation
of TEAdrsList follows the same pattern as in TAdrsList with the methods each
calling inherited with the appropriate types casting.</P>
<P>TAdrs and TEAdrs both descend from the common parent TAdrsAbs because the
have the property AdrsType in common. The interface of TAdrsAbs is shown
below:</P><PRE>TAdrsAbs = class( TPerObjAbs )
private
FAdrsType: string;
published
property AdrsType : string read FAdrsType write FAdrsType ;
end ;</PRE>
<P>In TAdrs, the type of the Owner property is changed to TPerson along with the
corresponding Get and Set methods. The properties Lines, Suburb, PCode, State,
and country are also added. The interface of TAdrs is shown below:</P><PRE>TAdrs = class( TAdrsAbs )
private
FCountry: string;
FSuburb: string;
FLines: string;
FPCode: string;
FState: string;
protected
function GetCaption : string ; override ;
function GetOwner: TAdrsList; reintroduce ;
procedure SetOwner(const Value: TAdrsList ); reintroduce ;
property Owner : TAdrsList read GetOwner write SetOwner ;
published
property Lines : string read FLines write FLines ;
property Suburb : string read FSuburb write FSuburb ;
property State : string read FState write FState ;
property PCode : string read FPCode write FPCode ;
property Country : string read FCountry write FCountry ;
end ;</PRE>
<P>You will now need to add the tiUtils unit to ContactMgr_BOM’s uses clause, as
this is where the tiStrTran function can be found. The implementation of
GetCaption creates a single line view of the address and is shown below:</P><PRE>function TAdrs.GetCaption: string;
begin
result :=
tiStrTran( tiStrTran( Lines, Cr, ' ' ), Lf, '' ) +
' ' + Suburb + ' ' + State + ' ' + PCode + ' ' +
Country ;
end;</PRE>
<P>The interface of TEAdrs is the same as TAdrs except that the properties
Lines, Suburb, etc are replaced with a single property called Text.</P>
<P>We can extend the PopulateContactMgr routine found in ContactMgr_TST.pas by
creating instances of TAdrs and TEAdrs. This code looks like this:</P><PRE>procedure PopulateContactMgr( pContactMgr : TContactMgr ) ;
var
lPerson : TPerson ;
lAdrs : TAdrs ;
lEAdrs : TEAdrs ;
begin
lPerson := TPerson.Create ;
lPerson.LastName := 'Hinrichsen' ;
lPerson.FirstName := 'Peter' ;
lPerson.Title := 'Mr' ;
lPerson.Initials := 'P.W.' ;
lPerson.Notes := 'Founder of the tiOPF project' ;
pContactMgr.People.Add( lPerson ) ;
lAdrs := TAdrs.Create ;
lAdrs.AdrsType := 'Street' ;
lAdrs.Lines := '23 Victoria Pde.' ;
lAdrs.Suburb := 'Collingwood' ;
lAdrs.PCode := '3066' ;
lAdrs.State := 'VIC' ;
lAdrs.Country := 'Australia' ;
lPerson.AdrsList.Add( lAdrs ) ;
lAdrs := TAdrs.Create ;
lAdrs.AdrsType := 'Postal' ;
lAdrs.Lines := 'PO Box 429' ;
lAdrs.Suburb := 'Abbotsford' ;
lAdrs.PCode := '3067' ;
lAdrs.State := 'VIC' ;
lAdrs.Country := 'Australia' ;
lPerson.AdrsList.Add( lAdrs ) ;
lEAdrs := TEAdrs.Create ;
lEAdrs.AdrsType := 'EMail' ;
lEAdrs.Text := 'peter_hinrichsen@techinsite.com.au' ;
lPerson.EAdrsList.Add( lEAdrs ) ;
lEAdrs := TEAdrs.Create ;
lEAdrs.AdrsType := 'Mobile' ;
lEAdrs.Text := '0418 108 353' ;
lPerson.EAdrsList.Add( lEAdrs ) ;
lEAdrs := TEAdrs.Create ;
lEAdrs.AdrsType := 'Fax' ;
lPerson.EAdrsList.Add( lEAdrs ) ;
lEAdrs := TEAdrs.Create ;
lEAdrs.Text := '+61 3 9419 1682' ;
lPerson.EAdrsList.Add( lEAdrs ) ;
end ;</PRE>
<P>When we run the application and click the button with the tiShowPerObjAbs( )
call, as expected we get the following dialog:</P>
<P><IMG height=399
src="tiOFP Documentation - A worked example of using the tiOPF_files/6_AWorkedExampleOfUsingTheTIOPF_clip_image002.jpg"
width=300> </P>
<P>This completes our work with the business object model, so next we can set up
the GUI to display the data using a combination of the TtiTreeView, TtiListView
and TtiPerAware controls.</P>
<H2>Setup the GUI to display the object hierarchy</H2>
<H2>Set-up the main form</H2>
<P>Add a TToolBar, TActionList, TMainMenu, TStatusBar and TImageList to the
form. Name the TToolBar TB, the TMainMenu MM, the TStatusBar SB, and the
TImageList to ILButtons (we will eventually have 2 image lists on this form).
Set the TToolBar’s Flat property to true, and change its height to 25. Add four
buttons and two separators to the toolbar, positioning the separators before the
3rd and 4th buttons. Now, add a Close menu item to the File menu, and New,
Delete and Save menu items to the Edit option of the main menu you added. We
will hook up the appropriate actions in a moment.
<P>
<P>Double click the image list and add the New, Delete, Save and Cancel images
from the \TechInsite\Images\Buttons directory. Arrange the images in order as
shown below:</P>
<P><IMG height=218
src="tiOFP Documentation - A worked example of using the tiOPF_files/6_AWorkedExampleOfUsingTheTIOPF_clip_image002_0000.jpg"
width=331> </P>
<P>Double click the Action list and add four actions: aNew, aDelete, aSave and
aClose. Setup their captions to read &New, &Delete, &Save and
&Close, with their shortcuts being Ins, Del, Ctrl+S and Alt+F4. Note, that
we can’t actually assign Alt+F4 as a shortcut key from the Object Inspector; we
must do this by writing the following piece of code in the main form’s
FormCreate event handler:</P><PRE>Procedure TFormMain.FormCreate(Sender : TObject);
Begin
aClose.ShortCut := ShortCut(VK_F4, [ssAlt]);
End;</PRE>
<P>The Shortcut function is defined in the Menus unit, so don’t forget to add
this to the uses clause, or your code will not compile.</P>
<P>Assign the image index properties of each action to match its image index in
the image list. Set the Hint properties to ‘New’, ‘Delete’, ‘Save’ and ‘Close’.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -