C# 如何使用带可滚动面板的PrintDocument?

C# 如何使用带可滚动面板的PrintDocument?,c#,winforms,printing,C#,Winforms,Printing,如何使用带有可滚动面板的PrintDocument 以下是我的一些代码: 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.Pag

如何使用带有可滚动面板的
PrintDocument

以下是我的一些代码:

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

这些方法集允许将文件的内容打印到位图

程序说明:

  • 控件首先回滚到原点(
    control.AutoScrollPosition=new Point(0,0);
    (否则会引发异常:位图大小错误。您可能希望存储当前的滚动位置,然后将其还原)
  • 验证并存储由或属性返回的容器的实际大小(取决于方法参数设置的条件和打印的容器类型)。此属性考虑容器的完整范围。
    这将是位图的大小
  • 使用容器的背景色清除位图
  • 迭代滚动控件。控件集合并在其相对位置打印所有第一级子控件(子控件的
    边界
    矩形相对于容器客户端区域)
  • 如果一级控件有子控件,则调用递归方法
    DrawNestedControls
    ,该方法将枚举并绘制所有嵌套的子容器/控件,保留内部剪辑边界
  • 包括对RichTextBox控件的支持
    RichEditPrinter
    类包含打印RichTextBox/RichEdit控件内容所需的逻辑。该类使用正在打印控件的位图的设备上下文向RichTextBox发送消息。
    MSDN文档中提供了更多详细信息:


    ScrollableControlToBitmap()
    方法仅将
    ScrollableControl
    类型作为参数:无法传递TextBox控件,即使它使用滚动条

    ► 将
    fullSize
    参数设置为
    true
    false
    以包含容器内的所有子控件或仅包含可见的子控件。如果设置为
    true
    ,则容器的
    ClientRectangle
    将展开以包含并打印其所有子控件

    ► 将
    includeHidden
    参数设置为
    true
    false
    以包括或排除隐藏控件(如果有)


    注意:此代码使用属性评估容器设备上下文的当前Dpi。此属性需要.Net Framework 4.7+。如果此版本不可用,您可以删除:

    bitmap.SetResolution(canvas.DeviceDpi, canvas.DeviceDpi);
    
    或使用其他方法导出值。请参阅。
    可能,请更新项目的框架版本:)


    使用系统图;
    使用系统、绘图、成像;
    使用System.Runtime.InteropServices;
    使用System.Windows.Forms;
    公共类控制打印机
    {
    公共静态位图ScrollableControlToBitmap(ScrollableControlCanvas,bool fullSize,bool includeHidden)
    {
    canvas.AutoScrollPosition=新点(0,0);
    如果(包括隐藏){
    canvas.SuspendLayout();
    foreach(canvas.Controls中的控件子级){
    可见=真实;
    }
    canvas.ResumeLayout(true);
    }
    canvas.PerformLayout();
    Size containerSize=canvas.DisplayRectangle.Size;
    if(全尺寸){
    containerSize.Width=Math.Max(containerSize.Width,canvas.ClientSize.Width);
    containerSize.Height=Math.Max(containerSize.Height,canvas.ClientSize.Height);
    }
    否则{
    containerSize=(画布是表单)?canvas.PreferredSize:canvas.ClientSize;
    }
    var bitmap=新位图(containerSize.Width、containerSize.Height、PixelFormat.Format32bppArgb);
    SetResolution(canvas.DeviceDpi,canvas.DeviceDpi);
    var graphics=graphics.FromImage(位图);
    if(canvas.BackgroundImage!=null){
    graphics.DrawImage(canvas.BackgroundImage,新矩形(Point.Empty,containerSize));
    }
    否则{
    图形。清晰(画布。背景色);
    }
    var rtfPrinter=新的RichEditPrinter(图形);
    试一试{
    DrawNestedControls(画布、画布、新矩形(Point.Empty、containerSize)、位图、rtfPrinter);
    返回位图;
    }
    最后{
    rtfPrinter.Dispose();
    graphics.Dispose();
    }
    }
    私有静态void DrawNestedControls(控件outerContainer、控件父级、矩形父边界、位图位图、RichEditPrinter rtfPrinter)
    {
    对于(int i=parent.Controls.Count-1;i>=0;i--){
    var ctl=parent.Controls[i];
    如果(!ctl.Visible | | |(ctl.Width<1 | | ctl.Height<1))继续;
    var clipBounds=Rectangle.Empty;
    如果(parent==outerContainer){clipBounds=ctl.Bounds;}
    否则{
    如果((父项!=ctl)&&parent为ScrollableControl scrctl){
    Size scrContainerSize=parentBounds.Size;
    if(父级为ScrollableControl scrctl){
    如果(scrctl.VerticalScroll.Visible)scrccontainersize.Width-=(SystemInformation.VerticalScrollBarWidth+1);
    如果(scrctl.horizontalcroll.Visible)scrccontainersize.Height-=(SystemInformation.horizontalcrollbarheight+1);
    }
    剪贴簿=Rectangle.Intersect(新矩形(Point.Empty,scrContainerSize),ctl.Bounds);
    }
    如果(剪贴簿宽度<1 | |剪贴簿高度<1)继续;
    var bounds=outerContainer.RectangleToClient(parent.RectangleToScreen(剪贴簿));
    如果(ctl是RichTextBox rtb){
    rtfPrinter.DrawRtf(rtb.Rtf,outerContainer.Bounds,Bounds,ctl.BackColor);
    }
    否则{
    ctl.DrawToBitmap(位图,边界
    
    // 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)
                }
            }
        }
    }