WinForm+VS2005下ReportViewer的打印问题

来源:互联网 发布:淘宝买家v6 编辑:程序博客网 时间:2024/05/16 08:44

        前几天,在做的一个项目中,用VS2005+ReportViewer进行报表套打,原来想的很简单,结果在试用过程中出现了很多问题。

        刚开始,非常顺利,经测试打印正常,但一试用问题就出现了。

        我开发用的机器的系统是win2003,客户的系统是xp,客户在打印时,打印出来的字都是竖的。我就奇怪了,难道这个操作系统还有关系,我又找了一个xp机器,试了试,结果证实确实和操作系统有关,不知道什么原因!

 

 

        第一个问题就是ReportViewer自带的打印按钮,第一次单击打印后,打印机没反应,第二次打印,打印机才有反应。这个问题非常令人奇怪,我觉得自己的程序没问题,就到网上搜了搜,结果发现我不是第一个,很多人都碰到了这个问题,而且这个问题ReportViewer的一个bug,是不治之症,我心里凉了半截,不过过了一会,又发现了一个外国人的一个狡猾的方法,代码如下,希望对你有用!

 

        private void reportViewer1_Print(object sender, CancelEventArgs e)
        {
            e.Cancel = true;

            timer1.Start();

            

        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            timer1.Stop();

            this.reportViewer1.PrintDialog();
            

        }

      这个方法就是用了一个定时器,触发第二次打印。我试了,确实起作用!

    

     第二个问题就是ReportViewer的页面设置问题。我套打的票据的宽度大于高度,所以ReportViewer的页面设置就自动地把打印的方向设置为“横向”,这样最终打印机打印出来的字在票据上是竖的。解决这个问题的方法就是对程序来定义页面设置,于是我又上网搜了搜,结果我仍然不是第一个,更令我心凉的是,这个问题仍然是ReportViewer的bug,是不治之症,看来我只有放弃用ReportViewer了,但我一时又找不到其他的好的打印方法,结果就死马当活马医了。我搜了搜,发现微软在页面设置时,采用的单位是英寸in,于是我也把报表的页面大小的单位用英寸in来表示,结果一试,还真行了,真是奇怪!不过好呆问题是解决了!不过这个方法有个后遗症。我这样改后,在xp可以正常打印了,在win2003下又不行了,看来还得针对操作系统发布不同的版本了,没办法。

 

    第三个问题就是“直接打印”问题了,客户要求票据打印的时候,不需要预览,不需要选择打印机,对于这个问题,我刚开始用了一个基于反射的方法,代码如下:

 

 public static class ReportViewerExt
 {
         /// <summary>
         /// 用默认打印机直接打印。
         /// </summary>
         /// <param name="reportViewer"></param>
         public static void DirectPrint(ReportViewer reportViewer)
         {
             Type reportViewerType = reportViewer.GetType();
 
             FieldInfo printFieldInfo = reportViewerType.GetField("Print", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField);
             CancelEventHandler printEvent = (CancelEventHandler)printFieldInfo.GetValue(reportViewer);
             if (printEvent != null)
             {
                 printEvent(reportViewer, null);
             }
 
             Object createEMFDeviceInfo = reportViewerType.InvokeMember("CreateEMFDeviceInfo", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod, null,  reportViewer, new object[] { 1, 1 });
             Delegate createAndRegisterStream = Delegate.CreateDelegate(typeof(CreateAndRegisterStream),  reportViewer, reportViewerType.GetMethod("CreateStreamEMFPrintOnly",  BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod));
             Type internalRenderingCompleteDelegateType = reportViewerType.Assembly.GetType("Microsoft.Reporting.WinForms.InternalRenderingCompleteDelegate");
             Delegate internalRenderingCompleteDelegate = Delegate.CreateDelegate(internalRenderingCompleteDelegateType,  reportViewer, reportViewerType.GetMethod("OnRenderingCompletePrintOnly", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod));
             Type postRenderArgsType = reportViewerType.Assembly.GetType("Microsoft.Reporting.WinForms.PostRenderArgs");
             Object postRenderArgs = Activator.CreateInstance(postRenderArgsType, false, true);
             Object report = reportViewerType.InvokeMember("Report", BindingFlags.NonPublic | BindingFlags.GetProperty |  BindingFlags.Instance, null, reportViewer, null);
             Object backgroundThread = reportViewerType.InvokeMember("BackgroundThread", BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance, null, reportViewer, null);
             backgroundThread.GetType().InvokeMember("BeginRender", BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.Instance, null, backgroundThread, new object[] { "IMAGE", true, createEMFDeviceInfo, createAndRegisterStream, internalRenderingCompleteDelegate, postRenderArgs, report });
 
             Type reportDocumentType = reportViewerType.Assembly.GetType("Microsoft.Reporting.WinForms.ReportPrintDocument");
             Object currentReport = reportViewerType.InvokeMember("CurrentReport", BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance, null, reportViewer, null);
             Object fileManager = currentReport.GetType().InvokeMember("FileManager", BindingFlags.GetProperty, null,  currentReport, null);
             Object pageSettings = reportViewerType.InvokeMember("PageSettings", BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance, null, reportViewer, null);
             Object pageSettingsClone = ((ICloneable)pageSettings).Clone();
             ConstructorInfo constructorInfo = reportDocumentType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { fileManager.GetType(), pageSettings.GetType() }, null);
 
             PrintDocument document = (PrintDocument)constructorInfo.Invoke(new object[] { fileManager, pageSettingsClone });
 
             document.DocumentName = (String)report.GetType().InvokeMember("DisplayNameForUse", BindingFlags.NonPublic | BindingFlags.GetProperty | BindingFlags.Instance, null, report, null);
             document.PrinterSettings = (PrinterSettings)reportViewerType.InvokeMember("CreateDefaultPrintSettings", BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance, null, reportViewer, null);
             document.PrinterSettings.PrintRange = PrintRange.AllPages;
             document.Print();
         }
 }

 

     这个方法刚开始的时候可以,但自从我把报表的单位从cm换成in之后,就再也不起作用了,我能力有限,不知道咋改,就又上网上搜了搜。这次发现微软官方到提供了解决方法,网址为:http://msdn.microsoft.com/en-us/library/ms252091(VS.80).aspx,我按照它的介绍,比葫芦画瓢,还真实现了,代码如下:

 

