2004-12-25
Painting a Control onto a Graphics object
Here's a little class in C#, that allows you to paint an existing Control onto a Graphics object. Nothing special, but useful if you need it. If you're using .NET 2.0, you don't need this class: see Control.DrawToBitmap.
Edited March 5th 2005: used some more flags, to also paint child controls and non-client elements like borders and titlebars. Thanks to Kent Boogaart. In this post on his blog, you can find a more flexible version of this method, where you can specify the flags yourself.
Edited October 29th 2005: as of .NET 2.0, the Control class has a method to draw itself to a Bitmap: DrawToBitmap. Thanks to an anonymous commenter for this tip.
using System; using System.Drawing; using System.Runtime.InteropServices; using System.Windows.Forms; public class ControlPainter { private const int WM_PRINT = 0x317, PRF_CLIENT = 4, PRF_CHILDREN = 0x10, PRF_NON_CLIENT = 2, COMBINED_PRINTFLAGS = PRF_CLIENT | PRF_CHILDREN | PRF_NON_CLIENT; [DllImport("USER32.DLL")] private static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, int lParam); public static void PaintControl(Graphics graphics, Control control) { // paint control onto graphics IntPtr hWnd = control.Handle; IntPtr hDC = graphics.GetHdc(); SendMessage(hWnd, WM_PRINT, hDC, COMBINED_PRINTFLAGS); graphics.ReleaseHdc(hDC); } }Using it is very easy: if you need to paint the Control myControl onto Graphics myGraphics, you just call ControlPainter.PaintControl(myGraphics, myControl)
Edited March 5th 2005: used some more flags, to also paint child controls and non-client elements like borders and titlebars. Thanks to Kent Boogaart. In this post on his blog, you can find a more flexible version of this method, where you can specify the flags yourself.
Edited October 29th 2005: as of .NET 2.0, the Control class has a method to draw itself to a Bitmap: DrawToBitmap. Thanks to an anonymous commenter for this tip.
Comments:
Post a Comment
Links to this post:
Very cool. I've long been trying to find a way to render a Form instance to an Image without showing the form on screen. Something like:
using (Form f = new MyForm()) {
Image i = new Image(f.Width, f.Height);
Graphics g = Graphics.FromImage(i);
ControlPainter.PaintControl(g, f);
}
However, I've never been able to get this to work and I don't know why. If you have any thoughts I'd love to hear them.
Cheers,
Kent
using (Form f = new MyForm()) {
Image i = new Image(f.Width, f.Height);
Graphics g = Graphics.FromImage(i);
ControlPainter.PaintControl(g, f);
}
However, I've never been able to get this to work and I don't know why. If you have any thoughts I'd love to hear them.
Cheers,
Kent
I just tried it, and it does work for simple controls like Buttons, but apparently not for Forms. It only draws the client rectangle (not the borders and titlebar), and it doesn't draw any of the child controls.
I don't really know how to solve this (yet).
I don't really know how to solve this (yet).
I just looked at this again and it turns out it was pretty easy to fix:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class ControlPainter
{
private const int WM_PRINT = 0x317, PRF_CLIENT = 4, PRF_CHILDREN = 0x10, PRF_NON_CLIENT = 2;
[DllImport("USER32.DLL")]
private static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, int lParam);
public static void PaintControl(Graphics graphics, Control control)
{ // paint control onto graphics
IntPtr hWnd = control.Handle;
IntPtr hDC = graphics.GetHdc();
SendMessage(hWnd, WM_PRINT, hDC, PRF_CLIENT | PRF_CHILDREN | PRF_NON_CLIENT);
graphics.ReleaseHdc(hDC);
}
}
The only problem is you still have to show the form for it to work (otherwise the non-client area has nothing in it). I got around this by showing the form off-screen:
using (Form f = new ToCapture())
{
f.StartPosition = FormStartPosition.Manual;
f.Location = new Point(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
f.Show();
Image i = new Bitmap(f.Width, f.Height);
using (Graphics g = Graphics.FromImage(i))
{
ControlPainter.PaintControl(g, f);
}
this.pictureBox1.Image = i;
}
That works and it's probably good enough for my needs. I'm still trying to understand why you need to show the form for it to work.
I can't thank you enough for pointing me in the right direction . . . :)
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class ControlPainter
{
private const int WM_PRINT = 0x317, PRF_CLIENT = 4, PRF_CHILDREN = 0x10, PRF_NON_CLIENT = 2;
[DllImport("USER32.DLL")]
private static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, int lParam);
public static void PaintControl(Graphics graphics, Control control)
{ // paint control onto graphics
IntPtr hWnd = control.Handle;
IntPtr hDC = graphics.GetHdc();
SendMessage(hWnd, WM_PRINT, hDC, PRF_CLIENT | PRF_CHILDREN | PRF_NON_CLIENT);
graphics.ReleaseHdc(hDC);
}
}
The only problem is you still have to show the form for it to work (otherwise the non-client area has nothing in it). I got around this by showing the form off-screen:
using (Form f = new ToCapture())
{
f.StartPosition = FormStartPosition.Manual;
f.Location = new Point(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
f.Show();
Image i = new Bitmap(f.Width, f.Height);
using (Graphics g = Graphics.FromImage(i))
{
ControlPainter.PaintControl(g, f);
}
this.pictureBox1.Image = i;
}
That works and it's probably good enough for my needs. I'm still trying to understand why you need to show the form for it to work.
I can't thank you enough for pointing me in the right direction . . . :)
Thanks for the link. Was wondering why I hadn't seen it before then I realised it was posted a couple of days ago ;)
That PrintWindow API function looks useful, even if the MSDN doco is a tad confusing. Does it work on 2000 or not? Will have to try later . . .
That PrintWindow API function looks useful, even if the MSDN doco is a tad confusing. Does it work on 2000 or not? Will have to try later . . .
I think it doesn't: the Platform SDK documentation says: Included in Windows XP and Windows Server 2003
As of .Net 2.0, you can do this with Control.DrawToBitmap:
Bitmap bitmap = new Bitmap(control.Width,control.Height);
control.DrawToBitmap(bitmap, new Rectangle(0,0,control.Width,control.Height));
Bitmap bitmap = new Bitmap(control.Width,control.Height);
control.DrawToBitmap(bitmap, new Rectangle(0,0,control.Width,control.Height));
What about painting an ActiveX control hosted insude a .Net application? None of the methods described work in case of an ActiveX.
I tried to use this method to paint a control every 50 milliseconds, and the memory is raising up quickly, an OOM exception thrown within one minute.
My code is:
System.Drawing.Image image = new Bitmap(_control.Width, _control.Height);
using (Graphics graphics = Graphics.FromImage(image))
{
ControlPainter.PaintControl(graphics, _control);
}
image.Dispose();
But if I use BitBlt instead of SendMessage, there is no such a memory leak issue. Any ideas?
My code is:
System.Drawing.Image image = new Bitmap(_control.Width, _control.Height);
using (Graphics graphics = Graphics.FromImage(image))
{
ControlPainter.PaintControl(graphics, _control);
}
image.Dispose();
But if I use BitBlt instead of SendMessage, there is no such a memory leak issue. Any ideas?
"What about painting an ActiveX control hosted insude a .Net application?"
----
I would love to know an anwer to this question as well.
Thanks
----
I would love to know an anwer to this question as well.
Thanks
Links to this post:

