delphidelphi-xetstringgridtcheckbox

Put a TCheckBox inside a TStringGrid in Delphi


I want to put a TCheckBox inside a TStringGrid in Delphi in every cell of certain column. I'm using Delphi XE.


Solution

  • You should draw your own checkboxes, preferably using visual themes, if enabled. This is a simple sketch of how to do that:

    const
      Checked: array[1..4] of boolean = (false, true, false, true);
    
    procedure TForm4.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
      Rect: TRect; State: TGridDrawState);
    const
      PADDING = 4;
    var
      h: HTHEME;
      s: TSize;
      r: TRect;
    begin
      if (ACol = 2) and (ARow >= 1) then
      begin
        FillRect(StringGrid1.Canvas.Handle, Rect, GetStockObject(WHITE_BRUSH));
        s.cx := GetSystemMetrics(SM_CXMENUCHECK);
        s.cy := GetSystemMetrics(SM_CYMENUCHECK);
        if UseThemes then
        begin
          h := OpenThemeData(StringGrid1.Handle, 'BUTTON');
          if h <> 0 then
            try
              GetThemePartSize(h,
                StringGrid1.Canvas.Handle,
                BP_CHECKBOX,
                CBS_CHECKEDNORMAL,
                nil,
                TS_DRAW,
                s);
              r.Top := Rect.Top + (Rect.Bottom - Rect.Top - s.cy) div 2;
              r.Bottom := r.Top + s.cy;
              r.Left := Rect.Left + PADDING;
              r.Right := r.Left + s.cx;
              DrawThemeBackground(h,
                StringGrid1.Canvas.Handle,
                BP_CHECKBOX,
                IfThen(Checked[ARow], CBS_CHECKEDNORMAL, CBS_UNCHECKEDNORMAL),
                r,
                nil);
            finally
              CloseThemeData(h);
            end;
        end
        else
        begin
          r.Top := Rect.Top + (Rect.Bottom - Rect.Top - s.cy) div 2;
          r.Bottom := r.Top + s.cy;
          r.Left := Rect.Left + PADDING;
          r.Right := r.Left + s.cx;
          DrawFrameControl(StringGrid1.Canvas.Handle,
            r,
            DFC_BUTTON,
            IfThen(Checked[ARow], DFCS_CHECKED, DFCS_BUTTONCHECK));
        end;
        r := Classes.Rect(r.Right + PADDING, Rect.Top, Rect.Right, Rect.Bottom);
        DrawText(StringGrid1.Canvas.Handle,
          StringGrid1.Cells[ACol, ARow],
          length(StringGrid1.Cells[ACol, ARow]),
          r,
          DT_SINGLELINE or DT_VCENTER or DT_LEFT or DT_END_ELLIPSIS);
      end;
    end;
    

    Of course, in a real scenario, the Checked array is not a constant, and you might wish to save the s metrics and h theme handle between cell painting events. But the principle is here.

    What is missing here is a function to alter the state of the checkboxes. You will probably want to toggle the state in an OnClick handler. If you are really serious, you'll also wish to respond to the motion of the mouse, and display the mouse hover effect on the checkboxes if themes are available.

    EDIT by bluish: To toggle checkbox state, this answer explains how you can use Invalidate method.