We'll use a special in-memory table from the FireDAC library. FireDAC is a new data access library from Embarcadero included in RAD Studio since version XE5. If some of the code seems unclear at the moment, consider the in-memory table as a normal TDataSet descendant that holds its data only in memory. However, at the end of the section, there are some links to the FireDAC documentation, and I strongly suggest that you read it if you still don't understand FireDAC:
- Create a brand new VCL application and drop a TFDMemTable, a TDBGrid, a TDataSource, and a TDBNavigator onto the form. Connect all the components in the usual way (TDBGrid connected to TDataSource, followed by TFDMemTable). Set the TDBGrid font size to 18. This will create more space in the cell for our graphical representation.
- Using the TFDMemTable fields editor, add the following fields and then activate the dataset by setting its Active property to True:
Field name
|
Field data type
|
Field type
|
FullName
|
String (size 50)
|
Data
|
TotalExams
|
Integer
|
Data
|
PassedExams
|
Integer
|
Data
|
Rating
|
Float
|
Data
|
PercPassedExams
|
Float
|
Calculated
|
MoreThan50Percent
|
Boolean
|
Calculated
|
- Now, add all the columns to TDBGrid by right-clicking and selecting Columns Editor.... Then, again right-click and select Add all fields in the resultant window. Then, rearrange the columns as shown here and give it a nice title caption:
- FullName
- TotalExams
- PassedExams
- PercPassedExams
- MoreThan50Percent
- Rating
- In a real application, we would load real data from some sort of database. However, for now, we'll use some custom data generated in code. We have to load this data into the dataset with the code that follows:
procedure TMainForm.FormCreate(Sender: TObject);
begin
FDMemTable1.AppendRecord(['Ludwig van Beethoven', 30, 10, 4]);
FDMemTable1.AppendRecord(['Johann Sebastian Bach', 24, 10, 2.5]);
FDMemTable1.AppendRecord(['Wolfgang Amadeus Mozart', 30, 30, 5]);
FDMemTable1.AppendRecord(['Giacomo Puccini', 25, 10, 2.2]);
FDMemTable1.AppendRecord(['Antonio Vivaldi', 20, 20, 4.7]);
FDMemTable1.AppendRecord(['Giuseppe Verdi', 30, 5, 5]);
FDMemTable1.AppendRecord(['John Doe', 24, 5, 1.2]);
end;
- Do you remember we have two calculated fields that need to be filled in some way? Calculated fields need a form of processing behind them to work. TFDMemTable, just like any other TDataSet descendant, has an event called OnCalcFields that allows the developer to do this. Create the OnCalcFields event handler for TFDMemTable and fill it with the following code:
procedure TMainForm.FDMemTable1CalcFields(DataSet: TDataSet);
var
LPassedExams: Integer;
LTotExams: Integer;
begin
LPassedExams := FDMemTable1.FieldByName('PassedExams').AsInteger;
LTotExams := FDMemTable1.FieldByName('TotalExams').AsInteger;
if LTotExams = 0 then
FDMemTable1.FieldByName('PercPassedExams').AsFloat := 0
else
FDMemTable1.FieldByName('PercPassedExams').AsFloat := LPassedExams /
LTotExams * 100;
FDMemTable1.FieldByName('MoreThan50Percent').AsBoolean :=
FDMemTable1.FieldByName('PercPassedExams').AsFloat > 50;
end;
- Run the application by hitting F9 (or by going to Run | Run) and you will get the following screenshot:
Figure 1.5: A normal form with some data
- This is useful, but a bit boring. Let's start our customization. Close the application and return to the Delphi IDE.
- Go to Properties of TDBGrid and set Default Drawing to False.
- Now, we have to organize the resources used to draw the grid cells. Calculated fields will be drawn directly using code, but the Rating field will be drawn using a five-star rating image from 0 to 5. It starts with a 0.5 incremental step (0, 0.5, 1, 1.5, and so on). So, drop TImageList on the form, and set the Height as 32 and the Width as 160.
- Select the TImageList component and open the image list's editor by right-clicking and then selecting ImageList Editor.... You can find the required PNG images in the recipe's project folder (ICONS\RATING_IMAGES). Load the images in the correct order, as shown here:
- Index 0 as image 0_0_rating.png
- Index 1 as image 0_5_rating.png
- Index 2 as image 1_0_rating.png
- Index 3 as image 1_5_rating.png
- Index 4 as image 2_0_rating.png
- Go to the TDBGrid event and create the event handler for OnDrawColumnCell. All the customization code goes in this event. Include the Vcl.GraphUtil unit, and write the following code in the DBGrid1DrawColumnCell event:
procedure TMainForm.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
var
LRect: TRect;
LGrid: TDBGrid;
LText: string;
LPerc: Extended;
LTextWidth: TSize;
LRating: Extended;
LNeedOwnerDraw: Boolean;
LImageIndex: Int64;
begin
LGrid := TDBGrid(Sender);
if [gdSelected, gdFocused] * State <> [] then
LGrid.Canvas.Brush.Color := clHighlight;
LNeedOwnerDraw := (Column.Field.FieldKind = fkCalculated) or
Column.FieldName.Equals('Rating');
// if doesn't need owner-draw, default draw is called
if not LNeedOwnerDraw then
begin
LGrid.DefaultDrawColumnCell(Rect, DataCol, Column, State);
exit;
end;
LRect := Rect;
if Column.FieldName.Equals('PercPassedExams') then
begin
LText := FormatFloat('##0', Column.Field.AsFloat) + ' %';
LGrid.Canvas.Brush.Style := bsSolid;
LGrid.Canvas.FillRect(LRect);
LPerc := Column.Field.AsFloat / 100 * LRect.Width;
LGrid.Canvas.Font.Size := LGrid.Font.Size - 1;
LGrid.Canvas.Font.Color := clWhite;
LGrid.Canvas.Brush.Color := clYellow;
LGrid.Canvas.RoundRect(LRect.Left, LRect.Top, Trunc(LRect.Left + LPerc),
LRect.Bottom, 2, 2);
LRect.Inflate(-1, -1);
LGrid.Canvas.Pen.Style := psClear;
LGrid.Canvas.Font.Color := clBlack;
LGrid.Canvas.Brush.Style := bsClear;
LTextWidth := LGrid.Canvas.TextExtent(LText);
LGrid.Canvas.TextOut(LRect.Left + ((LRect.Width div 2) -
(LTextWidth.cx div 2)), LRect.Top + ((LRect.Height div 2) -
(LTextWidth.cy div 2)), LText);
end
else if Column.FieldName.Equals('MoreThan50Percent') then
begin
LGrid.Canvas.Brush.Style := bsSolid;
LGrid.Canvas.Pen.Style := psClear;
LGrid.Canvas.FillRect(LRect);
if Column.Field.AsBoolean then
begin
LRect.Inflate(-4, -4);
LGrid.Canvas.Pen.Color := clRed;
LGrid.Canvas.Pen.Style := psSolid;
DrawCheck(LGrid.Canvas, TPoint.Create(LRect.Left,
LRect.Top + LRect.Height div 2), LRect.Height div 3);
end;
end
else if Column.FieldName.Equals('Rating') then
begin
LRating := Column.Field.AsFloat;
if Frac(LRating) < 0.5 then
LRating := Trunc(LRating)
else
LRating := Trunc(LRating) + 0.5;
LText := LRating.ToString;
LGrid.Canvas.Brush.Color := clWhite;
LGrid.Canvas.Brush.Style := bsSolid;
LGrid.Canvas.Pen.Style := psClear;
LGrid.Canvas.FillRect(LRect);
Inc(LRect.Left);
LImageIndex := Trunc(LRating) * 2;
if Frac(LRating) >= 0.5 then
Inc(LImageIndex);
ImageList1.Draw(LGrid.Canvas, LRect.CenterPoint.X -
(ImageList1.Width div 2), LRect.CenterPoint.Y - (ImageList1.Height div 2),
LImageIndex);
end;
end;
- That's all folks! Press F9 (or go to Run | Run), and we now have a nicer grid with more direct information about our data:
Figure 1.6: The same grid with a bit of customization