• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 4666
  • Last Modified:

Draw on Panel's canvas - richedit and image

Hi

I would like to draw a RichEdit content and Image on Panel. I guess this is drawing on Panel's canvas, right? I found a lot of examples, but nothing actually worked on Panel, or I couldn't make it work.

So, I have a TImage and TRichEdit and would like to have the content written on TPanel control. I also need to be able to change the Panel's color - which should be also a background of the text.

Thank you
0
Delphi_developer
Asked:
Delphi_developer
  • 5
  • 4
1 Solution
 
HypoCommented:
Hi, this is a crude example of how you can do the things you want. I think it will be enough to get you started with what you want to do.

Create a new application, add one TImage and load it with an image, one TRichEdit, one TPanel and then one TButton. Add the unit RichEdit to your uses-clause, and then insert the code below and connect the events. It's not the most perfect example, but it works for me, and does what you want to do. Changing the color of the TPanel is made by just setting the TPanels color property.

Note: When painting on a canvas as I've done in this example, it will be overwritten when the control is repainted (e.g. when a window is moved over the control). To make your painting persistent, you have to overwride wither WM_PAINT or the paint-method of the control.

regards
Hypo.
// Add this as the first type in your unit to make the Canvas property public for TPanel... 
  TPanel = class(ExtCtrls.TPanel)
    public
     Property Canvas;
  end;
 
// Add these functions to the implementation-section of your code. 
 
Function PixelsToTwips(ADC : HDC; APoint : TPoint) : TPoint;
var ppiX, ppiY : Integer;
begin
  ppiX := GetDeviceCaps(ADC, LOGPIXELSX);
  ppiY := GetDeviceCaps(ADC, LOGPIXELSY);
  if (ppiX <> 0) and (ppiY <> 0) then begin
    Result.X := Round(1440 / ppiX * APoint.X);
    Result.Y := Round(1440 / ppiY * APoint.Y);
  end else
    Result := Point(0, 0);
End;
 
Function RectToTwips(ADC : HDC; ARect : TRect) : TRect;
Begin
  Result.TopLeft := PixelsToTwips(ADC, ARect.TopLeft);
  Result.BottomRight := PixelsToTwips(ADC, ARect.BottomRight);
End;
 
procedure TForm1.Button1Click(Sender: TObject);
var fmtRange: TFormatRange;
    hDCTarget : HDC;
    aRect : TRect;
begin
  Panel1.Canvas.StretchDraw(Rect(10, 10, 100, 100), Image1.Picture.Graphic);
  RichEdit1.PaintTo(Panel1.Canvas, 110, 10);
  FillChar(fmtRange, SizeOf(fmtRange), 0);
  with fmtRange do begin
    hDC := Panel1.Canvas.Handle;
    hdcTarget := hDC;
    rcPage := RectToTwips(hdc, Panel1.BoundsRect);
    aRect := RichEdit1.ClientRect;
    OffsetRect(aRect, 113, 13);
    rc := RectToTwips(hdc, aRect);
    chrg.cpMin := 0;
    chrg.cpMax := RichEdit1.GetTextLen;
  end;
  Richedit1.Perform(EM_FORMATRANGE, 1, Longint(@fmtRange));
  { Free cached information }
  Richedit1.Perform(EM_FORMATRANGE, 0, 0);
end;

Open in new window

0
 
Delphi_developerAuthor Commented:
Hi

thank you.

1. About the color of the Panel changing, I thought that the text written on canvas will have Panel's background color, so when it changes, the text is still visible, and the background changes. So, I thought it would be 'transparent' area.

Also, I need for this to work even if RichEdit is not actual control (I create it in my code) - in this case line: RC.ClientRect; doesn't compile... I guess because

Sorry, I guess I should have explained in more details. I'm creating a component that will draw the Richedit text and image on Panel. And panel should behave correctly on MouseEnter,MouseLeave,Click, on color change... and the content on the Panel should stay visible - I guess this is what you were talking about in "WM_PAINT or the paint-method of the control.", right?

