Почему дочерние элементы управления TStringGrid не работают должным образом?

Я ставлю галочки (TCheckBox) в сетке строк (TStringGrid) в первом столбце. Флажки отображаются нормально, расположены правильно и реагируют на мышь, светясь при наведении на них курсора. Однако, когда я нажимаю на них, они не переключаются. Они реагируют на щелчок и выделяют, но, наконец, фактическое Checked свойство не меняется. Что делает его более загадочным, так это то, что у меня нет кода, изменяющего эти значения, когда они есть, и при этом у меня даже нет 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;

Добавление строк в сетку:

//...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 Grid:

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 не реагирует должным образом, когда у него есть другой элемент управления в качестве родителя. Как это побороть?

3 ответа

Решение

TStringGrid"s 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;

В Delphi7 по крайней мере я делаю это:

Вам нужно нарисовать флажок в ячейке и синхронизировать его с массивом логических значений (здесь fChecked[]), который указывает состояние флажка в каждой строке. Затем в DrawCell часть 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;

Чтобы получить флажок для ответа на пробел, используйте KeyDown событие и заставить перекрасить:

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

Аналогичный подход необходим для OnClick метод.

  1. Можно ли использовать VirtualTreeView в режиме toReportMode (эмуляция TListView) вместо сетки?

  2. Можете ли вы использовать TDBGrid поверх таблицы в памяти, такой как NexusDB или TClientDataSet?

  3. Гадкий подход будет представлять флажок как письмо с пользовательским шрифтом - как WinDings или http://fortawesome.github.com/Font-Awesome

Последний вариант наиболее прост в реализации, но наиболее уродлив и нагляден и негибок в обслуживании - бизнес-логика смешивается с обработчиками событий VCL

Другие вопросы по тегам