关于创建 "container" 组件的建议

Advice on creating a "container" component

我想创建一个 "panel component" 来容纳 x x y 按钮(实际上不完全是按钮,但你明白了)。

它应该看起来像一个扫雷器,您可以在其中单击任何按钮并且每个按钮都有相同的 "global click" 事件。但是使用 sender 和 button 作为参数,例如:Sender: TObject; Button: TButton) wherein Sender 是面板组件,而 Button 是面板内的按钮。

到目前为止,我设置了水平方向按钮数量和垂直方向按钮数量两个属性。

  property ButtonsHeight: Integer read fButtonsHeight write SetButtonsHeight;
  property ButtonsWidth: Integer read fButtonsWidth  write SetButtonsWidth;

procedure TMultipleDrawPanel.SetButtonsHeight(const Value: Integer);
begin
  if Value < 1 then begin
    raise Exception.Create('Mumarul minim de butoane este 1!');
  end;
  InflateButtonsHeight(fButtonsHeight, Value);
  fButtonsHeight := Value;
end;

这是在一个方向上改变按钮数量的伪代码:

procedure TMultipleDrawPanel.InflateButtonsHeight(oldValue, newValue: Integer);
begin
  if oldValue < newValue then begin
    // free extra buttons
  end else begin
    // create new buttons
  end;
end;

谁能给我一些建议?

  1. 如何存储按钮列表?
  2. 如何在数字从高值变为低值时释放按钮? (否则如何创建新的?)
  3. 我确定我需要覆盖调整大小的方法。我有一些想法,但我没有走那么远。

是这样的吗?

unit ButtonPanel;

interface

uses
  System.Classes, System.SysUtils, Vcl.Controls, Vcl.StdCtrls, System.Math;

type
  TCoord = record
    Col: Integer;
    Row: Integer;
  end;

  TCustomButtonPanel = class;

  TButtonClickEvent = procedure(Sender: TCustomButtonPanel;
    Coord: TCoord) of object;

  TCustomButtonPanel = class(Vcl.Controls.TWinControl)
  private
    FColCount: Integer;
    FOnClick: TButtonClickEvent;
    FRowCount: Integer;
    procedure ButtonClick(Sender: TObject);
    function CoordFromIndex(Index: Integer): TCoord;
    function CoordToIndex(Col, Row: Integer): Integer;
    function GetButton(Col, Row: Integer): TButton; overload;
    function GetButton(Index: Integer): TButton; overload;
    function GetButtonCount: Integer;
    procedure SetColCount(Value: Integer);
    procedure SetRowCount(Value: Integer);
    procedure SizeChanged;
  protected
    function CanResize(var NewWidth, NewHeight: Integer): Boolean; override;
    procedure DoClick(Index: Integer); virtual;
    procedure Resize; override;
    procedure ValidateInsert(AComponent: TComponent); override;
    property ButtonCount: Integer read GetButtonCount;
    property Buttons[Col, Row: Integer]: TButton read GetButton;
    property ColCount: Integer read FColCount write SetColCount default 5;
    property OnClick: TButtonClickEvent read FOnClick write FOnClick;
    property RowCount: Integer read FRowCount write SetRowCount default 5;
  public
    constructor Create(AOwner: TComponent); override;
  end;

  TButtonPanel = class(TCustomButtonPanel)
  public
    property ButtonCount;
    property Buttons;
  published
    property ColCount;
    property OnClick;
    property RowCount;
  end;

implementation

type
  TButtonPanelButton = class(Vcl.StdCtrls.TButton);

resourcestring
  SInvalidControlType = 'Invalid control type for ButtonPanel child';

function Round(Value, Rounder: Integer): Integer; overload;
begin
  if Rounder = 0 then
    Result := Value
  else
    Result := (Value div Rounder) * Rounder;
end;

{ TCustomButtonPanel }

procedure TCustomButtonPanel.ButtonClick(Sender: TObject);
begin
  DoClick(TButton(Sender).Tag);
end;

function TCustomButtonPanel.CanResize(var NewWidth, NewHeight: Integer): Boolean;
var
  EdgeSize: Integer;
