Sie sind auf Seite 1von 12

27/03/2019 Introducing PdfRport - CodeProject

Introducing PdfRport
Vahid_N, 14 Feb 2013

PdfReport is a code-first reporting engine which is built on top of the iTextSharp and EPPlus libraries.

Download sample - 1.7 MB

Introduction
PdfReport is a code-first reporting engine, which is built on top of the iTextSharp and EPPlus libraries. It's compatible with both .NET
3.5+ Web and Windows applications. PdfReport supports a wide range of data sources from data tables to in-memory strongly
typed lists without needing a database. It saves you time from searching and learning a lot of tips and tricks of iTextSharp and
EPPlus libraries. It's designed to be compatible with RTL languages.

https://www.codeproject.com/Articles/492055/Introducing-PdfRport?display=Print 1/12
27/03/2019 Introducing PdfRport - CodeProject

How to start using PdfRport 


To create your first PdfReport:

a. Create a new Class Library project in Visual Studio. We will use it as the base report classes container for both Windows and
Web applications.
b. Then add new references to the following assemblies: PdfReport, iTextSharp, and EPPlus. You can download them from
http://pdfreport.codeplex.com/releases/

Or just use the NuGet PowerShell console to add these references automatically:

PM> Install-Package PdfReport

http://nuget.org/packages/PdfReport/

c. Add the following classes to the Class Library project:

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;
}
}
}
}

We will use this class to specify the location of the produced PDF file. It needs the following references as well:

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;

https://www.codeproject.com/Articles/492055/Introducing-PdfRport?display=Print 2/12
27/03/2019 Introducing PdfRport - CodeProject
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("#");

https://www.codeproject.com/Articles/492055/Introducing-PdfRport?display=Print 3/12
27/03/2019 Introducing PdfRport - CodeProject
});

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"));
}
}
}

You can find its latest version here too.

To use this class and create a new PDF report file, we can write:

https://www.codeproject.com/Articles/492055/Introducing-PdfRport?display=Print 4/12
27/03/2019 Introducing PdfRport - CodeProject

var rpt = new IListPdfReport().CreatePdfReport();


// rpt.FileName

In the DocumentPreferences method we can specify the direction of the report (PdfRunDirection: RTL or LTR),
page size (PdfPageSize), its orientation (PageOrientation), and so on.
Then it's necessary to determine the default report font files in the DefaultFonts method. The first font will be the main
font and the second font will be used as the fallback font.
PdfReport comes with built-in footer and header samples. It's possible to customize these elements by implementing the
IPageFooter and IPageHeader interfaces. We will discuss it in the other How-To's.
By using MainTableTemplate, we can define the main grid's template. There are some predefined templates available
in the PdfReport library. Also it's possible to create new templates by implementing the ITableTemplate interface.
The MainTablePreferences method will be used for specifying the settings of the main report's grid, such as how
many rows per page should be available (if we don't specify it, rows count will be calculated automatically based on the
page size).

The ColumnsWidthsType method accepts four different values:

a. Relative: Each column has a relative width equal to 1. Example: Relative values = 2, 1, 1. This means that you
want to divide the width of the table into four parts (2 + 1 + 1): two parts for the first column, one part for columns
two and three.
b. Absolute: The absolute width expressed in user space units.
c. EquallySized: Equally sized columns. In this case, all of the specified widths will be ignored.
d. FitToContent: Tries to resize the columns automatically. In this case, all of the specified widths will be ignored.

The MainTableDataSource method sets the data source of the main grid. For instance, in the above example, the
StronglyTypedList method will process the list of users. There are other built-in data source methods in the
PdfReport library. For example, if you want to use raw SQL and work with the database directly, try the following methods:

//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)

It's possible to write parametric queries in all of the above methods. These parameters should start with the @ symbol. Here is a
quick sample which shows how to work with SQLite databases in PdfReport:

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 }
);

Add a reference to the  System.Data.SQLite assembly and then use the above generic data reader. The same rule applies to MySQL
and other databases.

