为什么当我改变属性时,我的自定义组件没有更新?

12
我创建了一个组件 TGridPaintBox,基于 TPaintBox。它基本上是一个具有“网格功能”的画布框架。它不是数据表格,更像是一个象棋盘组件。
在对象资源管理器中,我可以设置某些属性。最重要的是我可以设置网格尺寸(横向/纵向有多少个单元格),还可以设置跟绘制相关的选项,比如单元格是否应该为正方形,奇偶单元格的颜色等。
我的第一个版本将属性直接放在类上,当我更改属性时,设计时绘图会立即更新。由于组件越来越复杂,我想把我的属性整理得更好一些,引入了一些“选项属性”,如绘图选项、行为选项等。引入这些选项后,设计时绘图不再像以前那样即时更新。每次更改属性后,我都必须单击组件才能进行更新。有人能告诉我这是为什么吗?
下面是代码的简化版本。希望能够解释这种行为。 (PS:虽然我使用 Delphi 自 1997 年以来,但这是我第一个组件,如果有人能发现我做得有什么愚蠢的地方,请随便告诉我)
unit GridPaintBox;

interface

type
  TGridDrawOption = (gdoSquareCells,gdoCenterCells,gdoDrawCellEdges,gdoDrawFocus);
  TGridDrawOptions = set of TGridDrawOption;

  TGridOptions = class(TPersistent)
  private
    FCellsX : integer;
    FCellsY : integer;
    FDrawOptions : TGridDrawOptions;
  public
    constructor Create(aGridPaintBox : TGridPaintBox);
    procedure Assign(Source : TPersistent); override;
  published
    property CellsX : integer read FCellsX write FCellsX;
    property CellsY : integer read FCellsY write FCellsY;
    property DrawOptions : TGridDrawOptions read FDrawOptions write FDrawOptions;
  end;

  TGridPaintBox = class(TPaintBox)
  private
    FGridOptions : TGridOptions;
    FFocusedX,
    FFocusedY : integer;
    FOnFocusChanged: TNotifyEvent; 
    procedure CalculateSizeAndPosition; 
    procedure DrawCell(X,Y : integer);
    procedure DrawCells;
    procedure SetGridOptions(const Value: TGridOptions);
  protected
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
  public
    constructor Create(aOwner : TComponent); override;
    destructor Destroy; override;
    procedure Paint; override;
    procedure SetFocus(X,Y : integer);
  published
    property OnFocusChanged : TNotifyEvent read FOnFocusChanged write FOnFocusChanged;
    property Options : TGridOptions read FGridOptions write SetGridOptions;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('Samples', [TGridPaintBox]);
end;

procedure TGridPaintBox.CalculateSizeAndPosition;
begin
  <...>
end;

constructor TGridPaintBox.Create(aOwner: TComponent);
begin
  inherited;
  FGridOptions := TGridOptions.Create(self);
end;

procedure TGridPaintBox.DrawCell(X, Y: integer);
begin
  <...>
end;

procedure TGridPaintBox.DrawCells;
var
  X,Y : integer;
begin
  CalculateSizeAndPosition;

  for Y := 0 to FGridOptions.CellsY-1 do
    for X := 0 to FGridOptions.CellsX-1 do
      DrawCell(X,Y);
end;

procedure TGridPaintBox.Paint;
begin
  Canvas.Font := Font;
  Canvas.Brush.Color := Color;
  Canvas.Brush.Style := bsSolid;
  Canvas.FillRect(Rect(0,0,Width,Height));
  DrawCells;
  if Assigned(OnPaint) then
    OnPaint(Self); 
end;

procedure TGridPaintBox.SetGridOptions(const Value: TGridOptions);
begin
  FGridOptions.Assign(Value);
  invalidate;
end;

procedure TGridPaintBox.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  SetFocus(PixelToCellX(X),PixelToCellY(Y));
  inherited;
end;

procedure TGridPaintBox.SetFocus(X, Y: integer);
begin
  if (FocusedX=X) and (FocusedY=Y) then 
    exit;

  FFocusedX := X;
  FFocusedY := Y;

  if assigned(OnFocusChanged) then
    OnFocusChanged(self);

  invalidate;
end;

constructor TGridOptions.Create(aGridPaintBox : TGridPaintBox);
begin
  FCellsX := 20;
  FCellsY := 8;
  FDrawOptions := [gdoSquareCells,gdoCenterCells,gdoDrawCellEdges];
end;

procedure TGridOptions.Assign(Source : TPersistent);
begin
  if Source is TGridOptions then
  begin
    FCellsX := TGridOptions(Source).CellsX;
    FCellsY := TGridOptions(Source).CellsY;
    FDrawOptions := TGridOptions(Source).DrawOptions;
  end
  else
    inherited;
end;

end.
1个回答

13

发生这种情况是因为您没有为选项集设置setter,这将使您的控件无效。尽管在表单设计器中单击会调用控件进行无效操作,但您应该在此类选项设置器中自行处理此操作。因此,我建议为更好地访问直接所有者类实例而存储选项所有者,并在选项设置器中强制此所有者即控件重新绘制:

type
  TGridPaintBox = class;
  TGridDrawOption = (gdoSquareCells, gdoCenterCells, gdoDrawCellEdges, gdoDrawFocus);
  TGridDrawOptions = set of TGridDrawOption;
  TGridOptions = class(TPersistent)
  private
    FOwner: TGridPaintBox;
    FCellsX: Integer;
    FCellsY: Integer;
    FDrawOptions: TGridDrawOptions;
    procedure SetCellsX(AValue: Integer);
    procedure SetCellsY(AValue: Integer);
    procedure SetDrawOptions(const AValue: TGridDrawOptions);
  public
    constructor Create(AOwner: TGridPaintBox);
    procedure Assign(ASource: TPersistent); override;
  published
    property CellsX: Integer read FCellsX write SetCellsX;
    property CellsY: Integer read FCellsY write SetCellsY;
    property DrawOptions: TGridDrawOptions read FDrawOptions write SetDrawOptions;
  end;

implementation

constructor TGridOptions.Create(AOwner: TGridPaintBox);
begin
  FOwner := AOwner;
  FCellsX := 20;
  FCellsY := 8;
  FDrawOptions := [gdoSquareCells, gdoCenterCells, gdoDrawCellEdges];
end;

procedure TGridOptions.SetCellsX(AValue: Integer);
begin
  if FCellsX <> AValue then
  begin
    FCellsX := AValue;
    FOwner.Invalidate;
  end;
end;

procedure TGridOptions.SetCellsY(AValue: Integer);
begin
  if FCellsY <> AValue then
  begin
    FCellsY := AValue;
    FOwner.Invalidate;
  end;
end;

procedure TGridOptions.SetDrawOptions(const AValue: TGridDrawOptions);
begin
  if FDrawOptions <> AValue then
  begin
    FDrawOptions := AValue;
    FOwner.Invalidate;
  end;
end;

更多说明:

如果您不需要显示“画笔框”控件的属性和事件,例如ColorFontOnPaint事件,请从TGraphicControl派生您的控件,而不是从TPaintBox派生。您可以根据自己的需要选择要发布的属性和事件。


1
是的,打错字了。实际上是在TGridPaintBox类中,但我在编辑这篇文章时搞砸了。我会更新我的问题。 - Svein Bringsli
1
另外,感谢您关于从TGraphicControl派生而不是TPaintBox的提示。当然,还要感谢您提供的其他答案。它解决了我的问题 :-) - Svein Bringsli

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