Link to home
Start Free TrialLog in
Avatar of Chesso
Chesso

asked on

Better TStringGrid.

Ok I don't mind the standard TStringGrid as is for the most part but what I really could use is one that can have a column type with CheckBox's in it (I have found some but they treat every cell that contains one with the same value from the others as if they all have the same checkbox and just move it when focus is moved).

I need a seperate checkbox with seperate values for every row.....

Better cosmetic options would be nice too :) but not essential at all. Preferably freeware if anyone knows of any.

So far I have tried XStringGrid, jgStrGrd, TMS Components Grids(Though not freeware), NextGrid and probably more.

I use Delphi 7.
Avatar of Russell Libby
Russell Libby
Flag of United States of America image


If you don't mind the string grid, there is a cheap (free) alternative to do what you want. By using owner drawing for the columns you wish displayed as a checkbox, you can easily handle the displaying and editing (toggle on / off) for each row.

The example below displays the second (editable) column as a checkbox, and allows for doubleclick, space key, enter key to toggle the check state. Internally, the field is set to either 'T' or blank. I kept it simple just to show the basics, but don't hestitate to ask if you have questions.

Regards,
Russell

----

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Grids;

type
  TForm1            =  class(TForm)
     StringGrid1:   TStringGrid;
     procedure      StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
     procedure      StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean);
     procedure      StringGrid1DblClick(Sender: TObject);
     procedure      StringGrid1KeyPress(Sender: TObject; var Key: Char);
  private
     // Private declarations
  public
     // Public declarations
  end;

var
  Form1:            TForm1;

implementation
{$R *.DFM}

procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
var  Checked:       Boolean;
begin

  if (ACol = 2) and (ARow > 0) then
  begin
     InflateRect(Rect, -2, -2);
     if (StringGrid1.Cells[ACol, ARow] = 'T') then
        Checked:=True
     else
        Checked:=False;
     with StringGrid1.Canvas do
     begin
        FillRect(Rect);
        if Checked then
           DrawFrameControl(Handle, Rect, DFC_BUTTON, DFCS_BUTTONCHECK or DFCS_ADJUSTRECT or DFCS_CHECKED or DFCS_FLAT)
        else
           DrawFrameControl(Handle, Rect, DFC_BUTTON, DFCS_BUTTONCHECK or DFCS_ADJUSTRECT or DFCS_FLAT);
     end;
  end;

end;

procedure TForm1.StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer; var CanSelect: Boolean);
begin

  if (ACol = 2) then
     StringGrid1.Options:=StringGrid1.Options - [goEditing]
  else
     StringGrid1.Options:=StringGrid1.Options + [goEditing];

end;

procedure TForm1.StringGrid1DblClick(Sender: TObject);
begin

  if (StringGrid1.Col = 2) then
  begin
     if (StringGrid1.Cells[StringGrid1.Col, StringGrid1.Row] = 'T') then
        StringGrid1.Cells[StringGrid1.Col, StringGrid1.Row]:=''
     else
        StringGrid1.Cells[StringGrid1.Col, StringGrid1.Row]:='T';
  end;

end;

