3

I am trying to create a column editor (show/hide columns) for TStringGrid. The editor is a panel that contains a listbox, a label and a button. I want to create this editor directly into my TStringGrid control.

THE GRID:

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;

THE EDITOR:

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;

I have refresh problems with the editor: when the grid gets updated (add new columns for example) the editor gets gabbled: enter image description here I have to click it in order to make it look right.

I have tried the WMCommand trick but it won't work.

Community
  • 1
  • 1
Gabriel
  • 20,797
  • 27
  • 159
  • 293

2 Answers2

2

The problem lies within (the paint code of) TCustomPanel. Both controls, the Panel and the StringGrid, fully paint themselves on the same canvas that somehow collide with each other. For what it is worth, I have not succeeded to get satisfactory results with this Panel by using (any combination of) overrides and its property's settings. It obviously - likely at least - is not meant to be used in this setup.

But when you derive your editor from TCustomControl, or even TWinControl, then there are no painting issues left, and the code becomes like this:

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;

I think this is an answer to your question. But might I say that this solution is far from optimal. Because the editor control is a child control of the StringGrid and the StringGrid internally uses ScrollWindow when using its scroll bars, the representation of your editor gets copied all over the place. This is a problem, possibly among others.

The solution for this is to detach the editor as child from the StringGrid and set its Parent property to that of the Parent of StringGrid. A StringGrid simply is not meant to contain child controls, which is proven by a StringGrid in the designer not being able to accept child controls. When you place the editor up in the parenting chain, you even can fall back on the Panel solution.

Obviously, there are lots of other possible designs for making a visible column selection editor. One of them is using a PopupMenu, which' implementation could look like this:

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
  • 43,011
  • 8
  • 105
  • 200
  • @NGLN-There may be LOTS of columns so I don't know how nice will fit the 'Menu' solution. But thanks a lot for the 'refresh' fix!! Answer accepted. – Gabriel Oct 14 '15 at 10:36
1

It seems to me that you are going about this the wrong way. This is not really an editor at all and should not be classed as one. Instead I think that you should put this panel into a separate form and when the user presses F4 show the form modally (creating if necessary). You will need to set the form's style, border, icons, etc. to get the appearance that you want (assuming that is important to you) but the action that you want to perform definitely screams modal form to me.

Dsm
  • 5,870
  • 20
  • 24
  • Sorry, @frosty frog, but whether you call it an editor or not I stand by my answer. Now as far as code reuse is concerned you can include the dialog as an integral part of your component, so that is not really an issue. Just include your form in your package, and get your component to respond to F4 appropriately. That is no harder than what you are trying to do and requires no more effort for re-use. Furthermore it behaves how you want it to behave! – Dsm Oct 06 '15 at 13:50
  • Yes, its not easy. I have written something similar myself, but with in place per column editors that also display titles in the columns. There were lots of gotchas. What you are probably seeing here is the main form and the editor both trying to redraw at the same time, so you are seeing bits of the string grid and bits of the editor. One thing you could try is calling the editor repaint function at the end of the main form paint routine if the editor is visible, but I don't know if it will work. You might get a lot of flickering doing that. – Dsm Oct 07 '15 at 12:56
  • No, I am not surprised. Each component is a separate window, so you need to apply the same procedure down the chain, i.e. you need to execute CloseButton.Repaint. I still think the modal form is a much better approach. – Dsm Oct 07 '15 at 15:30
  • Answer accepted. But I will still accept other answers about how to fix the refresh problem. – Gabriel Oct 08 '15 at 09:47
  • I think that might be a separate question - how to stop a memory leak with PopupMode := pmExplicit. – Dsm Oct 08 '15 at 10:33
  • Ngln provided a direct answer to my question so I had to accept his answer. But your idea is good also. So, my upvote stays. – Gabriel Oct 14 '15 at 10:37