富文本的改进所见即所得打印对话框和页面预览





5.00/5 (6投票s)
带富文本的 Windows Forms 打印对话框,
引言
这个项目是从我第一次尝试创建一个有用的C#富文本框的打印预览/打印对话框演变而来的,这是标准Visual Studio工具或组件未提供的。
虽然我上面创建的简单对话框工作良好,但我想要更接近大多数商业软件中发现的东西,例如 Open Office 或 Acrobat。 具体来说,我想要一个可以准确显示打印页面外观的对话框,并根据选择的打印机和用户选择的任何功能实时更改分页。 这包括缩放页面的能力,并相应地重新分页。 如果支持,还可以打印每张纸多页。 由于并非所有打印机都支持所有功能,因此一个要求是枚举所选打印驱动程序中可用的功能。
背景
虽然.NET为从richtextbox
打印提供最少的支持,但Windows Presentation Foundation Classes为查看和打印FlowDocument
类提供支持,我发现这是处理RichTextBox
生成的富文本标记代码(RTF)以及任何基于它的扩展类(例如编辑器)的最佳方式。 Rtf可以很容易地转换为FlowDocument
。
Using the Code
包含的源代码生成一个类库 zoomprint.dll ,其中包含一个带有三个构造函数的ZPrintForm
类。 第一个创建一个带有Demo
Rtf文档的Form
来展示它的外观。 在第二个重载中,字符串“Document
”是来自richtextbox
的Rtf。 第三个表单允许程序员预先选择对话框的默认选项。 它们仍然可以由用户在打印之前更改。
//Constructors:
public ZprintForm()
public ZPrintForm(string Document)
public ZPrintForm(string Document,string Header,bool NumberPages,
bool IncludeDate, int PointSize)
//
// Example:
//
ZPrintForm ZPF = new ZPF(myRichTextBox.Rtf);
ZPF.ShowDialog(this);
ZPF.Dispose();
关注点
第一步是将 Rtf 转换为 FlowDocument
并使用以下两种方法初始化它
// CONVERT RTF TO FLOWDOC WYSIWYG
private bool CreateFlowDocFromRtf(ref FlowDocument f, string sourcertf)
{
bool success = false;
//f.SetValue(FlowDocument.TextAlignmentProperty, Left);
TextRange r = new TextRange(f.ContentStart, f.ContentEnd);
if (r.CanLoad(System.Windows.Forms.DataFormats.Rtf) &&
(!string.IsNullOrEmpty(sourcertf))) // 05-27-2020 ADDED SPECIFIC REF
// TO DATAFORMATS AMBIG ERROR
{
byte[] data = Encoding.ASCII.GetBytes(sourcertf);
using (MemoryStream stream = new MemoryStream(data))
{
r.Load(stream, System.Windows.Forms.DataFormats.Rtf);
}
success = true;
}
else
{
success = false;
}
return success;
}
// CREATE FLOWDOCUMENT
private void InitializeFlowDocument(ref FlowDocument fd,
ref System.Windows.Forms.RichTextBox rtb)
{
string richtext = rtb.Rtf;
fd = new FlowDocument();
fd.PageWidth = _printableareawidth * (100 / fitfactor);
fd.PageHeight = _printableareaheight * (100 / fitfactor); // MATCH SCALE
// FACTOR PLUS EMPIRICAL CORRECTION
fd.ColumnWidth = Double.PositiveInfinity; // fd.PageWidth;
fd.PagePadding = new System.Windows.Thickness
(_marginleft, _margintop, _marginright, _marginbot);
fd.TextAlignment = System.Windows.TextAlignment.Left;
if (!CreateFlowDocFromRtf(ref fd, richtext))
{
System.Windows.Forms.MessageBox.Show(this,"Unable
to create flowdocument from Rtf", "Initialize FlowDocument Error",
MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
//EHT.GeneralExceptionHandler("Unable to Convert RTF",
//"CreateFlowDocumentFromRtf()", false, null);
return;
}
GetPageCount(ref fd);
this.userControl11.ViewerZoom = fitfactor + 50;
if (_printersupportspagescaling)
{
this.userControl11.ViewerZoom = tbarPageScaling.Value; // ADDED
// 05-23-2020 1145AM
}
this.userControl11.PrintPreview = fd;
}
生成的flowdocument
显示在FlowDocument
Reader中,这是一个在Presentation Foundation Classes中可用的类,并通过Visual Studio中工具面板中的用户控件为WPF托管在打印预览表单上。 请注意,必须手动编辑控件的 XAML 代码和“代码隐藏”才能公开它
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
// Dependency Property
// SYNTAX = Register("Variable Name",typeof(variable),typeof(ownertype))
// 05-23-2020 Note naming convention xxxxProperty in declaration
// matches public type xxxx in getter setter
public static readonly DependencyProperty PrintPreviewProperty =
DependencyProperty.Register("PrintPreview", typeof(FlowDocument),
typeof(UserControl1));
public static readonly DependencyProperty ViewerZoomProperty =
DependencyProperty.Register("ViewerZoom", typeof(double), typeof(UserControl1));
private static Action EmptyDelegate = delegate () { };
private Size viewersize = new Size(603, 502);
private double viewerzoom = 100;
// .NET Property wrapper
public FlowDocument PrintPreview
{
get { return (FlowDocument)GetValue(PrintPreviewProperty); }
set
{
SetValue(PrintPreviewProperty, (FlowDocument)value);
UpdatePreview();
}
}
public double ViewerZoom
{
get { return viewerzoom; }
set
{
viewerzoom = value/4; // scale for display purposes 05-23-2020
UpdatePreviewZoom();
UpdatePreview();
}
}
public void PrintFlowDocument()
{
Previewer.Print();
}
public Size DesiredViewerSize
{
set
{
viewersize = value;
}
}
public void UpdateViewerSize()
{
Previewer.RenderSize = viewersize;
Previewer.Dispatcher.Invoke(EmptyDelegate,
System.Windows.Threading.DispatcherPriority.Render);
}
private void UpdatePreviewZoom()
{
Previewer.Zoom = viewerzoom;
}
private void UpdatePreview()
{
Previewer.Document = PrintPreview;
}
}
}
有关托管 FlowDocument Reader 的 UserControl
的更多详细信息,请参见包含的源代码。 创建 FlowDocument 并在 Reader Control 中显示后,下一个关键操作是生成一个 PrintDocument
,其中包含一个 PrintQueue
,该队列包含所选打印机的属性以及用户选择的可用选项。 当所选打印机或选项更改时,Reader 控件中的 FlowDocument
会立即更新,因此始终以缩略图形式显示生成的文档页面外观。 ResetPrintDocument()
通过合并 PrintTickets
将所选打印机的功能与用户选择的功能相结合
// RESET PRINTDOCUMENT
private void ResetPrintDocument()
{
System.Printing.ValidationResult VR = new System.Printing.ValidationResult();
System.Printing.PrintCapabilities PC;
pd = null;
pd = new System.Windows.Controls.PrintDialog();
pd.PrintQueue = new System.Printing.PrintQueue
(new System.Printing.PrintServer(), _selectedprintername);
if (_printerchanged)
{
try
{
PC = pd.PrintQueue.GetPrintCapabilities(); // Called only when a
// new printer is selected,
// otherwise
CurrentPrinterCapabilities = PC;
GetPaperSizesFromPrintCapabilties(PC);
PopulatePaperSizes();
_printerchanged = false;
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show
("Unable to load Paper Sizes from Driver", "Print Driver Error",
MessageBoxButtons.OK, MessageBoxIcon.Error);
// EHT.GeneralExceptionHandler
// ("Error Getting PaperSizes From Print Driver",
// "ResetPrintDocument()", false, ex);
}
GetCollatingOptions(CurrentPrinterCapabilities);
GetDuplexingCaps(CurrentPrinterCapabilities);
GetPrinterPageScalingSupportStatus(CurrentPrinterCapabilities);
GetPrinterPagesPerSheetOptions(CurrentPrinterCapabilities);
}
VR = pd.PrintQueue.MergeAndValidatePrintTicket
(pd.PrintTicket, CreateUserTicket());
pd.PrintTicket = VR.ValidatedPrintTicket;
if (VR.ConflictStatus != System.Printing.ConflictStatus.NoConflict)
{
System.Windows.Forms.MessageBox.Show
("Print Ticket Conflict Resolved",
"Validation Conflict", MessageBoxButtons.OK,
MessageBoxIcon.Information);
// DT.NotifyDialog(this, "Print Ticket Conflict(s) were resolved.");
}
_printableareawidth = pd.PrintableAreaWidth;
_printableareaheight = pd.PrintableAreaHeight;
UpdateDimLabel();
InitializeFlowDocument(ref FD, ref rtbSource);
}
// CREATE USER PRINTICKET
// This is desired PT to be merged and validated with that from the printer
// (via printqueue)
private System.Printing.PrintTicket CreateUserTicket()
{
System.Printing.PrintTicket PT = new System.Printing.PrintTicket();
PT.PageMediaSize = AvailablePageMediaSizes[SizeSelectedIndex];
if ( _printersupportspagespersheet && cbPagesPerSheet.SelectedItem != null)
{
PT.PagesPerSheet =(int) cbPagesPerSheet.SelectedItem;
}
// PT.PagesPerSheet = _pagespersheet;
PT.PageScalingFactor = fitfactor;
if (_printersupportspagescaling)
{
PT.PageScalingFactor = tbarPageScaling.Value;
}
PT.OutputQuality = System.Printing.OutputQuality.High;
PT.PageBorderless = System.Printing.PageBorderless.None; // Laser Printers
// Can't Print Borderless
if (_landscapemodeon)
{
PT.PageOrientation = System.Printing.PageOrientation.Landscape;
}
else
{
PT.PageOrientation = System.Printing.PageOrientation.Portrait;
}
PT.CopyCount = (int) nUdCopies.Value; // tested ok 05-23-2020
if (rbCollated.Enabled == true && rbCollated.Checked) // Only Applies
// If Multiple Copies Made
{
PT.Collation = Collation.Collated;
}
return PT;
}
创建 PrintDocument
及其关联的 PrintQueue
后,剩下的就是使用其内置的打印方法来打印文档。 然而,为了能够打印自定义范围的页面并向每个页面添加页眉,这两个都是我想提供的功能,因此有必要创建两个自定义 DocumentPaginator
类,每个选项一个,并在将页面发送到打印机之前通过这些类运行页面。 这些代码非常复杂,我已经尝试记录我引用的每个原始来源。 它涉及使用 abstract
类 Page.Visual
,它是 Presentation Foundation 中使用的基于矢量图形的图元。 有关详细信息,请参见源代码。
// PRINT BUTTON HANDLER
private void btnPrint_Click(object sender, EventArgs e)
{
DocumentPaginator dp2 = null;
DocumentPaginator PRDP = null;
if (_printersupportspagescaling)
{
pd.PrintTicket.PageScalingFactor = tbarPageScaling.Value;
}
DocumentPaginator paginator =
((IDocumentPaginatorSource)FD).DocumentPaginator; //first paginator
if (rbHeaderOn.Checked)
{
dp2 = new CustomStyleDocumentPaginator(FD, paginator, paginator.PageSize,
new System.Windows.Size(12, 12), cbNumberPages.Checked,
cbAddDateToHeader.Checked,
tbHeader.Text,(int)nudHeaderPointSize.Value);
}
if (_printcustompagerange && ParsePageRangeTextBox())
{
System.Windows.Controls.PageRange PR =
new System.Windows.Controls.PageRange();
PR.PageFrom = _firstpage;
PR.PageTo = _lastpage;
pd.PageRange = PR;
pd.PageRangeSelection = System.Windows.Controls.PageRangeSelection.UserPages;
pd.SelectedPagesEnabled = true;
pd.UserPageRangeEnabled = true;
if (dp2 != null)
{
PRDP = new PageRangeDocumentPaginator(dp2, _firstpage, _lastpage);
}
else
{
PRDP = new PageRangeDocumentPaginator(paginator, _firstpage, _lastpage);
};
pd.PrintDocument(PRDP, "Printing Document");
} else
{
if (dp2 != null)
{
pd.PrintDocument(dp2, "Printing Document");
} else
{
pd.PrintDocument(((IDocumentPaginatorSource)FD).DocumentPaginator,
"Printing Document");
}
}
this.Close();
}
出现的最后一个编程问题是如何访问每个 Windows 打印机驱动程序中内置的“设置”对话框。 当用户单击“属性”按钮时会显示此对话框。 此过程需要四个不同的 Windows API 调用和直接内存分配。 源代码已记录。 请注意,只有使用驱动程序的本机设置对话框所做的一些更改才会持久,我不确定为什么。
版本 1026 为预览显示添加了页面尺寸标签,为页面尺寸对话框添加了边距预设,并为打印范围对话框添加了“当前页面”按钮
历史
- 2020年8月13日:第一个版本 1.0.1.9
- 2022年12月13日:1.0.2.6