procedure TForm1.StringGrid1KeyPress(Sender: TObject; var Key: Char);
begin

  if (Key in [#13, #32]) and (StringGrid1.Col = 2) then
  begin
     Key:=#0;
     if (StringGrid1.Cells[StringGrid1.Col, StringGrid1.Row] = 'T') then
        StringGrid1.Cells[StringGrid1.Col, StringGrid1.Row]:=''
     else
        StringGrid1.Cells[StringGrid1.Col, StringGrid1.Row]:='T';
  end;

end;

end.

---- dfm ----

object Form1: TForm1
  Left = 296
  Top = 114
  Width = 656
  Height = 297
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object StringGrid1: TStringGrid
    Left = 12
    Top = 8
    Width = 557
    Height = 201
    ColCount = 10
    DefaultRowHeight = 18
    Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRangeSelect, goRowSizing, goColSizing, goEditing]
    TabOrder = 0
    OnDblClick = StringGrid1DblClick
    OnDrawCell = StringGrid1DrawCell
    OnKeyPress = StringGrid1KeyPress
    OnSelectCell = StringGrid1SelectCell
  end
end


There are a lot of StringGrids that can cover your needs:
http://www.torry.net/pages.php?id=114
Avatar of Chesso
Chesso

ASKER

I have used all the ones on torry that work for D7, they either don't do it or I can't figure out how lol.

I haven't tried the first suggestion yet as I haven't had enough time lately to really sit down and try anything like that.
Chesso, did you not like this post that I made on DelphiPages:
http://www.delphipages.com/threads/thread.cfm?ID=166998&G=166991

You state: "I was using another StringGrid component, but couldn't get it
working too well or rather not the way I wanted it to."

If you let us in on what stringgrid component you ARE using, maybe we can help
better.


Avatar of Chesso

ASKER

Oh yes that one, turns out it wasn't any good.

Like you would add the new StringGrid onto your form and then you could add a EditEditor and CheckBoxEditor components.

With the EditEditor values of cells are all reatedt uniquely but with the CheckBox editor set to a column the same value applies to all cells which is good for me i'm afraid.

If you still want the name I can take a look for you later.
Avatar of Chesso

ASKER

Sorry i've had a bit to drunk tonight haha, I meant "treated instead of rearedt" and "isn't instead of is".

Oh my a sign I have had too much I think.
Yes, it would be better for you to post the one you are using. It is apparent that it differs from the standard
TStringGrid enough that normal modifications don't work correctly.
A better stringgrid you could obtain by playing with the default one. Learn some of the steps here:
http://delphi.about.com/od/vclusing/l/aa072203a.htm
The page also includes screenshots of what the stringGrid will look like.

Regards
Avatar of Chesso

ASKER

Sorry for such a late reply.

I was using XStringGrid which appears to have everything I need and more, most of the additional editors like ComboBox work fine and treat each cell with seperate values but with the CheckBox, changing one cell changes the state of all cells.

If only that were fixed it would be perfect.
Avatar of Chesso

ASKER

Iv'e increased the points by 100. Gotta get this thing going heh heh.
Avatar of Chesso

ASKER

Umm so no one then?
been on vacation, will try to get back to it on Fri.
Avatar of Chesso

ASKER

Ahh ok, kool thanks for the help :)
I took a look at the XStringgrid and the checkbox editor is not what you want for that stringgrid.

I have modified the code previously posted on DP to work like you want. It will store the value
in the stringgrid but "draw" the checkbox either checked or unchecked. The values stored
are 1 for checked and 0 for unchecked.

You only have to modify the code to use the column you want to have checkboxes.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, OleCtrls, SHDocVw, StdCtrls, Grids, XStringGrid, CECheckbox;

type
  TForm1 = class(TForm)
    StringGrid1: TXStringGrid;
    procedure StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
      Rect: TRect; State: TGridDrawState);
    procedure StringGrid1KeyPress(Sender: TObject; var Key: Char);
    procedure StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer;
      var CanSelect: Boolean);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure StringGrid1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    FCheck, FNoCheck: TBitmap;
    procedure ToggleCheckbox(acol, arow: Integer);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}



type
  TGridCracker = Class( TStringgrid );
 
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);
var
  Grid: TStringGrid;
begin
  if not (gdFixed in State) and (ACol = 1) then
  begin
    Grid := Sender as TStringGrid;
    with Grid.Canvas do
    begin
      Brush.color := $F7F7F7;
      // checkboxes look better on a non-white background
      FillRect(Rect);
      // listbox state is encoded by the Objects property
      if Assigned(Grid.Objects[ACol, ARow]) then
      begin
        Draw((Rect.Right + Rect.Left - FCheck.Width) div 2,
             (Rect.Bottom + Rect.Top - FCheck.Height) div 2,
              FCheck);
      end
      else
      begin
        Draw((Rect.Right + Rect.Left - FNoCheck.Width) div 2,
             (Rect.Bottom + Rect.Top - FNoCheck.Height) div 2,
              FNoCheck)
      end;
    end;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  bmp: TBitmap;
