Saturday, April 18, 2009

 

Paginating TListView - Part 3 of 3


To finish this series of paginating TListViews, I'll show a simple method of automatic assignment of data from a database query to properties of an object.

Introducing RTTI

Delphi and FreePascal provides the ability to get and set the values of object's properties at run-time, this is called Run Time Type Information, RTTI for short. This allows a great deal of flexibility at the moment of reducing code size and automating repetitive tasks.

In the last example, the function GetCurrentPage executes a database query, then iterates trough it's dataset assigning each property of each item the FCustomers collection. One problem here, is that this function only works for TCustomers, so if we want to use another collection, we need to create a GetCurrentPage_for_our_new_tcollection and so on.

Automating GetCurrentPage

To generalize our GetCurrentPage method, we need to RTTI-enable the TCustomer class by replacing the "public" keyword by "published". This change tells the compiler that it's properties will be available to the RTTI functions like SetPropValue.

The second step is to include the TypInfo unit in the "uses" clause. This unit contains all the RTTI functions. I recommend further reading about it.

Now, the last step. Just replacing the lines 20 to 25 of GetCurrentPage function with this:


lCustomer := FCustomers.Add;
for I := 0 to IbQuery.Fields.Count - 1 do
SetPropValue(lCustomer,
IbQuery.Fields[I].FieldName,
IbQuery.Fields[I].Value);


Warning: This code works because the properties of TCustomer class have the same name as the fields returned by the query I used in the example.

Now, I'll remove the references to FCustomers in the function:


procedure TForm1.GetCurrentPage(ACurrentPage: Integer;
ACollection: TCollection);
var
lFrom: Integer;
lTo: Integer;
I: Integer;
lItem: TCollectionItem;

begin
(* Do the query *)
lFrom := ((ACurrentPage * cPageSize) - cPageSize) + 1;
lTo := (ACurrentPage * cPageSize) + 1;
IbQuery1.Close;
IbQuery1.SQL.Text :=
'select CustId, FirstName, LastName from customers ' +
'rows ' + IntToStr(lFrom) + ' to ' + IntToStr(lTo);
IbQuery1.Open;
(* Fill the collection *)
ACollection.Clear;
while not IbQuery1.Eof do
begin
lItem := ACollection.Add;
for I := 0 to IbQuery1.Fields.Count - 1 do
SetPropValue(lItem,
IbQuery1.Fields[I].FieldName,
IbQuery1.Fields[I].Value);
IbQuery1.Next;
end;
IbQuery1.Close;
end;


You can improve this by adding a new parameter for the Query to be executed inside the function. Also you have to adapt the ListView1Data event by this:


procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
var
lCurrPage: Integer;
lPos: Integer;
begin
(* Get current page index *)
lCurrPage := Item.Index div cPageSize;
(* Get the position in the current page *)
lPos := Item.Index - (lCurrPage * cPageSize);

(* Page changed? refresh the data *)
if FCurrentPage - 1 <> lCurrPage then
begin
FCurrentPage := lCurrPage + 1;
GetCurrentPage(FCurrentPage, FCustomers);
end;

(* Paint the ListView's item with our TCollection's items *)
Item.Caption := FCustomers[lPos].LastName;
Item.SubItems.Add(FCustomers[lPos].FirstName);
end;


This is the end of the series. This example could be improved even more, one way that comes to my mind is the creating a TListView descendant component.
Comments: Post a Comment



<< Home

This page is powered by Blogger. Isn't yours?