与可滚动面板一起使用

与可滚动面板一起使用

本文介绍了如何将 PrintDocument 与可滚动面板一起使用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何将 PrintDocument 与可滚动面板一起使用?

这是我的一些代码:

MemoryImage = new Bitmap(pnl.Width, pnl.Height);矩形 rect = new Rectangle(0, 0, pnl.Width, pnl.Height);pnl.DrawToBitmap(MemoryImage, new Rectangle(0, 0, pnl.Width,pnl.Height));矩形 pagearea = e.PageBounds;e.Graphics.DrawImage(MemoryImage, (pagearea.Width/2) -(pannel.Width/2), pannel.Location.Y);
解决方案

这些方法集允许打印

VB.Net版同程序

How do I use PrintDocument with a scrollable panel`?

Here is some of my code:

MemoryImage = new Bitmap(pnl.Width, pnl.Height);
Rectangle rect = new Rectangle(0, 0, pnl.Width, pnl.Height);
pnl.DrawToBitmap(MemoryImage, new Rectangle(0, 0, pnl.Width,
pnl.Height));

Rectangle pagearea = e.PageBounds;
e.Graphics.DrawImage(MemoryImage, (pagearea.Width / 2) -
(pannel.Width / 2), pannel.Location.Y);
解决方案

These sets of methods allow to print the content of a ScrollableControl to a Bitmap.

A description of the procedure:

  1. The control is first scrolled back to the origin (control.AutoScrollPosition = new Point(0, 0); (an exception is raised otherwise: the Bitmap has a wrong size. You may want to store the current scroll position and restore it after).
  2. Verifies and stores the actual size of the Container, returned by the PreferredSize or DisplayRectangle properties (depending on the conditions set by the method arguments and the type of container printed). This property considers the full extent of a container.
    This will be the size of the Bitmap.
  3. Clears the Bitmap using the background color of the Container.
  4. Iterates the ScrollableControl.Controls collection and prints all first-level child controls in their relative position (a child Control's Bounds rectangle is relative to the container ClientArea.)
  5. If a first-level Control has children, calls the DrawNestedControls recursive method, which will enumerate and draw all nested child Containers/Controls, preserving the internal clip bounds.

Includes support for RichTextBox controls.
The RichEditPrinter class contains the logic required to print the content of a RichTextBox/RichEdit control. The class sends an EM_FORMATRANGE message to the RichTextBox, using the Device context of the Bitmap where the control is being printed.
More details available in the MSDN Docs: How to Print the Contents of Rich Edit Controls.


The ScrollableControlToBitmap() method takes only a ScrollableControl type as argument: you cannot pass a TextBox control, even if it uses ScrollBars.

▶ Set the fullSize argument to true or false to include all child controls inside a Container or just those that are visible. If set to true, the Container's ClientRectangle is expanded to include and print all its child Controls.

▶ Set the includeHidden argument to true or false to include or exclude the hidden control, if any.


Note: this code uses the Control.DeviceDpi property to evaluate the current Dpi of the container's Device Context. This property requires .Net Framework 4.7+. If this version is not available, you can remove:

bitmap.SetResolution(canvas.DeviceDpi, canvas.DeviceDpi);

or derive the value with other means. See GetDeviceCaps.
Possibly, update the Project's Framework version :)


// Prints the content of the current Form instance,
// include all child controls and also those that are not visible
var bitmap = ControlPrinter.ScrollableControlToBitmap(this, true, true);

// Prints the content of a ScrollableControl inside a Form
// include all child controls except those that are not visible
var bitmap = ControlPrinter.ScrollableControlToBitmap(this.panel1, true, false);
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;

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

        canvas.PerformLayout();
        Size containerSize = canvas.DisplayRectangle.Size;
        if (fullSize) {
            containerSize.Width = Math.Max(containerSize.Width, canvas.ClientSize.Width);
            containerSize.Height = Math.Max(containerSize.Height, canvas.ClientSize.Height);
        }
        else {
            containerSize = (canvas is Form) ? canvas.PreferredSize : canvas.ClientSize;
        }

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

        var graphics = Graphics.FromImage(bitmap);
        if (canvas.BackgroundImage != null) {
            graphics.DrawImage(canvas.BackgroundImage, new Rectangle(Point.Empty, containerSize));
        }
        else {
            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 static 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];
            if (!ctl.Visible || (ctl.Width < 1 || ctl.Height < 1)) continue;

            var clipBounds = Rectangle.Empty;
            if (parent == outerContainer) { clipBounds = ctl.Bounds; }
            else {
                if ((parent != ctl) && parent is ScrollableControl scrctl) {
                Size scrContainerSize = parentBounds.Size;
                if (parent is ScrollableControl scrctl) {
                    if (scrctl.VerticalScroll.Visible) scrContainerSize.Width -= (SystemInformation.VerticalScrollBarWidth + 1);
                    if (scrctl.HorizontalScroll.Visible) scrContainerSize.Height -= (SystemInformation.HorizontalScrollBarHeight + 1);
                }
                clipBounds = Rectangle.Intersect(new Rectangle(Point.Empty, scrContainerSize), ctl.Bounds);
            }

            if (clipBounds.Width < 1 || clipBounds.Height < 1) continue;
            var bounds = outerContainer.RectangleToClient(parent.RectangleToScreen(clipBounds));

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

    internal 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, Color color)
        {
            rtb.Rtf = rtf;
            rtb.Draw(dc, canvas, layoutArea, color);
            rtb.Clear();
        }

        public void Dispose() => this.rtb.Dispose();

        private class RTBPrinter : RichTextBox
        {
            public void Draw(Graphics g, Rectangle hdcArea, Rectangle layoutArea, Color color)
            {
                using (var brush = new SolidBrush(color)) {
                    g.FillRectangle(brush, layoutArea);
                };

                IntPtr hdc = g.GetHdc();
                var canvasAreaTwips = new RECT().ToInches(hdcArea);
                var layoutAreaTwips = new RECT().ToInches(layoutArea);

                var formatRange = new FORMATRANGE() {
                    charRange = new CHARRANGE() { cpMax = -1, cpMin = 0 },
                    hdc = hdc,
                    hdcTarget = hdc,
                    rect = layoutAreaTwips,
                    rectPage = canvasAreaTwips
                };

                IntPtr lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(formatRange));
                Marshal.StructureToPtr(formatRange, 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://docs.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.92f;
                    return new RECT() {
                        Left = (int)(rectangle.Left * inch),
                        Top = (int)(rectangle.Top * inch),
                        Right = (int)(rectangle.Right * inch),
                        Bottom = (int)(rectangle.Bottom * inch)
                    };
                }
            }

            // https://docs.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 rect;             // The area within the rcPage rectangle to render to. Units are measured in twips.
                public RECT rectPage;         // The entire area of a page on the rendering device. Units are measured in twips.
                public CHARRANGE charRange;   // 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)
            }
        }
    }
}

This is how it works:

VB.Net version of the same procedure

这篇关于如何将 PrintDocument 与可滚动面板一起使用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-02 06:56