1. Using the Multi Select option of a DBGRID
  2. Sorting Columns in a DBGrid
  3. A Dbgrid with colored cells ?
  4. DBGrid that shows images
  5. DBGRID saving the user configuration
  6. DBGrid resize
  7. Dragging from DbGrid

Using the Multi Select option of a DBGRID

mike@sentex.net (Mike Tancsa)

There is an example in the Delphi TIs... Have a look at

http://loki.borland.com/winbin/bds.exe?getdoc+2976+Delphi


{*
   This example iterates through the selected rows
   of the grid and displays the second field of
   the dataset.

   The Method DisableControls is used so that the
   DBGrid will not update when the dataset is changed.
   The last position of the dataset is saved as
   a TBookmark.

   The IndexOf method is called to check whether or
   not the bookmark is still existent.
   The decision of using the IndexOf method rather
   than the Refresh method should be determined by the
   specific application.
*}

procedure TForm1.SelectClick(Sender: TObject);
var
  x: word;
  TempBookmark: TBookMark;
begin
  DBGrid1.Datasource.Dataset.DisableControls;
  with DBgrid1.SelectedRows do
  if Count <> 0 then
  begin
    TempBookmark:= DBGrid1.Datasource.Dataset.GetBookmark;
    for x:= 0 to Count - 1 do
    begin
      if IndexOf(Items[x]) > -1 then
      begin
        DBGrid1.Datasource.Dataset.Bookmark:= Items[x];
        showmessage(DBGrid1.Datasource.Dataset.Fields[1].AsString);
      end;
    end;
  end;
  DBGrid1.Datasource.Dataset.GotoBookmark(TempBookmark);
  DBGrid1.Datasource.Dataset.FreeBookmark(TempBookmark);
  DBGrid1.Datasource.Dataset.EnableControls;
end;

Sorting Columns in a DBGrid

Robert Vivrette - 76416.1373@compuserve.com

Many professional applications will display data in grid fields and allow you to sort on any one of the columns simply by clicking on the column header. Although what is proposed here is not the best way to accomplish this, it is a fairly simple way to mimic the same behavior.

The key hurdle in this problem is the DBGrid itself. It has no OnClick or OnMouseDown events, so it really was not designed to capture this kind of input. It does provide an OnDoubleClick, but this really doesn't work too well. What we need is a way to make the column headers clickable. Enter the THeaderControl component.

THeaderControl is a component that comes in Delphi 2.0 and provides the basic functions that we want. It can detect clicks on its individual panels, and the panels even go up and down when pressed (like a button). The key is to connect the THeaderControl to the DBGrid. Here is how it is done:

First, start a new application. Drop a THeaderControl on the form. It will automatically align to the top edge of the form. Now drop a DBGrid on the form and set its Align property to alClient. Next, add a TTable, and TDataSource component. Set the Tables DatabaseName property to DBDEMOS and its TableName to EVENTS.DB. Set the DataSource's DataSet property to point at Table1 and the DBGrid's DataSource property to point to DataSource1. Set Table's Active property to False in case it has been turned on. Now the fun begins!

Now we need to setup the THeaderControl component to look like the DBGrid's column headers. his will be done in code in the Form's FormCreate method. DoubleClick on Form1's OnCreate event and enter the following code:


procedure TForm1.FormCreate(Sender: TObject);
var
  TheCap : String;
  TheWidth,a : Integer;
begin
  DBGrid1.Options := DBGrid1.Options - [dgTitles];
  HeaderControl1.Sections.Add;
  HeaderControl1.Sections.Items[0].Width := 12;
  Table1.Exclusive := True;
  Table1.Active := True;
  For a := 1 to DBGrid1.Columns.Count do
    begin
      with DBGrid1.Columns.Items[a-1] do
        begin
          TheCap := Title.Caption;
          TheWidth := Width;
        end;
      with HeaderControl1.Sections do
        begin
          Add;
          Items[a].Text := TheCap;
          Items[a].Width := TheWidth+1;
          Items[a].MinWidth := TheWidth+1;
          Items[a].MaxWidth := TheWidth+1;
        end;
      try
        Table1.AddIndex(TheCap,TheCap,[]);
      except
        HeaderControl1.Sections.Items[a].AllowClick := False;
      end;
    end;
  Table1.Active := False;
  Table1.Exclusive := False;
  Table1.Active := True;
end;


Since the THeaderControl will be taking the place of the Grid's column headers, we first remove (set to False) the dgTitles option in the DBGrid's Options property. Then, we add a column to the HeaderControl and set its width to 12. This will be a blank column that is the same width as the Grid's status area on the left.