2. And Image is a bit distorted. Do you have a suggestion how to fix this?
0
 
HypoCommented:
Hi,
The image is distorted because I used StretchDraw instead of Draw. If you use Draw, then the image will be painted to the TPanels Canvas without being resized in any aspect... If you want to have the image resized to fit the panel, but still keep the aspect ratio of the image, then you need to do some more calculations before you paint the image. Otherwise, just replace the first StretchDraw line with this one:
"Panel1.Canvas.Draw(10, 10, Image1.Picture.Graphic);"

If you want to paint the text directly to the TPanel, without painting the borders of the RichEdit Control, then you shall remove the line "RichEdit1.PaintTo(Panel1.Canvas, 110, 10);" from my example, which paints the borders of the RichEdit Control. Doing that, you would end up just painting the text onto the TPanel.

The reason why the line with ClientRect doesn't work, is because ClientRect is depending on the width and height of the Control. If you have your control on the form, then It's not likely it has a ClientRect at all. Anyway, I only used ClientRect because it was fast and easy to use, you can replace RichEdit1.ClientRect with Panel1.ClientRect (and then remove the OffsetRect line below) and that would paint the text all over the Panel, instead of just in specific part of the Panel. You could also replace Panel1.ClientRect with any coordinates you want using Rect(left, top, right, bottom);

Also, The reason why the content of the panel is removed, is because we only paint on it when we click the button, and it needs to be repainted whenever something moved over it, or whenever itself moved behind or outside the screen. As you've already figured, this is done by overriding the Paint method, overwriding the messagehandler for the WMPaint message or just connecting to the OnPaint event of the TPanel (you have to make it public before you du that). See my new example for this...

I modified my example a bit, the new code is below. This code is for a Form with one TImage, One TRichEdit and one TPanel. It's not supposed to be pretty code, so you should definetly not use it as a design pattern :D As I said before, it's just a quick and dirty example of how to do things to show the effect you're after.

Anyway, What is the kind of RichEdit you are using, if you aren't using an actual Control?

regards
Hypo
unit Unit1;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, ExtCtrls, RichEdit;
 
type
  // Add this as the first type in your unit to make the Canvas property public for TPanel...
  TPanel = class(ExtCtrls.TPanel)
    protected
     FOnPaint : TNotifyEvent;
     Procedure Paint; Override;
    public
     Property Canvas;
     Property OnPaint : TNotifyEvent read FOnPaint write FOnPaint;
  end;
 
  TForm1 = class(TForm)
    Panel1: TPanel;
    Image1: TImage;
    RichEdit1: TRichEdit;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure Panel1Paint(Sender: TObject);
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
Function PixelsToTwips(ADC : HDC; APoint : TPoint) : TPoint;
var ppiX, ppiY : Integer;
begin
  ppiX := GetDeviceCaps(ADC, LOGPIXELSX);
  ppiY := GetDeviceCaps(ADC, LOGPIXELSY);
  if (ppiX <> 0) and (ppiY <> 0) then begin
    Result.X := Round(1440 / ppiX * APoint.X);
    Result.Y := Round(1440 / ppiY * APoint.Y);
  end else
    Result := Point(0, 0);
End;
 
Function RectToTwips(ADC : HDC; ARect : TRect) : TRect;
Begin
  Result.TopLeft := PixelsToTwips(ADC, ARect.TopLeft);
  Result.BottomRight := PixelsToTwips(ADC, ARect.BottomRight);
End;
 
 
{ TPanel }
 
procedure TPanel.Paint;
begin
  inherited;
  if Assigned(FOnPaint) then FOnPaint(Self);
end;
 
{ TForm1 }
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  Panel1.OnPaint := Panel1Paint;
  Panel1.Color := Richedit1.Color;
end;
 
procedure TForm1.Panel1Paint(Sender: TObject);
var fmtRange: TFormatRange;
    hDCTarget : HDC;
    aRect : TRect;
