如何使用滚动条打印容器的隐藏和可见内容

How to print hidden and visible content of a Container with ScrollBars

我有一个可滚动的面板:它的一些子控件是隐藏的,而另一些是可见的。

如何打印此面板上的所有内容,包括隐藏的或不滚动不可见的子控件?

Private Sub PrintDocument1_PrintPage(sender As Object, e As Printing.PrintPageEventArgs) Handles PrintDocument1.PrintPage
    Panel1.AutoSize = True
    Dim b As New Bitmap(Panel1.DisplayRectangle.Width, Panel1.DisplayRectangle.Height)
    Panel1.DrawToBitmap(b, Panel1.ClientRectangle)
    e.Graphics.DrawImage(b, New Point(40, 40))
    Panel1.AutoSize = False
End Sub

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

程序描述:

  1. 控件首先滚动回原点([ScrollableControl].AutoScrollPosition = new Point(0, 0)(否则会引发异常:Bitmap 大小错误。您可能需要存储当前滚动位置并在之后恢复它)。
  2. 验证并存储容器的实际大小,由 PreferredSize or DisplayRectangle 属性返回(取决于方法参数设置的条件和打印的容器类型)。 属性 考虑容器的完整范围。
    这将是位图的大小。
  3. 使用容器的背景颜色清除位图。
  4. 迭代ScrollableControl.Controls集合并在其相对位置打印所有一级子控件(子控件的Bounds矩形是相对于容器客户区).
  5. 如果一级控件有子控件,调用DrawNestedControls递归方法,枚举并绘制所有嵌套的子控件Containers/Controls,保留内部剪辑边界。

包括对 RichTextBox 控件的支持
RichEditPrinter class 包含打印 RichTextBox/RichEdit 控件内容所需的逻辑。 class 使用正在打印控件的位图的设备上下文向 RichTextBox 发送 EM_FORMATRANGE 消息。
MSDN 文档中提供了更多详细信息:How to Print the Contents of Rich Edit Controls.


ScrollableControlToBitmap() 方法仅接受 ScrollableControl 类型作为参数:您不能传递 TextBox 控件,即使它使用滚动条。

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

► 将 includeHidden 参数设置为 TrueFalse 以包含或排除隐藏控件(如果有)。


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

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

或通过其他方式推导出值。参见 GetDeviceCaps
可能的话,更新项目的框架版本:)


' Prints the content of the current Form instance, 
' include all child controls and also those that are not visible
Dim bitmap = ControlsPrinter.ScrollableControlToBitmap(Me, True, True)

' Prints the content of a ScrollableControl inside a Form
' include all child controls except those that are not visible
Dim bitmap = ControlsPrinter.ScrollableControlToBitmap(Me.Panel1, True, False)

Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Runtime.InteropServices
Imports System.Windows.Forms

