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没有这个逻辑,需要手动按点大小转像素的公式计算字体高度:
- 先获取组件字体的点大小:
OriginalPointSize := Abs(Self.Font.Height)(Delphi中Font.Height负数表示点大小) - 目标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
相关产品推荐
相关产品推荐