begin
  Panel1.Canvas.Draw(10, 10, Image1.Picture.Graphic);
  FillChar(fmtRange, SizeOf(fmtRange), 0);
  with fmtRange do begin
    hDC := Panel1.Canvas.Handle;
    hdcTarget := hDC;
    Panel1.Canvas.Brush.Color := clBtnFace;
    rcPage := RectToTwips(hdc, Panel1.BoundsRect);
    aRect := Panel1.ClientRect;
    rc := RectToTwips(hdc, aRect);
    chrg.cpMin := 0;
    chrg.cpMax := RichEdit1.GetTextLen;
  end;
  Richedit1.Perform(EM_FORMATRANGE, 1, Longint(@fmtRange));
  { Free cached information }
  Richedit1.Perform(EM_FORMATRANGE, 0, 0);
end;

Open in new window

0
VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

 
Delphi_developerAuthor Commented:
Thanx, I use TrichEdit because I need to format the text (bold, italic, underline, text color...), but I need to have it displayed on a panel along with TImage, which is also just a 'temp' control.

This is how it should work:

1. prepare and format the text (use TRichEdit since the text is easily formatted); I use local variable TRchEdit for this:

var RC:TRichEdit;
...


2. Read image

var vImage:TImage;

vImage:=TImage.Create(nil) ;
vImage.LoadFromFile();

3. Create a Panel and put image and text on it side by side.


This is how it should work, so I assumed it would be possible to paint RichEdit and Image content to Panel. Because I need to have behavior of Panel and it's content as if this is just one control, Panel control. So, if I 'hover' with a mouse the Panel should change background to visually show what content is being 'clicked' on... if mouse click is done.
This is the reason for non-visible controls, but they are all create and only Panel is put on form.

This is the reason why RichEdit.CLientRect doesn't work, because the control is not on form.

In your last example the RichEdit content is always on white background, even if I change the color of Panel.

I also tried with creating controls, but it doesn't work. I tried just for TRichEdit:

RC:TRichEdit;
begin