Public Class ControlPrinter
    Public Shared Function ScrollableControlToBitmap(canvas As ScrollableControl, fullSize As Boolean, includeHidden As Boolean) As Bitmap
        canvas.AutoScrollPosition = New Point(0, 0)
        If includeHidden Then
            canvas.SuspendLayout()
            For Each child As Control In canvas.Controls
                child.Visible = True
            Next
            canvas.ResumeLayout(True)
        End If

        canvas.PerformLayout()
        Dim containerSize As Size = canvas.DisplayRectangle.Size
        If fullSize Then
            containerSize.Width = Math.Max(containerSize.Width, canvas.ClientSize.Width)
            containerSize.Height = Math.Max(containerSize.Height, canvas.ClientSize.Height)
        Else
            containerSize = If((TypeOf canvas Is Form), canvas.PreferredSize, canvas.ClientSize)
        End If

        Dim bmp = New Bitmap(containerSize.Width, containerSize.Height, PixelFormat.Format32bppArgb)
        bmp.SetResolution(canvas.DeviceDpi, canvas.DeviceDpi)

        Dim g = Graphics.FromImage(bmp)
        g.Clear(canvas.BackColor)
        Dim rtfPrinter = New RichEditPrinter(g)

        Try
            DrawNestedControls(canvas, canvas, New Rectangle(Point.Empty, containerSize), bmp, rtfPrinter)
            Return bmp
        Finally
            rtfPrinter.Dispose()
            g.Dispose()
        End Try
    End Function

    Private Shared Sub DrawNestedControls(outerContainer As Control, parent As Control, parentBounds As Rectangle, bmp As Bitmap, rtfPrinter As RichEditPrinter)
        For i As Integer = parent.Controls.Count - 1 To 0 Step -1
            Dim ctl = parent.Controls(i)
            If Not ctl.Visible OrElse (ctl.Width < 1 OrElse ctl.Height < 1) Then Continue For

            Dim clipBounds = Rectangle.Empty
            If parent.Equals(outerContainer) Then
                clipBounds = ctl.Bounds
            Else
                Dim scrContainerSize As Size = parentBounds.Size
                If TypeOf parent Is ScrollableControl Then
                    Dim scrCtrl = DirectCast(parent, ScrollableControl)
                    With scrCtrl
                        If .VerticalScroll.Visible Then scrContainerSize.Width -= (SystemInformation.VerticalScrollBarWidth + 1)
                        If .HorizontalScroll.Visible Then scrContainerSize.Height -= (SystemInformation.HorizontalScrollBarHeight + 1)
                    End With
                End If
                clipBounds = Rectangle.Intersect(New Rectangle(Point.Empty, scrContainerSize), ctl.Bounds)
            End If
            If clipBounds.Width < 1 OrElse clipBounds.Height < 1 Then Continue For

            Dim bounds = outerContainer.RectangleToClient(parent.RectangleToScreen(clipBounds))

            If TypeOf ctl Is RichTextBox Then
                Dim rtb = DirectCast(ctl, RichTextBox)
                rtfPrinter.DrawRtf(rtb.Rtf, outerContainer.Bounds, bounds, ctl.BackColor)
            Else
                ctl.DrawToBitmap(bmp, bounds)
            End If

            If ctl.HasChildren Then
                DrawNestedControls(outerContainer, ctl, clipBounds, bmp, rtfPrinter)
            End If
        Next
    End Sub

    Friend Class RichEditPrinter
        Implements IDisposable
        Private dc As Graphics = Nothing
        Private rtb As RTBPrinter = Nothing

        Public Sub New(graphics As Graphics)
            dc = graphics
            rtb = New RTBPrinter() With {
                .ScrollBars = RichTextBoxScrollBars.None
            }
        End Sub

        Public Sub DrawRtf(rtf As String, canvas As Rectangle, layoutArea As Rectangle, color As Color)
            rtb.Rtf = rtf
            rtb.Draw(dc, canvas, layoutArea, color)
            rtb.Clear()
        End Sub

        Public Sub Dispose() Implements IDisposable.Dispose
            rtb.Dispose()
        End Sub

        Private Class RTBPrinter
            Inherits RichTextBox
            Public Sub Draw(g As Graphics, hdcArea As Rectangle, layoutArea As Rectangle, color As Color)
                Using brush = New SolidBrush(color)
                    g.FillRectangle(brush, layoutArea)
                End Using

                Dim hdc As IntPtr = g.GetHdc()
                Dim canvasAreaTwips = New RECT().ToInches(hdcArea)
                Dim layoutAreaTwips = New RECT().ToInches(layoutArea)

                Dim formatRange = New FORMATRANGE() With {
                    .charRange = New CHARRANGE() With {
                        .cpMax = -1,
                        .cpMin = 0
                    },
                    .hdc = hdc,
                    .hdcTarget = hdc,
                    .rect = layoutAreaTwips,
                    .rectPage = canvasAreaTwips
                }

                Dim lParam As IntPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(formatRange))
                Marshal.StructureToPtr(formatRange, lParam, False)

                SendMessage(Me.Handle, EM_FORMATRANGE, CType(1, IntPtr), lParam)
                Marshal.FreeCoTaskMem(lParam)
                g.ReleaseHdc(hdc)
            End Sub

            <DllImport("User32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
            Friend Shared Function SendMessage(hWnd As IntPtr, uMsg As Integer, wParam As IntPtr, lParam As IntPtr) As Integer
            End Function

            Friend Const WM_USER As Integer = &H400
            Friend Const EM_FORMATRANGE As Integer = WM_USER + 57

            <StructLayout(LayoutKind.Sequential)>
            Friend Structure RECT
                Public Left As Integer
                Public Top As Integer
                Public Right As Integer
                Public Bottom As Integer

                Public Function ToRectangle() As Rectangle
                    Return Rectangle.FromLTRB(Left, Top, Right, Bottom)
                End Function

                Public Function ToInches(rectangle As Rectangle) As RECT
                    Dim inch As Single = 14.92F
                    Return New RECT() With {
                        .Left = CType(rectangle.Left * inch, Integer),
                        .Top = CType(rectangle.Top * inch, Integer),
                        .Right = CType(rectangle.Right * inch, Integer),
                        .Bottom = CType(rectangle.Bottom * inch, Integer)
                    }
                End Function
            End Structure

            <StructLayout(LayoutKind.Sequential)>
            Friend Structure FORMATRANGE
                Public hdcTarget As IntPtr      ' A HDC for the target device to format for
                Public hdc As IntPtr            ' A HDC for the device to render to, if EM_FORMATRANGE is being used to send the output to a device
                Public rect As RECT             ' The area within the rcPage rectangle to render to. Units are measured in twips.
                Public rectPage As RECT         ' The entire area of a page on the rendering device. Units are measured in twips.
                Public charRange As CHARRANGE   ' The range of characters to format (see CHARRANGE)
            End Structure

            <StructLayout(LayoutKind.Sequential)>
            Friend Structure CHARRANGE
                Public cpMin As Integer   ' First character of range (0 for start of doc)
                Public cpMax As Integer   ' Last character of range (-1 for end of doc)
            End Structure
        End Class
    End Class
End Class

这是它的工作原理:

.