-1

I am trying to print a panel and it's child controls inside of it. But when I run the program, it is throwing an error:

System.ArgumentException: 'targetBounds'

in line ctl.DrawToBitmap(bitmap, bounds);

Here is the code I used from this answer

private void BtnPrint_Click(object sender, EventArgs e)
    {
        Print(this.pnlID);
    }


    public void Print(Panel pnl)
    {
        PrinterSettings ps = new PrinterSettings();
        //pnlID = pnl;
        //GetPrintArea(pnl);
        prntprvw.Document = printdoc;
        printdoc.PrintPage += new PrintPageEventHandler(printdoc_printpage);
        prntprvw.ShowDialog();

    }

    public void printdoc_printpage(Object sender, PrintPageEventArgs e)
    {
        var bitmap = ScrollableControlToBitmap(this.pnlID, false);
        Rectangle pagearea = e.PageBounds;
        e.Graphics.DrawImage(bitmap, new Rectangle( 0, 0, pnlID.Width, pnlID.Height));
    }


    public Bitmap ScrollableControlToBitmap(ScrollableControl canvas, bool includeHidden)
    {
        canvas.AutoScrollPosition = new Point(0, 0);
        if (includeHidden)
        {
            canvas.SuspendLayout();
            foreach (Control child in canvas.Controls)
            {
                child.Visible = true;
            }
            canvas.ResumeLayout(true);
        }

        Size containerSize = canvas.PreferredSize;
        var bitmap = new Bitmap(containerSize.Width, containerSize.Height, PixelFormat.Format32bppArgb);
        bitmap.SetResolution(this.DeviceDpi, this.DeviceDpi);

        var graphics = Graphics.FromImage(bitmap);
        graphics.Clear(canvas.BackColor);
        var rtfPrinter = new RichEditPrinter(graphics);

        try
        {
            DrawNestedControls(canvas, canvas, new Rectangle(Point.Empty, containerSize), bitmap, rtfPrinter);
            return bitmap;
        }
        finally
        {
            rtfPrinter.Dispose();
            graphics.Dispose();
        }
    }

    private void DrawNestedControls(Control outerContainer, Control parent, Rectangle parentBounds, Bitmap bitmap, RichEditPrinter rtfPrinter)
    {
        for (int i = parent.Controls.Count - 1; i >= 0; i--)
        {
            var ctl = parent.Controls[i];
            var clipBounds = (parent == outerContainer)
                           ? ctl.Bounds
                           : Rectangle.Intersect(new Rectangle(Point.Empty, parentBounds.Size), ctl.Bounds);

            if (ctl.Visible)
            {
                var bounds = outerContainer.RectangleToClient(parent.RectangleToScreen(clipBounds));

                if (ctl is RichTextBox rtb)
                {
                    rtfPrinter.DrawRtf(rtb.Rtf, parentBounds, clipBounds);
                }
                else
                {
                    ctl.DrawToBitmap(bitmap, bounds);
                }
            }
            if (ctl.HasChildren)
            {
                DrawNestedControls(outerContainer, ctl, clipBounds, bitmap, rtfPrinter);
            }
        }
    }


    public class RichEditPrinter : IDisposable
    {
        Graphics dc = null;
        RTBPrinter rtb = null;

        public RichEditPrinter(Graphics graphics)
        {
            this.dc = graphics;
            this.rtb = new RTBPrinter() { ScrollBars = RichTextBoxScrollBars.None };
        }

        public void DrawRtf(string rtf, Rectangle canvas, Rectangle layoutArea)
        {
            rtb.Rtf = rtf;
            rtb.Draw(dc, canvas, layoutArea);
            rtb.Clear();
        }
        public void Dispose() => this.rtb.Dispose();

        private class RTBPrinter : RichTextBox
        {
            //Code converted from code found here: http://support.microsoft.com/kb/812425/en-us
            public void Draw(Graphics g, Rectangle hdcArea, Rectangle layoutArea)
            {
                IntPtr hdc = g.GetHdc();
                var canvasAreaTwips = new RECT().ToInches(hdcArea);
                var layoutAreaTwips = new RECT().ToInches(layoutArea);

                FORMATRANGE fmtRange = new FORMATRANGE()
                {
                    chrg = new CHARRANGE() { cpMax = -1, cpMin = 0 },
                    hdc = hdc,
                    hdcTarget = hdc,
                    rc = layoutAreaTwips,
                    rcPage = canvasAreaTwips
                };

                IntPtr lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
                Marshal.StructureToPtr(fmtRange, lParam, false);

                SendMessage(this.Handle, EM_FORMATRANGE, (IntPtr)1, lParam);
                Marshal.FreeCoTaskMem(lParam);
                g.ReleaseHdc(hdc);
            }

            [DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            internal static extern int SendMessage(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam);

            internal const int WM_USER = 0x0400;
            // https://learn.microsoft.com/en-us/windows/win32/controls/em-formatrange
            internal const int EM_FORMATRANGE = WM_USER + 57;

            [StructLayout(LayoutKind.Sequential)]
            internal struct RECT
            {
                public int Left;
                public int Top;
                public int Right;
                public int Bottom;

                public Rectangle ToRectangle() => Rectangle.FromLTRB(Left, Top, Right, Bottom);
                public RECT ToInches(Rectangle rectangle)
                {
                    float inch = 14.9f;
                    return new RECT()
                    {
                        Left = (int)(rectangle.Left * inch),
                        Top = (int)(rectangle.Top * inch),
                        Right = (int)(rectangle.Right * inch),
                        Bottom = (int)(rectangle.Bottom * inch)
                    };
                }
            }

            // https://learn.microsoft.com/en-us/windows/win32/api/richedit/ns-richedit-formatrange?
            [StructLayout(LayoutKind.Sequential)]
            internal struct FORMATRANGE
            {
                public IntPtr hdcTarget;    // A HDC for the target device to format for
                public IntPtr hdc;          // A HDC for the device to render to, if EM_FORMATRANGE is being used to send the output to a device
                public RECT rc;             // The area within the rcPage rectangle to render to. Units are measured in twips.
                public RECT rcPage;         // The entire area of a page on the rendering device. Units are measured in twips.
                public CHARRANGE chrg;      // The range of characters to format (see CHARRANGE)
            }

            [StructLayout(LayoutKind.Sequential)]
            internal struct CHARRANGE
            {
                public int cpMin;           // First character of range (0 for start of doc)
                public int cpMax;           // Last character of range (-1 for end of doc)
            }
        }
    }

How can I fix the error?

kmandrew
  • 43
  • 6
  • 1
    Please, provide attribution when you copy code from someone else. `ScrollableControlToBitmap()` and related code is taken from [here](https://stackoverflow.com/a/57257205/7444103). The problem is, most probably, that at least one controls has one *null* dimension. Often this happens when you have Labels set to `AutoSize = true` and there is no content, so `Control.Bounds` returns a container that cannot be printed. I've updated the code. But you can just add `if (ctl.Width < 1 || ctl.Height < 1) continue;` after: `var ctl = parent.Controls[i];` in the `private DrawNestedControls()` method. – Jimi Apr 03 '20 at 12:00
  • Although, I'd prefer the helper method to throw if this condition is met: this code doesn't get to the User scope. It's in your code scope. This tells you, while designing, that you're providing elements that cannot be printed. You should know about it. The exception is thrown when debugging, so you can correct the problem, instruct to ignore it or whatever you deem necessary. Maybe pre-filter the collection to exclude non-printable objects. – Jimi Apr 03 '20 at 13:03
  • @Jimi thank you so much, it is all working now. I also edited my question and added the link to thewhere I copy the code – kmandrew Apr 03 '20 at 22:39

1 Answers1

0

Adding the if (ctl.Width < 1 || ctl.Height < 1) continue; fixed the problem.

kmandrew
  • 43
  • 6