RDLC 報表不預覽直接列印(Print RDLC Report without Preview)


RDLC 報表在使用時,一般情況下都會開啟預覽畫面,再按列印才能列印出來。

但是如果設計的程式沒有畫面呢?(Console Mode / Service Mode)

所以,這次我就使用 Console 程式設計方式把報表列印出來,並且使用 Class Data Mode 方式處理資料,不使用 RDLC 報表直接連接資料庫。

由於我使用的是 Visual Studio 2017,本身以不含RDLC報表設計功能,所以必須先到 NuGet 安裝 『Microsoft.ReportingServices.ReportViewerControl.Winforms』



這裡測試的方式是把一篇文章(SourceDoc.txt)讀取出來,然後放到 Report1.rdlc 做成的版面上,然後再將它印出來。

1、建立 DataModel.cs 用來儲存資料


namespace PrintRDLCWithoutPreview
{
    class DataModel
    {
        public int no { get; set; }
        public string line { get; set; }
    }
}


2、先進行方案的建置,因為RDLC要使用Class Model 資料來源時,需要建置過才能看到。



3、建立 Report1.rdlc



展開專案後可以看到自己定義的 DataModel.cs,選擇它作為資料來源


注意資料集的名稱,待會設定 DataSource 需要用到


拉取要顯示的欄位到需要顯示的類型,由於我們只有文字資料,所以只有拉『line』到顯示『值』上面


如果不需要統計值,直接『下一步』即可


調整位置與版面


4、需要用到的參考,由於會使用到Draw和winform集合的功能,所以必須引入


5、記得 資料檔案和 RDLC 檔案必須將屬性設成『一律複製』,才不會發生找不到檔案問題



6、程式碼(說明在注解上)


using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace PrintRDLCWithoutPreview
{
    class Program
    {
        //列印記數用的頁數紀錄
        static private int m_currentPageIndex;
        //列印用串流資料儲存區
        static private IList<Stream> m_streams;

        static void Main(string[] args)
        {
            List<DataModel> data = new List<DataModel>();
            int lineCount = 0;
            Console.WriteLine("讀取測試文字檔 SourceDoc.txt ....");
            using (StreamReader sr = new StreamReader("SourceDoc.txt"))
            {
                while(sr.Peek() >= 0)
                {
                    DataModel lineText = new DataModel()
                    {
                        no = ++lineCount,
                        line = sr.ReadLine()
                    };
                    data.Add(lineText);
                }
            }
            if(lineCount > 0)
            {
                Console.WriteLine("開始列印!");
                try
                {
                    // 列印!
                    RdlcPrint(data);
                }
                catch(Exception ex)
                {
                    // 發生處理錯誤時
                    Console.WriteLine("Print Error :" + ex.Message + "[" + ex.InnerException.ToString() + "]");
                }
            }
            else
            {
                Console.WriteLine("無資料可以列印!");
            }
            // 讓Console畫面停住,以便檢視相關錯誤訊息 ***按任意鍵關閉***
            Console.ReadKey();
        }
        // 使用RDLC 直接列印
        static void RdlcPrint(List<DataModel> data)
        {
            // 建立 LocalReport,並指定使用的 RDLC
            Microsoft.Reporting.WinForms.LocalReport localReport = new Microsoft.Reporting.WinForms.LocalReport();
            localReport.ReportPath = "Report1.rdlc";
            // 結繫資料時必須指定RDLC裡面資料集名稱
            // 且傳遞的資料必須是以 List<T> 的集合存在
            localReport.DataSources.Add(new Microsoft.Reporting.WinForms.ReportDataSource("DataSet1", data));

            Export(localReport);
            // 指定使用 印表機 『Microsoft XPS Document Writer』
            Print("Microsoft XPS Document Writer");

            localReport.Dispose();
        }
        // 提供給 the report renderer 使用, 用來建立列印用的 image stream
        static Stream CreateStream(string name, string fileNameExtension, Encoding encoding, string mimeType, bool willSeek)
        {
            Stream stream = new MemoryStream();
            m_streams.Add(stream);
            return stream;
        }
        // 把 RDLC 輸出成列印報表使用的 EMF(Enhanced Metafile file) 格式 的
        static void Export(Microsoft.Reporting.WinForms.LocalReport report)
        {
            //這裡是決定列印的紙張大小,RDLC只是用來排版的
            string deviceInfo =
              @"<DeviceInfo>
                <OutputFormat>EMF</OutputFormat>
                <PageWidth>8.27in</PageWidth>
                <PageHeight>11.69in</PageHeight>
                <MarginTop>0.25in</MarginTop>
                <MarginLeft>0.25in</MarginLeft>
                <MarginRight>0.25in</MarginRight>
                <MarginBottom>0.25in</MarginBottom>
            </DeviceInfo>"
;
            Microsoft.Reporting.WinForms.Warning[] warnings;
            m_streams = new List<Stream>();
            // 此處就是把 LocalReport 顯示的內容 轉成 Stream 資料
            report.Render("Image", deviceInfo, CreateStream, out warnings);
            foreach (Stream stream in m_streams)
                stream.Position = 0;
        }
        // 提供給 PrintDocument 作業用的 PrintPageEvents
        static void PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs ev)
        {
            //建立影像 Meta 同時把 Image Stream 倒入
            System.Drawing.Imaging.Metafile pageImage = new System.Drawing.Imaging.Metafile(m_streams[m_currentPageIndex]);

            // 調整繪製方塊大小同等印表機可列印空間
            System.Drawing.Rectangle adjustedRect = new System.Drawing.Rectangle(
                ev.PageBounds.Left - (int)ev.PageSettings.HardMarginX,
                ev.PageBounds.Top - (int)ev.PageSettings.HardMarginY,
                ev.PageBounds.Width,
                ev.PageBounds.Height);

            // 報表背景以白色塗刷
            ev.Graphics.FillRectangle(System.Drawing.Brushes.White, adjustedRect);

            // 繪製報表內容
            ev.Graphics.DrawImage(pageImage, adjustedRect);

            // 準備到下一頁繪製. 確保將資料繪製完畢
            m_currentPageIndex++;
            ev.HasMorePages = (m_currentPageIndex < m_streams.Count);
        }
        // 列印 m_streams 內的文件
        static void Print(string printerName)
        {
            if (m_streams == null || m_streams.Count == 0)
                throw new Exception("Error: no stream to print.");
            System.Drawing.Printing.PrintDocument printDoc = new System.Drawing.Printing.PrintDocument();
            // 可以設定要使用的印表機
            printDoc.PrinterSettings.PrinterName = printerName;
            if (!printDoc.PrinterSettings.IsValid)
            {
                throw new Exception("Error: cannot find the printer [" + printerName + "] .");
            }
            else
            {
                printDoc.PrintPage += new System.Drawing.Printing.PrintPageEventHandler(PrintPage);
                m_currentPageIndex = 0;
                printDoc.Print();
            }
        }
    }
}


