Sign In Start Free Trial
Account

Add to playlist

Create a Playlist

Modal Close icon
You need to login to use this feature.
  • Book Overview & Buying Delphi Cookbook
  • Table Of Contents Toc
Delphi Cookbook

Delphi Cookbook

By : Daniele Teti
4.5 (19)
close
close
Delphi Cookbook

Delphi Cookbook

4.5 (19)
By: Daniele Teti

Overview of this book

Intended to refresh the basics of Delphi as well as advance your knowledge to the next level, it is assumed you will know RAD studio and the Object Pascal language. However, if you are not an experienced RAD studio programmer this accessible guide will still develop those initial crucial skills.
Table of Contents (9 chapters)
close
close
8
Index

Customizing TDBGrid

The adage a picture is worth a thousand words refers to the notion that a complex idea can be conveyed with just a single still image. Sometimes, even a simple concept is easier to understand and nicer to see if it is represented by images. In this recipe, we'll see how to customize the TDBGrid object to visualize graphical representation of data.

Getting ready

Many VCL controls are able to delegate their drawing, or part of it, to user code. This means that we can use simple event handlers to draw standard components in different ways. It is not always simple, but TDBGrid is customizable in a really easy way. Let's say that we have a class of musicians that have to pass a set of exams. We want to show the percentage of musicians who have already passed exams with a progress bar, and if the percent is higher than 50 percent, there should also be a check in another column.

How to do it…

We'll use a special in-memory table from the FireDAC library. FireDAC is a new data access library from Embarcadero, which is 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. However, at the end of this section, there are some links to the FireDAC documentation, and I strongly suggest you to read them if you still don't know FireDAC. To customize TDBGrid, perform the following steps:

  1. Create a brand new VCL application and drop on the form the TFDMemTable, TDBGrid, TDataSource, and TDBNavigator component. Connect all these components in the usual way (TDBGrid->TDataSource->TFDMemTable). Set the TDBGrid font size to 24. This will create more space in the cell for our graphical representation.
  2. Using the TFDMemTable fields editor, add the following fields and then activate the dataset setting by setting its Active property to True:

    Field name

    Field datatype

    Field type

    FullName

    String (size 50)

    Data

    TotalExams

    Integer

    Data

    PassedExams

    Integer

    Data

    PercPassedExams

    Float

    Calculated

    MoreThan50Percent

    Boolean

    Calculated

  3. In a real application, we should load real data from some sort of database. But for now, we'll use some custom data generated in code. We have to load this data into the dataset with the following code:
    procedure TMainForm.FormCreate(Sender: TObject);
    begin
      FDMemTable1.InsertRecord(
             ['Ludwig van Beethoven',30,10]);
      FDMemTable1.InsertRecord(
             ['Johann Sebastian Bach',24,10]);
      FDMemTable1.InsertRecord(
             ['Wolfgang Amadeus Mozart',30,30]);
      FDMemTable1.InsertRecord(
             ['Giacomo Puccini',25,10]);
      FDMemTable1.InsertRecord(
             ['Antonio Vivaldi',20,20]);
      FDMemTable1.InsertRecord(
             ['Giuseppe Verdi',30,5]);
    end;
    
  4. Do you remember? We've two calculated fields that need to be filled in some way. Create the OnCalcFields event handler on the TFDMemTable component and fill it with the following code:
    procedure TMainForm.FDMemTable1CalcFields(
       DataSet: TDataSet);
    var
      p: Integer;
      t: Integer;
    begin
      p := FDMemTable1.FieldByName('PassedExams').AsInteger;
      t := FDMemTable1.FieldByName('TotalExams').AsInteger;
      if t = 0 then
      begin
        FDMemTable1.FieldByName('PercPassedExams').AsFloat := 0
      end
      else
      begin
        FDMemTable1.
             FieldByName('PercPassedExams').
             AsFloat := p / t * 100;
      end;
    
    FDMemTable1.FieldByName('MoreThan50Percent').AsBoolean := FDMemTable1.
       FieldByName('PercPassedExams').AsFloat > 50;
    end;
    
  5. Run the application by hitting F9 (or navigating to Run | Run) and you will get the following screenshot:
    How to do it…

    A normal form with some data

  6. This is useful, but a bit boring. Let's start our customization. Close the application and return to Delphi IDE.
  7. Go to the TDBGrid properties and set DefaultDrawing to false.

    Go to the TDBGrid event and create an 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
      R: TRect;
      Grid: TDBGrid;
      S: string;
      WPerc: Extended;
      SSize: TSize;
      SavedPenColor: Integer;
      SavedBrushColor: Integer;
      SavedPenStyle: TPenStyle;
      SavedBrushStyle: TBrushStyle;
    begin
      Grid := TDBGrid(Sender);
      if [gdSelected, gdFocused] * State <> [] then
        Grid.Canvas.Brush.Color := clHighlight;
    
      if Column.Field.FieldKind = fkCalculated then
      begin
        R := Rect;
        SavedPenColor := Grid.Canvas.Pen.Color;
        SavedBrushColor := Grid.Canvas.Brush.Color;
        SavedPenStyle := Grid.Canvas.Pen.Style;
        SavedBrushStyle := Grid.Canvas.Brush.Style;
      end;
    
      if Column.FieldName.Equals('PercPassedExams') then
      begin
        S := FormatFloat('##0', Column.Field.AsFloat) + ' %';
        Grid.Canvas.Brush.Style := bsSolid;
        Grid.Canvas.FillRect(R);
        WPerc := Column.Field.AsFloat / 100 * R.Width;
        Grid.Canvas.Font.Size := Grid.Font.Size - 1;
        Grid.Canvas.Font.Color := clWhite;
        Grid.Canvas.Brush.Color := clYellow;
        Grid.Canvas.RoundRect(R.Left, R.Top, 
             Trunc(R.Left + WPerc), R.Bottom, 2, 2);
        InflateRect(R, -1, -1);
        Grid.Canvas.Pen.Style := psClear;
        Grid.Canvas.Font.Color := clBlack;
        Grid.Canvas.Brush.Style := bsClear;
        SSize := Grid.Canvas.TextExtent(S);
        Grid.Canvas.TextOut(
             R.Left + ((R.Width div 2) - (SSize.cx div 2)),
             R.Top + ((R.Height div 2) - (SSize.cy div 2)), 
             S);
      end
      else if Column.FieldName.Equals('MoreThan50Percent') then
      begin
        Grid.Canvas.Brush.Style := bsSolid;
        Grid.Canvas.Pen.Style := psClear;
        Grid.Canvas.FillRect(R);
        if Column.Field.AsBoolean then
        begin
          InflateRect(R, -4, -4);
          Grid.Canvas.Pen.Color := clRed;
          Grid.Canvas.Pen.Style := psSolid;
          DrawCheck(Grid.Canvas, 
            TPoint.Create(R.Left, R.Top + R.Height div 2), R.Height div 3);
        end;
      end
      else
        Grid.DefaultDrawColumnCell(Rect, DataCol, 
             Column, State);
    
      if Column.Field.FieldKind = fkCalculated then
      begin
        Grid.Canvas.Pen.Color := SavedPenColor;
        Grid.Canvas.Brush.Color := SavedBrushColor;
        Grid.Canvas.Pen.Style := SavedPenStyle;
        Grid.Canvas.Brush.Style := SavedBrushStyle;
      end;
    end;
    
  8. That's all, folks! Hit F9 (or navigate to Run | Run) and we now have a nicer grid with more direct information about our data:
    How to do it…

    The same grid with a bit of customization

