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 {
get
;
set
; }
/// <summary>
/// 图形数据
/// </summary>
public
Bitmap extData {
get
;
set
; }
/// <summary>
/// 头部的表头数据(不包括文档标题,如果没有,应传入 null;每个元素为一行,如果一行无法容纳,会换行处理。)
/// </summary>
public
List<
string
> TitleData {
get
;
set
; }
/// <summary>
/// 页面中间的列表数据(如果没有,应传入 null)
/// </summary>
public
List<T> TableData {
get
;
set
; }
/// <summary>
/// 页面中间的列表数据的表头(与列表数据中的列一一对应,如果没有(仅限列表数据传入null的情况),应传入 null)
/// </summary>
public
List<
string
> ColumnNames {
get
;
set
; }
/// <summary>
/// 列表数据的各个列是否允许折行显示,允许为true,不允许为false(仅当自动计算的列宽不足以显示内容时生效,如果传入null则表示所有列均允许折行显示)
/// </summary>
public
List<
bool
> CanResetLine {
get
;
set
; }
/// <summary>
/// 底部的结尾数据(不包括页码,如果没有,应传入 null;每个元素为一行,如果一行无法容纳,会换行处理。)
/// </summary>
public
List<
string
> EndData {
get
;
set
; }
}
/// <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
t
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
p
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;
}
}
}