Why don't child controls of a TStringGrid work properly?
Asked Answered
L

3

4

I am placing checkboxes (TCheckBox) in a string grid (TStringGrid) in the first column. The checkboxes show fine, positioned correctly, and respond to mouse by glowing when hovering over them. When I click them, however, they do not toggle. They react to the click, and highlight, but finally, the actual Checked property does not change. What makes it more puzzling is I don't have any code changing these values once they're there, nor do I even have an OnClick event assigned to these checkboxes. Also, I'm defaulting these checkboxes to be unchecked, but when displayed, they are checked.

The checkboxes are created along with each record which is added to the list, and is referenced inside a record pointer which is assigned to the object in the cell where the checkbox is to be placed.

String grid hack for cell highlighting:

type
  THackStringGrid = class(TStringGrid); //used later...

Record containing checkbox:

  PImageLink = ^TImageLink;
  TImageLink = record
    ...other stuff...
    Checkbox: TCheckbox;
    ShowCheckbox: Bool;
  end;

Creation/Destruction of checkbox:

function NewImageLink(const AFilename: String): PImageLink;
begin
  Result:= New(PImageLink);
  ...other stuff...
  Result.Checkbox:= TCheckbox.Create(nil);
  Result.Checkbox.Caption:= '';
end;

procedure DestroyImageLink(AImageLink: PImageLink);
begin
  AImageLink.Checkbox.Free;
  Dispose(AImageLink);
end;

Adding rows to grid:

//...after clearing grid...
//L = TStringList of original filenames
if L.Count > 0 then
  lstFiles.RowCount:= L.Count + 1
else
  lstFiles.RowCount:= 2; //in case there are no records
for X := 0 to L.Count - 1 do begin
  S:= L[X];
  Link:= NewImageLink(S); //also creates checkbox
  Link.Checkbox.Parent:= lstFiles;
  Link.Checkbox.Visible:= Link.ShowCheckbox;
  Link.Checkbox.Checked:= False;
  Link.Checkbox.BringToFront;
  lstFiles.Objects[0,X+1]:= Pointer(Link);
  lstFiles.Cells[1, X+1]:= S;
end;

Grid's OnDrawCell Event Handler:

procedure TfrmMain.lstFilesDrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);
var
  Link: PImageLink;
  CR: TRect;
begin
  if (ARow > 0) and (ACol = 0) then begin
    Link:= PImageLink(lstFiles.Objects[0,ARow]); //Get record pointer
    CR:= lstFiles.CellRect(0, ARow); //Get cell rect
    Link.Checkbox.Width:= Link.Checkbox.Height;
    Link.Checkbox.Left:= CR.Left + (CR.Width div 2) - (Link.Checkbox.Width div 2);
    Link.Checkbox.Top:= CR.Top;
    if not Link.Checkbox.Visible then begin
      lstFiles.Canvas.Brush.Color:= lstFiles.Color;
      lstFiles.Canvas.Brush.Style:= bsSolid;
      lstFiles.Canvas.Pen.Style:= psClear;
      lstFiles.Canvas.FillRect(CR);
      if lstFiles.Row = ARow then
        THackStringGrid(lstFiles).DrawCellHighlight(CR, State, ACol, ARow);
    end;
  end;
end;

Here's how it looks when clicking...

Reacts to Mouse Click but Doesn't Change

What could be causing this? It's definitely not changing the Checked property anywhere in my code. There's some strange behavior coming from the checkboxes themselves when placed in a grid.

EDIT

I did a brief test, I placed a regular TCheckBox on the form. Check/unchecks fine. Then, in my form's OnShow event, I changed the Checkbox's Parent to this grid. This time, I get the same behavior, not toggling when clicked. Therefore, it seems that a TCheckBox doesn't react properly when it has another control as its parent. How to overcome this?

Lenes answered 30/7, 2012 at 10:16 Comment(5)
Don't you rather draw check boxes inside of your string grid instead of using real ones ? Mouse click isn't the only way how to switch the check box state, I would expect to use space key to switch them in your grid and that without string grid's key event handling wouldn't work as well.Balancer
@TLama, that looks promising, I'll have to check that one out.Lenes
But keep in mind that the check boxes won't be glowing when hovering and you'd need to extend that code for checked/unchecked press states.Balancer
I could handle that myself with mouse events :DLenes
This is just a guess. The checkbox as the child component is not receiving the event message. So, on the onclick event of the grid, you may need to setfocus & sendmessage to the checkbox. As for key press event, check for 'space' character, set focus to the check box and sendmessage. Also allow 'tab' key to escape, so it goes to next cell.Theorize
W
9