begin
  FCheck := TBitmap.Create;
  FNoCheck := TBitmap.Create;
  bmp := TBitmap.create;
  try
    bmp.Handle := LoadBitmap(0, PChar(OBM_CHECKBOXES));
    // bmp now has a 4x3 bitmap of divers state images
    // used by checkboxes and radiobuttons
    with FNoCheck do
    begin
      // the first subimage is the unchecked box
      width := bmp.Width div 4;
      height := bmp.Height div 3;
      Canvas.CopyRect(Canvas.ClipRect, bmp.Canvas, Canvas.ClipRect);
    end;
    with FCheck do
    begin
      // the second subimage is the checked box
      width := bmp.Width div 4;
      height := bmp.Height div 3;
      Canvas.CopyRect(
        Canvas.ClipRect,
        bmp.Canvas,
        Rect(width, 0, 2*width, height));
    end;
  finally
    bmp.Free
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FNoCheck.Free;
  FCheck.Free;
end;

procedure TForm1.StringGrid1Click(Sender: TObject);
var
  pt: TPoint;
  Grid: TStringGrid;
  ACol, ARow: Integer;
begin
  GetCursorPos( pt );
  Grid := Sender as TStringgrid;
  pt:= Grid.ScreenToClient(pt);
  Grid.MouseToCell(pt.x, pt.y, ACol, ARow);
  If (ACol = 1) and (ARow >= Grid.FixedRows) then
  begin
    // click landed in a checkbox cell
    ToggleCheckbox(ACol, ARow);
  end;
  ShowMessage(StringGrid1.Cells[ACol, ARow]);
end;

procedure TForm1.ToggleCheckbox(ACol, ARow: Integer);
begin
  if ACol = 1 then
  begin
    with TGridCracker(StringGrid1) do
    begin
      if Assigned(Objects[ACol, ARow]) then
      begin
        Objects[ACol, ARow] := nil;
      end
      else
      begin
        Objects[ACol, ARow] := Pointer(1);
      end;
      InvalidateCell(ACol, ARow);
    end;
    StringGrid1.Cells[ACol, ARow] := IntToStr(Ord(Assigned(StringGrid1.Objects[ACol, ARow])));
  end;
end;

procedure TForm1.StringGrid1SelectCell(Sender: TObject; ACol,
  ARow: Integer; var CanSelect: Boolean);
begin
  with Sender as TStringgrid do
  begin
    if aCol = 1 then
    begin
      Options := Options - [ goediting ];
    end
    else
    begin
      Options := Options + [ goediting ];
    end;
  end;
end;

procedure TForm1.StringGrid1KeyPress(Sender: TObject; var Key: Char);
begin
  // Toggle if spacebar is hit.
  if Key = #32 then
  begin
    with Sender as TstringGrid do
    begin
      if Col = 1 then
      begin
        ToggleCheckbox(Col, Row);
        Key := #0;
        ShowMessage(StringGrid1.Cells[Col, Row]);
      end;
    end;
  end;
end;

end.
Lol, I wish I had thought of something like that.....

Russell
LOL, Russell, it is modification of the post I made on DP. I don't care if he selects your response.

E.

