vcl-168002-expressspreadsheet-how-to-custom-paint-table-views.md
Custom painting allows much deeper appearance customization compared to the capabilities provided by the Spreadsheet and Report Designer controls’ API. Handle a OnCustomDraw~ event corresponding to a specific worksheet area or visual element(s) to custom paint a Table View worksheet:
OnCustomDrawTableViewCell allows you to perform custom drawing on worksheet cells;
OnCustomDrawTableViewCommonCell allows you to custom draw the frozen pane separators and other helper table elements;
OnCustomDrawTableViewHeaderCell allows you to custom paint the cells within the column and row headers.
Custom painting can be useful for expanding the control with custom functionality. For example, you want to show formula calculation error hints to provide an end-user with more informative notifications than the standard error codes displayed within the formula cell. The example below illustrates how you can implement custom paint tasks in these cases.
First, implement the callout painting procedure that displays an error code hint:
procedure DrawCallout(ACanvas: TcxCanvas; AViewInfo: TdxSpreadSheetTableViewCellViewInfo; AError: TdxSpreadSheetFormulaErrorCode);
var
AClipRgn, AFrameRgn: TcxRegion;
FramePoints: array[0..5] of TPoint;
AMessage: UnicodeString;
AHOffset: Integer;
begin
case AError of // Pick an appropriate callout message according to the formula error code
ecNull:
begin
AMessage := 'Place separators between specified cell ranges';
AHOffset := 320;
end;
ecDivByZero:
begin
AMessage := 'Division by zero: check formula arguments';
AHOffset := 300;
end;
ecValue:
begin
AMessage := 'Wrong types of arguments';
AHOffset := 200;
end;
ecRefErr:
begin
AMessage := 'Invalid cell reference';
AHOffset := 200;
end;
ecName:
begin
AMessage := 'Formula is not recognized';
AHOffset := 200;
end;
ecNUM:
begin
AMessage := 'Invalid numeric argument';
AHOffset := 200;
end;
ecNA:
begin
AMessage := 'Result is not found';
AHOffset := 180;
end
else
begin
AMessage := 'OK';
AHOffset := 50;
end;
end;
ATopLeft.x = AViewInfo->DisplayBounds.left;
ATopLeft.y = AViewInfo->DisplayBounds.top;
FramePoints[0] := cxPointOffset(ATopLeft, 0, 10);
FramePoints[1] := cxPointOffset(FramePoints[0], 5, -10);
FramePoints[2] := cxPointOffset(FramePoints[1], AHOffset, 0);
FramePoints[3] := cxPointOffset(FramePoints[2], 0, -20);
FramePoints[4] := cxPointOffset(FramePoints[3], -AHOffset, 0);
FramePoints[5] := FramePoints[0];
AFrameRgn := TcxRegion.Create;
AFrameRgn.Handle := CreatePolygonRgn(FramePoints, Length(FramePoints), WINDING);
ACanvas.SaveState;
AClipRgn := TcxRegion.Create(AViewInfo.ViewInfo.CellsArea);
AClipRgn.Combine(AFrameRgn, roIntersect, False);
ACanvas.SetClipRegion(AClipRgn, roSet, False);
FillRegionByColor(ACanvas.Handle, AFrameRgn.Handle, clInfoBk);
FrameRgn(ACanvas.Handle, AFrameRgn.Handle, GetStockObject(BLACK_BRUSH), 1, 1);
ACanvas.Font.Name := 'Tahoma';
ACanvas.Font.Size := 10;
ACanvas.Font.Color := clBlack;
ACanvas.Font.Style := [fsBold];
ACanvas.Brush.Style := bsClear;
ACanvas.DrawTexT(AMessage, cxRect(FramePoints[4], FramePoints[2]),
cxAlignVCenter or cxAlignHCenter or cxSingleLine);
ACanvas.RestoreState;
ACanvas.SetClipRegion(AClipRgn, roSubtract);
AFrameRgn.Free;
end;
void DrawCallout(TcxCanvas *ACanvas, TdxSpreadSheetTableViewCellViewInfo *AViewInfo, TdxSpreadSheetFormulaErrorCode AError)
{
TcxRegion *AClipRgn, *AFrameRgn;
TPoint ATopLeft, FramePoints[6];
UnicodeString AMessage = "";
int AHOffset = 0;
switch(AError) { // Pick the appropriate callout message according to the formula error code
case ecNone:
AMessage = "OK";
AHOffset = 50;
break;
case ecNull:
AMessage = "Place separators between specified cell ranges";
AHOffset = 320;
break;
case ecDivByZero:
AMessage = "Division by zero: check formula arguments";
AHOffset = 300;
break;
case ecValue:
AMessage = "Wrong type of arguments";
AHOffset = 200;
break;
case ecRefErr:
AMessage = "Invalid cell reference";
AHOffset = 200;
break;
case ecName:
AMessage = "Function name is not recognized";
AHOffset = 250;
break;
case ecNUM:
AMessage = "Invalid numeric argument";
AHOffset = 200;
break;
case ecNA:
AMessage = "Result is not found";
AHOffset = 180;
break;
default:
AMessage = "OK";
AHOffset = 50;
}
ATopLeft.x = AViewInfo->DisplayBounds.left;
ATopLeft.y = AViewInfo->DisplayBounds.top;
FramePoints[0] = cxPointOffset(ATopLeft, 0, 10);
FramePoints[1] = cxPointOffset(FramePoints[0], 10, -10);
FramePoints[2] = cxPointOffset(FramePoints[1], AHOffset, 0);
FramePoints[3] = cxPointOffset(FramePoints[2], 0, -20);
FramePoints[4] = cxPointOffset(FramePoints[3], -AHOffset, 0);
FramePoints[5] = FramePoints[0];
AFrameRgn = &TcxRegion();
AFrameRgn->Handle = CreatePolygonRgn(FramePoints, 6, WINDING);
ACanvas->SaveState();
AClipRgn = &TcxRegion(static_cast<TdxSpreadSheetTableViewInfo*>(AViewInfo->Owner)->CellsArea);
AClipRgn->Combine(AFrameRgn, roIntersect, false);
ACanvas->SetClipRegion(AClipRgn, roSet, false);
FillRegionByColor(ACanvas->Handle, AFrameRgn->Handle, clInfoBk);
FrameRgn(ACanvas->Handle, AFrameRgn->Handle, static_cast<HBRUSH__*>(GetStockObject(BLACK_BRUSH)), 1, 1);
ACanvas->Font->Name = "Tahoma";
ACanvas->Font->Size = 10;
ACanvas->Font->Color = clBlack;
ACanvas->Font->Style = TFontStyles() << fsBold;
ACanvas->Brush->Style = bsClear;
ACanvas->DrawTexT(AMessage, cxRect(FramePoints[4], FramePoints[2]), cxAlignVCenter || cxAlignHCenter || cxSingleLine);
ACanvas->RestoreState();
ACanvas->SetClipRegion(AClipRgn, roSubtract);
AFrameRgn->Free();
}
Then, call the DrawCallout procedure within the OnCustomDrawTableViewCell event handler:
procedure SpreadSheetCustomDrawTableViewCell(Sender: TdxSpreadSheetTableView; ACanvas: TcxCanvas; AViewInfo: TdxSpreadSheetTableViewCellViewInfo; var AHandled: Boolean);
var
AErrorCode: TdxSpreadSheetFormulaErrorCode;
begin
if (AViewInfo.Cell = nil) or (AViewInfo.Cell.DataType <> cdtFormula) then Exit;
AErrorCode := AViewInfo.Cell.AsFormula.ErrorCode;
if (AViewInfo.Cell.AsFormula.ErrorCode <> ecNone) or
(not VarIsNumeric(AViewInfo.Cell.AsFormula.Value) or (AViewInfo.Cell.AsFormula.Value < 0)) then
begin
ACanvas.Brush.Color := clRed;
ACanvas.Font.Color := clAqua;
DrawCallout(ACanvas, AViewInfo, AErrorCode);
end;
end;
void __fastcall SpreadSheetCustomDrawTableViewCell(TdxSpreadSheetTableView *Sender, TcxCanvas *ACanvas, TdxSpreadSheetTableViewCellViewInfo *AViewInfo, bool &AHandled)
{
TdxSpreadSheetFormulaErrorCode AErrorCode;
if((AViewInfo->Cell == NULL) || (AViewInfo->Cell->DataType != cdtFormula)) { return; }
AErrorCode = AViewInfo->Cell->AsFormula->ErrorCode;
if((AViewInfo->Cell->AsFormula->ErrorCode != ecNone) ||
((!VarIsNumeric(AViewInfo->Cell->AsFormula->Value) || (AViewInfo->Cell->AsFormula->Value < 0)))
{
ACanvas->Brush->Color = clRed;
ACanvas->Font->Color = clAqua;
DrawCallout(ACanvas, AViewInfo, AErrorCode);
}
}
As a result, the Spreadsheet/Report Designer control displays formula error hints in callout boxes. The image below shows the division by zero error hint: