1.10.07

Kompleksniji DBGrid

Pošto će nam za FinSyM trebati (u više navrata) Dbgrid koji u sebi ima CheckBox, evo primera kako sve to funkcioniše. Postoje 2 aritkla: prvi je opštiji za upoznavanje, drugi je konkretan primer.

Ovo je recimo jako zgodno za izbor nekih dokumenata za knjiženje, arhiviranje storniranje i slično. Ovo nam olakšava situaciju kada korisnik iz nekog grida sa dosta stavki, može da izabere one koje želi i da posle uradi nešto da njima.

Prvi artikal:
The power of the DBGrid
I've said this many times, but I'll say it again: Delphi's DBGrid ... what a powerful component! Contrary to most other Delphi data-aware controls, the DBGrid component has many nice features and is more powerful than you would have thought.

The "standard" DBGrid does its job of displaying and manipulating records from a dataset in a tabular grid. However, there are many ways (and reasons) why you should consider customizing the output of a DBGrid.
Here's an example:
Suppose you have a boolean field in your dataset. By default, the DBGrid displays boolean fields as "True" or "False" depending on the value of the data field. If you think the same way I do, it is much more visually attractive to be able to you use a "true" check box control to enable editing of such fields.

This article serves as an entry point to a series of articles describing how to place just about any component into a cell of a DBGrid.

Placing controls in DBGrid
As some of you might not know, in-place editing of a DBGrid cell's contents is accomplished via a "small" edit control that is displayed over the cell. Inside DBGrid, there is a TInplaceEdit that moves around the grid - the Edit component that you enter your data into. The rest of the unfocused cells are really just "pictures".

But a CheckBox, a ComboBox, ...?
So what? If there is a floating Edit over the DBGrid, we'll float any control we like. There are no new ideas here, in fact, the basic technique simply mimics what the DBGrid does internally. What you will learn here, is how to float any type of visual control around the DBGrid.

A sample database
Following the concepts set out in the Beginners Guide to Delphi Database Programming, examples below use ADO components (AdoQuery/AdoTable connected to ADOConnection, DBGrid connected to AdoQuery over DataSource) to display the records from a database table in a DBGrid component. All the component names were left as Delphi named them when dropped on the form (DBGrid1, ADOQuery1, AdoTable1, ...)
If you do not know how to display records from a database table (or query) in a DBGrid component, please explore the "Connecting to a database" chapter.

Here's a sample MS Access database we'll use to explore the topic of adding components to a DBGrid. The QuickiesContest.MDB databse has three tables: Subjects, Authors and Articles. The database was originally created to hold the entries to our Delphi Programming Quickies Contest.
The picture below displays the relationships between tables. For the moment note that the Winner field in the Articles table is a YesNo field (boolean).

Delphi Quickies Contest database
Download database
Components in a DBGrid - the Theory
When adding controls to a DBGrid, there are a couple of steps and questions to be revealed. Here's what has to be done to place a DBCheckBox inside a DBGrid cell, and enable editing of a boolean field using the CheckBox component:
  1. A DBCheckBox needs to be placed (invisible) on a Form and linked to a boolean field in the DataSource component that supplies the DBGrid with a dataset.
  2. If the cell holding a boolean field is focused the CheckBox component should be made visible and placed in the appropriate cell. When no longer needed the CheckBox needs to be made invisible again.
  3. If the cell holding a boolean field is NOT focused a sample graphics should be drawn on the cell indicating whether a field's value is True or False.
  4. When in editing mode, all keystrokes are going to the DBGrid's cell; we have to make sure they are sent to the CheckBox. Note that we are primarily interested in the [Tab] and the [Space] key. "Tab" should move the input focus to the next cell, and [Space] should toggle the state of the CheckBox.
Note: the above "steps" are more or less the same no matter what component is placed in (i.e. floating over) the cell.
Components in a DBGrid - Theory into Practice
Finally, here's how all the above theory looks in practice (with more examples coming in the near future):

