Invalidate into own bitmap
Asked Answered
B

3

11

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
Bullfight answered 14/12, 2011 at 22:54 Comment(6)
I'm not entirely clear on why DrawToBitmap (the WM_PRINT message) is unacceptable for your use. Are you worried about performance? It's very unlikely that drawing into a bitmap is going to be any slower than drawing onto the screen.Rebekah
I need a buffering mechanism. DrawToBitmap paints the entire control from scratch, while the invalidating system used on normal windows would only update the regions that actually changed.Bullfight
@Frank, did you considered using Bitblt ? codeproject.com/KB/GDI-plus/Bitblt_wrapper_class.aspxEadie
Yes but it doesn't work if the Window isn't actually visible on the screen.Bullfight
@FrankRazenberg seems to be trying to create a control for display on a separate LCD. Presumably he has an API allowing him to send a bitmap to that LCD but it is not a display recognised by Windows so he cannot simply display the control on that screen. To that end, he is trying to get an image of an off-screen control and be notified when it needs updating, so he can send the updated portion of the bitmap to the separate screen. Presumably it is bandwidth constrained so sending the whole bitmap each time is not an option. is that correct??? if so that is the real question.Ceremonious
Nearly correct. The LCD can only take on an entirely new bitmap, but it refreshes fast enough. The bottleneck is drawing waiting for the WM_PRINT to have drawn the entire form. Instead, I want the form's bitmap buffered, but cannot figure out how (when to update buffer)Bullfight
M
1

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.

Microprint answered 21/1, 2012 at 19:59 Comment(0)
A
0

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.

Anecdotic answered 17/12, 2011 at 22:33 Comment(5)
Sorry, I don't see how this is relevant.Bullfight
You say that your solution unfortunately draws child controls. My solution does not. I have tried it with a panel containing buttons. It only draws the panel to the bitmap but without the child controls. It also works when the control is not visible on the screen.Anecdotic
I don't really understand what you are trying to achieve. You say: "I'd want the form to behave as if it were visible on the screen (but it cannot be)". Why can it not be visible on the screen? Why do you want to render a form that is not visible on the screen?Anecdotic
The rendered form's bitmap is to be sent to a separate LCD. I'd like the control to automatically validate itself whenever it normally would, for example when changings it's window text. I'd also like to know when this happens.Bullfight
There is a Invalidated event in Control. You could subscribe it for all controls.Anecdotic
Q
0

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
Quianaquibble answered 21/12, 2011 at 13:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.