I have created a component, TGridPaintBox, based on TPaintBox. It is basically a paintbox with added "grid functionality". It's not a data grid. More like a chess board component.
In the object explorer I can set certain properties. Most importantly I can set the grid dimensions (how many cells across/down), but also options relating to drawing. Whether the cells should be square, the color of odd/even cells etc.
My first version of this component had properties directly on the class, and when I changed a property, the designtime drawing was updated immediately. As the component grew, I wanted to organize my properties a little better, and introduced some "options properties", like drawing options, behaviour options etc. After introducing this, the designtime drawing no longer updates like before. After changing a property, I have to click on the component for it to update. Can anyone tell me why this happens?
Here's a stripped down version of the code. I hope it will explain the behaviour:
(PS: This is my first component, even though I've been using Delphi since 1997, so if anyone can spot anything stupid in the way I've done it, please feel free to tell me)
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.
It happens because you don't have a setter for the options set which would invalidate your control which belongs to. The click in the form designer invokes the control to invalidate though, but you should handle this by your own in such options setter. So I would store the options owner for better access to the direct owner class instance and in the options setter force this owner, the control to redraw:
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;
Further notes:
If you don't explicitly need to have paint box' published properties and events like for instance Color
, Font
or OnPaint
event, derive your control from TGraphicControl
instead of from TPaintBox
. You can choose what properties and events will you publish by your own.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With