ECO Tutorial - Felix John COLIBRI.
- abstract : Writing a simple ECO application: the UML model, the in memory objects and the GUI presentation. We also will show how to evaluate OCL expressions
- key words : ECO - Enterprise Core Objects - UML - EcoSpace - OCL - Object Constraint Language - ExpressionHandle - UML class diagram
- software used : Windows XP Home, Delphi 2006
- hardware used : Pentium 2.800Mhz, 512 M memory, 140 G hard disc
- scope : Delphi 2006, Turbo Delphi for .Net
- level : Delphi developer
- plan :
1 - ECO modeling and coding
ECO is a Delphi modeling tool: the developer draws the model of his application, and the ECO framework builds an in-memory structure of this model
using objects. This representation is used to implement the model in order to add a visual interface to our application (Forms, Buttons etc) and transfer the data to disc (.XML files or Sql Database).
The overall objective is to let the developer work at a high model level, letting the ECO machinery take care of many of the low-level coding tasks (writing Sql requests, synchronizing the visual GUI with the Tables, handling
master / detail navigation etc).
This tutorial will present how to write a simple, application with ECO.
2 - An Invoicing example
2.1 - The Goal
We will build a tiny invoicing application with INVOICEs and ITEMs. Each ITEM corresponds to a PART, which can be either manufactured by our company, or purchased outside.
This corresponds to the following diagram:
In this paper, we are only going to use the invoice / item CLASSEs:
2.2 - The ECO architecture
Building and ECO application involves 2 basic steps
- create the UML model for this application
- generate the Delphi code corresponding to this model, and write some code
This can be represented by the following figure:
and
- we draw the UML Class diagram using any kind of UML editor (Rational Rose, Model Maker, Together)
- this "picture" is converted into a code structure. This is the EcoSpace which contains:
- the classes from the UML Class diagram (in the UMLRt, which is the Run Time representation of the diagram)
- the objects which are instances of those UML Classes which will contain the values of our Database
- we can access those EcoSpace objects (for creation, reading, writing) using
EcoHandles. Those handles are used for displaying the values in GUI controls
2.3 - Create the UML model
To create the ECO model, we will start an ECO project. This can be done using
the
Delphi Wizard. So let's start a new
Windows Forms ECO application:
load Delphi 2006 (or Delphi 2006, Windows Forms personality, or Turbo Delphi for .Net)
select "File | New | 0ther"
Delphi displays the "other" files:
image
Note that the menu used to reach this dialog, the specific menu treeview item (the root "Delphi for .Net Project" in our case) and the content of this dialog may be different, depending on the Delphi version). We simply
must locate the "ECO WinForms Application" wizard somewhere.
select "Eco WinForms Application"
Delphi presents the path and project dialog:
image
type your path and project name, and click "Ok"
Delphi create the new "ECO Windows Forms Application"
rename the main unit WINFORMS.PAS by selecting the Project Manager tab from
the top-right notebook, then select "WINFORM.PAS | right click | Rename" and type U_01_INVOICING_FORM.PAS
the Project Manager displays the following files:
image
where:
- PACKAGE_1UNIT.PAS will contain the CLASSes corresponding to our model
(a c_invoice CLASS, a c_item CLASS etc). At this stage, there is only an empty Package_1 CLASS (an "UML package" meaning a set of UML things, not the "Delphi .DLL / package")
- P_01_INVOICINGECOSPACE.PAS contains the Tp_01_invoicingEcoSpace CLASS which represents the ECO engine and all its properties and methods.
This CLASS will represent the model as objects in memory, and allow
communication with the visual GUI components, as well as the disc data. It will have access to our custom application-dependent CLASSes defined in the PACKAGE_1UNIT.PAS UNIT
- U_01_INVOICING_FORM.PAS is the usual Windows Form. The tWinForm CLASS contains an EcoSpace PROPERTY of type Tp_01_invoiceEcoSpace which is a reference to the eco space
By selecting the "Design" tab at the bottom of the central notebook, the tWinForm's design surface with the component tray is displayed:
image
You can also remove all the components (EcoGlobalActions, EcoAutoForms, EcoListActions and EcoDragDrop) other than the rhRoot, since they are not required and we will not use them in this tutorial.
But do keep the rhRoot component.
2.4 - Building the Model
Let's create the basic UML model with the 3
CLASSes.
The diagrams are created on the modeling surface, which must be displayed:
select the "model view" tab in the top-right notebook, and in the treeview, click on the grey "PACKAGE_1" item (called "CORE" in some old Delphi
versions)
an empty modeling surface is displayed:
image
To create the diagram for our INVOICE
CLASS unfold the
Tools Palette (by clicking on the "+"), and select the "ECO Class" element:
image
and drop it on the modeling surface
the
Class_1 is displayed
image
type the name of the CLASS, for instance c_invoice
the
CLASS name is updated. Also notice that the diagram attributes are displayed in the
Object Inspector:
image
Those properties are graphic properties (background color, border size etc)
to add the customer field, select the
c_invoice CLASS, right click on the "Attributes" element, to display the contextual menu, and select the "Add | Attribute" menu item:
image
a new attribute is created with a default "Attribute_1: Integer" definition
type the name and type of your attribute. In our case "m_customer: String"
image
add the other attribute "m_invoice_number: Integer"
this is our
c_invoice CLASS diagram so far:
image
A this stage, compile the project, in order to synchronize the model (the UML
drawings) with the
EcoSpace (the object representation of those drawings). So:
compile the project
This compilation is required, since the Delphi IDE will use the in-memory representation of the UML drawing. So compiling is often required, as well as clicking on the "Regenerate Eco Source Code" - like icons a the top of the model view notebook.
You may look at the PACKAGE_1UNIT.PAS, which now contains 2 more definitions
- the c_invoice CLASS
- a Ic_invoiceList INTERFACE which is used to represent the list of
invoices (the in-memory representation of our future disc data content)
Note that
- compiling from time to time seems to be a good idea. The p_01_invoicingEcoSpace design surface even reminds you to compile each time
you load the project in the Delphi IDE.
2.5 - Adding Invoices
To add invoices, we have to call a
c_invoice CONSTRUCTOR and initialize the attributes:
- one of the c_invoice CONSTRUCTORs uses the tWinForm.EcoSpace as its parameter. This allows the object to be added to the invoice list contained in the EcoSpace
- the c_invoice CLASS contains the m_customer and m_invoice_number members which can be used to initialize the object
Therefore
select the main Windows Forms designer by selecting the "Project Manager" tab from the top-right notebook, and the U_01_INVOICING_FORM.PAS item (caution: there is also a U_01_INVOICING_FORM element nested inside the
"ModelSupport_p_01_invoice" summary element, but it is not a .PAS element. Our treeview item is, usually, at the bottom of the treeview).
the traditional Delphi grey dotted designer surface is displayed
from the
Tools Palette, select a
Button an drop it on the
Form, rename it "create_invoices_". Create its Click event, and type the code which will add some
c_invoice objects:
procedure TWinForm.
create_invoices__Click(
sender:
System.
Object;
e:
System.
EventArgs);
procedure add_invoice(p_customer: String; p_invoice_number: Integer);
begin
with c_invoice.Create(EcoSpace) do
begin
m_customer:= p_customer;
m_invoice_number:= p_invoice_number;
end; // with c_customer
end; // add_invoice
begin // create_invoices_
add_invoice('Miller', 12345);
add_invoice('Smith', 5234);
add_invoice('Adams', 5234);
end; // create_invoices_
compile the project
2.6 - Saving the Result in an .XML file
To save the objects to disc, we can place them in .XML files or into some Sql Database. To avoid mixing ADO .Net components in our tutorial, we chose to use the more simple .XML files.
Persisting the data in an .XML file only requires a PersistencMapperXML component which takes all data objects contained in the EcoSpace and saves it to (or reads it from) an .XML file.
That's quite easy:
from the top-right notebook, select the "project manager" tab, and select P_01_INVOICINGECOSPACE.PAS
image
and in the central notebook, select the "Design" tab
an empty
EcoSpace designer is displayed
image
from the
Tool Palette, select a
PersistencMapperXML:
image
and drop it on the EcoSpace designer
in the
Object Inspector, set the
FileName property, for instance
save_invoicing.xml
select the main
Form, and add a
Button, rename it "save_xml_", create its
Click event, and add the updating code:
compile and run
Click "create_invoices_" and then "save_xml_"
the result is a 4K .XML file (in the .EXE folder). The format is SOAP envelope, and it contains
- informations about the CLASSes
- information about the attributes (2 auto generated keys and our two attributes)
- information about the invoices list
- the values of our objects
Here is an partial dump of this file:
<SOAP-ENV:Envelope xmlns="http://www.w3.org/" ...ooo...>
<SOAP-ENV:Body>
<a1:Datablock id="ref-1" xmlns=" ...ooo... Eco.Interfaces">
...ooo...
<a2:Hashtable id="ref-3" xmlns=" ...ooo... System.Collections">
...ooo...
<SOAP-ENC:Array id="ref-4" SOAP="xsd:anyType[3]">
<item href="#ref-6"/>
<item href="#ref-7"/>
<item href="#ref-8"/>
</SOAP-ENC:Array>
<SOAP-ENC:Array id="ref-5" SOAP="xsd:anyType[3]">
<item href="#ref-9"/>
<item href="#ref-10"/>
<item href="#ref-11"/>
</SOAP-ENC:Array>
<a1:DefaultId id="ref-6" xmlns=" ...ooo... Eco.Interfaces">
<ObjectId_x002B_key xsi="xsd:int">
308242888
</ObjectId_x002B_key>
<ObjectId_x002B_classId>
0
</ObjectId_x002B_classId>
</a1:DefaultId>
...ooo...
<a1:ObjectContents id="ref-9" xmlns=" ...ooo... Eco.Interfaces">
<objectId href="#ref-6"/>
<existenceState>
New
</existenceState>
<members href="#ref-12"/>
<isReadOnly>
false
</isReadOnly>
<timestamp>
-1
</timestamp>
</a1:ObjectContents>
...ooo...
<a2:ArrayList id="ref-12" xmlns="...ooo... System.Collections">
<_items href="#ref-15"/>
<_size>
2
</_size>
<_version>
4
</_version>
</a2:ArrayList>
...ooo...
<SOAP-ENC:Array id="ref-15" SOAP="xsd:anyType[16]">
<item id="ref-18" xsi="SOAP-ENC:string">
Miller
</item>
<item xsi="xsd:int">
12345
</item>
</SOAP-ENC:Array>
<SOAP-ENC:Array id="ref-16" SOAP="xsd:anyType[16]">
<item id="ref-19" xsi="SOAP-ENC:string">
Smith
</item>
<item xsi="xsd:int">
5234
</item>
</SOAP-ENC:Array>
<SOAP-ENC:Array id="ref-17" SOAP="xsd:anyType[16]">
<item id="ref-20" xsi="SOAP-ENC:string">
Adams
</item>
<item xsi="xsd:int">
5234
</item>
</SOAP-ENC:Array>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Once the .XML file has been created, the data will automatically be loaded in
the
EcoSpace at each run. This is similar to the
tClientDataSet briefcase saving. Of course, if you remove the file name from the
PersistentMapper, this will not happen.
From an architectural point of view, we have the following (partial) diagram:
and:
- our tWinForm contains an EcoSpace attribute
- the EcoSpace CLASS contains a PersistenceMapper attribute
- there is a whole hierarchy of persistence mappers. We used the .XML mapper, but we could have chosen a PersistencMapperBDP, or
PersistenceMapperSqlServer
At this stage, I would suggest that you start looking into the Help. Clicking F1 and navigating to the ECO Framework, you will find all the Assemblies involved (partial view):
image
The main Assemblies are:
- Borland.ECO.UmlRt where all the CLASSES and INTERFACES for the in-memory
object representation of the UML graphic are located
- Borland.ECO.ObjectRepresentation which contains all the objects containing our data (the values of our invoices)
- Borland.ECO.Services, for all the ECO services. We will not present them in detail, but they are at the heart of the ECO machinery
- Borland.ECO.Handles, which contains our EcoSpace, as well as the
ReferenceHandle and ExpressionHandle that we will use shortly to display the objects in a DataGrid
- Borland.ECO.Persistence for all disc handling
2.7 - Displaying the invoices
We will now display the values of our invoices in a
TextBox.
To list the in-memory invoices, we need to use an ExpressionHandle. The handles are a way to read or write the EcoSpace elements. Handles can return
values, objects or object lists. What will be returned depends on the ExpressionHandle.Expression, and this Expression is written in some special
language called OCL (Object Constraint Language). In our case, we want all the invoice objects, and the OCL expression is
ExpressionHandles can be chained: we can reach a list of objects using a first
expression, and add a chain another ExpressionHandle to do some filtering, for instance.
The ExpressionHandle chains must have a starting point, which is specified in the ExpressionHandle.RootHandle property.
In our case we will use the rhRoot Handle. And we must initialize the rhRoot.EcoSpaceType property with the Tp_01_invoicingEcoSpace.
So :
select the main form designer
select the
rhRoot object that was created by the Wizard
In the
Object Inspector, select the
EcoSpaceType property and set its property to
p_01_invoicingEcoSpace.Tp_01_invoicingEcoSpace
in the
Tools Palette, select an
ExpressionHandle:
image
Drop it ON THE FORM
In the Object Inspector
- change its (Name) property to "invoice_list_expression_handle"
- select the RootHandle and select rhRoot
- select the Expression property and type
c_object.AllInstances
from the
Tools Palette, select a
TextBox, drop in on the
Form, and set its
MultiLine property to
True
from the
Tools Palette, select a
Button, drop it on the
Form, rename it "list_invoices", create its
Click event and type the code which displays
the values of the invoices in the
TextBox1
procedure TWinForm.
list_invoices__Click(
sender:
System.
Object;
e:
System.
EventArgs);
var l_c_invoice_list:
Borland.
Eco.
ObjectRepresentation.
iObjectList;
l_invoice_index:
Integer;
l_c_invoice:
c_invoice;
begin
l_c_invoice_list:=
invoice_list_expression_handle.
Element
as Borland.
Eco.
ObjectRepresentation.
iObjectList;
for l_invoice_index:= 0 to l_c_invoice_list.Count- 1 do
begin
l_c_invoice:= l_c_invoice_list[l_invoice_index].AsObject as c_invoice;
with l_c_invoice do
display(m_customer+ ' '+ m_invoice_number.ToString);
end; // for l_invoice_index
end; // list_invoices__Click
where display is our standard procedure for adding text to the TextBox1.Text String.
compile, execute and click "list_invoices"
here is the display of our invoices
image
Note that
- OCL might be case sensitive (it seems Bold was, maybe ECO I, ECO II and ECO III are not)
To put those handles into perspective, here is a (partial) UML Class diagram:
and:
- we start the access chain with a ReferenceHandle. This handle is used to
reach into the EcoSpace. Usually we use the rhRoot handle which was generated by the Wizard. We simply must check that this rhRoot has been initialized with some EcoSpace, and that has been done after the first
project compilation by setting the rhRoot.EcoSpaceType to p_01_invoicingEcoSpace.Tp_01_invoicingEcoSpacepoint
- we use an ExpressionHandle linked to this ReferenceHandle
- now the ExpressionHandle has two basic functionalities :
- it allows us to write an OCL Expression, which will yield in the ExpressionHandle.Element some iElement descendent. In our case an
iObjectList.
We cannot directly display an iElement. It lives in the outer EcoSpace there, some kind of Walhalla filled with thunder and lightning. So we
first have to cast it using iElement.AsObject. More seriously, it seems that the EcoSpace has a fragmented representation of our c_invoice CLASS, and AsObject brings all the parts back together, yielding a
nice tObject with all the attributes and methods. And since we know that this object is a c_invoice, we cast it into a c_invoice and can then use the c_invoice.m_customer members.
- the ExpressionHandles are mainly used as visual control DataSource, since ExpressionHandles implement the iBindingList INTERFACE. This will be presented below
2.8 - Implementing ToString
If we want a general display of our classes, we can implement the
VIRTUAL ToString FUNCTION in our
CLASS.
To do so
select the model view, select the
c_invoice diagram, and select the "Operations" compartment. Right click to find "Add Operation" and type the function name
here is the diagram:
image
on the ToString operation, right click and select "Go to Definition" (or somehow direclty display the PACKAG1_1UNIT.PAS text in the central editor)
this brings you into the PACKAG1_1UNIT.PAS
UNIT, at the position of this definition:
image
Notice that despite the Result Type, this was generated as a PROCEDURE and not as a FUNCTION
change the
PROCEDURE into
FUNCTION, add
OVERRIDE
function ToString: String; Override;
and go to the IMPLEMENTATION to write the body:
function c_invoice.ToString: String;
begin
Result:= m_customer+ m_invoice_number.ToString;
end; // ToString
drop a
Button, rename it "to_string_", create its
Click event, and display the invoices:
procedure TWinForm.
to_string__Click(
sender:
System.
Object;
e:
System.
EventArgs);
var l_c_invoice_list:
Borland.
Eco.
ObjectRepresentation.
iObjectList;
l_invoice_index:
Integer;
begin
l_c_invoice_list:=
invoice_list_expression_handle.
Element
as Borland.
Eco.
ObjectRepresentation.
iObjectList;
with l_c_invoice_list do
for l_invoice_index:= 0 to Count- 1 do
display(Item[l_invoice_index].AsObject.ToString);
end; // to_string__Click
compile and run
2.9 - Deleting an invoice
To remove an invoice from the invoice list, we mark it as "deleted". The next saving to the persistent media will physically remove this invoice when
UpdateDataBase is called (by removing it in the new .XML file version, or by generating an
DELETE Sql request).
Here is an example which deletes the second invoice (index 1):
add a
Button to the
Form, rename it "delete_1_" and create its
Click event, and type the following code:
procedure TWinForm.
delete_1__Click(
sender:
System.
Object;
e:
System.
EventArgs);
var l_c_invoice_list:
Borland.
Eco.
ObjectRepresentation.
iObjectList;
l_c_invoice:
c_invoice;
begin
l_c_invoice_list:=
invoice_list_expression_handle.
Element
as Borland.
Eco.
ObjectRepresentation.
iObjectList;
if l_c_invoice_list.Count< 1
then display('no_[1]_invoice')
else begin
l_c_invoice:= l_c_invoice_list[1].AsObject as c_invoice;
l_c_invoice.AsIObject.Delete;
end;
end; // delete_1__Click
compile, execute and click "list_invoices_", "delete_1_", "list_invoices_"
here is the display of our invoices
image
Note that
- you should NOT remove the object with Free or Destroy, since the
in-memory list will no longer contain any information about this object, and it will not be deleted from the disc files (.XML or Sql Database)
- the Delete method is part of the iObject (see the UML diagram above). To
call this method, we first must cast the c_invoice object into an iObject object, and this casting is available in c_invoice (as represented in the UML diagram above)
2.10 - Display in a DataGrid
Instead of manually dumping the invoices values in a
TextBox, we can display them in visual controls, using the
Windows Forms binding mechanism (see the
Ado .Net tutorial for more details about binding).
Here is how to display the invoices in a DataGrid:
from the
Tools Palette, select the "Data Controls" tab, and then the
DataGrid Control:
image
and drop it on the Form
In the Object Inspector, set the following properties
- (Name) to invoice_datagrid
- DataSource property to invoice_list_expression_handle
the attributes of the
c_invoice CLASS are displayed in the
DataGrid image
compile and run
the
invoice_datagrid is displayed with the invoices values
image
Note that
- in usual ECO applications, you will go directly from the UML diagram to the DataGrid, without the creation and listing steps. All that is required is
- some kind of PersistentMapper to get the data
- an initialized rhRoot
- an ExpressionHandle with the relevant OCL Expression
- and, of course, a DataGrid
You can change the values in the
DataGrid, but cannot insert new invoices. To do so, you must first create the
c_invoice object, and you can then set the values in the
DataGrid. Here is an example:
add a
Button to your
Form, rename it "create_invoice_", and in its
Click event create a new
c_invoice object:
procedure TWinForm.create_invoice__Click(sender: System.Object;
e: System.EventArgs);
begin
c_invoice.Create(EcoSpace);
end; // create_invoice__Click
compile and run. Click on "create_invoice_"
the
DataGrid displays a NULL initialized new invoice:
image
The
DataGrid can be used to navigate the invoice list. If we want to perform
some action on the current invoice, we must find a way to find which invoice is the selected invoice in the
DataGrid.
This can be achieved using a Windows Forms CurrencyManager-like component
(see the Ado .Net tutorial for more details about the CurrencyManager).
Therefore:
select the main designer
from the
Tools Palette, select the ECO tab and then the
CurrencyManagerHandle:
image
and drop it on the Form and from the Object Inspector, set the following properties:
- (Name) to invoice_currency_manager_handle
- RootHandle to invoice_list_expression_handle (not to rhRoot)
- BindingCOntext to invoice_datagrid
drop a
Button on the
Form, rename it "delete_current_" and in its
Click event, type the code which deletes the invoice selected in the
invoice_datagrid:
procedure TWinForm.delete_current__Click(sender: System.Object;
e: System.EventArgs);
var l_c_selected_invoice: c_invoice;
begin
if invoice_currency_manager_handle.Element.AsObject is c_invoice
then begin
l_c_selected_invoice:=
c_invoice(invoice_currency_manager_handle.Element.AsObject);
l_c_selected_invoice.AsIObject.Delete;
end;
end; // delete_current__Click
compile, run, in the invoice_datagrid navigate to the second row and click "delete_current_"
the second row object is deleted (and the
DataGrid updated):
image
If you save the result (EcoSpace.UpdateDataBase) the disc data will no longer contain this second row
3 - Handling Related Classes
3.1 - Master Detail relation
We want to add invoice items: each invoice contains several items with price and quantity.
This is done using the following steps
- we add a new c_item class to the UML class diagram
- we add an association between the c_invoice and the c_item classes. We must specify on both ends of this association
- the multiplicity (1 on the c_invoice side, 1..* on the c_item side,
meaning that any item MUST belong to a single invoice, and any invoice has at least 1 item, possibly many more)
- the name of the association end.
- on the c_item end, we chose has_item, which can be read from left
to right "a invoice has_item the items'
- on the c_invoice end we chose from_invoice, which means "an item comes from_invoice an invoice"
The class diagram looks like this:
- now the important part: we want to add an invoice with 2 items
- we create the my_c_invoice object, as explained above
my_c_invoice:= c_invoice.Create(EcoSpace);
my_c_invoice.m_customer:= 'Miller';
my_c_invoice.m_invoice_number:= 1234;
- for each item
- we create the item
my_c_item:= c_item.Create(EcoSpace);
my_c_item.m_price:= 765.33;
my_c_item.m_quantity:= 18;
- we add this item to the invoice's item list:
my_c_invoice.has_item.Add(m_c_item);
This is the crux: we use the has_item relation-end name as a COLLECTION of c_item objects
We could also have worked the other way around, by specifying to which
c_invoice the newly created c_item belongs:
m_c_item.from_invoice:= my_c_invoice;
Note that
- this use of the relation end names is called OCL navigation
- we did not include any key in the c_invoice table. There is an underlying key, but is is automatically managed by the ECO framework
- using the my_c_invoice.has_item collection is similar to a tObjectList
nested inside each c_invoice. But OCL navigation is more general then simple containerpart relations
3.2 - Add the invoice_items
So let's implement this relation in our code.
You may use the previous project, but for this presentation, we will start a new project. Here are the steps to create the invoices part (everything here has been explained above):
create a new ECO Windows Forms project, rename it p_02_invoicing_relation / u_02_invoice_relation
compile to generate the UML runtime model, and close the running project
from the project manager, select the u_02_invoice_relation.PAS, select the
center "design" tab, and remove all components but
rhRoot.
Select
rhRoot and in the
Object Inspector set its
EcoSpaceType to
p_02_invoicing_relationEcoSpace.
Tp_02_invoicing_EcoSpace
create the
c_invoice class diagram, as explained above (select the "Model View", click on the grey "Package_1" icon, drop an "Eco Class" and fill it)
add a Button to populate the c_invoice list and type the code which adds a couple of invoices
on the p_02_invoicing_relationEcoSpace designer surface drop an PersistenceManagerXML, initialize its file name, then switch back to the
main form designer, add a "save_xml" button which updates the EcoSpace
compile and run
We will now add the
c_item CLASS:
select the "Model View", click on the greay "Package_1" icon, drop an "Eco
Class", rename it c_item class diagram, and fill it with an m_price and an m_quantity attribute.
from the
Tools Palette, select the "Association" shape, click on the
c_item class diagram and drag to the
c_invoice class diagram
In the Object Inspector
- select the End1 property, expand it, and
- set its Multiplicity to "1"
- and set its Name to from_invoice. This name IS IMPORTANT since it will be used for navigation
- select the End2 property, expand it, and set its Multiplicity to "1..*" and its Name to has_item
The UML class diagram should look like this:
image
compile the project
If you look at the
c_invoice CLASS (in PACKAGE_1UNIT.PAS) you will notice that the
CLASS now contains a
has_item PROPERTY (the code has been heavily changed):
unit Package_1Unit;
interface
type
Ic_invoiceList=
interface(ICollection)
function Add(value: c_invoice): Integer;
// -- ..ooo...
end; // ic_invoiceList
Ic_itemList=
interface(ICollection)
// -- ..ooo...
end; // ic_itemList
c_invoice=
class(System.Object, ILoopBack)
// -- ...ooo...
public
function AsIObject: IObjectInstance;
property m_customer: string read get_m_customer
write set_m_customer;
property m_invoice_number: Integer read get_m_invoice_number
write set_m_invoice_number;
property has_item: Ic_itemList read get_has_item;
constructor Create(serviceProvider: IEcoServiceProvider); overload;
end; // c_invoice
Here is how to add a couple of items to our first invoice:
select the main form designer
drop a
Button on the
Form, create its
Click event and type the following code which creates three
c_items and links them to the first
c_invoice:
procedure TWinForm.
add_first_invoice_items__Click(
sender:
System.
Object;
e:
System.
EventArgs);
procedure add_item_to_invoice(p_price: Double; p_quantity: Integer;
p_c_invoice: c_invoice);
begin
with c_item.Create(EcoSpace) do
begin
m_price:= p_price;
m_quantity:= p_quantity;
from_invoice:= p_c_invoice;
end; // with c_invoice.Create
end; // add_item_to_invoice
var l_c_invoice_list: Borland.Eco.ObjectRepresentation.iObjectList;
l_c_invoice: c_invoice;
begin // create_items__Click
l_c_invoice_list:= invoice_list_expression_handle.Element
as Borland.Eco.ObjectRepresentation.iObjectList;
// -- use any c_invoice
l_c_invoice:= l_c_invoice_list[0].AsObject as c_invoice;
add_item_to_invoice(1245.45, 12, l_c_invoice);
add_item_to_invoice(452.33, 7, l_c_invoice);
add_item_to_invoice(5832.55, 19, l_c_invoice);
end; // add_first_invoice_items__Click
To check that the invoices and the items are correctly linked, we can display them in a
TextBox
drop a
Button on the
Form and in its
Click event, enumerate the elements of the
c_invoice list, and for each
c_invoice, enumerate the
c_items:
procedure TWinForm.
list_all__Click(
sender:
System.
Object;
e:
System.
EventArgs);
var l_c_invoice_list:
Borland.
Eco.
ObjectRepresentation.
iObjectList;
l_invoice_index:
Integer;
l_c_invoice:
c_invoice;
l_item_index:
Integer;
begin
display('> list_all');
l_c_invoice_list:=
invoice_list_expression_handle.
Element
as Borland.
Eco.
ObjectRepresentation.
iObjectList;
for l_invoice_index:= 0 to l_c_invoice_list.Count- 1 do
begin
l_c_invoice:= l_c_invoice_list[l_invoice_index].AsObject as c_invoice;
display(l_c_invoice.m_customer);
for l_item_index:= 0 to l_c_invoice.has_item.Count- 1 do
display(' '+ l_c_invoice.has_item[l_item_index].m_price.ToString
+ ' '+ l_c_invoice.has_item[l_item_index].m_quantity.ToString);
end; // for l_invoice_index
display('< list_all');
end; // list_all__Click
compile, run, click "add_first_invoice_items_" and then "list_all_"
here is the snapshot of the application:
image
Using the same technique, we added a couple of items to the other invoices (the
"create_items_"
Button which is in the .ZIP sources).
To add the item DataGrid display, we follow those steps:
first display the invoices:
- add a ExpressionHandle, set
- its (Name) to invoice_list_expression_handle
- its RootHandle to rhRoot
- its Expression to c_invoice.AllInstances
- drop a DataGrid, set
- its (Name) to invoice_datagrid
- its DataSource to invoice_list_expression_handle
the field names of the
c_invoice class are displayed in the
invoice_datagrid
to know which invoice is selected, add a
CurrencyManagerHandle:
- from the Tools Palette, drop a CurrencyManagerHandle and set
- its (Name) to invoice_currency_manager_handle
- its RootHandle to invoice_list_expression_handle
- its BindingContext to invoice_datagrid
- drop an ExpressionHandle for the items and set
- its (Name) to item_list_expression_handle
- its RootHandle to invoice_currency_manager_handle
- its Expression to self.has_item
- drop a DataGrid and set
- its (Name) to item_datagrid
- its DataSource to item_list_expression_handle
compile and run
when we move in the
invoice_datagrid, the items of this invoice are displayed in the
item_datagrid image
Note that
- we could not impose the attribute order in the c_item UML class diagram. We first chose (m_price, m_quantity) and then tried to change it to (m_quantity, m_price) but could'nt succeed
- for the (m_price, m_quantity) first trial, the invoice_grid had a last "has_item" column in the invoice_datagrid. After trying to switch to
(m_quantity, m_price) we have a first "from_invoice" column.
By the same token, we could not increase the size of the invoice_datagrid last column width. There must be a way to do those things, but since this is
not the most important part, we
In a way similar to how we deleted an invoice, we can add some items to the current invoice using the
CurrencyManagerHandle component
drop a
Button on the
Form, a in it
Click event, create an new empty item to the currently selected invoice:
compile, run, click "add_item_to_invoice_"
procedure TWinForm.
add_item_to_invoice__Click(
sender:
System.
Object;
e:
System.
EventArgs);
var l_c_selected_invoice:
c_invoice;
l_c_new_item:
c_item;
begin
if invoice_currency_manager_handle.
Element.
AsObject is c_invoice
then begin l_c_selected_invoice:=
c_invoice(
invoice_currency_manager_handle.
Element.
AsObject);
l_c_new_item:= c_item.Create(EcoSpace);
l_c_selected_invoice.has_item.Add(l_c_new_item);
// -- or
// l_c_new_item.from_invoice:= l_c_selected_invoice;
end;
end; // add_item_to_invoice__Click
here is a snapshot of this addition:
image
4 - OCL: the Object Constraint Language
4.1 - OCL for navigation and for modeling
OCL at the start was created to impose pre and post-conditions, similar to the pre and post-condition that the Eiffel language introduced to implement "Design
by Contract". For a bank withdraw() method, you require that at the start of the method, just after
BEGIN, the balance should be greater then the amount taken out, and after the withdrawal, just before the
END of the method, the
balance must be equal to the starting amount less the amount taken out. It basically is an expression language allowing to specify in a non procedural way how the methods of our
CLASSes are changing the state of our system. In other
words specifying the behaviour of the model. Since the methods of one
CLASS often uses other
CLASSes, OCL also formalized navigation, allowing for instance to specify that the total of the
c_item amounts of a
c_invoice
should be greater than 100ドル:
Self.has_item.AllInstances->collect(m_price* m_quantity)->sum > 100
In our projects above, we already used OCL expressions to navigate the model. For instance, to be able to visualize the invoice collection in a DataGrid, we used:
my_c_expression_handle.Expression:= 'c_invoice.AllInstances';
We now are going to investigate some other OCL expressions.
4.2 - Evaluating OCL expression by Code
So lets start with the expression that we already used,
c_invoice.
AllInstances. This expression computes the list of all
c_invoice objects. The result of this computation is some kind of list. To be more
accurate, it is an
iObjectList.
To compute this list we call the EcoSpace.OclService.Evaluate function, which returns the iObjectList:
VAR my_c_ielement:
iElement;
my_c_ielement:=
EcoSpace.OclService.Evaluate(Nil, 'c_invoice.AllInstances', Nil);
This iObjectList has a Count and an Item property. Each Item[nnn] is an iElement. So to display it we have to cast it back into our Delphi object
world, using AsObject:
with iObjectList(my_c_ielement) do
for l_index:= 0 to Count- 1 do
display( Item[l_index].AsObject.ToString );
Since our objects (c_invoice and c_item) implement the ToString FUNCTION, we will see the values displayed.
This is how we implemented this evaluation
we started a new project, but you can of course use your current project (or, alternately, use the source code from the
downloadable .ZIP below).
Our project contains simply the 2 classes with the association, the ToString implementation in each CLASS, and a Button for initializing the values
drop a
Button, call it "all_instances_", and in it
Click event, call our
iObjectList method:
procedure TWinForm.all_instances__Click(sender: System.Object;
e: System.EventArgs);
begin
evaluate_iobjectlist('c_invoice.AllInstances');
end; // all_instances__Click
in the
tWinForm CLASS define the evaluation method:
unit u_03_invoicing_ocl;
interface
type TWinForm=
class(System.Windows.Forms.Form)
// -- ...ooo...
public
procedure evaluate_iobjectlist(p_ocl_expression: String);
end; // TWinForm
type the code of this method:
procedure TWinForm.
evaluate_iobjectlist(
p_ocl_expression:
String);
var l_c_ielement:
iElement;
l_index:
Integer;
begin
l_c_ielement:=
EcoSpace.
OclService.
Evaluate(
Nil,
p_ocl_expression,
Nil);
if l_c_ielement is iObjectList
then begin
with iObjectList(l_c_ielement) do
for l_index:= 0 to Count- 1 do
display(' '+ Item[l_index].AsObject.ToString);
end
else display('not_iObjectList ');
end; // evaluate_iobjectlist
compile, run, create the values, click "all_instances_"
here is the snapshot of our project
image
Here is an UML Class diagram of this evaluation:
and:
- our tWinForm contains the EcoSpace attribute, this EcoSpace containing the OclService member
- the OclService.Evaluate returns one of the iElement descendents
4.3 - Evaluating different iElement expressions
How do we know that the type of the
AllInstances is
iObjectList ? Well OCL expressions compute values in the
EcoSpace world. The object kinds in this
outer world are all descendents of
iElement, and the
iElement CLASS has a
ContentType enumeration, which tells us which descendent of
iElement should
be used. So while spelunking in this
EcoSpace world, we used the following
FUNCTION which displays the
iElement descendent that should be used
Here is the function (which is in the downloadable .ZIP):
function TWinForm.f_content_type_name(p_content_type: ContentType): String;
begin
case p_content_type of
ContentType.Primitive : Result:= 'Primitive';
ContentType.Collection : Result:= 'Collection';
ContentType.Tuple : Result:= 'Tuple';
ContentType.Object : Result:= 'Object';
ContentType.ObjectList : Result:= 'ObjectList';
ContentType.ConstraintCheck : Result:= 'ConstraintCheck';
else Result:= '???_unknown';
end; // case
end; // f_content_type_name
Note that
- since ContentType is defined in the Borland.Eco.ObjectRepresentation Namespace, we also had to move this USES from the IMPLEMENTATION to the INTERFACE level
To be able to display other
iElement descendent expression, we then implemented the other evaluation methods. Here are all our OCL expression evaluation and display methods:
procedure TWinForm.
evaluate_iprimitive(
p_ocl_expression:
String);
var l_c_ielement:
iElement;
begin
l_c_ielement:=
EcoSpace.
OclService.
Evaluate(
Nil,
p_ocl_expression,
Nil);
if l_c_ielement is iPrimitive
then display(' '+ iPrimitive(l_c_ielement).AsObject.ToString )
else display(' not_iPrimitive_but '
+ f_content_type_name(l_c_ielement.ContentType));
end; // evaluate_iprimitive
procedure TWinForm.evaluate_iobject(p_ocl_expression: String);
var l_c_ielement: iElement;
begin
l_c_ielement:= EcoSpace.OclService.Evaluate(Nil, p_ocl_expression, Nil);
if l_c_ielement is iObject
then display(' '+ iObject(l_c_ielement).AsObject.ToString )
else display(' not_iObject_but '
+ f_content_type_name(l_c_ielement.ContentType));
end; // evaluate_iobject
procedure TWinForm.evaluate_ielementcollection(p_ocl_expression: String);
var l_c_ielement: iElement;
l_index: Integer;
begin
l_c_ielement:= EcoSpace.OclService.Evaluate(Nil, p_ocl_expression, Nil);
if l_c_ielement is iElementCollection
then
with iElementCollection(l_c_ielement) do
for l_index:= 0 to Count- 1 do
display(' '+ Item[l_index].AsObject.ToString)
else display(' not_iCollection_but '
+ f_content_type_name(l_c_ielement.ContentType));
end; // evaluate_ielementcollection
procedure TWinForm.evaluate_iobjectlist(p_ocl_expression: String);
var l_c_ielement: iElement;
l_index: Integer;
begin
l_c_ielement:= EcoSpace.OclService.Evaluate(Nil, p_ocl_expression, Nil);
if l_c_ielement is iObjectList
then
with iObjectList(l_c_ielement) do
for l_index:= 0 to Count- 1 do
display(' '+ Item[l_index].AsObject.ToString)
else display('not_iObjectList_but '
+ f_content_type_name(l_c_ielement.ContentType));
end; // evaluate_iobjectlist
At the start of the method we also display the expression string (this is not shown here)
4.4 - OCL expression gallore
4.4.1 - Class member collection
We can request to evaluate the values of the members of a
CLASS. For instance, to get the list of all customer names, we can use:
c_invoice.AllInstances.m_customer
The result of this expression is a collection, with the iElementCollection TYPE. So we called:
evaluate_ielementcollection('c_invoice.AllInstances.m_customer');
4.4.2 - OCL operators
OCL contains some predefined functions, like
Size. We can call this on any
iObjectList or
iElementCollection values. To avoid some conflict with any
CLASS attribute which could be named "Size", OCL decided to use the "->"
symbol to specify a
FUNCTION call, like this:
c_invoice.AllInstances->Size
The result of Size is an iPrimitive Integer value, so we called:
evaluate_iprimitive('c_invoice.AllInstances->Size');
Also not that those functions are called "operators" (like UML methods)
4.4.3 - ForAll
To check that some condition is true for all members of a list, we can call the
ForAll function:
c_invoice.AllInstances->forAll(my_invoice | my_invoice.m_invoice_number> 100
The result of Size is an iPrimitive Boolean value, so we called:
evaluate_iprimitive('c_invoice.AllInstances'
+ '->forAll(my_invoice | my_invoice.m_invoice_number> 100)');
4.4.4 - Select and Self
The
Select FUNCTION allows us to extract a list of
iElements from a list:
c_invoice.AllInstances->select(m_invoice_number= 12345)
The result of Size is an iObjectList, so we called:
evaluate_iobjectlist('c_invoice.AllInstances'
+ '->select(m_invoice_number= 12345)');
We used this Select to specify a single c_invoice object. All OCL expressions
are evaluated in some context. In our case, we used the global EcoSpace context. But if some expression is in the context of one object (say the invoice object of Mr Miller) we could use the Self OCL specifier. Like this:
4.4.5 - Navigating to a related object
To navigate to another object, we use the relation end names. For instance, to display the items of some invoice:
c_invoice.AllInstances->select(m_invoice_number= 12345).has_item'
And to display the iObjectList values:
The result of Size is an iObjectList, so we called:
evaluate_iobjectlist('c_invoice.AllInstances'
+ '->select(m_invoice_number= 12345).has_item');
s
Checking the OCL expression
To make sure that our OCL expression is correct, we can use the OCL Editor. This is accessible from the ExpressionHandle.Expression property in the Object Inspector.
So:
select the u_03_invoicing_ocl main form designer
select rhRoot and make sure that its EcoSpaceType property is set to your EcoSpace type (p_03_invoicing_oclEcoSpace.Tp_03_invoicing_oclEcoSpace in our case)
drop an ExpressionHandle on the Form
Set its RootHandle to rhRoot
select Expression and click on the Ellipsis (or right click and select "Edit Expression", or use the link at the bottom of the Object Inspector
the
OCL Editor is displayed
image
double click on c_invoice in the right treeview
this element of the expression is displayed in the left memo, and the right treeview is updated to present what we can now add to our expression
image
click on .AllInstances
to see what happens if an expression is incorrect, type now directly in the memo "customer" (instead of "m_customer")
the incorrect part is displayed in red:
image
5 - Comments on this ECO III with Delphi 2006 tutorial
5.1 - What's Next ?
Among the many things that were not presented, we can list
- the use of Sql Database persistence. Since we already presented many papers about ADO .Net, the connection should be easy. However, ECO has also some
specific parts for handling transactions and tweaking the generated Sql requests
- the uses of ASP .Net Web pages, and Web Services. There are published papers in this area (see the references)
- the creation of "derived attributes", which in Delphi language means calculated fields
- the creation of "ECO Variables", which are the equivalent in OCL world of the parameters in parametrized Sql requests
- the creation of custom OCL operations (adding new OCL function, in addition to Size etc)
- using OCL constraints at the UML model level
- the possibility to execute the UML state diagram, and in ECO III to use an
Action Language (wich can change the state of the objects, whereas OCL only evaluates expressions, without side effects)
5.2 - OCL for modeling
My primary motivation to look at the ECO framework was to try the OCL language.
To be able to code at a more abstract level than Pascal code, UML (or any other modeling language) looks like a very good choice for structural modeling (Class diagram). On the behavioral side, things get more complicated.
First of all you have two schools:
- one trying to work at the UML model level only, and the framework will blindly generate the Pascal code. The same jump from model to Pascal as
from Pascal to Assembler. This is the MDA approach, with Action Languages, implemented by Kennedy Carter, PathFinder or xtUML and MELLOR at Bridgepoint. Quite heavy machinery, with many marketing enthusiatic
presentations, and very few real life examples, if any. In addition these models generate the code from State Diagrams, since those frameworks are anchored in the telecom world (the phone is "on hook", "ringing", etc)
- one trying to use the UML model as a starting point, this model being used to generate some stub code that the developer has to fine tune. This is the "round trip" approach.
In this area you find both generation from State Diagram or from Activity Diagram (or Object Diagrams). The Activity Diagram approach focuses more on chunks of work, than state. This is obviously more in line with business
thinking ("process the order", "manufacture the product" etc), and somehow gave birth to the Workflow models.
At the end of this ECO trial, we now know how to use some specification
language at the UML model level, like OCL, to navigate the different Classes of the model, and this was our main target at this point. And ECO is the only implementation we have, with State Diagram execution and Action Language.
6 - Download the Sources
Here are the source code files:
The .ZIP file(s) contain:
- the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM), and any other auxiliary form
- any .TXT for parameters, samples, test data
- all units (.PAS) for units
Those .ZIP
- are self-contained: you will not need any other product (unless expressly mentioned).
- for Delphi 6 projects, can be used from any folder (the pathes are RELATIVE)
- will not modify your PC in any way beyond the path where you placed the .ZIP (no registry changes, no path creation etc).
To use the .ZIP:
- create or select any folder of your choice
- unzip the downloaded file
- using Delphi, compile and execute
To remove the .ZIP simply delete the folder.
The Pascal code uses the Alsacian notation, which prefixes identifier by
program area: K_onstant, T_ype, G_lobal, L_ocal, P_arametre, F_unction, C_lasse etc. This notation is presented in the Alsacian Notation paper.
As usual:
- please tell us at fcolibri@felix-colibri.com if you found some errors, mistakes, bugs, broken links or had some problem downloading the file. Resulting corrections will
be helpful for other readers
- we welcome any comment, criticism, enhancement, other sources or reference suggestion. Just send an e-mail to fcolibri@felix-colibri.com.
- or more simply, enter your (anonymous or with your e-mail if you want an answer) comments below and clic the "send" button
- and if you liked this article, talk about this site to your fellow developpers, add a link to your links page ou mention our articles in your blog or newsgroup posts when relevant. That's the way we operate:
the more traffic and Google references we get, the more articles we will write.
7 - ECO documentation
We are not overhelmed with documentation, but here is what I found:
- the basic tutorials are those from Anthony RICHARDSON
- there are a couple of slides (use Google to find the links)
- Jason VOKES, Gerard VAN DER POL (marketing slides)
- Peter MORRIS, did some presentations, and also has a blog (see below)
- Tim JARVIS
- some blogs will keep you up to date
- there is a book:
- Alois SCHMID who wrote a book in german, which seems to have been translated in English
- Peter MORRIS has several interesting places:
- there is a Eco Community page on the CodeGear site
- also there is a Wiki page at CogeGear with very nice
tutorials on some detailed points of ECO II or ECO III. Exactly what a Borland tutorial should have looked like in the first place (but for the whole framework, not for some specific points only)
- ECO Wikia a wiki about ECO. And there are other Wiki pages about ECO
- projects
- Delphi_Unleashing_enterprise_models_with_Delphi_8_Architect_for_.NET.pdf"
- by Christoph FLOURY - A Whitepaper with a Project Mangement example, and the most complete Class diagram about the ECO Framework
-
BLOGGING WITH DELPHI is a 4 part paper from Bob SWART explaining how to build a blog application with ECO and ASP.Net
- Real life complexity with
ECOII by Hans KARLSEN, is a time keeping application
- using Tamaracka to search the Eco newsgroup, you will find many ECO questions, and, what is very nice, is that
the ECO developer often directly answer to the questions
Overall, I feel a little bit disapointed that ECO changed so often, and that the official documentation (manuals or Help) are so thin. But all those
resources should get you started in this brave new ECO world.
8 - The author
Felix John COLIBRI works at the
Pascal
Institute. Starting with Pascal in 1979, he then became involved with Object Oriented Programming, Delphi, Sql, Tcp/Ip, Html, UML. Currently, he is mainly
active in the area of
custom software development (new projects, maintenance, audits, BDE migration, Delphi
Xe_n migrations, refactoring),
Delphi Consulting and
Delph
training. His
web site features tutorials, technical papers about programming with full downloadable
source code, and the
description and calendar of forthcoming
Delphi, FireBird, Tcp/IP, Web Services, OOP / UML, Design Patterns, Unit Testing training sessions.