How it works…

By setting the DBGrid property DefaultDrawing to false, we told the grid that we want to manually draw all the data into every cell. The OnDrawColumnCell event allows us to actually draw using the standard Delphi code. For each cell we are about to draw, the event handler is called with a list of useful parameters to know which cell we're about to draw and what data we have to read considering the column currently drawn. In this case, we want to draw only the calculated columns in a customized way. This is not a rule, but this can be done to manipulate all cells. We can draw any cell in the way we like. For the cells where we don't want to do custom drawing, a simple DefaultDrawColumnCell call method passing the same parameters we got from the event and the VCL code will draw the current cell as usual.

Among the event parameters, there is Rect (of the TRect type) that represents the specific area we're about to draw, there is Column (of the TColumn type) that is a reference to the current column of the grid, and there is State (of the TGridDrawState type) that is a set of the grid cell states (for example, Selected, Focused, HotTrack, and so on). If our drawing code ignores the State parameter, all the cells will be drawn in the same way and users cannot see which cell or row is selected.

The event handler uses a sets intersection to know whether the current cell should be drawn as a selected or focused cell:

if [gdSelected, gdFocused] * State <> [] then
  Grid.Canvas.Brush.Color := clHighlight;

Tip

Remember that if your dataset has 100 records and 20 fields, the OnDrawColumnCell method will potentially be called 2,000 times! So the event code must be fast, otherwise the application will become less responsive.

There's more...

Owner drawing is a really large topic and can be simple or tremendously complex involving much Canvas related code. However, often the kind of drawing you need will be relatively similar. So, if you need checks, arrows, color gradients, and so on, check the procedures in the Vcl.GraphUtil unit. Otherwise, if you need images, you could use a TImageList class to hold all the images needed by your grid.

The good news is that the drawing code can be reused by different kind of controls, so try to organize your code in a way that allows code reutilization avoiding direct dependencies to the form where the control is.

The code in the drawing events should not contain business logic or presentation logic. If you need presentation logic, put it in a separate and testable function or class.

CONTINUE READING
83
Tech Concepts
36
Programming languages
73
Tech Tools
Icon Unlimited access to the largest independent learning library in tech of over 8,000 expert-authored tech books and videos.
Icon Innovative learning tools, including AI book assistants, code context explainers, and text-to-speech.
Icon 50+ new titles added per month and exclusive early access to books as they are being written.
Delphi Cookbook
notes
bookmark Notes and Bookmarks search Search in title playlist Add to playlist font-size Font size

Change the font size

margin-width Margin width

Change margin width

day-mode Day/Sepia/Night Modes

Change background colour

Close icon Search
Country selected

Close icon Your notes and bookmarks

Confirmation

Modal Close icon
claim successful

Buy this book with your credits?

Modal Close icon
Are you sure you want to buy this book with one of your credits?
Close
YES, BUY

Submit Your Feedback

Modal Close icon
Modal Close icon
Modal Close icon