zoukankan      html  css  js  c++  java
  • WPF中打印问题的探讨[转]

    转自:http://blog.sina.com.cn/s/blog_624dc0120100ld6m.html

        最近在做一个WPF方面的项目,在打印功能实现上费了很大劲。因为我原来是在做Winform方面的项目,接受WPF时感觉还很相似,可仔细往里做下去却发现两者外看相似,实则差异很大,打印就是其中很大的差距。Winform有对应的对话框围绕打印核心类PrintDocument进行操作,而WPF里则是在PrintDialog里进行扩展。WPF简单的打印很简单,几行代码就够,可要实现较大功能时就需要很多的扩展,如分页打印及对打印允许客户有较大的设置自由度时,就很麻烦了。我以我这次的功能实现与大家进行探讨。

        常见的打印方式有以下三中:

        第一种:对单一控件内容的打印。

        private void billtitle_btn_PrintClick(object sender, RoutedEventArgs e)
            {

                PrintDialog printDialog = new PrintDialog();
                if (printDialog.ShowDialog() == true)
                {
                    printDialog.PrintVisual(Mainwindow, "123");
                }
            }

         其中Mainwindow是控件名,也可以是ListView等控件,只要把名称传入即可。很简单,不过不实用,因为这种方法没用自由度,是按系统默认进行打印且只能打印在一页上,数据多了就不行。

         第二种:根据PrintDialog进行功能扩展,就可以对打印功能在一定程度上扩展。  

    /// <summary>
    /// 打印类
    /// </summary>
    public class PrintService
    {
        public PrintService()
       {
          //创建一个PrintDialog的实例
          PrintDialog dlg = new PrintDialog();

          //创建一个PrintDocument的实例
          PrintDocument docToPrint = new PrintDocument();

          //将事件处理函数添加到PrintDocument的PrintPage事件中
          docToPrint.PrintPage += new System.Drawing.Printing.PrintPageEventHandler                 (docToPrint_PrintPage);

          //把PrintDialog的Document属性设为上面配置好的PrintDocument的实例
          dlg.Document = docToPrint;

          //根据用户的选择,开始打印
          if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
         {
            docToPrint.Print();//开始打印
          }
       }

       //设置打印机开始打印的事件处理函数
       private void docToPrint_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
      {
            e.Graphics.DrawString("Hello, world!", new System.Drawing.Font("Arial", 16,  System.Drawing.FontStyle.Regular), System.Drawing.Brushes.Black, 100, 100);
        }
    }

        在这就可以看出Winform与WPF打印的不同了,在WPF在PrintDocument是一个打印方法。WPF有两种打印途径,一种是第一种方法中的PrintVisual,另一种就是PrintDocument。

        第三种:具体步骤如下:

      1.PrintDialog

      This sample illustrates how to create an instance of a simple PrintDialog and then display it. The sample uses both Extensible Application Markup Language (XAML) and procedural code.

       这个示例演示了如何进行一个最简单的打印工作,为此需要引入两个dll:ReachFramework.dll和System.Printing。

      InvokePrint方法只是显示了一个PrintDialog打印框,并未进行打印工作:

      PrintDialog pDialog = new PrintDialog();
       pDialog.PageRangeSelection = PageRangeSelection.AllPages;
       pDialog.UserPageRangeEnabled = true;
       pDialog.ShowDialog();

      有 PrintableAreaHeight和PrintableAreaWidth两个属性,分别用来表示可打印区域的高和宽。

      而对 PrintDialog的设置,可以保存在PrintTicket中,下次再打开PrintDialog,就不必重复进行设置了。

      PrintDialog pDialog = new PrintDialog();
       PrintTicket pt = pDialog.PrintTicket;  

      同样,选择使用哪一台打印机的设置,存放在PrintQueue中,下次再打开PrintDialog,也不用再次设置了。

      PrintDialog pDialog = new PrintDialog();
        PrintQueue pq = pDialog.PrintQueue;   

      如果要把特定的内容打印输出,则需要调用PrintDialog的PrintVisual方法:

      if ((bool)pDialog.ShowDialog().GetValueOrDefault())
       {
            DrawingVisual vis = new DrawingVisual();
            DrawingContext dc = vis.RenderOpen();
            dc.DrawLine(new Pen(), new Point(0, 0), new Point(0, 1));
            dc.Close();
            pDialog.PrintVisual(vis, "Hello, world!");
         }

      我们能打印的,都是Visual类型的对象,其中UIElement派生于 Visual,从而我们可以打印所有Panel、控件和其它元素,最一般的方法是使用派生于Visual的DrawingVisual类,利用它的 RenderOpen方法生成DrawingContext对象,为其绘制图形,最后使用PrintDialog的PrintVisual方法,输出图形 和文字。

      注意到,pDialog.ShowDialog()返回的是可空类型?bool,为此需要使用 GetValueOrDefault将其转为bool值,对于null值也会转为false。

       2.EnumerateSubsetOfPrintQueues

       EnumerateSubsetOfPrintQueues shows how to use the EnumeratedPrintQueueTypes enumeration to get a subset of available print queues.

      这个程序演示了如何得到本地和共享的所有打印机列表。为此,需要 使用到EnumeratedPrintQueueTypes枚举中的Local和Shared两个值,组合成一个数组,

       EnumeratedPrintQueueTypes[] enumerationFlags =

                          {EnumeratedPrintQueueTypes.Local,EnumeratedPrintQueueTypes.Shared};

      作为参数传递到查询方法GetPrintQueues中:

      LocalPrintServer printServer = new LocalPrintServer();
        PrintQueueCollection printQueuesOnLocalServer = printServer.GetPrintQueues(enumerationFlags);

      接着就可 以对PrintQueueCollection进行遍历了,获取每一个的PrintQueue名称和所在位置:

      foreach (PrintQueue printer in printQueuesOnLocalServer)
       {
             Console.WriteLine(""tThe shared printer " + printer.Name + " is located at " + printer.Location + ""n");
       }

       下面就看我的方法了,先创建一个打印用的类:DataPaginator,它继承了Document虚基类,并进行扩展,从而为我们的功能实现做铺垫。

      public class DataPaginator : DocumentPaginator
        {
            #region  属性及字段
            private DataTable dataTable;
            private Typeface typeFace;
            private double fontSize;
            private double margin;
            private int rowsPerPage;
            private int pageCount;
            private Size pageSize;

            public override Size PageSize
            {
                get
                {
                    return pageSize;
                }
                set
                {
                    pageSize = value;
                    PaginateData();
                }
            }
            public override bool IsPageCountValid
            {
                get { return true; }
            }
            public override int PageCount
            {
                get { return pageCount; }
            }
            public override IDocumentPaginatorSource Source
            {
                get { return null; }
            }
            #endregion

            #region  构造函数相关方法
            //构造函数
            public DataPaginator(DataTable dt, Typeface typeface, int fontsize, double margin, Size pagesize)
            {
                this.dataTable = dt;
                this.typeFace = typeface;
                this.fontSize = fontsize;
                this.margin = margin;
                this.pageSize = pagesize;
                PaginateData();
            }
            /// <summary>
            /// 计算页数pageCount
            /// </summary>
            private void PaginateData()
            {
                //字符大小度量标准
                FormattedText ft = GetFormattedText("A");  //取"A"的大小计算行高等;
                //计算行数
                rowsPerPage = (int)((pageSize.Height - margin * 2) / ft.Height);
                //预留标题行
                rowsPerPage = rowsPerPage - 1;
                pageCount = (int)Math.Ceiling((double)dataTable.Rows.Count / rowsPerPage);
            }
            /// <summary>
            /// 格式化字符
            /// </summary>
            private FormattedText GetFormattedText(string text)
            {
                return GetFormattedText(text, typeFace);
            }
            /// <summary>
            /// 按指定样式格式化字符
            /// </summary>
            private FormattedText GetFormattedText(string text, Typeface typeFace)
            {
                return new FormattedText(text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeFace, fontSize, Brushes.Black);
            }
            /// <summary>
            /// 获取对应页面数据并进行相应的打印设置
            /// </summary>
            public override DocumentPage GetPage(int pageNumber)
            {
                //设置列宽
                FormattedText ft = GetFormattedText("A");
                List<double> columns = new List<double>();
                int rowCount = dataTable.Rows.Count;
                int colCount = dataTable.Columns.Count;
                double columnWith = margin;
                columns.Add(columnWith);
                for (int i = 1; i < colCount; i++)
                {
                    columnWith += ft.Width * 15;
                    columns.Add(columnWith);
                }
                //获取页面对应行数
                int minRow = pageNumber * rowsPerPage;
                int maxRow = minRow + rowsPerPage;
                //绘制打印内容
                DrawingVisual visual = new DrawingVisual();
                Point point = new Point(margin, margin);
                using (DrawingContext dc = visual.RenderOpen())
                {
                    Typeface columnHeaderTypeface = new Typeface(typeFace.FontFamily, FontStyles.Normal, FontWeights.Bold, FontStretches.Normal);
                    //获取表头
                    for (int i = 0; i < colCount; i++)
                    {
                        point.X = columns[i];
                        ft = GetFormattedText(dataTable.Columns[i].Caption, columnHeaderTypeface);
                        dc.DrawText(ft, point);
                    }
                    dc.DrawLine(new Pen(Brushes.Black,3), new Point(margin, margin + ft.Height), new Point(pageSize.Width - margin, margin + ft.Height));
                    point.Y += ft.Height;
                    //获取表数据
                    for (int i = minRow; i < maxRow; i++)
                    {
                        if (i > (rowCount - 1)) break;
                        for (int j = 0; j < colCount; j++)
                        {
                            point.X = columns[j];
                            string colName = dataTable.Columns[j].ColumnName;
                            ft = GetFormattedText(dataTable.Rows[i][colName].ToString());
                            dc.DrawText(ft, point);
                        }
                        point.Y += ft.Height;
                    }
                }
                return new DocumentPage(visual);
            }
            #endregion
        }

        又构造函数可知,我们需传入一个DataTable,这样可打印的内容、样式等就宽泛多了。DataTable可直接获取,也可自己根据需要构建,我在项目时是根据显示的数据构建一个DataTable。代码如下:

        /// <summary>
            /// 获取要打印的数据
            /// </summary>
            private DataTable GetDataTable()
            {
                DataTable table = new DataTable("Data Table");           
                // Declare variables for DataColumn and DataRow objects.
                DataColumn column;
                DataRow row;

                // Create new DataColumn, set DataType,
                // ColumnName and add to DataTable.   
                column = new DataColumn();
                column.DataType =Type.GetType("System.Int32");
                column.ColumnName = "id";
                column.Caption = "编号";
                column.ReadOnly = true;
                column.Unique = true;
                // Add the Column to the DataColumnCollection.
                table.Columns.Add(column);

                // Create second column.
                column = new DataColumn();
                column.DataType = Type.GetType("System.String");
                column.ColumnName = "Name";
                column.AutoIncrement = false;
                column.Caption = "姓名";
                column.ReadOnly = false;
                column.Unique = false;
                // Add the column to the table.
                table.Columns.Add(column);

                //Create third column
                column = new DataColumn();
                column.DataType = Type.GetType("System.String");
                column.ColumnName = "Age";
                column.AutoIncrement = false;
                column.Caption = "年龄";
                column.ReadOnly = false;
                column.Unique = false;
                // Add the column to the table.
                table.Columns.Add(column);


                //Create forth column
                column = new DataColumn();
                column.DataType = Type.GetType("System.String");
                column.ColumnName = "Pay";
                column.AutoIncrement = false;
                column.Caption = "工资";
                column.ReadOnly = false;
                column.Unique = false;
                // Add the column to the table.
                table.Columns.Add(column);
                // Make the ID column the primary key column.
                DataColumn[] PrimaryKeyColumns = new DataColumn[1];
                PrimaryKeyColumns[0] = table.Columns["id"];
                table.PrimaryKey = PrimaryKeyColumns;

                // Instantiate the DataSet variable.
                //dataSet = new DataSet();
                // Add the new DataTable to the DataSet.
                //dataSet.Tables.Add(table);

                // Create three new DataRow objects and add
                // them to the DataTable
                for (int i = 0; i <= 60; i++)
                {
                    row = table.NewRow();              
                    row["id"] = i+1;
                    row["Name"] = "zhangsan " + (i+1).ToString();
                    row["Age"] = 20 + i;
                    row["Pay"] = 50 * (i + 1);
                    table.Rows.Add(row);
                }
                return table;
            }

          由此就可以开始打印了:

        private void BT_MultiPrint_Click(object sender, RoutedEventArgs e)
            {
                PrintDialog printDialog = new PrintDialog();
                if (printDialog.ShowDialog() == true)
                {
                    DataTable dt = GetDataTable();
                    try
                    {
                        DataPaginator dp = new DataPaginator(dt, new Typeface("SimSun"), 16, 96 * 0.75, new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight));
                        printDialog.PrintDocument(dp, "Test Page");             
                    }
                    catch
                    {
                        MessageBox.Show("无法打印!");
                    }               
                }                                                                                                                                        
            }

         试用后效果还不错。Winform里的打印功能比这强大的多,可以将其使用的控件扩展为WPF里的控件,或进行别的处理。在这就不论述,有兴趣的可以自己去试试。

  • 相关阅读:
    docker基本命令
    vscode 保存提示运行"XXX"的保存参与者: 快速修复
    Vue 2.6 插槽
    代码大全-PartOne-变量命名
    Axure 8.0.1.3388 注册码 授权码 破解
    乱七八糟记一下乱七八糟的碎片化知识
    JavaScript需记的一些细节
    Python3.6问题
    python3.6- shape mismatch: objects cannot be broadcast to a single shape
    Angular+ng-zorro遇坑记
  • 原文地址:https://www.cnblogs.com/weivyuan/p/2851411.html
Copyright © 2011-2022 走看看