The MainTableSummarySettings method determines the auto generated summary/total labels and their position to
show. It's optional.
By using the optional MainTableColumns method, it's possible to determine the exact columns of the report's grid.
Each column should be present in the data source. Also it's possible to define the calculated fields as well. It will be
discussed in other How-To's later. In the MainTableColumns method, you can specify the related property of the
column, its width, visibility, order, and so on. Here by using the ColumnItemsTemplate method, we can determine the
type of the current field and how it should be displayed. If it should be displayed as a text, use
the template.TextBlock() method (it's the default method). Also there are some other built-in cell templates such
as image, hyperlinks, etc. It's possible to use custom column templates by implementing the IColumnItemsTemplate

https://www.codeproject.com/Articles/492055/Introducing-PdfRport?display=Print 5/12
27/03/2019 Introducing PdfRport - CodeProject

interface too.
If you want to format the cell's value before rendering, use the template.DisplayFormatFormula method. It's a
callback method, which gives you the actual value of the cell and then you can format it and return the final result to show
on the report.
By using the column.AggregateFunction method, we can determine the related aggregate method of the current
column. There are some predefined numeric aggregate functions available in the PdfReport library. Also it's possible to write
custom ones by implementing the IAggregateFunc interface.
The MainTableEvents method provides access to the internal events of the main grid. For instance if the data source is
empty, the DataSourceIsEmpty event will be raised.
Also it's possible to export the main table's data as Excel, CSV, XML, etc. files. All of these exported files will be embedded in
the final PDF file automatically.

How to create auto generated/dynamic columns


Specifying the MainTableColumns method and all of its definitions is arbitrary in PdfReport. Just omit this part and then the
final report will be created dynamically based on the available columns in the provided data source. This feature gives us great
flexibility, but after some time we need to customize these kinds of reports: how to format DateTimes, how to add the total number
of numeric fields and so on.

Here are some tips about customizing the auto generated columns:

a) Use aliases to specify the header cells:

If you are using the SQL based data sources such as GenericDataReader, to customize the header cells just define the column
aliases in your final SQL:

SELECT [NumberOfPosts] as 'Number of posts'


FROM [tblBlogs]
WHERE [NumberOfPosts]>=@p1

b) Specifying the rendering conventions, based on the data types of columns

By using the MainTableAdHocColumnsConventions method, it's possible to alter the rendering conditions of the dynamic
columns. In the MainTableAdHocColumnsConventions method, we can include the auto generated row column in the
final report:

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); }
);

Here we are altering the rendering value of all of the DateTime columns.

adHocColumns.AddTypeAggregateFunction(
typeof(Int64),
new AggregateProvider(AggregateFunction.Sum)
{
DisplayFormatFormula = obj => obj == null ? string.Empty : string.Format("
{0:n0}", obj)
});

Or we can determine the specific AggregateFunction of the given data type.

c) Using data annotations to define column properties

If you are using a generic list data source, it's possible to omit the MainTableColumns method and all of its definitions by
replacing them with data annotations:

https://www.codeproject.com/Articles/492055/Introducing-PdfRport?display=Print 6/12
27/03/2019 Introducing PdfRport - CodeProject

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.
}
}

If you don't want to show a property in the final report, use the [IsVisible(false)] attribute.
DisplayName attribute will be used to define the header cells of the report.
Specifying the ColumnItemsTemplate attribute is optional and if it's not defined, TextBlockField will be used
automatically.

Other predefined column cell templates are defined in the PdfRpt.ColumnsItemsTemplates namespace.

To format the displayed dates or numbers, use the DisplayFormat attribute.


To add the total rows, specify the CustomAggregateFunction attribute. There are some built-in aggregate functions
in the  PdfRpt.Aggregates.Numbers namespace.
To define a calculated field/column, specify the [IsCalculatedField(true)] attribute. And then add a new static
field (not a property) to define the related formula. This new field should be decorated with the
CalculatedFieldFormula attribute to determine the property name of the calculated field.

https://www.codeproject.com/Articles/492055/Introducing-PdfRport?display=Print 7/12
27/03/2019 Introducing PdfRport - CodeProject

Here are some full samples about auto generated columns:

AdHocColumns
DataAnnotations

How to define a calculated column


Some times we need to create a new column which is not present in the data source, based on the values of other columns. There
are two types of calculated columns in PdfReport:

a) Row numbers columns

Just set column.IsRowNumber(true);. And then a new row number column which is not included in the data source will be
available in the final report.

b) To define a custom calculated column, we need to specify its calculation formula by


setting the column.CalculatedField method:

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);
});

The column.CalculatedField method's argument gives us the list of values of the current row:

public void CalculatedField(bool isCalculatedField, Func<IList<CellData>, object>


calculatedFieldFormula)

