Delphi 为什么不';TStringGrid的子控件是否正常工作?

Delphi 为什么不';TStringGrid的子控件是否正常工作?,delphi,checkbox,mouseevent,delphi-xe2,tstringgrid,Delphi,Checkbox,Mouseevent,Delphi Xe2,Tstringgrid,我将复选框(TCheckBox)放在字符串网格(TStringGrid)的第一列。复选框显示精细、位置正确,并在鼠标悬停时通过发光响应鼠标。但是,当我单击它们时,它们不会切换。它们会对单击作出反应,并高亮显示,但最后,实际的已选中的属性不会更改。更令人费解的是,我没有任何代码在这些值出现后更改它们,甚至没有为这些复选框分配OnClick事件。此外,我将这些复选框默认为未选中,但在显示时,它们将被选中 复选框与添加到列表中的每条记录一起创建,并在指定给要放置复选框的单元格中的对象的记录指针内引用

我将复选框(
TCheckBox
)放在字符串网格(
TStringGrid
)的第一列。复选框显示精细、位置正确,并在鼠标悬停时通过发光响应鼠标。但是,当我单击它们时,它们不会切换。它们会对单击作出反应,并高亮显示,但最后,实际的
已选中的
属性不会更改。更令人费解的是,我没有任何代码在这些值出现后更改它们,甚至没有为这些复选框分配
OnClick
事件。此外,我将这些复选框默认为未选中,但在显示时,它们将被选中

复选框与添加到列表中的每条记录一起创建,并在指定给要放置复选框的单元格中的对象的记录指针内引用

单元格高亮显示的字符串网格攻击:

type
  THackStringGrid = class(TStringGrid); //used later...
包含复选框的记录:

  PImageLink = ^TImageLink;
  TImageLink = record
    ...other stuff...
    Checkbox: TCheckbox;
    ShowCheckbox: Bool;
  end;
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;
创建/销毁复选框:

  PImageLink = ^TImageLink;
  TImageLink = record
    ...other stuff...
    Checkbox: TCheckbox;
    ShowCheckbox: Bool;
  end;
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;
将行添加到网格:

//...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;
网格的OnDrawCell事件处理程序:

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;
以下是单击时的外观

这可能是什么原因造成的?它肯定不会更改我代码中任何地方的
Checked
属性。在网格中放置时,复选框本身会出现一些奇怪的行为

编辑

我做了一个简短的测试,在表单上放置了一个常规的
TCheckBox
。勾选/取消勾选即可。然后,在表单的
OnShow
事件中,我将复选框的
Parent
更改为该网格。这一次,我得到了相同的行为,单击时不切换。因此,当另一个控件作为其父控件时,
TCheckBox
似乎不能正确响应。如何克服这个问题

  • 你能在toReportMode(TListView仿真)模式下使用VirtualTreeView而不是网格吗

  • 您是否可以在某些内存表(如NexusDB或TClientDataSet)上使用TDBGrid

  • 丑陋的方法是将复选框呈现为带有自定义字体的字母,或者


  • 后者最容易实现,但最难看,维护最不灵活-业务逻辑混合到VCL事件处理程序中,在Delphi7中至少我这样做:

    您需要在单元格上绘制一个复选框,并使其与指示每行复选框状态的布尔数组(此处为
    fChecked[]
    )保持同步。然后,在
    TStringGrid
    DrawCell
    部分中:

    var
     cbstate: integer;
    begin
    ...
    if fChecked[Arow] then cbState:=DFCS_CHECKED else cbState:=DFCS_BUTTONCHECK;
    DrawFrameControl(StringGrid.canvas.handle, Rect, DFC_BUTTON, cbState);
    ...
    end;
    
    要使复选框响应空格键,请使用
    KeyDown
    事件,然后强制重新绘制:

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

    OnClick
    方法也需要类似的方法。

    TStringGrid
    WMCommand
    处理程序不允许子控件处理消息(InplaceEdit除外)

    因此,您可以使用插入类(基于)或手动绘制控件,正如一些人所建议的那样。以下是插入类的代码:

    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;
    
    使用
    网格;
    类型
    TStringGrid=class(Grids.TStringGrid)
    私有的
    程序WMCommand(var AMessage:TWMCommand);消息WM_命令;
    结束;
    实施
    过程TStringGrid.WMCommand(var-AMessage:TWMCommand);
    开始
    如果EditorMode和(AMessage.Ctl=InplaceEditor.Handle),则
    继承
    其他的
    如果AMessage.Ctl为0,则
    开始
    AMessage.Result:=SendMessage(AMessage.Ctl,CN_命令,
    TMessage(AMessage).WParam,TMessage(AMessage).LParam;
    结束;
    结束;
    
    你不是更喜欢在字符串网格内部而不是使用真实的网格吗?鼠标点击并不是切换复选框状态的唯一方法,我希望在网格中使用空格键来切换它们,而没有字符串网格的键事件处理也无法正常工作。@TLama,这看起来很有希望,我必须签出这个。但请记住,当鼠标悬停时,复选框不会发光,您需要为选中/未选中的按键状态扩展该代码。我可以自己用鼠标事件处理它:d这只是一个猜测。作为子组件的复选框未接收事件消息。因此,在网格的onclick事件中,您可能需要将focus&sendmessage设置为复选框。对于按键事件,检查“空格”字符,将焦点设置为复选框并发送消息。也允许“tab”键转义,这样它就转到下一个单元格。坦白地说,这里我使用DBGrid方法,因为它需要最少的更改。十年前,有一个项目可以像TDataset一样呈现任何记录数组,但它停止了。但即使是ClientDataSet也会这样做。1)我可能会,只是以前从未使用过,2)似乎太多了,因为这不是来自数据库,3)这可能就是我所做的,不仅不使用字体,而是自定义绘图。-1你实际上并没有试图在这里提供帮助。特拉马的答案在这里是最明智的…取决于观点。如果有人问如何喝开水而不感到疼痛,你可以给他吗啡。或者建议不要喝开水,用更合适的方法达到更广泛的目标。我是第二名。我认为,试图欺骗用户和GDI的行为几乎是一个复选框,它几乎是活生生的,这将是相当脆弱的,很难在几年内维持。我们的目标是找到组件,并以设计的方式使用它们,而不是强迫它们进行非标准行为和模拟不真实存在的事物。提供其他建议也不错,但如果你打算在回答中这样做,你至少应该提供所问问题的答案,然后提出其他做事方法的建议。事实上,你自己问了两个问题,然后提出了一个建议。这些都不是问题的答案,充其量也应该是对原始问题的评论。+1和公认的、漂亮的修正。当然比用另一个控件重新构建整个东西或自己绘制所有东西要好得多。迅捷