隆重推出 PdfRport
PdfReport 是一个基于 iTextSharp 和 EPPlus 库构建的代码优先报告引擎。
引言
PdfReport 是一个代码优先报告引擎,它构建在 iTextSharp 和 EPPlus 库之上。它兼容 .NET 3.5+ 的 Web 和 Windows 应用程序。PdfReport 支持从数据表到内存中强类型列表的广泛数据源,而无需数据库。它为您节省了搜索和学习 iTextSharp 和 EPPlus 库的大量技巧和窍门的时间。它设计用于兼容 RTL 语言。

如何开始使用 PdfRport
创建您的第一个 PdfReport
- 在 Visual Studio 中创建一个新的类库项目。我们将将其用作 Windows 和 Web 应用程序的基础报表类容器。
- 然后添加对以下程序集的引用:PdfReport、iTextSharp 和 EPPlus。您可以从 http://pdfreport.codeplex.com/releases/ 下载它们
或者直接使用 NuGet PowerShell 控制台自动添加这些引用
PM> Install-Package PdfReport
- 将以下类添加到类库项目中
using System.Web;
using System.Windows.Forms;
namespace PdfReportSamples
{
public static class AppPath
{
public static string ApplicationPath
{
get
{
if (isInWeb)
return HttpRuntime.AppDomainAppPath;
return Application.StartupPath;
}
}
private static bool isInWeb
{
get
{
return HttpContext.Current != null;
}
}
}
}
我们将使用这个类来指定生成的 PDF 文件的位置。它还需要以下引用
- System.Windows.Forms.dll
- System.Web.dll
using System;
namespace PdfReportSamples.IList
{
public class User
{
public int Id { set; get; }
public string Name { set; get; }
public string LastName { set; get; }
public long Balance { set; get; }
public DateTime RegisterDate { set; get; }
}
}
//"User" class will be used for creating an in-memory generic list data source.
//And now add the main report class:
using System;
using System.Collections.Generic;
using PdfReportSamples.Models;
using PdfRpt.Core.Contracts;
using PdfRpt.FluentInterface;
namespace PdfReportSamples.IList
{
public class IListPdfReport
{
public IPdfReportData CreatePdfReport()
{
return new PdfReport().DocumentPreferences(doc =>
{
doc.RunDirection(PdfRunDirection.LeftToRight);
doc.Orientation(PageOrientation.Portrait);
doc.PageSize(PdfPageSize.A4);
doc.DocumentMetadata(new DocumentMetadata { Author = "Vahid",
Application = "PdfRpt", Keywords = "IList Rpt.",
Subject = "Test Rpt", Title = "Test" });
})
.DefaultFonts(fonts =>
{
fonts.Path(Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\arial.ttf",
Environment.GetEnvironmentVariable("SystemRoot") + "\\fonts\\verdana.ttf");
})
.PagesFooter(footer =>
{
footer.DefaultFooter(DateTime.Now.ToString("MM/dd/yyyy"));
})
.PagesHeader(header =>
{
header.DefaultHeader(defaultHeader =>
{
defaultHeader.RunDirection(PdfRunDirection.LeftToRight);
defaultHeader.ImagePath(AppPath.ApplicationPath + "\\Images\\01.png");
defaultHeader.Message("Our new rpt.");
});
})
.MainTableTemplate(template =>
{
template.BasicTemplate(BasicTemplate.ClassicTemplate);
})
.MainTablePreferences(table =>
{
table.ColumnsWidthsType(TableColumnWidthType.Relative);
table.NumberOfDataRowsPerPage(5);
})
.MainTableDataSource(dataSource =>
{
var listOfRows = new List<User>();
for (int i = 0; i < 200; i++)
{
listOfRows.Add(new User { Id = i, LastName = "LastName " + i,
Name = "Name " + i, Balance = i + 1000 });
}
dataSource.StronglyTypedList(listOfRows);
})
.MainTableSummarySettings(summarySettings =>
{
summarySettings.OverallSummarySettings("Summary");
summarySettings.PreviousPageSummarySettings("Previous Page Summary");
summarySettings.PageSummarySettings("Page Summary");
})
.MainTableColumns(columns =>
{
columns.AddColumn(column =>
{
column.PropertyName("rowNo");
column.IsRowNumber(true);
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(0);
column.Width(1);
column.HeaderCell("#");
});
columns.AddColumn(column =>
{
column.PropertyName<User>(x => x.Id);
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(1);
column.Width(2);
column.HeaderCell("Id");
});
columns.AddColumn(column =>
{
column.PropertyName<User>(x => x.Name);
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(2);
column.Width(3);
column.HeaderCell("Name");
});
columns.AddColumn(column =>
{
column.PropertyName<User>(x => x.LastName);
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(3);
column.Width(3);
column.HeaderCell("Last Name");
});
columns.AddColumn(column =>
{
column.PropertyName<User>(x => x.Balance);
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(4);
column.Width(2);
column.HeaderCell("Balance");
column.ColumnItemsTemplate(template =>
{
template.TextBlock();
template.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
});
column.AggregateFunction(aggregateFunction =>
{
aggregateFunction.NumericAggregateFunction(AggregateFunction.Sum);
aggregateFunction.DisplayFormatFormula(obj => obj == null ? string.Empty : string.Format("{0:n0}", obj));
});
});
})
.MainTableEvents(events =>
{
events.DataSourceIsEmpty(message: "There is no data available to display.");
})
.Export(export =>
{
export.ToExcel();
export.ToCsv();
export.ToXml();
})
.Generate(data => data.AsPdfFile(AppPath.ApplicationPath + "\\Pdf\\RptIListSample.pdf"));
}
}
}
您可以在此处找到它的最新版本 here。
要使用这个类并创建一个新的 PDF 报表文件,我们可以这样做
var rpt = new IListPdfReport().CreatePdfReport();
// rpt.FileName
- 在
DocumentPreferences
方法中,我们可以指定报表的方向(PdfRunDirection
:RTL 或 LTR)、页面大小(PdfPageSize
)、方向(PageOrientation
)等。 - 然后,有必要在
DefaultFonts
方法中确定默认报表字体文件。第一个字体将是主字体,第二个字体将用作回退字体。 - PdfReport 提供了内置的页脚和页眉示例。通过实现
IPageFooter
和IPageHeader
接口,可以自定义这些元素。我们将在其他操作指南中讨论这一点。 - 通过使用
MainTableTemplate
,我们可以定义主网格的模板。PdfReport 库中提供了一些预定义模板。也可以通过实现ITableTemplate
接口来创建新模板。 MainTablePreferences
方法将用于指定主报表网格的设置,例如每页应有多少行(如果未指定,行数将根据页面大小自动计算)。Relative
:每列的相对宽度等于 1。示例:相对值 = 2, 1, 1。这意味着您想将表的宽度分成四部分(2 + 1 + 1):第一列两部分,第二列和第三列各一部分。Abso
lute:以用户空间单位表示的绝对宽度。EquallySized
:等宽列。在这种情况下,指定的所有宽度都将被忽略。FitToContent
:尝试自动调整列的大小。在这种情况下,指定的所有宽度都将被忽略。MainTableDataSource
方法设置主网格的数据源。例如,在上面的示例中,StronglyTypedList
方法将处理用户列表。PdfReport 库中还有其他内置的数据源方法。例如,如果您想使用原始 SQL 并直接处理数据库,请尝试以下方法
ColumnsWidthsType
方法接受四种不同的值
//SQL server data source
public void SqlDataReader(string connectionString, string sql, params object[] parametersValues)
//.mdb or .accdb files
public void AccessDataReader(string filePath, string password, string sql, params object[] parametersValues)
//Odbc data source
public void OdbcDataReader(string connectionString, string sql, params object[] parametersValues)
// A generic data reader data source
public void GenericDataReader(string providerName, string connectionString, string sql, params object[] parametersValues)
可以在上述所有方法中编写参数化查询。这些参数应以 @ 符号开头。以下是一个快速示例,演示了如何在 PdfReport 中使用 SQLite 数据库
dataSource.GenericDataReader(
providerName: "System.Data.SQLite",
connectionString: "Data Source=" + AppPath.ApplicationPath + "\\data\\blogs.sqlite",
sql: @"SELECT [url], [name], [NumberOfPosts], [AddDate]
FROM [tblBlogs]
WHERE [NumberOfPosts]>=@p1",
parametersValues: new object[] { 10 }
);
添加对 System.Data.SQLite 程序集的引用,然后使用上述通用数据读取器。MySQL 等数据库也适用相同的规则。
MainTableSummarySettings
方法确定自动生成的摘要/总计标签及其显示位置。它是可选的。- 通过使用可选的
MainTableColumns
方法,可以确定报表网格的精确列。每列都应存在于数据源中。还可以定义计算字段。这将在以后的操作指南中讨论。在MainTableColumns
方法中,您可以指定列的相关属性、宽度、可见性、顺序等。在此处使用ColumnItemsTemplate
方法,我们可以确定当前字段的类型以及如何显示它。如果它应显示为文本,请使用template.TextBlock()
方法(这是默认方法)。还有其他内置单元格模板,如图像、超链接等。也可以通过实现IColumnItemsTemplate
接口来使用自定义列模板。
如果您想在渲染之前格式化单元格的值,请使用template.DisplayFormatFormula
方法。这是一个回调方法,它为您提供单元格的实际值,然后您可以对其进行格式化并返回最终结果以显示在报表中。 - 通过使用
column.AggregateFunction
方法,我们可以确定当前列的相关聚合方法。PdfReport 库提供了一些预定义的数字聚合函数。也可以通过实现IAggregateFunc
接口来编写自定义函数。 MainTableEvents
方法提供了对主网格内部事件的访问。例如,如果数据源为空,将引发DataSourceIsEmpty
事件。- 还可以将主表的*数据*导出为 Excel、CSV、XML 等文件。所有这些导出的文件将自动嵌入到最终的 PDF 文件中。
如何创建自动生成/动态列
在 PdfReport 中,指定 MainTableColumns
方法及其所有定义是任意的。只需省略这部分,最终的报表将根据提供的数据源中的可用列动态创建。此功能为我们提供了极大的灵活性,但经过一段时间后,我们需要自定义这类报表:如何格式化 DateTime、如何添加数字字段的总数等。
以下是一些关于自定义自动生成列的提示
a) 使用别名指定标题单元格
如果您使用的是 SQL 数据源,如 GenericDataReader
,要自定义标题单元格,只需在最终 SQL 中定义列别名即可。
SELECT [NumberOfPosts] as 'Number of posts'
FROM [tblBlogs]
WHERE [NumberOfPosts]>=@p1
b) 根据列的数据类型指定渲染约定
通过使用 MainTableAdHocColumnsConventions
方法,可以修改动态列的渲染条件。在 MainTableAdHocColumnsConventions
方法中,我们可以将自动生成的行*列*包含在最终报表中。
adHocColumns.ShowRowNumberColumn(true);
adHocColumns.RowNumberColumnCaption("#");
Or it's possible to format a cell's value based on its type:
adHocColumns.AddTypeDisplayFormatFormula(
typeof(DateTime),
data => { return PersianDate.ToPersianDateTime((DateTime)data); }
);
在这里,我们正在修改所有 DateTime
列的渲染值。
adHocColumns.AddTypeAggregateFunction(
typeof(Int64),
new AggregateProvider(AggregateFunction.Sum)
{
DisplayFormatFormula = obj => obj == null ? string.Empty : string.Format("{0:n0}", obj)
});
或者我们可以确定给定数据类型的特定 AggregateFunction
。
c) 使用数据注释定义列属性
如果您使用的是通用列表数据源,则可以通过用数据注释替换 MainTableColumns
方法及其所有定义来省略它们。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using PdfReportSamples.Models;
using PdfRpt.Aggregates.Numbers;
using PdfRpt.ColumnsItemsTemplates;
using PdfRpt.Core.Contracts;
using PdfRpt.Core.Helper;
using PdfRpt.DataAnnotations;
namespace PdfReportSamples.DataAnnotations
{
public class Person
{
[IsVisible(false)]
public int Id { get; set; }
[DisplayName("User name")]
//Note: If you don't specify the ColumnItemsTemplate, a new TextBlockField() will be used automatically.
[ColumnItemsTemplate(typeof(TextBlockField))]
public string Name { get; set; }
[DisplayName("Job title")]
public JobTitle JobTitle { set; get; }
[DisplayName("Date of birth")]
[DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime DateOfBirth { get; set; }
[DisplayName("Date of death")]
[DisplayFormat(NullDisplayText = "-", DataFormatString = "{0:MM/dd/yyyy}")]
public DateTime? DateOfDeath { get; set; }
[DisplayFormat(DataFormatString = "{0:n0}")]
[CustomAggregateFunction(typeof(Sum))]
public int Salary { get; set; }
[IsCalculatedField(true)]
[DisplayName("Calculated Field")]
[DisplayFormat(DataFormatString = "{0:n0}")]
[AggregateFunction(AggregateFunction.Sum)]
public string CalculatedField { get; set; }
[CalculatedFieldFormula("CalculatedField")]
public static Func<IList<CellData>, object> CalculatedFieldFormula =
list =>
{
if (list == null) return string.Empty;
var salary = (int)list.GetValueOf<Person>(x => x.Salary);
return salary * 0.8;
};//Note: It's a static field, not a property.
}
}
- 如果您不想在最终报表中显示某个属性,请使用
[IsVisible(false)]
属性。 DisplayName
属性将用于定义报表的标题单元格。- 指定
ColumnItemsTemplate
属性是可选的,如果未定义,则会自动使用TextBlockField
。
其他预定义列单元格模板定义在 PdfRpt.ColumnsItemsTemplates
命名空间中。
- 要格式化显示的日期或数字,请使用
DisplayFormat
属性。 - 要添加总计行,请指定
CustomAggregateFunction
属性。PdfRpt.Aggregates.Numbers
命名空间中提供了一些内置的聚合函数。 - 要定义计算字段/列,请指定
[IsCalculatedField(true)]
属性。然后添加一个新*静态*字段(不是属性)来定义相关的公式。这个新字段应该用CalculatedFieldFormula
属性进行修饰,以确定计算字段的属性名称。
以下是一些关于自动生成列的完整示例
如何定义计算列
有时我们需要创建一个新的列,该列不在数据源中,而是基于其他列的值。PdfReport 中有两种类型的计算列
a) 行号列
只需设置 column.IsRowNumber(true);
。然后,一个未包含在数据源中的新行号列将在最终报表中可用。
b) 要定义自定义计算列,我们需要通过设置 column.CalculatedField 方法来指定其计算公式
columns.AddColumn(column =>
{
column.PropertyName("CF1");
column.CalculatedField(
list =>
{
if (list == null) return string.Empty;
var name = list.GetSafeStringValueOf<User>(x => x.Name);
var lastName = list.GetSafeStringValueOf<User>(x => x.LastName);
return name + " - " + lastName;
});
column.HeaderCell("Full name");
column.Width(3);
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(4);
});
column.CalculatedField
方法的参数为我们提供了当前行值的列表。
public void CalculatedField(bool isCalculatedField, Func<IList<CellData>, object> calculatedFieldFormula)
现在我们可以根据其他列值构建当前单元格的值。PdfRpt.Core.Helper
命名空间中定义了一些辅助方法,例如 GetSafeStringValueOf
,以帮助更轻松地处理 IList<CellData>
数据。定义 PropertyName
是强制性的,但在本例中,它可以是任意文本。
以下是演示如何定义计算列的完整示例:CalculatedFields
如何在 PdfReport 中显示*图像*
通常,报表中*图像*有两种类型
a) 从文件系统加载的*图像*
要显示此类*图像*,我们需要将默认的 ColumnItemsTemplate
(即 TextBlock
)更改为 ImageFilePath
。
columns.AddColumn(column =>
{
column.PropertyName<ImageRecord>(x => x.ImagePath);
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(2);
column.Width(3);
column.HeaderCell("Image");
column.ColumnItemsTemplate(t => t.ImageFilePath(defaultImageFilePath: string.Empty, fitImages: false));
});
在这里,defaultImageFilePath
是在*图像*丢失时默认的*图像*路径。
b) 存储在数据库中的*图像*
显示存储在数据库中的二进制*图像*与 (a) 类似。我们只需使用合适的 ColumnItemsTemplate
,即 ByteArrayImage
模板。
columns.AddColumn(column =>
{
column.PropertyName("thumbnail");
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(5);
column.HeaderCell("Image");
column.ColumnItemsTemplate(t => t.ByteArrayImage(defaultImageFilePath: string.Empty, fitImages: false));
});
您可以在此处找到 (a) 和 (b) 的完整示例
PdfReport 中的条件格式设置
假设我们想显示一年中人员列表。在此报表中,月份为 7 的每个月单元格都应以不同的颜色显示。为了实现此目标,我们可以使用 template.ConditionalFormatFormula
方法。
columns.AddColumn(column =>
{
column.PropertyName("Month");
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(2);
column.Width(2);
column.HeaderCell("Month");
column.ColumnItemsTemplate(template =>
{
template.TextBlock();
template.ConditionalFormatFormula(list =>
{
var cellValue = int.Parse(list.GetSafeStringValueOf("Month", nullValue: "0"));
if (cellValue == 7)
{
return new CellBasicProperties
{
PdfFontStyle = DocumentFontStyle.Bold | DocumentFontStyle.Underline,
FontColor = new BaseColor(System.Drawing.Color.Brown),
BackgroundColor = new BaseColor(System.Drawing.Color.Yellow)
};
}
return new CellBasicProperties { PdfFontStyle = DocumentFontStyle.Normal };
});
});
});
template.ConditionalFormatFormula
是一个回调方法,它为我们提供当前行的数据列表。现在,根据月份(或其他属性)的值,我们可以返回具有不同字体颜色、样式等的“new CellBasicProperties”。您可以在此处找到相关示例:here。
如何创建自定义主表*模板*
PdfReport 库中提供了一些内置的报表*模板*,例如 BasicTemplate.RainyDayTemplate
、BasicTemplate.SilverTemplate
等。也可以通过实现 ITableTemplate
接口来创建新的主表*模板*。例如
using System.Collections.Generic;
using System.Drawing;
using iTextSharp.text;
using PdfRpt.Core.Contracts;
namespace PdfReportSamples.HexDump
{
public class GrayTemplate : ITableTemplate
{
public HorizontalAlignment HeaderHorizontalAlignment
{
get { return HorizontalAlignment.Center; }
}
public BaseColor AlternatingRowBackgroundColor
{
get { return new BaseColor(Color.WhiteSmoke); }
}
public BaseColor CellBorderColor
{
get { return new BaseColor(Color.LightGray); }
}
public IList<BaseColor> HeaderBackgroundColor
{
get { return new List<BaseColor> { new BaseColor(
ColorTranslator.FromHtml("#990000")),
new BaseColor(ColorTranslator.FromHtml("#e80000")) }; }
}
public BaseColor RowBackgroundColor
{
get { return null; }
}
public IList<BaseColor> PreviousPageSummaryRowBackgroundColor
{
get { return new List<BaseColor> { new BaseColor(Color.LightSkyBlue) }; }
}
public IList<BaseColor> SummaryRowBackgroundColor
{
get { return new List<BaseColor> { new BaseColor(Color.LightSteelBlue) }; }
}
public IList<BaseColor> PageSummaryRowBackgroundColor
{
get { return new List<BaseColor> { new BaseColor(Color.Yellow) }; }
}
public BaseColor AlternatingRowFontColor
{
get { return new BaseColor(ColorTranslator.FromHtml("#333333")); }
}
public BaseColor HeaderFontColor
{
get { return new BaseColor(Color.White); }
}
public BaseColor RowFontColor
{
get { return new BaseColor(ColorTranslator.FromHtml("#333333")); }
}
public BaseColor PreviousPageSummaryRowFontColor
{
get { return new BaseColor(Color.Black); }
}
public BaseColor SummaryRowFontColor
{
get { return new BaseColor(Color.Black); }
}
public BaseColor PageSummaryRowFontColor
{
get { return new BaseColor(Color.Black); }
}
public bool ShowGridLines
{
get { return true; }
}
}
}
要使用这个新*模板*,我们可以这样做
.MainTableTemplate(template =>
{
template.CustomTemplate(new GrayTemplate());
})
一些技巧和窍门
- iTextSharp 库中的颜色由
BaseColor
类定义。如果您想将System.Drawing
的颜色转换为BaseColor
,只需将其传递给BaseColor
的构造函数即可。
var blackColor = new BaseColor(Color.Black);
ColorTranslator.FromHtml
等 .NET 类中有一些有用的辅助方法,用于转换和使用 HTML 颜色。null
即可。ITableTemplate
接口中,一些颜色被定义为列表。这些属性可以接受一种或最多两种颜色。如果指定了两种颜色,则报表中将显示这些颜色的自动渐变。如何使用 HTML 创建自定义单元格*模板*
假设我们有一个用户列表,包含他们的姓名和照片。我们想在*一个*单元格中显示每个用户的姓名和照片,而不是*两个*单独的单元格。PdfReport 的内置单元格*模板*仅适用于在*一个*单元格中显示*一个*对象。要定义自定义单元格*模板*,我们需要实现IColumnItemsTemplate
接口或使用快捷方式。columns.AddColumn(column =>
{
column.PropertyName("User");
column.CellsHorizontalAlignment(HorizontalAlignment.Center);
column.IsVisible(true);
column.Order(1);
column.Width(3);
column.HeaderCell("User");
column.CalculatedField(list =>
{
var user = list.GetSafeStringValueOf("User");
var photo = new Uri(list.GetSafeStringValueOf("Photo"));
var image = string.Format("<img src='{0}' />", photo);
return
@"<table style='width: 100%; font-size:9pt;'>
<tr>
<td>" + user + @"</td>
</tr>
<tr>
<td>" + image + @"</td>
</tr>
</table>
";
});
column.ColumnItemsTemplate(template =>
{
template.Html(); // Using iTextSharp's limited HTML to PDF capabilities (HTMLWorker class).
});
});
这里在后台使用了 iTextSharp 的 HTMLWorker
类。通过使用 CalculatedField
,我们可以注入单元格的新值,然后由所选的 ColumnItemsTemplate
进行处理。注意:iTextSharp 的 HTMLWorker
类的 HTML 到 PDF 功能非常有限,不要对此抱太大期望。您可以在此处找到此示例:here。