Now we have time to build the current cell's value based on the other column values. There are some helper methods defined in
the  PdfRpt.Core.Helper namespace such as GetSafeStringValueOf to help working with IList<CellData>
data more easily. Defining a PropertyName is mandatory, but in this case it can be an arbitrary text.

Here is the full sample which shows how to define a calculated column: CalculatedFields

How to show images in PdfReport


Usually we have two kinds of images in reports:

a) Images loaded from the file system

To show these kind of images we need to change the default ColumnItemsTemplate which is TextBlock to
ImageFilePath:

columns.AddColumn(column =>
{
column.PropertyName<ImageRecord>(x => x.ImagePath);

https://www.codeproject.com/Articles/492055/Introducing-PdfRport?display=Print 8/12
27/03/2019 Introducing PdfRport - CodeProject
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));
});

Here defaultImageFilePath is the default image path in the case of missing images.

b) Images stored in databases

Showing images stored as binary data in databases is similar to (a). We just need to use the suitable ColumnItemsTemplate
which is the ByteArrayImage template:

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));
});

Here you can find the complete samples of (a) and (b):

ImageFilePath
DbImagePdfReport

Conditional formatting in PdfReport


Suppose we want to display the list of personnel in a year. In this report each month cell with month=7 should be displayed with a
different color. To achieve this goal we can use the template.ConditionalFormatFormula method:

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 is a callback method which gives us the list of the current row's data. Now based
on the value of the month (or other properties) we can return the "new CellBasicProperties" with a different font color, style, etc. You
https://www.codeproject.com/Articles/492055/Introducing-PdfRport?display=Print 9/12
27/03/2019 Introducing PdfRport - CodeProject

can find the related sample here.

How to create custom main table's templates


There are some built-in report templates in the PdfReport library such as BasicTemplate.RainyDayTemplate,
BasicTemplate.SilverTemplate, etc. Also it's possible to create the new main table's templates by implementing the
ITableTemplate interface. For instance:

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

https://www.codeproject.com/Articles/492055/Introducing-PdfRport?display=Print 10/12
27/03/2019 Introducing PdfRport - CodeProject
{
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; }
}
}
}

To use this new template, we can write:

.MainTableTemplate(template =>
{
template.CustomTemplate(new GrayTemplate());
})

Some tips and tricks:

Colors in the iTextSharp library are defined by the BaseColor class. If you want to convert the colors of
System.Drawing to BaseColor, just pass it to the BaseColor's constructor:

var blackColor = new BaseColor(Color.Black);

There are some useful helper methods in .NET classes such as ColorTranslator.FromHtml to convert and use the
HTML colors.
If you want to define a transparent color here, just return null.
In the ITableTemplate interface, some colors are defined as a list. These properties can accept one or max two colors. If
two colors are specified, an automatic gradient of these colors will be shown on the report.

How to use HTML to create custom cell templates


Suppose we have a list of users with their names and photos. We want to show the name and photo of each user in one cell and not
two separate cells. The built-in cell templates of PdfReport are suitable for displaying only one object per cell. To define a custom
cell template, we need to implement the IColumnItemsTemplate interface or use a shortcut:

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;'>

https://www.codeproject.com/Articles/492055/Introducing-PdfRport?display=Print 11/12
27/03/2019 Introducing PdfRport - CodeProject
<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).
});
});

Here iTextSharp's HTMLWorker class is used behind the scenes. By using CalculatedField, we can inject our new value of a
cell and then process it by the selected ColumnItemsTemplate. Note: HTML to PDF capabilities of  iTextSharp's
HTMLWorker class are very limited, and don't expect too much about it. You can find this sample here.

More guides and samples


PdfReport comes with more than 40 samples. Please download its source code from CodePlex and check out its samples folder.

License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author


Vahid_N No Biography provided
Web Developer
Iran (Islamic Republic of)

Comments and Discussions


21 messages have been posted for this article Visit https://www.codeproject.com/Articles/492055/Introducing-PdfRport to
post and view comments on this article, or click here to get a print view with messages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile Article Copyright 2013 by Vahid_N
Web01 | 2.8.190306.1 | Last Updated 14 Feb 2013 Everything else Copyright © CodeProject, 1999-2019

https://www.codeproject.com/Articles/492055/Introducing-PdfRport?display=Print 12/12

Das könnte Ihnen auch gefallen