TStringGrid's WMCommand handler doesn't allow children controls to handle messages (except for InplaceEdit).

So you can use e.g. an interposed class (based on code by Peter Below) or draw controls by hands, as some people have adviced. Here is the code of the interposed class:

uses
  Grids;

type
  TStringGrid = class(Grids.TStringGrid)
  private
    procedure WMCommand(var AMessage: TWMCommand); message WM_COMMAND;
  end;

implementation

procedure TStringGrid.WMCommand(var AMessage: TWMCommand);
begin
  if EditorMode and (AMessage.Ctl = InplaceEditor.Handle) then
    inherited
  else
  if AMessage.Ctl <> 0 then
  begin
    AMessage.Result := SendMessage(AMessage.Ctl, CN_COMMAND,
      TMessage(AMessage).WParam, TMessage(AMessage).LParam);
  end;
end;
Wortham answered 30/7, 2012 at 14:53 Comment(1)
+1 and accepted, beautiful fix. Sure beats re-building the whole thing with another control or drawing everything myself. Quick and easy way to salvage what I already had :DLenes
T
3

In Delphi7 at least I do this:

You need to draw a checkbox on the cell, and keep it in sync with an array of boolean (here fChecked[]) that indicates the state of the checkbox in each row. Then, in the DrawCell part of the TStringGrid:

var
 cbstate: integer;
begin
...
if fChecked[Arow] then cbState:=DFCS_CHECKED else cbState:=DFCS_BUTTONCHECK;
DrawFrameControl(StringGrid.canvas.handle, Rect, DFC_BUTTON, cbState);
...
end;

To get the checkbox to respond to the space-bar, use the KeyDown event, and force a repaint:

if (Key = VK_SPACE) And (col=ColWithCheckBox) then begin
  fChecked[row]:=not fChecked[row];
  StringGrid.Invalidate;
  key:=0;
end;

A similar approach is needed for the OnClick method.

Traveller answered 30/7, 2012 at 11:59 Comment(0)
A
0
  1. Can u use VirtualTreeView in toReportMode (TListView emulating) mode instead of grid ?

  2. Can u use TDBGrid over some in-memory table like NexusDB or TClientDataSet ?

  3. Ugly approach would be presenting checkbox like a letter with a custom font - like WinDings or http://fortawesome.github.com/Font-Awesome

This latter is most easy to implement, yet most ugly to see and most inflexible to maintain - business logic gets intermixed into VCL event handlers

Amateurism answered 30/7, 2012 at 10:39 Comment(7)
Well, frankly here i'd go with DBGrid approach, for it would require least changes. Decade ago there was a project to present any array of records like a TDataset, but it ceased. But even ClientDataSet would do the trick IMHO.Northwestwards
1) I probably could, just never used it before, 2) Seems like too much, because this is not from a database, and 3) This might be what I do, only not using fonts but custom drawing.Lenes
-1 you're not actually trying to help here. TLama's answer is the most sensible here...Herdic
Depends on point of view. If someone would ask how to drink boiling water without feeling pain, you might offer him morphine. Or suggest not drinking boiling water and reach some broader goal with more proper means. I am for second. I think that attempt at cheating with user and with GDI like it is almost a checkbox, it is almost living would be rather fragile and hard to maintain in few years. The goal is to be finding components and using them in a way they were designed to, rather than forcing them into non-standard behavior and simulation of things that are not really present.Northwestwards
Suppose I ask how to go to Jamaica without an airplane. Rather than offering a boat, you're offering how about going to Florida instead? I think that's the point whosrdaddy's trying to make. PS - I hate having to resort to third party controls for that exact reason - future maintainability.Lenes
Only 1st of three solution required 3rd-party control, and that one the community driven, so it is no worse than your own code. Don't know about Jamaica, but if you'd ask for Cuba i'd definitely started with survival statistics rather than boat. BTW, you was not offered a boat, but something that looks alike. Few sheets from boat DIY magazine. And hope that your route is calm enough that boat-like construct would always be okay.Northwestwards
It's fine to offer other suggestions, but if you're going to do so in an answer, you should at least provide an answer to the question asked, and then offer suggestions for other ways to do things. As it is, you asked two questions yourself, and then made a suggestion. None of those are answers to the question asked, and at best should have been comments to the original question.Gentilis

© 2022 - 2024 — McMap. All rights reserved.