#region 打印

        private int m_currentPageIndex;
        private IList<Stream> m_streams;

        // Routine to provide to the report renderer, in order to
        //    save an image for each page of the report.
        private Stream CreateStream(string name, string fileNameExtension, Encoding encoding, string mimeType, bool willSeek)
        {
            string filenameext = DateTime.Now.Year.ToString() + DateTime.Now.Day.ToString() + DateTime.Now.Month.ToString() + DateTime.Now.Hour.ToString() + DateTime.Now.Minute.ToString() + DateTime.Now.Second.ToString();
            Stream stream = new FileStream(name  + "." + fileNameExtension, FileMode.Create);
            m_streams.Add(stream);
            return stream;
        }
        // Export the given report as an EMF (Enhanced Metafile) file.
        private void Export(LocalReport report)
        {
            string deviceInfo =
              "<DeviceInfo>" +
              "  <OutputFormat>EMF</OutputFormat>" +
              "  <PageWidth>7.5in</PageWidth>" +
              "  <PageHeight>3.66in</PageHeight>" +
              "  <MarginTop>0in</MarginTop>" +
              "  <MarginLeft>0in</MarginLeft>" +
              "  <MarginRight>0in</MarginRight>" +
              "  <MarginBottom>0in</MarginBottom>" +
              "</DeviceInfo>";
            Warning[] warnings;
            m_streams = new List<Stream>();
            report.Render("Image", deviceInfo, CreateStream, out warnings);

            foreach (Stream stream in m_streams)
            {
                stream.Position = 0;
            }
        }
        // Handler for PrintPageEvents
        private void PrintPage(object sender, PrintPageEventArgs ev)
        {
            Metafile pageImage = new Metafile(m_streams[m_currentPageIndex]);
            ev.Graphics.DrawImage(pageImage, ev.PageBounds);
            m_currentPageIndex++;
            ev.HasMorePages = (m_currentPageIndex < m_streams.Count);
        }

        private void Print()
        {
            if (m_streams == null || m_streams.Count == 0)
                return;

            PrintDocument printDoc = new PrintDocument();
            //printDoc.PrinterSettings.PrinterName = printDocument1.PrinterSettings.PrinterName;

            if (!printDoc.PrinterSettings.IsValid)
            {
                string msg = String.Format(
                   "Can't find printer /"{0}/".", "默认打印机!");
                MessageBox.Show(msg, "找不到默认打印机");
                return;
            }

            printDoc.PrintPage += new PrintPageEventHandler(PrintPage);
            printDoc.Print();
        }

        private void DirectPrintReport()
        {
            //直接打印
            try
            {
                LocalReport report = this.reportViewer1.LocalReport;

                Export(report);
                m_currentPageIndex = 0;
                Print();

                if (m_streams != null)
                {
                    foreach (Stream stream in m_streams)
                        stream.Close();
                    m_streams = null;
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("在打印过程中出现异常!");
                WlCommon.Log("在打印过程中出现异常:" + ex.Message);
            }
       

            //直接退出
            this.Close();
        }
        #endregion

       这个方法是先把报表保存成EMF格式的图片,然后再打印图片。这个方法应该比较通用。

 

      经过这一顿折腾,我算是领教了ReportViewer,我的结论就是:ReportViewer在程序中最好仅用来显示报表,打印最好还是用最后介绍的打印方法比较保险!

     

      最后说明一下,好像在VS2008里面好像已经没这个问题了,所以,如果有条件的话,最好用VS2008或更新的。