Next we need to make sure the Table is opened for Exclusive use (no other users can be using it). I will explain why in just a bit.

Now we add the HeaderControl sections. For each one we add, we will be giving it the same text as the caption of that column in the DBGrid. We loop through the DBGrid columns, and for each one we copy over the column's caption and width. We also set the HeaderControl's MinWidth and MaxWidth properties to the same as the column width. This will prevent the column from being resized. If you need resizeable columns, you will need a bit more code, and I wanted to keep this short and sweet.

Now comes the interesting part. We are going to create an index for each column in the DBGrid. The name of the index will be the same as the columns title. This step is in a try..finally structure because there are some fields that cannot be indexed (Blobs & Memos for example). When it tries to index on these fields, it will generate an exception. We catch this exception and turn off the ability to click that column. This means that non-indexed columns will not respond to mouse clicks. The creation of these indexes is why we had to open the table in Exclusive mode. After we are all done, we close the table, set Exclusive off and reopen then table.

One last step. When the HeaderControl is clicked, we need to turn on the correct index for the Table. The HeaderControl's OnSectionClick method should be as follows:


procedure TForm1.HeaderControl1SectionClick(
                HeaderControl: THeaderControl; 
                Section: THeaderSection);
begin
  Table1.IndexName := Section.Text;
end;


That's it! When the column is clicked, the Table's IndexName property is set to the same as the HeaderControl's caption.

Pretty simple, huh? There is a lot of room for improvement however. It would be nice if clicking on a column a second time would reverse the sort order. Also, column resizing would be a nice added touch. I am going to leave these to you folks!

Improvements

The Graphical Gnome <uddf@gnomehome.demon.nl>

The improvement over the previous version is in the usage of the fieldname as indexname instead of the caption.

This improves the flexibility. Changes are indicated as italics


    
procedure TfrmDoc.FormCreate(Sender: TObject);
Var
   TheCap    : String;
   TheFn     : String;
   TheWidth  : Integer;
   a         : Integer;
begin
     Dbgrid1.Options := DBGrid1.Options - [DGTitles];
     Headercontrol1.sections.Add;
     Headercontrol1.Sections.Items[0].Width := 12;
     For a := 1 to DBGRID1.Columns.Count do
     begin
        with DBGrid1.Columns.Items[ a - 1 ] do
        begin
           TheFn    := FieldName;
           TheCap   := Title.Caption;
           TheWidth := Width;
        end;
        With Headercontrol1.Sections DO
        BEGIN
          Add;
          Items[a].Text     := TheCap;
          Items[a].Width    := TheWidth + 1;
          Items[a].MinWidth := TheWidth + 1;
          Items[a].MaxWidth := TheWidth + 1;
        END; (* WITH Headercontrol1.Sections *)
        try (* except *)
           { Use indexes with the same name as the fieldname }
           (DataSource1.Dataset as TTable).IndexName := TheFn;   { Try to set the index name }
        except
           HeaderControl1.Sections.Items[a].AllowClick := False; { Index not Available }
        end; (* EXCEPT *)
     END; (* FOR *)
END; (* PROCEDURE *)


Use the fieldname property of the DBGrid to set an index with the same name as the fieldname.


procedure TfrmDoc.HeaderControl1SectionClick(HeaderControl: THeaderControl;
  Section: THeaderSection);
begin
     (DataSource1.Dataset as TTable).IndexName :=
           DBGrid1.Columns.Items[ Section.Index - 1 ].FieldName;

end;


A Dbgrid with colored cells ?

Ed_P._Hillmann@mail.amsinc.com (Ed Hillmann)

I don't know if this helps, but I could color individual cells in a DBGrid without having to make a new DBGrid component. This is what I just tested....

I created a form, dropped a TTable component it, and pointed it to the EMPLOYEE.DB database in the DBDEMOS database. I dropped a Datasource and DBGrid on the form so that it showed on the form.

I thought a simple test would be, for the employee number in the EMPLOYEE.DB table, check if it's an odd number. If it's an odd number, then turn that cell green.

Then, the only code I attached was to the DBGrid's OnDrawColumnCell event, which looks as follows....


procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect:
TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState);
var
  holdColor: TColor;
begin
  holdColor := DBGrid1.Canvas.Brush.Color; {store the original color}
  if Column.FieldName = 'EmpNo' then {only do for the cell displaying
EmpNo}
    if (Column.Field.AsInteger mod 2 <> 0) then begin
      DBGrid1.Canvas.Brush.Color := clGreen;
      DBGrid1.DefaultDrawColumnCell(Rect, DataCol, Column, State);
      DBGrid1.Canvas.Brush.Color := holdColor;
    end;
end;

This uses the DefaultDrawColumnCell method that is defined with the TCustomDBGrid component, of which TDBGrid is a child. This turned each cell green of an employee whose emp no was odd.

DBGrid that shows images

From: sraike@iconz.co.nz (Bill Raike)

I've had second thoughts and decided to post my DBGrid descendant that shows images, since it's such a small amount of code.

Here it is:


{
// DBPICGRD.PAS (C) 1995 W. Raike
//              ALL RIGHTS RESERVED.
//
//    DESCRIPTION:
//      Data-aware grid that can display graphic fields.
//    REVISION HISTORY:
//      15/04/95  Created.    W. Raike
}

unit DBPicGrd;

interface

uses
  DBGrids, DB, DBTables, Grids, WinTypes, Classes, Graphics;

type
  TDBPicGrid = class(TDBGrid)
  protected
    procedure DrawDataCell(const Rect: TRect;
      Field: TField; State: TGridDrawState); override;
  public
    constructor Create(AOwner : TComponent); override;
  published
    property DefaultDrawing default False;
  end;

procedure Register;

implementation

constructor TDBPicGrid.Create(AOwner : TComponent);
begin
  inherited Create(AOwner);
  DefaultDrawing := False;
end;

procedure TDBPicGrid.DrawDataCell(const Rect: TRect; Field: TField; 
State: TGridDrawState);
var
  bmp : TBitmap;
begin
  with Canvas do
  begin
    FillRect(Rect);
    if Field is TGraphicField then
        try
          bmp := TBitmap.Create;
          bmp.Assign(Field);
          Draw(Rect.Left, Rect.Top, bmp);
        finally
          bmp.Free;
        end
    else
      TextOut(Rect.Left, Rect.Top, Field.Text);
  end;
end;

procedure Register;
begin
  RegisterComponents('Custom', [TDBPicGrid]);
end;

end.

DBGRID saving the user configuration

[Cosimo Laddomada, mimmoladd@mail.clio.it]

Is their a way to save the column order of a grid after the user
reorders the columns via drag n drop.
I resolved this problem time ago for my one application. Following code is adapted for you, not tested, but I think it works fine. It create, save and load configuration's file for order AND SIZE too of fields. I'm at your disposal for further into something.


procedure TMainForm.NewIni(const NomeIni: string);
var F: System.Text;
    i: Byte;
begin
  System.Assign(F, NomeIni);
  System.ReWrite(F);
  System.WriteLn(F, '[Campi_Ordine]');
  for i:=1 to Table1.FieldCount do
    System.WriteLn(F, 'Campo',i,'=',Table1.Fields[i-1].FieldName);
  System.WriteLn(F, '');
  System.WriteLn(F, '[Campi_Size]');
  for i:=1 to Table1.FieldCount do
    System.WriteLn(F, 'Campo',i,'=',Table1.Fields[i-1].DisplayWidth);
  System.Close(F);
end;

procedure TMainForm.SaveIni(const FN: String);
var Ini: TIniFile;
    i: Integer;
begin
  NewIni(FN);
  Ini := TIniFile.Create(FN);
  with Ini do
  begin
    for i:=1 to Table1.FieldCount do
    begin
      S:= Table1.Fields[i-1].FieldName;
      WriteString('Campi_Ordine', 'Campo'+IntToStr(i), 
        Table1.Fields[i-1].FieldName);
      WriteInteger('Campi_Size', 'Campo'+IntToStr(i),
        Table1.Fields[i-1].DisplayWidth);
    end;
  end;
  Ini.Free;
end;

procedure TMainForm.LoadIni(const FN: String);
var Ini: TIniFile;
    i: Integer;
    j: Longint;
    S: String;

    function MyReadInteger(const Section, Ident: string): Longint;
    begin
      result := Ini.ReadInteger(Section, Ident, -1);
      if result=-1 then
        raise Exception.Create('Errore nel file di configurazione.');
    end;

    function MyReadString(const Section, Ident: string): String;
    begin
      result := Ini.ReadString(Section, Ident, '');
      if result='' then
        raise Exception.Create('Errore nel file di configurazione.');
    end;

begin
  Ini := TIniFile.Create(FN);
  try
    with Ini do
    begin
      for i:=1 to Table1.FieldCount do
      begin
        S:= MyReadString('Campi_Ordine', 'Campo'+IntToStr(i));
        j:= MyReadInteger('Campi_Size', 'Campo'+IntToStr(i));
        Table1.FieldByName(S).Index := i-1;
        Table1.FieldByName(S).DisplayWidth := j;
      end;
    end;
  finally
    Ini.Free;
  end;