begin
  Result := inherited CanResize(NewWidth, NewHeight);
  EdgeSize := 2 * (BorderWidth + BevelWidth);
  NewWidth := Round(NewWidth - EdgeSize, FColCount) + EdgeSize;
  NewHeight := Round(NewHeight - EdgeSize, FRowCount) + EdgeSize;
end;

function TCustomButtonPanel.CoordFromIndex(Index: Integer): TCoord;
begin
  Result.Col := Index mod ColCount;
  Result.Row := Index div ColCount;
end;

function TCustomButtonPanel.CoordToIndex(Col, Row: Integer): Integer;
begin
  Result := FColCount * Row + Col;
end;

constructor TCustomButtonPanel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ControlStyle := [];
  FColCount := 5;
  FRowCount := 5;
  SizeChanged;
end;

procedure TCustomButtonPanel.DoClick(Index: Integer);
begin
  if Assigned(FOnClick) then
    FOnClick(Self, CoordFromIndex(Index));
end;

function TCustomButtonPanel.GetButton(Col, Row: Integer): TButton;
begin
  Result := GetButton(CoordToIndex(Col, Row));
end;

function TCustomButtonPanel.GetButton(Index: Integer): TButton;
begin
  Result := TButton(Controls[Index]);
end;

function TCustomButtonPanel.GetButtonCount: Integer;
begin
  Result := ControlCount;
end;

procedure TCustomButtonPanel.Resize;
var
  ColWidth: Integer;
  RowHeight: Integer;
  Col: Integer;
  Row: Integer;
begin
  inherited Resize;
  ColWidth := ClientWidth div FColCount;
  RowHeight := ClientHeight div FRowCount;
  for Col := 0 to FColCount - 1 do
    for Row := 0 to FRowCount - 1 do
      Buttons[Col, Row].SetBounds(Col * ColWidth, Row * RowHeight, ColWidth,
        RowHeight);
end;

procedure TCustomButtonPanel.SetColCount(Value: Integer);
begin
  if FColCount <> Value then
  begin
    FColCount := Max(1, Value);
    SizeChanged;
  end;
end;

procedure TCustomButtonPanel.SetRowCount(Value: Integer);
begin
  if FRowCount <> Value then
  begin
    FRowCount := Max(1, Value);
    SizeChanged;
  end;
end;

procedure TCustomButtonPanel.SizeChanged;
var
  I: Integer;
  OldCount: Integer;
  NewCount: Integer;
  Button: TButton;
begin
  OldCount := ButtonCount;
  NewCount := FColCount * FRowCount;
  for I := OldCount - 1 downto NewCount do
    GetButton(I).Free;
  for I := OldCount to NewCount - 1 do
  begin
    Button := TButtonPanelButton.Create(Self);
    Button.Tag := I;
    Button.OnClick := ButtonClick;
    Button.Parent := Self;
  end;
  AdjustSize;
end;

procedure TCustomButtonPanel.ValidateInsert(AComponent: TComponent);
begin
  inherited ValidateInsert(AComponent);
  if not (AComponent is TButtonPanelButton) then
    raise EInvalidInsert.Create(SInvalidControlType);
end;

end.

我确信上面的代码提供了大量的学习内容 material、思想的食粮和定制的空间。简而言之:

  • 滥用 组件的 Controls 属性 将其含义延伸到按钮。因此,该控件不允许插入其他类型的控件。参见 GetButtonGetButtonCountValidateInsert
  • 因为每个按钮的大小都是一样的,而组件里全是按钮,所以组件的大小被限制为按钮大小的倍数。参见 CanResize
  • 它有一个重新引入的 OnClick 事件,因为面板本身不会被点击(它充满了按钮)。该事件有一个额外的参数,请参阅 ButtonClickDoClick
  • 二维列-行坐标映射到索引的线性数组,请参见CoordFromIndexCoordToIndex
  • 按钮由 Tag 属性 标识,请参阅创建按钮的 SizeChanged

您的下一步是使用虚拟方法重新设计它,其中不使用实际的 TButton 控件,而按钮只是模仿绘制的。

玩得开心。