You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi中TStringGrid派生组件打印与预览的通用绘制问题

TStringGrid打印与预览的通用绘制适配方案

1. 通用适配打印与预览的绘制过程实现

当然存在通用的绘制逻辑,核心是抛弃依赖Canvas自身的DPI自动适配,统一手动处理所有元素的DPI缩放——不管是Printer.Canvas还是PaintBox.Canvas,都用相同的缩放规则计算坐标、尺寸和字体参数,彻底规避两者Font.Assign的行为差异。

核心步骤:

  • 获取目标Canvas的DPI值:Printer.Canvas用Printer.PixelsPerInch,PaintBox.Canvas用GetDeviceCaps(PaintBox.Handle, LOGPIXELSX)获取精确屏幕DPI
  • 计算缩放因子:ScaleFactor := TargetDPI / OriginalDPI,其中OriginalDPI是组件设计时的基准DPI(通常为96)
  • 所有绘制的坐标、单元格宽高都乘以ScaleFactor,字体高度按点大小转换规则重新计算
  • 避免使用SetWorldTransform做整体缩放,因为GDI对字体和图形的缩放逻辑不一致,会导致字体尺寸异常

2. 字体正确渲染与小分辨率文本重叠问题修复

字体渲染修正

Printer.Canvas.Font.Assign会自动将组件字体的屏幕DPI尺寸转换为打印机DPI的点大小,而PaintBox.Canvas没有这个逻辑,需要手动按点大小转像素的公式计算字体高度:

  1. 先获取组件字体的点大小:OriginalPointSize := Abs(Self.Font.Height)(Delphi中Font.Height负数表示点大小)
  2. 目标Canvas下的字体高度计算:TargetHeight := -MulDiv(OriginalPointSize, TargetDPI, 72)(1点=1/72英寸,像素高度=点大小×DPI÷72)

小分辨率文本重叠修复

小分辨率下缩放后,单元格内边距的像素值会被压缩到几乎为0,导致文本和边框重叠。解决方法是:

  • 设置自适应内边距:设计时预留的内边距(比如2像素)乘以缩放因子后,强制保证最小为1像素,避免内边距消失
  • 绘制文本前,将单元格矩形向内收缩内边距,确保文本和边框之间有足够空间

完整代码示例

procedure TMyStringGrid.DrawPage(ACanvas: TCanvas; const TargetDPI: Integer);
var
  ScaleFactor: Double;
  OriginalPointSize: Integer;
  CellPadding: Integer;
  I, J: Integer;
  CellRect: TRect;
  ColWidth, RowHeight: Integer;
  SumColWidths: array of Integer;
  SumRowHeights: array of Integer;
begin
  // 预计算列宽、行高的累加值(可提前缓存)
  SetLength(SumColWidths, ColCount + 1);
  SetLength(SumRowHeights, RowCount + 1);
  SumColWidths[0] := 0;
  SumRowHeights[0] := 0;
  for I := 0 to ColCount - 1 do
    SumColWidths[I+1] := SumColWidths[I] + ColWidths[I];
  for I := 0 to RowCount - 1 do
    SumRowHeights[I+1] := SumRowHeights[I] + RowHeights[I];

  // 初始化Canvas状态
  ACanvas.Brush.Assign(Self.Brush);
  ACanvas.Pen.Assign(Self.Pen);

  // 1. 计算缩放因子(基于屏幕DPI为基准)
  ScaleFactor := TargetDPI / Screen.PixelsPerInch;

  // 2. 处理字体:按点大小转换到目标DPI
  OriginalPointSize := Abs(Self.Font.Height);
  ACanvas.Font.Assign(Self.Font);
  ACanvas.Font.Height := -MulDiv(OriginalPointSize, TargetDPI, 72);

  // 3. 计算自适应内边距(最小1像素)
  CellPadding := Max(1, Round(2 * ScaleFactor)); // 设计时2像素内边距

  // 4. 遍历绘制所有单元格
  for I := 0 to RowCount - 1 do
  begin
    RowHeight := Round(RowHeights[I] * ScaleFactor);
    for J := 0 to ColCount - 1 do
    begin
      ColWidth := Round(ColWidths[J] * ScaleFactor);
      // 计算缩放后的单元格矩形
      CellRect := Rect(
        Round(SumColWidths[J] * ScaleFactor),
        Round(SumRowHeights[I] * ScaleFactor),
        Round(SumColWidths[J+1] * ScaleFactor),
        Round(SumRowHeights[I+1] * ScaleFactor)
      );

      // 绘制单元格边框
      ACanvas.Rectangle(CellRect);

      // 收缩矩形,留出内边距
      InflateRect(CellRect, -CellPadding, -CellPadding);

      // 绘制单元格文本(支持自动换行,可根据需求调整对齐方式)
      ACanvas.TextRect(CellRect, CellRect.Left, CellRect.Top, Cells[J, I]);
    end;
  end;
end;

调用方式

  • 打印时:
Printer.BeginDoc;
try
  DrawPage(Printer.Canvas, Printer.PixelsPerInch);
finally
  Printer.EndDoc;
end;
  • 预览时(PaintBox的OnPaint事件):
procedure TForm1.PaintBox1Paint(Sender: TObject);
var
  TargetDPI: Integer;
begin
  TargetDPI := GetDeviceCaps(PaintBox1.Handle, LOGPIXELSX);
  MyStringGrid.DrawPage(PaintBox1.Canvas, TargetDPI);
end;

内容的提问来源于stack exchange,提问作者Petro K

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 11:27:24