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里的控件,或进行别的处理。在这就不论述,有兴趣的可以自己去试试。

原文地址:https://www.cnblogs.com/weivyuan/p/2851411.html