Capture a Form to a Bitmap after the Form has been resized

694 Views Asked by At

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?

1

There are 1 best solutions below

0
On

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);
                    }
                }
            }
        }
    }
}