1

Working in C# I have a project with the requirement to capture a Control or Form to a bitmap. I have a class which takes a Control parameter in the constructor and then executes the following code (simplified for this example) to save a bitmap of the Control.

public MyItem(Control control)
    {
        if (control != null)
        {
            Control rootParent = control;
            while (rootParent.Parent != null)
                rootParent = rootParent.Parent;

            rootParent.BringToFront();

            _bounds = control.Bounds;
            Rectangle controlBounds;

            if (control.Parent == null)
            {
                _bounds = new Rectangle(new Point(0, 0), control.Bounds.Size);
                controlBounds = _bounds;
            }
            else
            {
                _bounds.Intersect(control.Parent.ClientRectangle);
                _bounds = new Rectangle(rootParent.PointToClient(control.Parent.PointToScreen(_bounds.Location)), _bounds.Size);
                controlBounds = new Rectangle(rootParent.PointToClient(control.Parent.PointToScreen(control.Location)), control.Size);
            }

            if (_bounds.Height > 0 && _bounds.Width > 0)
            {
                IntPtr hDC = IntPtr.Zero;

                if (control.Parent == null && !Clarity.ClientAreaOnly)
                    // Used for capturing a form including non-client area
                    hDC = Win32.GetWindowDC(control.Handle);
                else
                    // Used for capturing a form excluding non-client area or a control
                    hDC = control.CreateGraphics().GetHdc();

                try
                {
                    _controlBitmap = new Bitmap(_bounds.Width, _bounds.Height);
                    using (Graphics bitmapGraphics = Graphics.FromImage(_controlBitmap))
                    {
                        IntPtr bitmapHandle = bitmapGraphics.GetHdc();
                        Win32.BitBlt(bitmapHandle, 0, 0, _bounds.Width, _bounds.Height, hDC, _bounds.X - controlBounds.X, _bounds.Y - controlBounds.Y, 13369376);
                        bitmapGraphics.ReleaseHdc(bitmapHandle);
                    }
                }
                finally
                {
                    if (hDC != IntPtr.Zero)
                        Win32.ReleaseDC(control.Handle, hDC);
                }
            }
        }
    }

An instance of this class is created for each control I need to capture, the bitmap is used (in this case drawing it to the screen) and the object is disposed of when no longer needed. This works great and gives me a bitmap of the specified Control or Form, including the non-client area in the case of the latter, as shown below:

https://i.stack.imgur.com/nXi2C.png

However, if I attempt to capture a Form again I am running into issues. If I have resized the Form before I capture it again the second capture will show the non-client area as incorrect.

Below is an image to illustrate this - on the left is how the form looks on-screen (correct) and on the right how the above code captures it (incorrect).

https://i.stack.imgur.com/BKggN.png

I've not come up with anything from my own searches and so wondered if someone could point out what I'm doing wrong/not doing?

  • Perhaps there is no code to fix this so you'll just have to get around it. Use a timer or similar new thread to count a short period of time before capturing the form image. – Colin Steel Aug 13 '13 at 15:32
  • Does this happen _during_ a resize, or anytime after a resize has finished? – DonBoitnott Aug 13 '13 at 15:34
  • @DonBoitnott Sorry, I should have made this clearer - it happens anytime after a resize. For example, I could resize the form (and the resize has finished), wait 10 seconds, then capture it and it still appears incorrect. What confuses me though is that everything in the _client area_ is correct, it's only the _non-client area_ that is incorrect. – Simon Peckmore Aug 13 '13 at 22:02
  • Looks to me like the Control bounds is not sized according to the form. Is it docked or anchored to ensure it resizes, or is it handled in code somehow? Because if your hDC is of the Control, and it's larger, you'd get a capture as in your 2nd example. – DonBoitnott Aug 14 '13 at 02:30
  • From what I can ascertain the bounds are correct - the bitmap is created based on the control bounds and this is always created correctly. Further, the client area is always captured correctly, it's the non-client area that always seems to fail. – Simon Peckmore Aug 27 '13 at 11:21

1 Answers1

0

I wasn't able to solve this issue as desired (by keeping the existing capture method) and, as this is for a prototype application, I haven't got the time at the moment to delve into it any further. If this prototype gets picked up or I have another opportunity to look into this again I will update this answer accordingly.

In the meantime I've used the following approach as an alternative. This produces output as desired although using a different mechanism to capture the content. I'm not hugely fond of this as it involves "messing" with the windows and forms more than I'd like but it suffices for now.

Basically I now just bring the window/form in question to the front, set it to top-most, then perform a screen capture on just the bounds of the window. As I say, messier than I'd like but it works well enough in practice!

public MyItem(Control control)
{
    if (control != null)
    {
        Control rootParent = control;
        while (rootParent.Parent != null)
            rootParent = rootParent.Parent;

        rootParent.BringToFront();

        _bounds = control.Bounds;
        Rectangle controlBounds;

        if (control.Parent == null)
        {
            _bounds = new Rectangle(new Point(0, 0), control.Bounds.Size);
            controlBounds = _bounds;
        }
        else
        {
            _bounds.Intersect(control.Parent.ClientRectangle);
            _bounds = new Rectangle(rootParent.PointToClient(control.Parent.PointToScreen(_bounds.Location)), _bounds.Size);
            controlBounds = new Rectangle(rootParent.PointToClient(control.Parent.PointToScreen(control.Location)), control.Size);
        }

        if (_bounds.Height > 0 && _bounds.Width > 0)
        {
            _controlBitmap = new Bitmap(_bounds.Width, _bounds.Height);
            using (Graphics bitmapGraphics = Graphics.FromImage(_controlBitmap))
            {
                if (control.Parent == null)
                {
                    Form form = control as Form;
                    Boolean currentTopMost = form.TopMost;
                    form.TopMost = true;
                    control.BringToFront();
                    bitmapGraphics.CopyFromScreen(control.Location, Point.Empty, _bounds.Size);
                    form.TopMost = currentTopMost;
                }
                else
                {
                    IntPtr hDC = IntPtr.Zero;
                    try
                    {
                        hDC = control.CreateGraphics().GetHdc();
                        IntPtr bitmapHandle = bitmapGraphics.GetHdc();
                        Win32.BitBlt(bitmapHandle, 0, 0, _bounds.Width, _bounds.Height, hDC, _bounds.X - controlBounds.X, _bounds.Y - controlBounds.Y, 13369376);
                        bitmapGraphics.ReleaseHdc(bitmapHandle);
                    }
                    finally
                    {
                        if (hDC != IntPtr.Zero)
                            Win32.ReleaseDC(control.Handle, hDC);
                    }
                }
            }
        }
    }
}