E, Im just laughing because now that I saw your post on DP (which I hadn't when I offered the code), the code we both offered is based on the same idea. And in both cases, the asker of this question hasn't addressed either of the code samples. Go figure..

Russell
The grid supports a checkbox editor but it does not store a different value for each cell in the column.
Or so that is what I gathered. My change, outlined above, would allow him to have a different value
per cell, which is what he needed.

I have not looked at the demo for XStringGrid, but the author told me it included the CheckBox editor and
that it worked. I may take a look later today.
Yours is shorter and mine is based upon some old Peter Below code from D2, I believe.
Kind of funny that a lot of those hacks still work all the way to D2006, huh?
How many VB "hacks" from VB2 or VB3 do you think would still work on VB6?

Chesso,
  I took a closer look at the demo supplied with XStringGrid and I think I have what you want.

Make these changes to the Demo and you'll see what I mean.

procedure TDemoForm.CheckBoxCellEditor1SetState(
  Sender: TCheckBoxCellEditor);
begin
  if xg.Cells[xg.Col, xg.Row] = 'b' then
    Sender.State := cbChecked
  else
    Sender.State := cbUnchecked;
end;

procedure TDemoForm.CheckBoxCellEditor1SetText(Sender: TCheckBoxCellEditor;
  var Value: String);
begin
  if Sender.State = cbChecked then
    Value := 'b'
  else
    Value := '';
end;

procedure TDemoForm.xgCellProps(Sender: TObject; Canvas: TCanvas;
  var Alignment: TAlignment; var CellText: String; AState: TGridDrawState;
  Row, Col: Integer);
begin
  if (col <> 3) and (col <> 4) and (row mod 2 = 1) and not(gdSelected in AState) then
    Canvas.Brush.Color := clGray;
  if (col = 9) and (row > 0) then
  begin
    Canvas.Font.Name := 'Marlett';
    Canvas.Font.Size := 11;
  end;
end;


Now, to get the state of the checkbox in the cell, use the following code. It gets the
state of the currently selected checkbox cell:

  case CheckBoxCellEditor1.State of
  cbChecked:     ShowMessage('Checked');
  cbUnchecked:   ShowMessage('Unchecked');
  end;

Or if you want to check the state of any cell, use this:

  if Length(Trim(xg.Cells[9,2])) > 0 then
    ShowMessage('Checked')
  else
    ShowMessage('Unchecked');
I will say, however, that I do not like the way he draws his checkboxes as I think they should
have the "flat" look like the OBM_CHECKBOXES. Using DrawFrameControl kaes the 3D checkbox and
it just doesn't look quite as clean, IMHO. The Caption property in the CheckBoxCellEditor is text that
shows next to the checkbox like a regular checkbox. I do not think it would be needed and set it to
blank in the demo.
To add flat checkboxes, add a CreateParams procedure to the TCheckBoxInplace class and
add this code:

procedure TCheckBoxInplace.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  with Params do
    Style := Style or BS_FLAT;
end;

Avatar of Chesso

ASKER

Wow hey, quite a bit of activity since I last checked lol.

I only checked last night and bam so much going on here now haha. I'll give your suggestions a try and see what happens when I can, Iv'e been a bit reluctant to crack open Delphi of late as I caught the flu from my sister for the second time in a row and am constantly rushing for tissues lol.
Avatar of Chesso

ASKER

Uhh I see, I have tried your example code to replace in the Demo with the CheckBoxCellEditor component.

I completely forgot about the Demo and had only tried this component as is from scratch on a new application.

I can see that all cells are treated differently in the demo but this behaviour did not go down in a new application, do you know why???

I haven't had a look too much through the demo source yet but I figured it would handle that sort of thing on it's own, the other editors seem to.
Avatar of Chesso

ASKER

Triple post... ouch, well anyway I upped the points another 120 :)
ASKER CERTIFIED SOLUTION
Avatar of Eddie Shipman
Eddie Shipman
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Chesso

ASKER

Well I have no idea about the flat check boxes but I got it to work eventually. But this leaves me with another problem.... I thought this had a save/load feature but it doesn't seem to, could be a problem for Check/Combo Box columns.
save/load of what?
Avatar of Chesso

ASKER

Ahh don't worry Iv'e figured it out, it's pretty simple (silly me) seen as it all has text values anyway to define check/combo box values.

Thanks alot for all the help :)

Avatar of Chesso

ASKER

Oh btw if you have the time, how exactly do I make the checkbox's flat? I'm not sure what to do with the example you provided me with.
[quote]add a CreateParams procedure to the TCheckBoxInplace class[/quote]

How much of that don't you understand? Find the TCheckBoxInplace class in
CECheckbox.pas and just add a CreateParams procedure with the code above.
Avatar of Chesso

ASKER

Ahhh yep I see, got it. Thanks for all the help.