【问题标题】:How to print hidden and visible content of a Container with ScrollBars如何使用 ScrollBars 打印 Container 的隐藏和可见内容
【发布时间】:2019-12-09 10:34:15
【问题描述】:

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

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

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

【问题讨论】:

    标签: vb.net winforms graphics scrollbar panel


    【解决方案1】:

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

    过程说明:

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

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


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

    ► 将 fullSize 参数设置为 TrueFalse 以包含容器内的所有子控件或仅包含可见的子控件。如果设置为True,Container 的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
    

    这就是它的工作原理:

    C# version of the same procedure.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-16
      • 2023-03-09
      • 1970-01-01
      • 2017-05-02
      • 2019-04-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多