65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2020年8月13日

CPOL

4分钟阅读

viewsIcon

18467

downloadIcon

1407

带富文本的 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 类,每个选项一个,并在将页面发送到打印机之前通过这些类运行页面。 这些代码非常复杂,我已经尝试记录我引用的每个原始来源。 它涉及使用 abstractPage.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
© . All rights reserved.