如何为TStringGrid创建自定义编辑器?(TStringGrid作为其他控件的父级)

3

我正在尝试为TStringGrid创建一个列编辑器(显示/隐藏列)。该编辑器是一个包含列表框、标签和按钮的面板。我希望直接将此编辑器创建到我的TStringGrid控件中。

表格:

type 
 TAvaGrid= class(TStringGrid;

constructor TAvaGrid.Create(AOwner: TComponent);  
begin
 inherited Create(AOwner);
 ColEditor:= TColEditor.Create(Self);
end;

procedure TAvaGrid.CreateWnd;
begin
 inherited CreateWnd;   
 ColEditor.Parent  := Self;        
 ColEditor.Visible := FALSE; 
end;

编辑:

constructor TColEditor.Create(AOwner: TComponent);
begin
 inherited Create(AOwner);

 Self.Top     := 60;                       { DO NOT move this in CreateWnd because controls based on this won't size properly during Create }
 Self.Left    := 60;
 Self.Width   := 220;
 Self.Height  := 280;

 TopBar      := TLabel.Create(Self);    
 CloseButton := TButton.Create(Self);     
 VisChkBox   := TCheckListBox.Create(Self);

 DoubleBuffered:= FALSE;                   { Mandatory! }
 Visible := FALSE;

 { Blue caption }
 TopBar.Parent       := Self;
 TopBar.AutoSize     := FALSE;
 TopBar.Height       := 21;
 TopBar.Align        := alTop;
 TopBar.Caption      := ' Visible columns';
 TopBar.ParentColor  := FALSE;
 TopBar.Transparent  := FALSE;
 TopBar.Cursor       := crHandPoint;
 TopBar.Font.Name    := 'Tahoma';
 TopBar.Font.Style   := [System.UITypes.TFontStyle.fsBold];
 TopBar.ParentFont   := FALSE;
 TopBar.Layout       := tlCenter;
 TopBar.Visible      := TRUE;
 TopBar.Color        := TColors.Navy;
 TopBar.Font.Color   := TColors.White;
 TopBar.OnMouseDown  := TopBarMouseDown;
 TopBar.OnMouseMove  := TopBarMouseMove;
 TopBar.OnMouseUp    := TopBarMouseUp;

 { The Close button }
 CloseButton.Parent  := Self;
 CloseButton.Width   := 22;
 CloseButton.Height  := 20;
 CloseButton.Top     := 1;
 CloseButton.Anchors := [akRight, akBottom];
 CloseButton.Hint    := 'Close';
 CloseButton.Caption := 'X';
 CloseButton.Visible := TRUE;
 CloseButton.OnClick := CloseButtonClick;

 { The listbox }
 VisChkBox.Parent          := Self;
 VisChkBox.AlignWithMargins:= TRUE;
 VisChkBox.Align           := alClient;
 VisChkBox.ItemHeight      := 13;
 VisChkBox.Visible         := TRUE;
end;


{THIS is not called until when the user presses F4 to show the 'Column Visibility' (this) panel ! }
procedure TColEditor.CreateWnd;    
begin
 inherited CreateWnd;
 CloseButton.Left:= Self.ClientWidth- CloseButton.Width- 1;
end;

我在编辑器中遇到了刷新问题:当网格更新(例如添加新列)时,编辑器会变得混乱:enter image description here 我必须点击它才能使其看起来正确。
我尝试过WMCommand trick,但它不起作用。

更准确地说,WMCommand从未被调用。 - Gabriel
你能展示一下它看起来混乱的截图吗? - Jerry Dodge
@JerryDodge - 已添加截图。 - Gabriel
2个回答

2
问题出在(TCustomPanel的)paint code上。面板和StringGrid两个控件都会在同一个画布上完全绘制自己,从而发生冲突。就我的经验而言,无论是使用重载还是其属性设置,我都没有成功地解决这个面板的问题。很明显,它可能并不适用于此种设置。

但是当您从TCustomControl甚至TWinControl中派生您的编辑器时,就不会有绘制问题了,代码就变成了这样:

type
  TColEditor = class(TWinControl)
  private
    FTopBar: TLabel;
    FCloseButton: TButton;
    FVisChkBox: TCheckListBox;
    procedure CloseButtonClick(Sender: TObject);
    procedure TopBarMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure TopBarMouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
    procedure TopBarMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  public
    constructor Create(AOwner: TComponent); override;
  end;

  TStringGrid = class(Grids.TStringGrid)
  private
    FColEditor: TColEditor;
  public
    constructor Create(AOwner: TComponent); override;
  end;

constructor TColEditor.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  SetBounds(60, 60, 220, 280);
  BevelKind := bkTile;
  BevelInner := bvLowered;
  BevelOuter := bvRaised;
  FTopBar := TLabel.Create(Self);
  FTopBar.AutoSize := False;
  FTopBar.Height := 21;
  FTopBar.Align := alTop;
  FTopBar.Caption := ' Visible columns';
  FTopBar.Transparent := False;
  FTopBar.Color := TColors.Navy;
  FTopBar.Cursor := crHandPoint;
  FTopBar.Font.Name := 'Tahoma';
  FTopBar.Font.Style := [System.UITypes.TFontStyle.fsBold];
  FTopBar.Font.Color := TColors.White;
  FTopBar.Layout := tlCenter;
  FTopBar.OnMouseDown := TopBarMouseDown;
  FTopBar.OnMouseMove := TopBarMouseMove;
  FTopBar.OnMouseUp := TopBarMouseUp;
  FTopBar.Parent := Self;
  FCloseButton := TButton.Create(Self);
  FCloseButton.SetBounds(Width - 22, 0, 22, 20);
  FCloseButton.Anchors := [akTop, akRight];
  FCloseButton.Hint := 'Close';
  FCloseButton.Caption := 'X';
  FCloseButton.OnClick := CloseButtonClick;
  FCloseButton.Parent := Self;
  FVisChkBox := TCheckListBox.Create(Self);
  FVisChkBox.AlignWithMargins := True;
  FVisChkBox.Align := alClient;
  FVisChkBox.ItemHeight := 13;
  FVisChkBox.Parent := Self;
end;

constructor TStringGrid.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FColEditor := TColEditor.Create(Self);
  FColEditor.Parent := Self;
  FColEditor.Visible := False;
end;

我认为这是对你问题的回答。但我可以说,这个解决方案远非最佳。因为编辑器控件是StringGrid的子控件,而StringGrid在使用其滚动条时内部使用ScrollWindow,你的编辑器的表示会被复制到所有地方。这是一个问题,可能还有其他问题。
解决方法是将编辑器从StringGrid中分离出来,并将其Parent属性设置为StringGrid的Parent属性。StringGrid根本不应该包含子控件,这在设计器中的StringGrid无法接受子控件就可以证明。当你将编辑器放在父级链中时,甚至可以退回到Panel解决方案。
显然,还有许多其他可能的设计来制作可见的列选择编辑器。其中之一是使用PopupMenu,其实现方式可能如下:
type
  TStringGrid = class(Vcl.Grids.TStringGrid)
  private
    FColumsMenu: TMenuItem;
    procedure ColumnItemClick(Sender: TObject);
    procedure InitializePopupMenu;
  protected
    procedure SizeChanged(OldColCount, OldRowCount: Integer); override;
    procedure Loaded; override;
  end;

procedure TStringGrid.ColumnItemClick(Sender: TObject);
var
  Item: TMenuItem absolute Sender;
begin
  Item.Checked := not Item.Checked;
  if Item.Checked then
    ColWidths[Item.Tag] := DefaultColWidth
  else
    ColWidths[Item.Tag] := -GridLineWidth;
end;

procedure TStringGrid.InitializePopupMenu;
var
  MenuItem: TMenuItem;
begin
  if PopupMenu = nil then
  begin
    PopupMenu := TPopupMenu.Create(Self);
    FColumsMenu := PopupMenu.Items;
  end
  else
  begin
    MenuItem := TMenuItem.Create(Self);
    MenuItem.Caption := 'Visible columns...';
    PopupMenu.Items.Insert(0, MenuItem);
    FColumsMenu := MenuItem;
  end;
  SizeChanged(0, 0);
end;

procedure TStringGrid.Loaded;
begin
  inherited Loaded;
  InitializePopupMenu;
end;

procedure TStringGrid.SizeChanged(OldColCount, OldRowCount: Integer);
var
  Checked: array of Boolean;
  I: Integer;
  MenuItem: TMenuItem;
begin
  inherited SizeChanged(OldColCount, OldRowCount);
  if not (csDesigning in ComponentState) and (ColCount <> OldColCount) then
  begin
    SetLength(Checked, ColCount);
    for I := FixedCols to OldColCount - 1 do
      Checked[I] := ColWidths[I] > 0;
    for I := OldColCount to ColCount - 1 do
      Checked[I] := True;
    FColumsMenu.Clear;
    for I := FixedCols to ColCount - 1 do
    begin
      MenuItem := TMenuItem.Create(Self);
      MenuItem.Checked := Checked[I];
      MenuItem.Tag := I;
      MenuItem.Caption := Format('Column %d', [I]);
      MenuItem.OnClick := ColumnItemClick;
      FColumsMenu.Add(MenuItem);
    end;
  end;
end;

@NGLN-可能会有很多列,所以我不知道'Menu'解决方案是否适合。但是非常感谢您提供的“刷新”修复!答案已被接受。 - Gabriel

1

在我看来,你正在错误的方向上进展,这并不是一个真正的编辑器,也不应该被分类为编辑器。相反,我认为���应该将面板放入单独的表单中,当用户按下F4时以模态方式显示表单(必要时创建)。您需要设置表单的样式、边框、图标等以获得所需的外观(假设这对您很重要),但您想要执行的操作绝对呼喊出了模态表单。


抱歉,@冰霜青蛙,无论你是否称其为编辑器,我仍然坚持我的答案。就代码重用而言,您可以将对话框作为组件的一个组成部分包含在内,因此这并不是真正的问题。只需将表单包含在软件包中,并使组件以适当的方式响应F4即可。这并不比您尝试做的更难,并且不需要更多的重用努力。此外,它的行为符合您的预期! - Dsm
是的,这并不容易。我自己写过类似的东西,但是使用了原地逐列编辑器,同时在列中显示标题。有很多需要注意的地方。你可能看到的是主表单和编辑器同时尝试重新绘制,所以你会看到字符串网格的一部分和编辑器的一部分。你可以尝试的一件事是,在主表单绘制例程结束时调用编辑器重绘函数(如果编辑器可见),但我不知道它是否有效。你可能会遇到很多闪烁。 - Dsm
不,我并不感到惊讶。每个组件都是一个单独的窗口,因此您需要沿着链应用相同的过程,即您需要执行CloseButton.Repaint。我仍然认为模态表单是更好的方法。 - Dsm
答案已被接受。但我仍然会接受其他关于如何解决刷新问题的答案。 - Gabriel
我认为这可能是一个单独的问题——如何使用PopupMode := pmExplicit停止内存泄漏。 - Dsm
Ngln给了我一个直接的答案,所以我不得不接受他的答案。但是你的想法也很好。所以,我的赞同保留。 - Gabriel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接