C# Winform 批量打印

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Printing;
using System.Windows.Forms;
using System.Reflection;
using System.Data;
using System.IO;
 
namespace Common
{
    /// <summary>
    /// 通用打印模型(T-->列表數據的模型,該模型必須爲自定義的對象模型,嚴禁使用linq或其他工具生成的數據庫表模型,其中的各個字段均需要使用string類型)
    /// </summary>
    /// <typeparam name="T">列表數據的模型,該模型必須爲自定義的對象模型,嚴禁使用linq或其他工具生成的數據庫表模型,其中的各個字段均需要使用string類型</typeparam>
    public class PrintDataModel<T>
    {
        /// <summary>
        /// 文檔標題
        /// </summary>
        public string pageTitle { getset; }
        /// <summary>
        /// 圖形數據
        /// </summary>
        public Bitmap extData { getset; }
        /// <summary>
        /// 頭部的表頭數據(不包括文檔標題,如果沒有,應傳入 null;每個元素爲一行,如果一行無法容納,會換行處理。)
        /// </summary>
        public List<string> TitleData { getset; }
        /// <summary>
        /// 頁面中間的列表數據(如果沒有,應傳入 null)
        /// </summary>
        public List<T> TableData { getset; }
        /// <summary>
        /// 頁面中間的列表數據的表頭(與列表數據中的列一一對應,如果沒有(僅限列表數據傳入null的情況),應傳入 null)
        /// </summary>
        public List<string> ColumnNames { getset; }
        /// <summary>
        /// 列表數據的各個列是否允許折行顯示,允許爲true,不允許爲false(僅當自動計算的列寬不足以顯示內容時生效,如果傳入null則表示所有列均允許折行顯示)
        /// </summary>
        public List<bool> CanResetLine { getset; }
        /// <summary>
        /// 底部的結尾數據(不包括頁碼,如果沒有,應傳入 null;每個元素爲一行,如果一行無法容納,會換行處理。)
        /// </summary>
        public List<string> EndData { getset; }
    }
    /// <summary>
    /// 通用打印類(調用本類的方法是,請使用try-catch語句塊,內部錯誤消息將以異常的形式拋出)
    /// </summary>
    /// <typeparam name="T">列表數據的模型,該模型必須爲自定義的對象模型,嚴禁使用linq或其他工具生成的數據庫表模型,其中的各個字段均需要使用string類型</typeparam>
    public class CommonPrintTools<T>
    {
        private PrintDocument docToPrint = new System.Drawing.Printing.PrintDocument();//創建一個PrintDocument的實例
        /// <summary>
        /// 需打印的文檔數據
        /// </summary>
        private List<PrintDataModel<T>> _printDataModels;
        /// <summary>
        /// 數據打印時的頁碼
        /// </summary>
        private int pageIndex = 1;
        /// <summary>
        /// 需要打印的文檔繪製出的圖片數據
        /// </summary>
        private List<Bitmap> _printBmps = new List<Bitmap>();
        private int count = 0;
        /// <summary>
        /// 初始化打印類的各項參數
        /// </summary>
        /// <param name="PrintDataModels">需打印的文檔數據</param>
        public CommonPrintTools(List<PrintDataModel<T>> PrintDataModels)
        {
            this.docToPrint.PrintPage += new PrintPageEventHandler(docToPrint_PrintPage);//將事件處理函數添加到PrintDocument的PrintPage中
            _printDataModels = PrintDataModels;
        }
        /// <summary>
        /// 打印機開始打印的事件處理函數
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void docToPrint_PrintPage(object sender, PrintPageEventArgs e)
        {
            Font f = new Font("宋體", 10, FontStyle.Regular);
            decimal Chinese_OneWidth = Convert.ToDecimal(e.Graphics.MeasureString("測", f).Width);
            int pageWidth = Convert.ToInt32(Math.Round(e.PageSettings.PrintableArea.Width, 0)) - 1;//打印機可打印區域的寬度
            int onePageHeight = Convert.ToInt32(Math.Round(e.PageSettings.PrintableArea.Height, 0)) - 1;//打印機可打印區域的高度
            if (_printBmps == null || _printBmps.Count <= 0)
                _printBmps = DrawPrintPic(e.Graphics, pageWidth, onePageHeight);
 
            e.Graphics.DrawImage(_printBmps[count], new Rectangle((int)Math.Ceiling(Chinese_OneWidth), 0, _printBmps[count].Width, _printBmps[count].Height));
 
            /********************start--判斷是否需要再打印下一頁--start*************************/
            count++;
            if (_printBmps.Count > count)
                e.HasMorePages = true;
            else
                e.HasMorePages = false;
            /**********************end--判斷是否需要再打印下一頁--end*************************/
        }
        /// <summary>
        /// 繪製需打印的內容
        /// </summary>
        /// <param name="eventG"></param>
        /// <param name="pageWidth"></param>
        /// <param name="allPageHeight"></param>
        /// <param name="msg"></param>
        /// <returns></returns>
        private List<Bitmap> DrawPrintPic(Graphics eventG, int pageWidth, int pageHeight)
        {
            Font f = new Font("宋體", 10, FontStyle.Regular);
            decimal Chinese_OneWidth = Convert.ToDecimal(eventG.MeasureString("測", f).Width);
            decimal Chinese_OneHeight = Convert.ToDecimal(eventG.MeasureString("測", f).Height);
            decimal English_OneWidth = Convert.ToDecimal(eventG.MeasureString("c", f).Width);
            decimal English_OneHeight = Convert.ToDecimal(eventG.MeasureString("c", f).Height);
            List<Bitmap> bmpList = new List<Bitmap>();
            //循環需打印的文檔集合
            foreach (var pdm in _printDataModels)
            {
                Bitmap bp = new Bitmap(pageWidth, pageHeight);
                Graphics g = Graphics.FromImage(bp);
                //填上底色
                g.FillRectangle(Brushes.White, 0, 0, bp.Width, bp.Height);
                /************************start初始化每個文檔的數據start************************/
                //頁面中間的列表數據
                List<T> middleData = pdm.TableData;
                //頁面中間的列表數據的表頭
                List<string> columnNames = pdm.ColumnNames;
                //頭部的表頭數據(不包括文檔標題)
                List<string> topData = pdm.TitleData;
                //底部的結尾數據(不包括頁碼)
                List<string> bottomData = pdm.EndData;
                //文檔標題
                string pageTitle = pdm.pageTitle;
                //各個列是否允許折行
                List<bool> CanResetLine = pdm.CanResetLine;
                //圖形數據
                Bitmap codeBP = pdm.extData;
                //檢查數據列表的列與列明是否對應
                if (middleData != null && middleData.Count > 0)
                {
                    System.Reflection.PropertyInfo[] pInfo = middleData[0].GetType().GetProperties();
                    if (pInfo.Length != columnNames.Count)
                    {
                        throw new Exception("列表數據的列數與表頭數據的列數不相符!");
                    }
                }
                /**************************end初始化每個文檔的數據end**************************/
                /*********************start計算各部分高度以及頁面數據start*********************/
                //計算表頭高度
                decimal headEndHeight = ComputeHeadEndHeight(pageTitle, topData, Chinese_OneHeight, pageWidth, g, f);
                //計算結尾高度
                headEndHeight += ComputeHeadEndHeight(null, bottomData, Chinese_OneHeight, pageWidth, g, f);
                //如果列表中有數據,並且表頭部分+一行空行的高度大於等於頁面高度(頁面中沒有剩餘空間繪製列表數據),拋出異常信息
                if ((headEndHeight + Chinese_OneHeight >= pageHeight) && middleData != null && middleData.Count > 0)
                {
                    throw new Exception("表頭或表尾數據太多,頁面中將沒有繪製數據行的空間!");
                }
                //數據列表的各列寬度
                decimal[] columnWidths = null;
                //數據列表的各行行高
                decimal[] rowHeights = null;
                DataTable dt = new DataTable();
                if (middleData != null && middleData.Count > 0)
                {
                    string msg = "";
                    dt = ReflactionToDataTable(middleData, columnNames, ref msg);
                    if (dt == null && !string.IsNullOrEmpty(msg))
                    {
                        throw new Exception(msg);
                    }
                    columnWidths = GetColumnWidths(dt, CanResetLine, Chinese_OneWidth, g, f);
                    columnWidths = GetColumnWidthToPage(columnWidths, pageWidth, Chinese_OneWidth, CanResetLine, g, f);
                    rowHeights = GetRowHeights(dt, columnWidths, g, f);
                }
                /***********************end計算各部分高度以及頁面數據end***********************/
                /****start繪製繪製繪製繪製繪製繪製繪製繪製繪製繪製繪製繪製繪製繪製繪製start****/
                //中間數據列表存在的情況
                if (middleData != null && middleData.Count > 0 && dt != null && dt.Rows.Count > 0)
                {
                    bmpList.AddRange(DrawPage(pageWidth - (int)Math.Ceiling(Chinese_OneWidth * 2), pageHeight, f, Chinese_OneWidth, Chinese_OneHeight, topData, bottomData, pageTitle, codeBP, CanResetLine, rowHeights, dt, headEndHeight));
                }
                else//沒有中間數據列表的情況
                {
                    bmpList.AddRange(DrawPageWithOutDataTable(pageWidth - (int)Math.Ceiling(Chinese_OneWidth * 2), pageHeight, f, Chinese_OneHeight, topData, bottomData, pageTitle));
                }
                /******end繪製繪製繪製繪製繪製繪製繪製繪製繪製繪製繪製繪製繪製繪製繪製end******/
            }
            return bmpList;
        }
        /// <summary>
        /// 繪製打印頁面
        /// </summary>
        /// <param name="pageWidth">頁面寬度</param>
        /// <param name="pageHeight">頁面高度</param>
        /// <param name="f">字體</param>
        /// <param name="Chinese_OneWidth">單字寬度</param>
        /// <param name="Chinese_OneHeight">單字高度</param>
        /// <param name="topData">頭部的表頭數據(不包括文檔標題)</param>
        /// <param name="bottomData">底部的結尾數據(不包括頁碼)</param>
        /// <param name="pageTitle">文檔標題</param>
        /// <param name="columnWidths">列寬度集合</param>
        /// <param name="rowHeights">行高集合</param>
        /// <param name="dt">需打印的所有列表數據</param>
        /// <returns>所有需要打印的頁面</returns>
        private List<Bitmap> DrawPage(int pageWidth, int pageHeight, Font f, decimal Chinese_OneWidth, decimal Chinese_OneHeight, List<string> topData, List<string> bottomData, string pageTitle, Bitmap codeBP, List<bool> CanResetLine, decimal[] rowHeights, DataTable dt, decimal headEndHeight)
        {
            List<Bitmap> bmpList = new List<Bitmap>();
            //獲取每頁中需要打印的列表數據
            List<DataTable> dts = GetDataTableToPages(dt, rowHeights, pageHeight - headEndHeight - Chinese_OneHeight);
            //未分割DataTable時DataTable的行索引
            int sourceRowIndex = 1;
            //未分割DataTable時DataTable的行索引(用於計算)
            int sourceRowIndexForCompute = 1;
            //頁索引
            int pageIndex = 1;
            //循環繪製每一頁
            foreach (DataTable drawDt in dts)
            {
                Bitmap bpItem = new Bitmap(pageWidth, pageHeight);
                Graphics gItem = Graphics.FromImage(bpItem);
                //當前顯示的數據列表的各列寬度
                decimal[] columnWidths = GetColumnWidths(drawDt, CanResetLine, Chinese_OneWidth, gItem, f);
                columnWidths = GetColumnWidthToPage(columnWidths, pageWidth, Chinese_OneWidth, CanResetLine, gItem, f);
                //填上底色
                gItem.FillRectangle(Brushes.White, 0, 0, bpItem.Width, bpItem.Height);
                //當前繪製行距離頁面最頂部的距離
                decimal currentMarginTop = 0;
                /******************************start表頭部分start******************************/
                //標題
                if (!string.IsNullOrEmpty(pageTitle))
                {
                    currentMarginTop += Chinese_OneHeight;
                    List<string> drawValues = GetMultiLineString(pageTitle, pageWidth, gItem, f);
                    foreach (string dv in drawValues)
                    {
                        gItem.DrawString(dv, f, Brushes.Black, (float)(pageWidth - (decimal)gItem.MeasureString(dv, f).Width) / 2, (float)currentMarginTop);
                        currentMarginTop += Chinese_OneHeight;
                    }
                }
                //圖形
                if (codeBP != null && codeBP.Height > 0 && codeBP.Width > 0)
                {
                    gItem.DrawImage(codeBP, 0, (float)currentMarginTop);
                    currentMarginTop += codeBP.Height;
                }
                //表頭數據
                if (topData != null && topData.Count > 0)
                {
                    foreach (string tdTitle in topData)
                    {
                        currentMarginTop += Chinese_OneHeight/2;
                        List<string> drawValues = GetMultiLineString(tdTitle, pageWidth, gItem, f);
                        foreach (string dv in drawValues)
                        {
                            gItem.DrawString(dv, f, Brushes.Black, 0, (float)currentMarginTop);
                            currentMarginTop += Chinese_OneHeight;
                        }
                    }
                }
                /********************************end表頭部分end********************************/
                /****************************start中間列表部分start****************************/
                //繪製標題行
                //每個單元格距離左側的距離
                decimal colLeft = 0;
                decimal drawDtHeight = rowHeights[0];//GetRowHeights(drawDt, Chinese_OneHeight, columnWidths, gItem, f).Sum();
                for (int rowIndex = 0; rowIndex < drawDt.Rows.Count; rowIndex++)
                {
                    drawDtHeight += rowHeights[sourceRowIndexForCompute];
                    sourceRowIndexForCompute++;
                }
                currentMarginTop += Chinese_OneHeight / 2;
                //繪製各個列的標題
                for (int colIndex = 0; colIndex < drawDt.Columns.Count; colIndex++)
                {
                    //列名
                    string colName = drawDt.Columns[colIndex].ColumnName;
                    //計算需要繪製的文字需要在幾行中顯示
                    List<string> drawValues = GetMultiLineString(colName, (float)columnWidths[colIndex], gItem, f);
                    decimal rowMarginTop = currentMarginTop;
                    foreach (string dv in drawValues)
                    {
                        gItem.DrawString(dv, f, Brushes.Black, (float)colLeft, (float)rowMarginTop);
                        rowMarginTop += Chinese_OneHeight;
                    }
                    //繪製表格的列
                    gItem.DrawLine(new Pen(Brushes.Black), new Point((int)Math.Ceiling(colLeft), (int)Math.Floor(currentMarginTop) - 1), new Point((int)Math.Ceiling(colLeft), (int)Math.Ceiling(currentMarginTop + drawDtHeight) - 2));
                    //每個單元格的數據需要向右移動一個當前單元格的寬度
                    colLeft += columnWidths[colIndex];
                }
                //繪製表格的第一行
                gItem.DrawLine(new Pen(Brushes.Black), new Point(0, (int)Math.Floor(currentMarginTop) - 1), new Point((int)Math.Ceiling(columnWidths.Sum() - 2), (int)Math.Floor(currentMarginTop) - 1));
                //繪製表格的最後一列
                gItem.DrawLine(new Pen(Brushes.Black), new Point((int)Math.Ceiling(columnWidths.Sum() - 2), (int)Math.Floor(currentMarginTop) - 1), new Point((int)Math.Ceiling(columnWidths.Sum() - 2), (int)Math.Ceiling(currentMarginTop + drawDtHeight) - 2));
                currentMarginTop += rowHeights[0];
                //繪製列表中的數據
                for (int rowIndex = 0; rowIndex < drawDt.Rows.Count; rowIndex++)
                {
                    //每個單元格距離左側的距離
                    decimal rowMarginLeft = 0;
                    for (int colIndex = 0; colIndex < dt.Columns.Count; colIndex++)
                    {
                        //需要繪製的當前單元格的文字
                        string drawValue = drawDt.Rows[rowIndex][colIndex].ToString();
                        //計算需要繪製的文字需要在幾行中顯示
                        List<string> drawValues = GetMultiLineString(drawValue, (float)columnWidths[colIndex], gItem, f);
                        //當前行的各個列均是從同一個高度(距離頂部的距離)開始繪製的
                        decimal rowMarginTop = currentMarginTop;
                        foreach (string dv in drawValues)
                        {
                            gItem.DrawString(dv, f, Brushes.Black, (float)rowMarginLeft, (float)rowMarginTop);
                            rowMarginTop += Chinese_OneHeight;
                        }
                        //每個單元格的數據需要向右移動一個當前單元格的寬度
                        rowMarginLeft += columnWidths[colIndex];
                    }
                    //繪製表格的行
                    gItem.DrawLine(new Pen(Brushes.Black), new Point(0, (int)Math.Floor(currentMarginTop) - 1), new Point((int)Math.Ceiling(columnWidths.Sum() - 2), (int)Math.Floor(currentMarginTop) - 1));
                    //繪製完一行的數據後,將高度(距離頂部的距離)累加當前行的高度
                    currentMarginTop += rowHeights[sourceRowIndex];
                    sourceRowIndex++;
                }
                //繪製表格的行
                gItem.DrawLine(new Pen(Brushes.Black), new Point(0, (int)Math.Floor(currentMarginTop) - 1), new Point((int)Math.Ceiling(columnWidths.Sum() - 2), (int)Math.Floor(currentMarginTop) - 1));
                /******************************end中間列表部分end******************************/
                /******************************start結尾部分start******************************/
                if (bottomData != null && bottomData.Count > 0)
                {
                    currentMarginTop += Chinese_OneHeight / 2;
                    foreach (string bdData in bottomData)
                    {
                        //當前行的各個列均是從同一個高度(距離頂部的距離)開始繪製的
                        decimal rowMarginTop = currentMarginTop;
                        //計算需要繪製的文字需要在幾行中顯示
                        List<string> drawValues = GetMultiLineString(bdData, (float)pageWidth, gItem, f);
                        foreach (string dv in drawValues)
                        {
                            gItem.DrawString(dv, f, Brushes.Black, 0, (float)rowMarginTop);
                            rowMarginTop += Chinese_OneHeight;
                        }
                        currentMarginTop += Chinese_OneHeight;
                    }
                }
                string pageInfo = string.Format("當前第{0}頁,共{1}頁", pageIndex.ToString(), dts.Count.ToString());
                gItem.DrawString(pageInfo, f, Brushes.Black, (float)(pageWidth - (decimal)gItem.MeasureString(pageInfo, f).Width - Chinese_OneWidth), (float)currentMarginTop);
                /********************************end結尾部分end********************************/
                bmpList.Add(bpItem);
                pageIndex++;
            }
            return bmpList;
        }
        /// <summary>
        /// 繪製打印頁面(無數據列表)
        /// </summary>
        /// <param name="pageWidth"></param>
        /// <param name="pageHeight"></param>
        /// <param name="f"></param>
        /// <param name="Chinese_OneWidth"></param>
        /// <param name="Chinese_OneHeight"></param>
        /// <param name="topData"></param>
        /// <param name="bottomData"></param>
        /// <param name="pageTitle"></param>
        /// <returns></returns>
        private List<Bitmap> DrawPageWithOutDataTable(int pageWidth, int pageHeight, Font f, decimal Chinese_OneHeight, List<string> topData, List<string> bottomData, string pageTitle)
        {
            List<Bitmap> bmpList = new List<Bitmap>();
            //所有需要繪製的數據(不包括標題)
            List<string> drawList = new List<string>();
            if (topData != null && topData.Count > 0)
                drawList.AddRange(topData);
            if (bottomData != null && bottomData.Count > 0)
                drawList.AddRange(bottomData);
            Bitmap bp = new Bitmap(pageWidth, pageHeight);
            Graphics g = Graphics.FromImage(bp);
            //可用繪製數據的高度
            decimal dataHeight = pageHeight - Chinese_OneHeight;//默認爲頁面高度減去一個空行高度
            if (!string.IsNullOrEmpty(pageTitle))
            {
                //標題的高度
                decimal titleHeight = 0;
                //標題切割成的多行
                List<string> titles = GetMultiLineString(pageTitle, pageWidth, g, f);
                //計算標題的總高度
                foreach (string in titles)
                {
                    titleHeight += (decimal)g.MeasureString(t, f).Width;
                }
                //計算可用繪製數據的高度(頁面高度減去標題高度再減去兩個空行的高度)
                dataHeight = (decimal)pageHeight - titleHeight - Chinese_OneHeight;
            }
            //每頁需要繪製的數據集合
            List<List<string>> drawData = GetLinesToPages(drawList, Chinese_OneHeight, dataHeight, pageWidth, g, f);
            //循環繪製每頁的數據
            foreach (List<string> drawD in drawData)
            {
                Bitmap bpItem = new Bitmap(pageWidth, pageHeight);
                Graphics gItem = Graphics.FromImage(bpItem);
                //填上底色
                gItem.FillRectangle(Brushes.White, 0, 0, bpItem.Width, bpItem.Height);
                //當前繪製行距離頁面最頂部的距離
                decimal currentMarginTop = 0;
                //標題
                if (!string.IsNullOrEmpty(pageTitle))
                {
                    currentMarginTop += Chinese_OneHeight;
                    List<string> drawValues = GetMultiLineString(pageTitle, pageWidth, gItem, f);
                    foreach (string dv in drawValues)
                    {
                        gItem.DrawString(dv, f, Brushes.Black, (float)(pageWidth - (decimal)gItem.MeasureString(dv, f).Width) / 2, (float)currentMarginTop);
                        currentMarginTop += Chinese_OneHeight;
                    }
                }
                foreach (string dv in drawD)
                {
                    gItem.DrawString(dv, f, Brushes.Black, 0, (float)currentMarginTop);
                    currentMarginTop += Chinese_OneHeight;
                }
                string pageInfo = string.Format("當前第{0}頁,共{1}頁", pageIndex.ToString(), drawData.Count.ToString());
                gItem.DrawString(pageInfo, f, Brushes.Black, 0, (float)currentMarginTop);
                bmpList.Add(bpItem);
                pageIndex++;
            }
            return bmpList;
        }
        /// <summary>
        /// 開始打印
        /// </summary>
        /// <param name="printname">計算機上已安裝的打印機的名稱</param>
        public void StartPrint(string printname)
        {
            System.Windows.Forms.PrintDialog PrintDialog1 = new PrintDialog();//創建一個PrintDialog的實例。
            PrintDialog1.AllowSomePages = true;
            PrintDialog1.ShowHelp = true;
            PrintDialog1.Document = docToPrint;//把PrintDialog的Document屬性設爲上面配置好的PrintDocument的實例
            docToPrint.PrinterSettings.PrinterName = printname; //_366KF.Manage.Common.PickOrderPrinter; //設置打印機,填寫計算機上已安裝的打印機的名稱
            docToPrint.Print();//開始打印
        }
        /// <summary>
        /// 打印預覽
        /// </summary>
        /// <param name="dt">要打印的DataTable</param>
        /// <param name="Title">打印文件的標題</param>
        public void PrintPriview()
        {
            try
            {
                PrintPreviewDialog PrintPriview = new PrintPreviewDialog();
                PrintPriview.Document = docToPrint;
                PrintPriview.WindowState = FormWindowState.Maximized;
                PrintPriview.ShowDialog();
            }
            catch (Exception ex)
            {
                MessageBox.Show("打印錯誤,請檢查打印設置!消息:" + ex.Message);
 
            }
        }
        public void TestPrint()
        {
            int pageWidth = 300; //Convert.ToInt32(Math.Round(e.PageSettings.PrintableArea.Width, 0)) - 1;//打印機可打印區域的寬度
            int onePageHeight = 1000;// Convert.ToInt32(Math.Round(e.PageSettings.PrintableArea.Height, 0)) - 1;//打印機可打印區域的高度
            Bitmap bp = new Bitmap(pageWidth, onePageHeight);
            Graphics g = Graphics.FromImage(bp);
            List<Bitmap> bmps = DrawPrintPic(g, pageWidth, onePageHeight);
            int bmpName = 1;
            string path = Application.StartupPath + "/pic";
            List<string> paths = Directory.GetFiles(path).ToList();
            foreach (string in paths)
            {
                File.Delete(p);
            }
            foreach (Bitmap b in bmps)
            {
                if (!Directory.Exists(path))
                    Directory.CreateDirectory(path);
                b.Save(path + "/" + bmpName + ".jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
                bmpName++;
            }
        }
        /// <summary>
        /// 利用反射將數據對象集合轉換爲DataTable
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dataModel"></param>
        /// <param name="columnNames"></param>
        /// <returns></returns>
        private DataTable ReflactionToDataTable<T>(List<T> dataModel, List<string> columnNames, ref string msg)
        {
            try
            {
                DataTable dt = new DataTable();
                if (typeof(T).Equals(typeof(String)))
                {
                    msg = "數據集合必須爲自定義對象集合!";
                    return new DataTable();
                }
                if (dataModel == null || dataModel.Count <= 0)
                {
                    msg = "傳入的數據集合爲空!";
                    return new DataTable();
                }
                if (columnNames == null || columnNames.Count <= 0)
                {
                    msg = "傳入的列名數據集合爲空!";
                    return new DataTable();
                }
                PropertyInfo[] pInfos = ((object)dataModel[0]).GetType().GetProperties();
                if (pInfos.Length != columnNames.Count)
                {
                    msg = "數據列數與列名數量不一致!";
                    return new DataTable();
                }
                for (int i = 0; i < columnNames.Count; i++)
                {
                    dt.Columns.Add(columnNames[i], pInfos[i].PropertyType);
                }
                for (int i = 0; i < dataModel.Count; i++)
                {
                    object[] objArray = new object[pInfos.Length];
                    for (int j = 0; j < pInfos.Length; j++)
                    {
                        object ob = pInfos[j].GetValue(dataModel[i], null);
                        objArray.SetValue(ob, j);
                    }
                    dt.LoadDataRow(objArray, true);
                }
                return dt;
            }
            catch (Exception ex)
            {
                msg = "請檢查傳入的數據類型,是否爲List<自定義對象>類型,自定義對象必須有屬性值!(異常信息:" + ex.Message + ")";
                return null;
            }
        }
        /// <summary>
        /// 講一個字符串按照固定的長度切分爲多個適合長度的字符串
        /// </summary>
        /// <param name="dataLine">需要切分的字符串</param>
        /// <param name="width">固定的長度</param>
        /// <param name="g">繪製對象</param>
        /// <param name="f">字體</param>
        /// <returns>切分後得到的字符串集合</returns>
        private List<string> GetMultiLineString(string dataLine, float width, Graphics g, Font f)
        {
            List<string> dataLines = new List<string>();
            char[] chars = dataLine.ToCharArray();
            decimal widthT = Convert.ToDecimal(width);
            decimal charWidth = Convert.ToDecimal(g.MeasureString("c".ToString(), f).Width);
            if (widthT < charWidth)
            {
                throw new Exception("數據無法正常顯示,經優化計算後的列寬不足以存放一個字符!");
            }
            decimal widthC = 0;
            int i = 0;
            string dLine = "";
            string tmpLine = "";
            while (true)
            {
                if (i == chars.Length)
                    widthC = decimal.MaxValue;
                else
                {
                    tmpLine += chars[i].ToString();
                    widthC = Convert.ToDecimal(g.MeasureString(tmpLine, f).Width);
                }
                if (widthC < widthT)
                {
                    dLine = tmpLine;
                }
                else
                {
                    dataLines.Add(dLine);
                    widthC = 0;
                    dLine = "";
                    tmpLine = "";
                    if (i < chars.Length)
                        i--;
                }
                if (i >= chars.Length)
                    break;
                i++;
            }
            return dataLines;
        }
        /// <summary>
        /// 獲取各個列的最大寬度值
        /// </summary>
        /// <param name="DataTablePrint">數據列表</param>
        /// <param name="CanResetLine">各列是否允許折行顯示</param>
        /// <param name="Chinese_OneWidth">單字寬度</param>
        /// <param name="g">繪製對象</param>
        /// <param name="f">字體</param>
        /// <returns></returns>
        private decimal[] GetColumnWidths(DataTable DataTablePrint, List<bool> CanResetLine, decimal Chinese_OneWidth, Graphics g, Font f)
        {
            decimal[] res = new decimal[DataTablePrint.Columns.Count]; ;
            foreach (DataRow dr in DataTablePrint.Rows)
            {
                for (int i = 0; i < DataTablePrint.Columns.Count; i++)
                {
                    //後面加的半個單字寬度用來抵消decimal類型尾數被捨棄的情形
                    decimal colwidth = Convert.ToDecimal(g.MeasureString(dr[i].ToString().Trim(), f).Width) + (CanResetLine[i] ? 0 : Chinese_OneWidth / 2);
                    if (colwidth > res[i])
                    {
                        res[i] = colwidth;
                    }
                }
            }
            for (int Cols = 0; Cols <= DataTablePrint.Columns.Count - 1; Cols++)
            {
                string ColumnText = DataTablePrint.Columns[Cols].ColumnName.ToString();
                //後面加的半個單字寬度用來抵消decimal類型尾數被捨棄的情形
                decimal colwidth = Convert.ToInt32(g.MeasureString(ColumnText, f).Width) + (CanResetLine[Cols] ? 0 : Chinese_OneWidth / 2);
                if (colwidth > res[Cols])
                {
                    res[Cols] = colwidth;
                }
            }
            return res;
        }
        /// <summary>
        /// 計算表頭部分高度
        /// </summary>
        /// <param name="pageTitle">頁面標題</param>
        /// <param name="topData">表頭數據</param>
        /// <param name="Chinese_OneHeight">單行高度</param>
        /// <param name="pageWidth">頁面寬度</param>
        /// <param name="g">繪製對象</param>
        /// <param name="f">字體</param>
        /// <returns></returns>
        private decimal ComputeHeadEndHeight(string pageTitle, List<string> topData, decimal Chinese_OneHeight, float pageWidth, Graphics g, Font f)
        {
            decimal headHeight = 0;
            if (!string.IsNullOrEmpty(pageTitle))
            {
                List<string> pts = GetMultiLineString(pageTitle, pageWidth, g, f);
                headHeight += Chinese_OneHeight;
                headHeight += GetRowHeight(pts, g, f);
                headHeight += Chinese_OneHeight;
            }
            if (topData != null && topData.Count > 0)
            {
                foreach (string tds in topData)
                {
                    List<string> tdss = GetMultiLineString(tds, pageWidth, g, f);
                    headHeight += GetRowHeight(tdss, g, f);
                }
            }
            return headHeight;
        }
        /// <summary>
        /// 獲取各個行的最大高度值(索引爲0的行高爲列標題的最大行高)
        /// </summary>
        /// <param name="dt">數據列表</param>
        /// <param name="columnWidths">各個列的最大寬度值</param>
        /// <param name="g">繪製對象</param>
        /// <param name="f">字體</param>
        /// <returns>返回行最大高度值集合</returns>
        private decimal[] GetRowHeights(DataTable dt, decimal[] columnWidths, Graphics g, Font f)
        {
            decimal[] rowHeights = new decimal[dt.Rows.Count + 1];
            int columnNameIndex = 0;
            //計算標題行的高度
            foreach (DataColumn dc in dt.Columns)
            {
                string dValue = dc.ColumnName;
                decimal cwidth = columnWidths[columnNameIndex];
                List<string> mLines = GetMultiLineString(dValue, (float)cwidth, g, f);
                decimal h = GetRowHeight(mLines, g, f);
                if (h > rowHeights[0])
                {
                    rowHeights[0] = h;
                }
                columnNameIndex++;
            }
            int columnIndex = 0;
            //計算各個數據行的高度
            foreach (DataColumn dc in dt.Columns)
            {
                for (int i = 0; i < dt.Rows.Count; i++)
                {
                    string dValue = dt.Rows[i][dc.ColumnName].ToString();
                    decimal cwidth = columnWidths[columnIndex];
                    List<string> mLines = GetMultiLineString(dValue, (float)cwidth, g, f);
                    decimal h = GetRowHeight(mLines, g, f);
                    if (h > rowHeights[i + 1])
                    {
                        rowHeights[i + 1] = h;
                    }
                }
                columnIndex++;
            }
            return rowHeights;
        }
        /// <summary>
        /// 計算行高
        /// </summary>
        /// <param name="lines">一個DataRow需要顯示的行集合</param>
        /// <param name="g">繪製對象</param>
        /// <param name="f">字體</param>
        /// <returns>返回DataRow行高</returns>
        private decimal GetRowHeight(List<string> lines, Graphics g, Font f)
        {
            decimal h = 0;
            foreach (string line in lines)
            {
                h += (decimal)g.MeasureString(line, f).Height;
            }
            return h;
        }
        /// <summary>
        /// 根據實際的列寬計算適應頁面所需的列寬度
        /// </summary>
        /// <param name="columnWidths">實際列寬</param>
        /// <param name="pageWidth">頁面寬度</param>
        /// <param name="Chinese_OneWidth">單字寬度</param>
        /// <param name="CanResetLine">各列是否允許折行顯示</param>
        /// <param name="g">繪製對象</param>
        /// <param name="f">字體</param>
        /// <returns>按比例計算之後的列寬</returns>
        private decimal[] GetColumnWidthToPage(decimal[] columnWidths, decimal pageWidth, decimal Chinese_OneWidth, List<bool> CanResetLine, Graphics g, Font f)
        {
            string cWidthString = "";
            for (int i = 0; i < columnWidths.Length; i++)
            {
                cWidthString += "測";
            }
            if (pageWidth < (decimal)g.MeasureString(cWidthString, f).Width)
            {
                throw new Exception("列數太多,當前紙張無法呈現(以每列一個漢字算,所有列的寬度之和,大於紙張寬度)!");
            }
            //允許折行的列數
            int canResetCount = 0;
            //不允許折行的列的總寬度
            decimal solidWidth = 0;
            //允許折行的列的總寬度
            decimal columnSumWidth = columnWidths.Sum();
            //是否存在不允許折行的列
            if (CanResetLine != null && CanResetLine.Where(c => !c).Count() > 0)
            {
                columnSumWidth = 0;
                for (int i = 0; i < columnWidths.Length; i++)
                {
                    //如果當前列不允許折行,累加不允許折行的列的總寬度
                    if (!CanResetLine[i])
                    {
                        solidWidth += columnWidths[i];
                    }
                    else//累加允許折行的列的總寬度,累加允許折行的列數
                    {
                        columnSumWidth += columnWidths[i];
                        canResetCount++;
                    }
                }
                //計算可以進行折行處理的剩餘總寬度
                pageWidth -= solidWidth;
            }
            //如果 按單字寬度計算,允許折行的總寬度 大於(>) 可以進行折行處理的剩餘總寬度,則拋出異常
            string crWidthString = "";
            for (int i = 0; i < canResetCount; i++)
            {
                crWidthString += "測";
            }
            if ((decimal)g.MeasureString(crWidthString, f).Width > pageWidth)
            {
                throw new Exception("當前紙張無法呈現所有內容(以每列一個漢字算,所有可折行列的寬度之和,大於可以進行折行處理的剩餘總寬度)!");
            }
            decimal[] res = new decimal[columnWidths.Length];
            columnWidths.CopyTo(res, 0);
            for (int i = 0; i < columnWidths.Length; i++)
            {
                if (CanResetLine == null || CanResetLine[i])
                    res[i] = (res[i] / columnSumWidth) * pageWidth;
            }
            return res;
        }
        /// <summary>
        /// 獲取每頁的數據列表
        /// </summary>
        /// <param name="dt">所有列表數據</param>
        /// <param name="rowHeights">行高集合</param>
        /// <param name="dataHeight">顯示數據的可用高度</param>
        /// <returns></returns>
        private List<DataTable> GetDataTableToPages(DataTable dt, decimal[] rowHeights, decimal dataHeight)
        {
            dataHeight -= rowHeights[0];
            List<DataTable> dts = new List<DataTable>();
            int rowIndex = 0;
            decimal currentHeight = 0;
            decimal[] rowHs = new decimal[rowHeights.Length - 1];
            for (int i = 0; i < rowHs.Length; i++)
            {
                rowHs[i] = rowHeights[i + 1];
            }
            List<DataRow> drs = new List<DataRow>();
            while (rowIndex <= dt.Rows.Count)
            {
                if (rowIndex == rowHs.Length)
                {
                    DataTable ndt = dt.Clone();
                    foreach (DataRow dr in drs)
                    {
                        DataRow ndtDr = ndt.NewRow();
                        foreach (DataColumn dc in dt.Columns)
                        {
                            ndtDr[dc.ColumnName] = dr[dc.ColumnName];
                        }
                        ndt.Rows.Add(ndtDr);
                    }
                    dts.Add(ndt);
                    break;
                }
                if ((currentHeight + rowHs[rowIndex] > dataHeight))
                {
                    DataTable ndt = dt.Clone();
                    foreach (DataRow dr in drs)
                    {
                        DataRow ndtDr = ndt.NewRow();
                        foreach (DataColumn dc in dt.Columns)
                        {
                            ndtDr[dc.ColumnName] = dr[dc.ColumnName];
                        }
                        ndt.Rows.Add(ndtDr);
                    }
                    dts.Add(ndt);
                    rowIndex--;
                    currentHeight = 0;
                    drs.Clear();
                }
                else
                {
                    DataRow dr = dt.NewRow();
                    for (int i = 0; i < dt.Columns.Count; i++)
                    {
                        dr[i] = dt.Rows[rowIndex][i].ToString();
                    }
                    drs.Add(dr);
                    currentHeight += rowHs[rowIndex];
                }
                rowIndex++;
            }
            return dts;
        }
        /// <summary>
        /// 獲取每頁的非數據列表的數據行
        /// </summary>
        /// <param name="data">所有數據行</param>
        /// <param name="Chinese_OneHeight">單行高度</param>
        /// <param name="dataHeight">顯示數據的可用高度</param>
        /// <param name="pageWidth">頁面寬度</param>
        /// <param name="g">繪製對象</param>
        /// <param name="f">字體</param>
        /// <returns></returns>
        private List<List<string>> GetLinesToPages(List<string> data, decimal Chinese_OneHeight, decimal dataHeight, decimal pageWidth, Graphics g, Font f)
        {
            List<List<string>> res = new List<List<string>>();
            List<string> resData = new List<string>();
            decimal currentHeight = 0;
            foreach (string dLine in data)
            {
                List<string> dvs = GetMultiLineString(dLine, (float)pageWidth, g, f);
                foreach (string dv in dvs)
                {
                    if (currentHeight + Chinese_OneHeight > dataHeight)
                    {
                        res.Add(resData);
                        resData.Clear();
                        currentHeight = 0;
                        resData.Add(dv);
                        currentHeight += Chinese_OneHeight;
                    }
                    else
                    {
                        resData.Add(dv);
                        currentHeight += Chinese_OneHeight;
                    }
                }
            }
            return res;
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章