end;

DBGrid resize

I have a form. In that an Edit field, an SQL Query, a DBGrid and a Button.
I can write into the edit, and the Query result will put into the grid.
How can I resize the grid and the form to the fields size which appears in
the grid. The fields Which I select with the query does not fill the full
size of the grid or does not fit into it.
You can change the size of a column at run-time by changing the DisplayWidth property of the underlying field object...


MyTableMyField.DisplayWidth := Length(MyTableMyField.value);

If you need to actually calculate the width of the entire grid, use the following (from a tips library)...


function NewTextWidth(fntFont : TFont; const sString : OpenString) :

  integer;
var
  fntSave : TFont;
begin
  result := 0;
  fntSave := Application.MainForm.Font;
  Application.MainForm.Font := fntFont;
  try
    result := Application.MainForm.Canvas.TextWidth(sString);
  finally
    Application.MainForm.Font := fntSave;
  end;
end;


{ calculate the width of the grid needed to exactly display with no   }
{ horizontal scrollbar and with no extra space between the last       }
{ column and the vertical scrollbar.  The grid's datasource must be   }

{ properly set and the datasource's dataset must be properly set,     }
{ though it need not be open.  Note:  this width includes the width   }
{ of the vertical scrollbar, which changes based on screen            }
{ resolution.  These changes are compensated for.                     }

function iCalcGridWidth
  (
  dbg : TDBGrid { the grid to meaure }
  )
  : integer; { the "exact" width }

const
  cMEASURE_CHAR   = '0';
  iEXTRA_COL_PIX  = 4;
  iINDICATOR_WIDE = 11;

var
  i, iColumns, iColWidth, iTitleWidth, iCharWidth : integer;
begin
  iColumns := 0;
  result := GetSystemMetrics(SM_CXVSCROLL);
  iCharWidth := NewTextWidth(dbg.Font, cMEASURE_CHAR);
  with dbg.dataSource.dataSet do
    for i := 0 to FieldCount - 1 do with Fields[i] do
      if visible then
      begin
        iColWidth := iCharWidth * DisplayWidth;
        if dgTitles in dbg.Options then
        begin
          iTitleWidth := NewTextWidth(dbg.TitleFont, DisplayLabel);

          if iColWidth < iTitleWidth then iColWidth := iTitleWidth;
        end;
        inc(iColumns, 1);
        inc(result, iColWidth + iEXTRA_COL_PIX);
      end;
  if dgIndicator in dbg.Options then
  begin
    inc(iColumns, 1);
    inc(result, iINDICATOR_WIDE);
  end;
  if dgColLines in dbg.Options
    then inc(result, iColumns)
    else inc(result, 1);
end;

I had to use the function NewTextWidth, rather than the Grid's Canvas.TextWith as the Canvas of the Grid may not initialized when you need to call iCalcGridWidth.

Dragging from DbGrid

Has someone achieved dragging things from a DbGrid ?
You can create your own descendant of TDBGrid (or TDBCustomGrid) and
customize it to your needs.
[Demian, demian@unix.horizontes.com.br]

Do an Origami with the code at the end of this message, save it as DBGrid.pas, and install it. You'll have a new component EDBGrid with two new events: OnMouseDown and OnMouseUp. I don't consider it a 'proprietary information': it's a Delphi Bug! Originally, this two events should have been part of the DBGrid component.


unit Dbgrid;

interface

uses
  DBGrids, Controls, Classes;

type
  TEDBGrid = class(TDBGrid)
  private
    FOnMouseDown: TMouseEvent;
    FOnMouseUp: TMouseEvent;
  protected
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y:
Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y:
Integer); override;
  published
    Property OnMouseDown : TMouseEvent read FOnMouseDown write
FOnMouseDown ;
    Property OnMouseUp : TMouseEvent read FOnMouseUp write FOnMouseUp ;
end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Data Controls',[TEDBGrid]);
end;

procedure TEDBGrid.MouseDown(Button: TMouseButton; Shift: TShiftState;
X, Y: Integer);
begin
  if Assigned(FOnMouseDown) then
    FOnMouseDown(Self,Button,Shift,X,Y);
  inherited MouseDown(Button,Shift,X,Y);
end;

procedure TEDBGrid.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
Y: Integer);
begin
  if Assigned(FOnMouseUp) then
    FOnMouseUp(Self,Button,Shift,X,Y);
  inherited MouseUp(Button,Shift,X,Y);
end;

end.


Please email me and tell me if you liked this page.