var RC:TRichEdit;
begin
  RC:=TrichEdit.Create(nil);
  RC.Width:=100;
  RC.Height:=100;

  RC.SelAttributes.Style := [fsbold];
  RC.SelAttributes.Size := 10;

  RC.Lines.Add('Get more info on product here:);
  RC.Lines.Add('Next line in RichEdit...);
...

But id doesn't compile the line:

chrg.cpMax := RC.GetTextLen;

0
 
HypoCommented:
One way you can do the transparent text, is to first paint the RichEdit to a bitmap, to which you set the transparancy proeprty, and then you paint this bitmap onto the Panel... I've made an example for you to look at which does that.
Anyhow, If you intend to repaint the panel often, then I would recommend that you create a bitmap in the forms OnCreate instead, and hold it throughout the lifetime of the application to prevent the application from reallocating and freeing the bitmap memory all the time; if not, then you can do it as I've done it in my example, which is to create and free the bitmap whenever time the panel is repainted.

regards
Hypo
unit Unit1;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, ExtCtrls, RichEdit, jpeg;
 
type
  // Add this as the first type in your unit to make the Canvas property public for TPanel...
  TPanel = class(ExtCtrls.TPanel)
    protected
     FOnPaint : TNotifyEvent;
     Procedure Paint; Override;
    public
     Property Canvas;
     Property OnPaint : TNotifyEvent read FOnPaint write FOnPaint;
  end;
 
  TForm1 = class(TForm)
    Panel1: TPanel;
    Image1: TImage;
    RichEdit1: TRichEdit;
    procedure FormCreate(Sender: TObject);
    procedure RichEdit1Change(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    procedure Panel1Paint(Sender: TObject);
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
Function PixelsToTwips(ADC : HDC; APoint : TPoint) : TPoint;
var ppiX, ppiY : Integer;
begin
  ppiX := GetDeviceCaps(ADC, LOGPIXELSX);
  ppiY := GetDeviceCaps(ADC, LOGPIXELSY);
  if (ppiX <> 0) and (ppiY <> 0) then begin
    Result.X := Round(1440 / ppiX * APoint.X);
    Result.Y := Round(1440 / ppiY * APoint.Y);
  end else
    Result := Point(0, 0);
End;
 
Function RectToTwips(ADC : HDC; ARect : TRect) : TRect;
Begin
  Result.TopLeft := PixelsToTwips(ADC, ARect.TopLeft);
  Result.BottomRight := PixelsToTwips(ADC, ARect.BottomRight);
End;
 
Procedure PaintRichEditToCanvas(ARichEdit : TRichEdit; ACanvas : TCanvas; ARect : TRect);
var fmtRange: TFormatRange;
begin
  FillChar(fmtRange, SizeOf(fmtRange), 0);
  with fmtRange do begin
    hDC := ACanvas.Handle;
    hdcTarget := hDC;
    rcPage := RectToTwips(hdc, ARect);
    rc := RectToTwips(hdc, ARect);
    chrg.cpMin := 0;
    chrg.cpMax := ARichEdit.GetTextLen;
  end;
  ARichedit.Perform(EM_FORMATRANGE, 1, Longint(@fmtRange));
  { Free cached information }
  ARichedit.Perform(EM_FORMATRANGE, 0, 0);
End;
 
 
{ TPanel }
 
procedure TPanel.Paint;
begin
  inherited;
  if Assigned(FOnPaint) then FOnPaint(Self);
end;
 
{ TForm1 }
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  Panel1.OnPaint := Panel1Paint;
end;
 
procedure TForm1.Panel1Paint(Sender: TObject);
var aText : TBitmap;
begin
  // Paint the image
  Panel1.Canvas.Draw(10, 10, Image1.Picture.Graphic);
  // Paint the text...
  aText := TBitmap.Create;
  aText.Width := Panel1.Width;
  aText.Height := Panel1.Height;
  aText.PixelFormat := pf32bit;
  aText.Canvas.Brush.Color := clWhite;
  aText.Canvas.Pen.Color := clWhite;
  aText.Canvas.Rectangle(0, 0, aText.Width, aText.Height);
  aText.TransparentColor := clWhite;
  aText.Transparent := True;
  try
    PaintRichEditToCanvas(RichEdit1, aText.Canvas, Rect(0, 0, aText.Width-1, aText.Height-1));
    Panel1.Canvas.Draw(0, 0, aText);
  finally
    aText.Free;
  end;
end;
 
procedure TForm1.RichEdit1Change(Sender: TObject);
begin
  Panel1.Repaint;
end;
 
end.

Open in new window

0
 
Delphi_developerAuthor Commented:
Great! It works, thanx. It just needs some polishing... when the background of the Panel changes to darker color (clGray) then the text (each characters) get non-smooth edges... I tried to manipulate the bitmap's colors:

   aText.Canvas.Brush.Color := clWhite;
   aText.Canvas.Pen.Color := clWhite;
and
   aText.TransparentColor := clWhite;

 but no luck. Any advise?

0
 
HypoCommented:
Ok, try this :)

  aText.Canvas.Brush.Color := Panel1.Color;
  aText.Canvas.Pen.Color := Panel1.Color;
  ...
  aText.TransparentColor := Panel1.Color;

/Hypo
0
 
HypoCommented:
Oops, you also need to set the RichEdits color, So try this instead...  

  RichEdit1.Color := Panel1.Color;
  aText.Canvas.Brush.Color := Panel1.Color;
  aText.Canvas.Pen.Color := Panel1.Color;
  ...
  aText.TransparentColor := Panel1.Color;

/Hypo
0
 
Delphi_developerAuthor Commented:
Great!
Thank you, very much.
0

Featured Post

Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

  • 5
  • 4
Tackle projects and never again get stuck behind a technical roadblock.
Join Now