Placing a CheckBox in a DBGrid
Here's how to place a check box into a DBGrid. Create visually more attractive user interfaces for editing boolean fields inside a DBGrid.
DBCheckBox in DBGrid

Drop down pick list inside a DBGrid - part 1
Here's how to place a drop down pick list into a DBGrid. Create visually more attractive user interfaces for editing lookup fields inside a DBGrid - using the PickList property of a DBGrid column.

Lookup field with PickList

Drop down list (DBLookupComboBox) inside a DBGrid - part 2
Here's how to place a DBLookupComboBox into a DBGrid. Create visually more attractive user interfaces for editing lookup fields inside a DBGrid - place a DBLookupComboBox into a cell of a DBGrid.

DBLookupComboBox in a DBGrid

DateTimePicker inside a DBGrid
Here's how to place a TDateTimePicker into a DBGrid. Create visually more attractive user interfaces for editing date/time fields inside a DBGrid - place a drop down calendar into a cell of a DBGrid.

DateTimePicker in a DBGrid

Drugi artikal:

This is the first article, in the series of articles named "Adding components to a DBGrid". The idea is to show how to place just about any Delphi control (visual component) into a cell of a DGBrid. If you are unfamiliar with the idea, please first read the "Adding components to a DBGrid" article.
CheckBox in a DBGrid?
As discussed in the above article, there are many ways (and reasons) why you should consider customizing the output of a DBGrid: suppose you have a boolean field in your dataset. By default, the DBGrid displays boolean fields as "True" or "False" depending on the value of the data field. If you think the same way I do, it is much more visually attractive to be able to you use a "true" check box control to enable editing of such fields.
Creating a sample application
To begin, start Delphi and, on that default empty new form, place a TDBGrid, a TADOTable, and a TADOConnection, TDataSource. Leave all the component names as Delphi named them when dropped on the form (DBGrid1, ADOQuery1, AdoTable1, ...). Use the Object Inspector to set a ConnectionString property of the ADOConnection1 (TADOConnection) component to point to the sample QuickiesContest.mdb MS Access database. Connect DBGrid1 to DataSource1, DataSource1 to ADOTable1, and finally ADOTable1 to ADOConnection1. ADOTable1's TableName property should point to the Articles table (thus making the DBGrid display the records of the Articles table).

If you have set all the properties correctly, when you run the application (given that the Active property of the ADOTable1 component is True) you should see the following output:

Boolean fields in a DBGrid

What you need to "see" in the above picture is that, by default, the DBGrid displays the boolean field's value as "True" or "False" depending on the value of the data field. The field that holds the boolean value is "Winner".

What we are up against in this article is to make the above picture look like the one below:

DBCheckBox in DBGrid
CheckBox in a DBGrid!
Or, better to say, a DBCheckBox in a DBGrid.

Ok, here we go. To show a check box inside a cell of a DBGrid we'll need to make one available for us at run time. Select the "Data controls" page on the Component Palette and pick a TDBCheckbox. Drop one anywhere on the form - it doesn't matter where, since most of the time it will be invisible or floating over the grid. TDBCheckBox is a data-aware control that allows the user to select or deselect a single value - most appropriate for boolean fields.

Next, set its Visible property to False. Change the Color property of DBCheckBox1 to the same color as the DBGrid (so it blends in with the DBGrid) and remove the Caption. And most importantly, make sure the DBCheckBox1 is connected to the DataSource1 and to the correct field (DataSource = DataSource1, DataField = Winner).

Note that all the above DBCheckBox1's property values can be set in the form's OnCreate event like:

procedure TForm1.FormCreate(Sender: TObject);
begin
DBCheckBox1.DataSource := DataSource1;
DBCheckBox1.DataField := 'Winner';
DBCheckBox1.Visible := False;
DBCheckBox1.Color := DBGrid1.Color;
DBCheckBox1.Caption := '';

