如何使用滚动条打印容器的隐藏和可见内容
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 的内容打印到位图。
程序描述:
- 控件首先滚动回原点(
[ScrollableControl].AutoScrollPosition = new Point(0, 0)
(否则会引发异常:Bitmap 大小错误。您可能需要存储当前滚动位置并在之后恢复它)。
- 验证并存储容器的实际大小,由 PreferredSize or DisplayRectangle 属性返回(取决于方法参数设置的条件和打印的容器类型)。 属性 考虑容器的完整范围。
这将是位图的大小。
- 使用容器的背景颜色清除位图。
- 迭代
ScrollableControl.Controls
集合并在其相对位置打印所有一级子控件(子控件的Bounds
矩形是相对于容器客户区).
- 如果一级控件有子控件,调用
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
参数设置为 True
或 False
以包含容器内的所有子控件或仅包含可见的子控件.如果设置为 True
,容器的 ClientRectangle
将展开以包含并打印其所有子控件。
► 将 includeHidden
参数设置为 True
或 False
以包含或排除隐藏控件(如果有)。
注意:此代码使用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
这是它的工作原理:
.
我有一个可滚动的面板:它的一些子控件是隐藏的,而另一些是可见的。
如何打印此面板上的所有内容,包括隐藏的或不滚动不可见的子控件?
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 的内容打印到位图。
程序描述:
- 控件首先滚动回原点(
[ScrollableControl].AutoScrollPosition = new Point(0, 0)
(否则会引发异常:Bitmap 大小错误。您可能需要存储当前滚动位置并在之后恢复它)。 - 验证并存储容器的实际大小,由 PreferredSize or DisplayRectangle 属性返回(取决于方法参数设置的条件和打印的容器类型)。 属性 考虑容器的完整范围。
这将是位图的大小。 - 使用容器的背景颜色清除位图。
- 迭代
ScrollableControl.Controls
集合并在其相对位置打印所有一级子控件(子控件的Bounds
矩形是相对于容器客户区). - 如果一级控件有子控件,调用
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
参数设置为 True
或 False
以包含容器内的所有子控件或仅包含可见的子控件.如果设置为 True
,容器的 ClientRectangle
将展开以包含并打印其所有子控件。
► 将 includeHidden
参数设置为 True
或 False
以包含或排除隐藏控件(如果有)。
注意:此代码使用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
这是它的工作原理: