Question

I wish to off-screen render a Control to some bitmap so that I have quick access to it.

Unfortunately Control.DrawToBitmap seems to draw the entire control on which it is called including all it's child controls. Internally it issues a WM_PRINT message with self-provided DC to a bitmap. This temporary bitmap is then blitted to the user-provided Bitmap. Unacceptable for me, I'd rather have this bitmap updated as needed so my performance hit when the bitmap is needed is minimized.

In the ideal scenario I'd want the form to behave as if it were visible on the screen (but it cannot be). That would mean that if, say, some Control has its Text property changed, the form would be partially invalidated. Catching the relevant messages/events would then allow me to either specify my own DC for the form to be drawn on, or to simply BitBlt the forms DC to my own.

Some directions I have looked in:

  • The PaintEventArgs parameter in OnPaint seems to hold a member savedGraphicsState, perhaps it could be used to figure out what doesn't require invalidating
  • Having the form visible but outside the screen area. Controls then don't get painted, though.
  • Manually calling RedrawWindow() on the window, same story
Was it helpful?

Solution

I think there are two problems:

  1. finding out the invalid area of the control (without help from windows)
  2. rendering only the invalidated portion.

For the first issue, I think you are largely on your own. You should keep track which controls change, and have bookkeeping which need updating.

For the second issue, you can try to send the WM_PRINT message yourself, and provide a DC referring to only a small bitmap. The original DC API's allowed you to offset and clip the valid drawing area of a HDC. If you are very lucky, windows will deduce the render region from the HDC, and if it does not, most of the render commands that fall entirely out of the bitmap should be quite cheap as there are no pixels that need to change.

You should be able to verify this by printing to a 1x1 bitmap and test if it is faster, and/or verify if the clip region sent in WM_PAINT is reduced to the bitmap size.

OTHER TIPS

I think it will work, if the control is cloned, so that you get a control that does not sit on a form and which does not have child controls:

Control ctrl = ControlFactory.CloneCtrl(this.button3);
Bitmap bmp = new Bitmap(ctrl.Width, ctrl.Height);
ctrl.DrawToBitmap(bmp, new Rectangle(0, 0, ctrl.Width, ctrl.Height));
bmp.Save(@"C:\Users\Oli\Desktop\test.bmp");

I used the ControlFactory written by lxwde found in The Code Project.

The ControlFactory is not perfect, but it is simple enough and could easily be improved.

I've made an example project for you where I showed some onPaint events. If you cannot see it solved that way, just update the example.

Regards! OnPaint example

Download here: http://www.goldengel.ch/temp/OnPaintExample.zip

Private Sub Button1_Paint(sender As System.Object, e As System.Windows.Forms.PaintEventArgs) Handles Button1.Paint
    Dim bm As New Bitmap(Me.Button1.Width, Me.Button1.Height, PixelFormat.Format32bppRgb)

    Button1.DrawToBitmap(bm, New Rectangle(0, 15, bm.Width -5, bm.Height+2))
    Using gr As Graphics = Graphics.FromImage(bm)
        gr.DrawString(DateTime.Now.ToLongTimeString, Me.Font, Brushes.Lime, 0, 0)
    End Using
    Me.PictureBox1.BackgroundImageLayout = ImageLayout.Tile
    Me.PictureBox1.BackgroundImage = bm

End Sub
    Public Class myTextBox
        Inherits System.Windows.Forms.TextBox


        Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
            MyBase.OnPaint(e)
            e.Graphics.Clear(Color.Yellow)
            e.Graphics.DrawString(DateTime.Now.ToLongTimeString, Me.Font, Brushes.Gray, 0, 0)
        End Sub

        Public Sub New()
            SetStyle(ControlStyles.UserPaint, True)
        End Sub
    End Class
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top