更改包含已写入文本的区域的背景
Change the background of an area containing already written text
(Delphi DX 10.3)
我有一个很大的空白(白色)canvas(在 Tpanel 的后代上),我在其中绘制了一些文本(使用 Textout(),假设文本始终为黑色)和图形(线条、矩形,没有那么复杂的东西)。
画完后,我需要改变一些特定区域的白色背景的颜色,从白色变成另一种颜色。
我想要达到的效果 很像带有彩色单元格的 excel sheet。在附加示例中,所有列都创建为空白(白色),如“value”和“difference”,然后是黄色(price) 和红色 (result) 列已被着色。
如果我可以在 写入文本之前 填写区域,我会使用 SetBkMode(TRANSPARENT) 并获得最佳结果。不幸的是,我需要在 之后 填写文本和图形。
我想到的第一个解决方案是逐像素替换(使用 Pixels[] 函数),但速度非常慢且图形效果不尽如人意。
所以我的问题是:如何为包含已写入文本的区域的背景着色?
这里是一个最小可重现示例。
按钮 1 执行像素替换,非常慢并且图形结果不令人满意。
按钮 2 在写入文本后使用 SetBkMode(TRANSPARENT) 填充区域。完美的结果,但我做不到
program BK_mode;
uses
Vcl.Forms,
main in 'main.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
////////////////////////////////////
unit main;
interface
uses Windows, Forms, SysUtils, Vcl.StdCtrls, UiTypes, System.Classes, Vcl.Controls, Vcl.ExtCtrls, Graphics, Dialogs;
type
TForm1 = class(TForm)
btn_01: TButton;
btn_02: TButton;
procedure btn_02Click(Sender: TObject);
procedure btn_01Click(Sender: TObject);
private
procedure write_text(canvas: TCanvas;x, y : integer;i_fontsize : smallint;const str_text: String;style : TFontStyles;bo_transparent : boolean);
procedure switch_color(canvas : TCanvas;color_source, color_target : TColor);
procedure prepare_example(bo_transparent : boolean);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
const
BASE_COLOR = clWhite;
ALTERNATIVE_COLOR = clRed;
procedure TForm1.write_text(canvas: TCanvas;x, y : integer;i_fontsize : smallint;const str_text: String;style : TFontStyles;bo_transparent : boolean);
begin
var lo_old_BK_color : TColor := canvas.Brush.Color;
var lo_old_BK_mode := GetBKMode(canvas.Handle);
if bo_transparent then SetBKMode(canvas.Handle, TRANSPARENT)
else begin
SetBKMode(canvas.Handle, OPAQUE);
canvas.Brush.Color := BASE_COLOR
end;
canvas.Font.Color := clBlack;canvas.Font.Size := i_fontsize;canvas.Font.Style := style;
TextOut(canvas.Handle, x, y, PChar(str_text), Length(str_text));
if NOT bo_transparent then canvas.Brush.Color := lo_old_BK_color;
SetBKMode(canvas.Handle, lo_old_BK_mode)
end;
procedure TForm1.switch_color(canvas : TCanvas;color_source, color_target : TColor);
begin
for var x := 0 to clientWidth-1 do
for var y := 0 to clientHeight - 1 do
if (canvas.Pixels[x, y] = color_source) then canvas.Pixels[x, y] := color_target
end;
procedure TForm1.prepare_example(bo_transparent : boolean);
begin
if bo_transparent then Color := ALTERNATIVE_COLOR else Color := BASE_COLOR;
invalidate;
application.MessageBox('Click me', 'Test');
canvas.Rectangle(10, 10, 200, 100);
canvas.MoveTo(10, 110);canvas.LineTo(200, 140);
canvas.MoveTo(10, 140);canvas.LineTo(200, 110);
write_text(canvas, 30, 30, 14, 'This is a text!', [], bo_transparent);
write_text(canvas, 30, 60, 11, 'This is another text!', [fsBold, fsItalic], bo_transparent)
end;
procedure TForm1.btn_01Click(Sender: TObject);
begin
prepare_example(FALSE);
switch_color(canvas, BASE_COLOR, ALTERNATIVE_COLOR)
end;
procedure TForm1.btn_02Click(Sender: TObject);
begin
prepare_example(TRUE)
end;
end.
/////////////////////////////////////////// ////////////
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 283
ClientWidth = 208
Color = clWhite
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
DesignSize = (
208
283)
PixelsPerInch = 96
TextHeight = 13
object btn_01: TButton
Left = 17
Top = 161
Width = 178
Height = 51
Anchors = [akLeft, akRight, akBottom]
Caption = '1. write text and fill area'
Font.Charset = ANSI_CHARSET
Font.Color = clWindowText
Font.Height = -16
Font.Name = 'Arial'
Font.Style = [fsBold]
ParentFont = False
TabOrder = 0
WordWrap = True
OnClick = btn_01Click
end
object btn_02: TButton
Left = 17
Top = 220
Width = 178
Height = 51
Anchors = [akLeft, akRight, akBottom]
Caption = '2. fill area then write text'
Font.Charset = ANSI_CHARSET
Font.Color = clWindowText
Font.Height = -16
Font.Name = 'Arial'
Font.Style = [fsBold]
ParentFont = False
TabOrder = 1
WordWrap = True
OnClick = btn_02Click
end
end
你所要求的是这种方式根本无法实现。您需要重绘整个 Canvas,在绘制背景之前在顶部绘制线条+文本。绘制完成后,您需要重新绘制以重做所有内容。不要在 Paint 事件之外的 Canvas 上绘制。
您的示例借鉴了 TForm.Canvas
,因此请使用 TForm.OnPaint
事件。对于 TPanel
后代,改写虚拟 Paint()
方法。无论哪种方式,保留一些具有所需设置的变量,在绘制时使用这些变量,并在更新变量后调用 Invalidate()
并触发重绘。
例如:
unit main;
interface
uses Windows, Forms, SysUtils, Vcl.StdCtrls, UiTypes, System.Classes, Vcl.Controls, Vcl.ExtCtrls, Graphics, Dialogs;
type
TForm1 = class(TForm)
btn_01: TButton;
btn_02: TButton;
procedure btn_02Click(Sender: TObject);
procedure btn_01Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormPaint(Sender: TObject);
private
FDrawTransparent : Boolean;
FDrawColor : TColor;
procedure write_text(ACanvas: TCanvas; x, y : integer; i_fontsize : smallint; const str_text: String; style : TFontStyles; bo_transparent : boolean);
procedure prepare_example(bo_transparent : boolean; color_target : TColor);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
const
BASE_COLOR = clWhite;
ALTERNATIVE_COLOR = clRed;
procedure TForm1.FormCreate(Sender: TObject);
begin
FDrawTransparent := False;
FDrawColor := BASE_COLOR;
end;
procedure TForm1.FormPaint(Sender: TObject);
begin
Canvas.Brush.Color := Self.Color;
Canvas.Rectangle(10, 10, 200, 100);
Canvas.MoveTo(10, 110);
Canvas.LineTo(200, 140);
Canvas.MoveTo(10, 140);
Canvas.LineTo(200, 110);
write_text(Canvas, 30, 30, 14, 'This is a text!', [], FDrawTransparent);
write_text(Canvas, 30, 60, 11, 'This is another text!', [fsBold, fsItalic], FDrawTransparent);
end;
procedure TForm1.write_text(ACanvas: TCanvas; x, y : integer; i_fontsize : smallint; const str_text: String;style : TFontStyles; bo_transparent : boolean);
begin
var lo_old_BK_color := ACanvas.Brush.Color;
var lo_old_BK_mode := GetBKMode(ACanvas.Handle);
if bo_transparent then
SetBKMode(ACanvas.Handle, TRANSPARENT)
else begin
SetBKMode(ACanvas.Handle, OPAQUE);
ACanvas.Brush.Color := FDrawColor;
end;
ACanvas.Font.Color := clBlack;
ACanvas.Font.Size := i_fontsize;
ACanvas.Font.Style := style;
TextOut(ACanvas.Handle, x, y, PChar(str_text), Length(str_text));
if NOT bo_transparent then ACanvas.Brush.Color := lo_old_BK_color;
SetBKMode(ACanvas.Handle, lo_old_BK_mode);
end;
procedure TForm1.prepare_example(bo_transparent : boolean; color_target: TColor);
begin
FDrawTransparent := bo_transparent;
FDrawColor := color_target;
if bo_transparent then Color := ALTERNATIVE_COLOR else Color := BASE_COLOR;
Invalidate;
Application.MessageBox('Click me', 'Test');
end;
procedure TForm1.btn_01Click(Sender: TObject);
begin
prepare_example(False, ALTERNATIVE_COLOR);
end;
procedure TForm1.btn_02Click(Sender: TObject);
begin
prepare_example(True, BASE_COLOR);
end;
end.
(Delphi DX 10.3)
我有一个很大的空白(白色)canvas(在 Tpanel 的后代上),我在其中绘制了一些文本(使用 Textout(),假设文本始终为黑色)和图形(线条、矩形,没有那么复杂的东西)。
画完后,我需要改变一些特定区域的白色背景的颜色,从白色变成另一种颜色。
我想要达到的效果 很像带有彩色单元格的 excel sheet。在附加示例中,所有列都创建为空白(白色),如“value”和“difference”,然后是黄色(price) 和红色 (result) 列已被着色。
如果我可以在 写入文本之前 填写区域,我会使用 SetBkMode(TRANSPARENT) 并获得最佳结果。不幸的是,我需要在 之后 填写文本和图形。 我想到的第一个解决方案是逐像素替换(使用 Pixels[] 函数),但速度非常慢且图形效果不尽如人意。
所以我的问题是:如何为包含已写入文本的区域的背景着色?
这里是一个最小可重现示例。
按钮 1 执行像素替换,非常慢并且图形结果不令人满意。
按钮 2 在写入文本后使用 SetBkMode(TRANSPARENT) 填充区域。完美的结果,但我做不到
program BK_mode;
uses
Vcl.Forms,
main in 'main.pas' {Form1};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
////////////////////////////////////
unit main;
interface
uses Windows, Forms, SysUtils, Vcl.StdCtrls, UiTypes, System.Classes, Vcl.Controls, Vcl.ExtCtrls, Graphics, Dialogs;
type
TForm1 = class(TForm)
btn_01: TButton;
btn_02: TButton;
procedure btn_02Click(Sender: TObject);
procedure btn_01Click(Sender: TObject);
private
procedure write_text(canvas: TCanvas;x, y : integer;i_fontsize : smallint;const str_text: String;style : TFontStyles;bo_transparent : boolean);
procedure switch_color(canvas : TCanvas;color_source, color_target : TColor);
procedure prepare_example(bo_transparent : boolean);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
const
BASE_COLOR = clWhite;
ALTERNATIVE_COLOR = clRed;
procedure TForm1.write_text(canvas: TCanvas;x, y : integer;i_fontsize : smallint;const str_text: String;style : TFontStyles;bo_transparent : boolean);
begin
var lo_old_BK_color : TColor := canvas.Brush.Color;
var lo_old_BK_mode := GetBKMode(canvas.Handle);
if bo_transparent then SetBKMode(canvas.Handle, TRANSPARENT)
else begin
SetBKMode(canvas.Handle, OPAQUE);
canvas.Brush.Color := BASE_COLOR
end;
canvas.Font.Color := clBlack;canvas.Font.Size := i_fontsize;canvas.Font.Style := style;
TextOut(canvas.Handle, x, y, PChar(str_text), Length(str_text));
if NOT bo_transparent then canvas.Brush.Color := lo_old_BK_color;
SetBKMode(canvas.Handle, lo_old_BK_mode)
end;
procedure TForm1.switch_color(canvas : TCanvas;color_source, color_target : TColor);
begin
for var x := 0 to clientWidth-1 do
for var y := 0 to clientHeight - 1 do
if (canvas.Pixels[x, y] = color_source) then canvas.Pixels[x, y] := color_target
end;
procedure TForm1.prepare_example(bo_transparent : boolean);
begin
if bo_transparent then Color := ALTERNATIVE_COLOR else Color := BASE_COLOR;
invalidate;
application.MessageBox('Click me', 'Test');
canvas.Rectangle(10, 10, 200, 100);
canvas.MoveTo(10, 110);canvas.LineTo(200, 140);
canvas.MoveTo(10, 140);canvas.LineTo(200, 110);
write_text(canvas, 30, 30, 14, 'This is a text!', [], bo_transparent);
write_text(canvas, 30, 60, 11, 'This is another text!', [fsBold, fsItalic], bo_transparent)
end;
procedure TForm1.btn_01Click(Sender: TObject);
begin
prepare_example(FALSE);
switch_color(canvas, BASE_COLOR, ALTERNATIVE_COLOR)
end;
procedure TForm1.btn_02Click(Sender: TObject);
begin
prepare_example(TRUE)
end;
end.
/////////////////////////////////////////// ////////////
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 283
ClientWidth = 208
Color = clWhite
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
DesignSize = (
208
283)
PixelsPerInch = 96
TextHeight = 13
object btn_01: TButton
Left = 17
Top = 161
Width = 178
Height = 51
Anchors = [akLeft, akRight, akBottom]
Caption = '1. write text and fill area'
Font.Charset = ANSI_CHARSET
Font.Color = clWindowText
Font.Height = -16
Font.Name = 'Arial'
Font.Style = [fsBold]
ParentFont = False
TabOrder = 0
WordWrap = True
OnClick = btn_01Click
end
object btn_02: TButton
Left = 17
Top = 220
Width = 178
Height = 51
Anchors = [akLeft, akRight, akBottom]
Caption = '2. fill area then write text'
Font.Charset = ANSI_CHARSET
Font.Color = clWindowText
Font.Height = -16
Font.Name = 'Arial'
Font.Style = [fsBold]
ParentFont = False
TabOrder = 1
WordWrap = True
OnClick = btn_02Click
end
end
你所要求的是这种方式根本无法实现。您需要重绘整个 Canvas,在绘制背景之前在顶部绘制线条+文本。绘制完成后,您需要重新绘制以重做所有内容。不要在 Paint 事件之外的 Canvas 上绘制。
您的示例借鉴了 TForm.Canvas
,因此请使用 TForm.OnPaint
事件。对于 TPanel
后代,改写虚拟 Paint()
方法。无论哪种方式,保留一些具有所需设置的变量,在绘制时使用这些变量,并在更新变量后调用 Invalidate()
并触发重绘。
例如:
unit main;
interface
uses Windows, Forms, SysUtils, Vcl.StdCtrls, UiTypes, System.Classes, Vcl.Controls, Vcl.ExtCtrls, Graphics, Dialogs;
type
TForm1 = class(TForm)
btn_01: TButton;
btn_02: TButton;
procedure btn_02Click(Sender: TObject);
procedure btn_01Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormPaint(Sender: TObject);
private
FDrawTransparent : Boolean;
FDrawColor : TColor;
procedure write_text(ACanvas: TCanvas; x, y : integer; i_fontsize : smallint; const str_text: String; style : TFontStyles; bo_transparent : boolean);
procedure prepare_example(bo_transparent : boolean; color_target : TColor);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
const
BASE_COLOR = clWhite;
ALTERNATIVE_COLOR = clRed;
procedure TForm1.FormCreate(Sender: TObject);
begin
FDrawTransparent := False;
FDrawColor := BASE_COLOR;
end;
procedure TForm1.FormPaint(Sender: TObject);
begin
Canvas.Brush.Color := Self.Color;
Canvas.Rectangle(10, 10, 200, 100);
Canvas.MoveTo(10, 110);
Canvas.LineTo(200, 140);
Canvas.MoveTo(10, 140);
Canvas.LineTo(200, 110);
write_text(Canvas, 30, 30, 14, 'This is a text!', [], FDrawTransparent);
write_text(Canvas, 30, 60, 11, 'This is another text!', [fsBold, fsItalic], FDrawTransparent);
end;
procedure TForm1.write_text(ACanvas: TCanvas; x, y : integer; i_fontsize : smallint; const str_text: String;style : TFontStyles; bo_transparent : boolean);
begin
var lo_old_BK_color := ACanvas.Brush.Color;
var lo_old_BK_mode := GetBKMode(ACanvas.Handle);
if bo_transparent then
SetBKMode(ACanvas.Handle, TRANSPARENT)
else begin
SetBKMode(ACanvas.Handle, OPAQUE);
ACanvas.Brush.Color := FDrawColor;
end;
ACanvas.Font.Color := clBlack;
ACanvas.Font.Size := i_fontsize;
ACanvas.Font.Style := style;
TextOut(ACanvas.Handle, x, y, PChar(str_text), Length(str_text));
if NOT bo_transparent then ACanvas.Brush.Color := lo_old_BK_color;
SetBKMode(ACanvas.Handle, lo_old_BK_mode);
end;
procedure TForm1.prepare_example(bo_transparent : boolean; color_target: TColor);
begin
FDrawTransparent := bo_transparent;
FDrawColor := color_target;
if bo_transparent then Color := ALTERNATIVE_COLOR else Color := BASE_COLOR;
Invalidate;
Application.MessageBox('Click me', 'Test');
end;
procedure TForm1.btn_01Click(Sender: TObject);
begin
prepare_example(False, ALTERNATIVE_COLOR);
end;
procedure TForm1.btn_02Click(Sender: TObject);
begin
prepare_example(True, BASE_COLOR);
end;
end.