說明:
在 main 一開始建立紀錄用的 DataModel ,由於我打算把文字資料一行一行儲存,所以DataModel 必須以 List<T> 方式記錄,然後把測試用文字檔以 StreamReader 一行一行讀進來,並加入 data 中,然後把 data 送到 RdlcPrint 處理。

在 RdlcPrint 中建立 localreport 物件,並把 data 送入  localreport  的 dataset1 內,產生列印報表,此時這個報表只是在 localreport 內的記憶體呈現,然後把 localreport 送到 Export 中,把 localreport 的畫面 Render 出來成為串流資料 stream 紀錄在 m_streams 變數中。

串流資料產生後,再交由 Print 方法處理,在 Print 中先產生PrintDocument 物件,然後設定好 PrintPageEvent 繪製 m_streams 方式,當把 m_streams 繪製好以後 就由 PrintDocument 直接列印及可。

7、列印結果








留言

Amy寫道…
您好:
先前參考您的電子發票熱感機列印程式,獲得許多幫助 謝謝您
現在又遇到了RDLC的A4格式列印問題想請教您
因User希望A4尺寸發票證明聯可連續列印多筆發票起訖
我使用RDLC列印時在SubReport內無法正常顯示頁首的頁碼資料
有何方法可以在SubReport 依發票字軌顯示出例如: 第1頁/共2頁...第2頁/共2頁的頁碼.
我如何提供您我的rdlc程式碼
WILDOX寫道…
Re: Amy <4470915237887936810>

Hi Amy:
您可以先參考下面網頁提供的解決方法試試看:
https://social.msdn.microsoft.com/Forums/sqlserver/en-US/c4f45c9a-82e6-4e07-8475-8a270a70f611/subreport-header-is-not-showing-inside-a-report-with-a-header-on-its-own?forum=sqlreportingservices
Unknown寫道…
您好,請問此程式可以使用於web嗎謝謝您
WILDOX寫道…
Q.您好,請問此程式可以使用於web嗎>>>

A:此程式寫法適用於Console模式,直接操作印表機的Meta檔,但是web受限瀏覽器權限無法直接操作印表機,所以無法使用此方式喔

這個網誌中的熱門文章

【研究】列印的條碼為什麼很難刷(掃描)

C# 使用 Process.Start 執行外部程式

統一發票列印小程式