Solved

Better TStringGrid.

Posted on 2006-07-01
32
3,084 Views
Last Modified: 2008-06-22
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.
0
Comment
Question by:Chesso
  • 14
  • 13
  • 3
  • +2
32 Comments
 
LVL 26

Expert Comment

by:Russell Libby
ID: 17024749

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


0
 
LVL 12

Expert Comment

by:Ivanov_G
ID: 17026511
There are a lot of StringGrids that can cover your needs:
http://www.torry.net/pages.php?id=114
0
 

Author Comment

by:Chesso
ID: 17026539
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.
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 17030845
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.


0
 

Author Comment

by:Chesso
ID: 17031508
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.
0
 

Author Comment

by:Chesso
ID: 17031517
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.
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 17031884
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.
0
 
LVL 11

Expert Comment

by:calinutz
ID: 17056460
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
0
 

Author Comment

by:Chesso
ID: 17064291
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.
0
 

Author Comment

by:Chesso
ID: 17064414
Iv'e increased the points by 100. Gotta get this thing going heh heh.
0
 

Author Comment

by:Chesso
ID: 17081880
Umm so no one then?
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 17096000
been on vacation, will try to get back to it on Fri.
0
 

Author Comment

by:Chesso
ID: 17099401
Ahh ok, kool thanks for the help :)
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 17109927
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.
0
 
LVL 26

Expert Comment

by:Russell Libby
ID: 17110059
Lol, I wish I had thought of something like that.....

Russell
0
 
LVL 26

Expert Comment

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

E.
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 26

Expert Comment

by:Russell Libby
ID: 17110548

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
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 17111438
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.
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 17111451
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?

0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 17111967
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');
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 17111993
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.
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 17112140
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;

0
 

Author Comment

by:Chesso
ID: 17112764
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.
0
 

Author Comment

by:Chesso
ID: 17113027
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.
0
 

Author Comment

by:Chesso
ID: 17163010
Triple post... ouch, well anyway I upped the points another 120 :)
0
 
LVL 26

Accepted Solution

by:
EddieShipman earned 400 total points
ID: 17163660
Why? You have a solution, already. All you have to do is take the demo code and do your app the same way.
I also showed you how to make flat checkboxes.
0
 

Author Comment

by:Chesso
ID: 17163711
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.
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 17165123
save/load of what?
0
 

Author Comment

by:Chesso
ID: 17165353
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 :)

0
 

Author Comment

by:Chesso
ID: 17165359
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.
0
 
LVL 26

Expert Comment

by:EddieShipman
ID: 17169473
[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.
0
 

Author Comment

by:Chesso
ID: 17169761
Ahhh yep I see, got it. Thanks for all the help.
0

Featured Post

Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

Join & Write a Comment

A lot of questions regard threads in Delphi.   One of the more specific questions is how to show progress of the thread.   Updating a progressbar from inside a thread is a mistake. A solution to this would be to send a synchronized message to the…
The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
Internet Business Fax to Email Made Easy - With eFax Corporate (http://www.enterprise.efax.com), you'll receive a dedicated online fax number, which is used the same way as a typical analog fax number. You'll receive secure faxes in your email, fr…
You have products, that come in variants and want to set different prices for them? Watch this micro tutorial that describes how to configure prices for Magento super attributes. Assigning simple products to configurable: We assigned simple products…

746 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now