//explained later in the article
DBCheckBox1.ValueChecked := 'Yes a Winner!';
DBCheckBox1.ValueUnChecked := 'Not this time.';
end;

What comes next is the most interesting part. While editing the boolean field in the DBGrid, we need to make sure the DBCheckBox1 is placed above ("floating") the cell in the DBGrid displaying the boolean field. For the rest of the (non-focused) cells carrying the boolean fields (in the "Winner" column), we need to provide some graphical representation of the boolean value (True/False). This means you need at least two images for drawing: one for the checked (True value) state, and one for the unchecked (False value) state. The easiest way to accomplish this is to use the Windows API DrawFrameControl function to draw directly on the DBGrid's canvas.

Here's the code in the DBGrid's OnDrawColumnCell event handler that occurs when the grid needs to paint a cell.

procedure TForm1.DBGrid1DrawColumnCell(
Sender: TObject; const Rect: TRect; DataCol:
Integer; Column: TColumn; State: TGridDrawState);

const IsChecked : array[Boolean] of Integer =
(DFCS_BUTTONCHECK, DFCS_BUTTONCHECK or DFCS_CHECKED);
var
DrawState: Integer;
DrawRect: TRect;
begin
if (gdFocused in State) then
begin
if (Column.Field.FieldName = DBCheckBox1.DataField)
then
begin
DBCheckBox1.Left := Rect.Left + DBGrid1.Left + 2;
DBCheckBox1.Top := Rect.Top + DBGrid1.top + 2;
DBCheckBox1.Width := Rect.Right - Rect.Left;
DBCheckBox1.Height := Rect.Bottom - Rect.Top;

DBCheckBox1.Visible := True;
end
end
else
begin
if (Column.Field.FieldName = DBCheckBox1.DataField)
then
begin
DrawRect:=Rect;
InflateRect(DrawRect,-1,-1);

DrawState := ISChecked[Column.Field.AsBoolean];

DBGrid1.Canvas.FillRect(Rect);
DrawFrameControl(DBGrid1.Canvas.Handle, DrawRect,
DFC_BUTTON, DrawState);
end;
end;
end;

To finish this step, we need to make sure DBCheckBox1 is invisible when we leave the cell:

procedure TForm1.DBGrid1ColExit(Sender: TObject);
begin
if DBGrid1.SelectedField.FieldName =
DBCheckBox1.DataField then
DBCheckBox1.Visible := False
end;

We need just two more events to handle.
Note that when in editing mode, all keystrokes are going to the DBGrid's cell, we have to make sure they are sent to the CheckBox. In the case of a CheckBox we are primarily interested in the [Tab] and the [Space] key. [Tab] should move the input focus to the next cell, and [Space] should toggle the state of the CheckBox.

procedure TForm1.DBGrid1KeyPress
Sender: TObject; var Key: Char);
begin
if (key = Chr(9)) then Exit;

if (DBGrid1.SelectedField.FieldName
= DBCheckBox1.DataField)
then
begin
DBCheckBox1.SetFocus;
SendMessage
(DBCheckBox1.Handle, WM_Char, word(Key), 0);
end;
end;

And finally, the last touch. It could be appropriate for the Caption of the checkbox to change as the user checks or unchecks the box. Note that the DBCheckBox has two properties (ValueChecked and ValueUnChecked) used to specify the field value represented by the check box when it is checked / unchecked. My ValueChecked property holds 'Yes a Winner!' and ValueUnChecked equals 'Not this time.'

procedure TForm1.DBCheckBox1Click(Sender: TObject);
begin
if DBCheckBox1.Checked then
DBCheckBox1.Caption := DBCheckBox1.ValueChecked
else
DBCheckBox1.Caption := DBCheckBox1.ValueUnChecked;
end;

That's it. Run the project and voila ... check boxes all over the Winner field's column.

www.baco.co.yu