优秀开源项目介绍:Printing Reports in .NET

来源:互联网 发布:陕西广电网络邮箱 编辑:程序博客网 时间:2024/05/16 12:19

 

Printing Reports in .NET - Step by Step Introduction
作者:mag37

入门:

通常报表打印是一件很棘手的事情,在这里我们用ReportPrinting library,你可以看到用少量的代码实现复杂的报表。

报表由文本片段(例如上图中Birthdays)或者由数据库(具体的所说是从DataView对象)生成的表格组成。同时也得支持图片线条等。框架体现处了能够满足基本得扩展需求。

如果你想快速了解它,看这个文档就够了,如果你想了解最新信息,请登陆http://www.mag37.com/projects/Printing/.

这一节将一步步带你了解ReportPrinting library的使用过程。

Create a DataView:建立数据视图

首先建一个DataView或者DataTable,下面我们将建一个关于一些名人的出生年月信息的DataTable

public static DataView GetDataView()

{

    DataTable dt = new DataTable("People");

    dt.Columns.Add("FirstName", typeof(string));

    dt.Columns.Add("LastName", typeof(string));

    dt.Columns.Add("Birthdate", typeof(DateTime));

    dt.Rows.Add(new Object[] {"Theodore", "Roosevelt", 

        new DateTime(1858, 11, 27)});

    dt.Rows.Add(new Object[] {"Winston",  "Churchill", 

        new DateTime(1874, 11, 30)});

    dt.Rows.Add(new Object[] {"Pablo",    "Picasso",   

        new DateTime(1881, 10, 25)});

    dt.Rows.Add(new Object[] {"Charlie",  "Chaplin",   

        new DateTime(1889,  4, 16)});

    dt.Rows.Add(new Object[] {"Steven",   "Spielberg", 

        new DateTime(1946, 12, 18)});

    dt.Rows.Add(new Object[] {"Bart",     "Simpson",   

        new DateTime(1987,  4, 19)});

    dt.Rows.Add(new Object[] {"Louis",    "Armstrong", 

        new DateTime(1901,  8,  4)});

    dt.Rows.Add(new Object[] {"Igor",     "Stravinski",

        new DateTime(1882,  6, 17)});

    dt.Rows.Add(new Object[] {"Bill",     "Gates",     

        new DateTime(1955, 10, 28)});

    dt.Rows.Add(new Object[] {"Albert",   "Einstein",  

        new DateTime(1879,  3, 14)});

    dt.Rows.Add(new Object[] {"Marilyn",  "Monroe",    

        new DateTime(1927,  6,  1)});

    dt.Rows.Add(new Object[] {"Mother",   "Teresa",    

        new DateTime(1910,  8, 27)});

    DataView dv = dt.DefaultView;

    return dv;

}

这个函数将会建一个DataTable,包含了一打的名人信息和他们的出生日期

Create a ReportMaker:建一个ReportMaker

接口ReportPrinting.IreportMaker 被用来建一个对象来启动一个ReportDocument。一个继承了IreportMaker的对象包含一个报表所有的代码。在例子中,这个类叫做SampleReportMaker1它有一个继承的方法:

public void MakeDocument(ReportDocument reportDocument)

{

我们一步步的看看这个继承的方法,首先,他有一个好的创意,重设文本的样式的类TextStyleTextStyle提供了一个全局样式和字体格式。例如标题,页头页脚,主体等等。这个类的应用是非常广泛的,它在报表创建时显式的重设。

TextStyle.ResetStyles();

下一步将设置页面间距:

    //设置一个默认的文档间距 (单位是 1/100 英寸)

    reportDocument.DefaultPageSettings.Margins.Top = 50;

    reportDocument.DefaultPageSettings.Margins.Bottom = 50;

    reportDocument.DefaultPageSettings.Margins.Left = 75;

    reportDocument.DefaultPageSettings.Margins.Right = 75;

如上可以看出,TextStyle提供了几个静态方法,全局的样式会应用的所有的文本快上,当然也可以为某一特定快单独的设置样式,就像代码中的显示,我们会改变一些字体和颜色。

    // Setup the global TextStyles

    TextStyle.Heading1.FontFamily = new FontFamily("Comic Sans MS");

    TextStyle.Heading1.Brush = Brushes.DarkBlue;

    TextStyle.Heading1.SizeDelta = 5.0f; 

    TextStyle.TableHeader.Brush = Brushes.White;

    TextStyle.TableHeader.BackgroundBrush = Brushes.DarkBlue;

    TextStyle.TableRow.BackgroundBrush = Brushes.AntiqueWhite;

    TextStyle.Normal.Size = 12.0f;

    // Add some white-space to the page.  By adding a 1/10 inch margin

    // to the bottom of every line, quite a bit of white space will be added

TextStyle.Normal.MarginBottom = 0.1f;

用此方法非常简单,我们将得到一个dataview,在一个GUI中设定默认的排序(注意:这里是一个hack,好的封装应该是低耦合的对话框,后期设定)

    // create a data table and a default view from it.

    DataView dv = GetDataView();

    // set the sort on the data view

    if (myPrintDialog.cmbOrderBy.SelectedItem != null)

    {

        string str = myPrintDialog.cmbOrderBy.SelectedItem.ToString();

        if (myPrintDialog.chkReverse.Checked)

        {

            str += " DESC";

        }

        dv.Sort = str;

    }

下一步将建一个ReportPrinting.ReportBuilder的实例,这个对象的任务就是组合文本,数据和包含的段落。

    // create a builder to help with putting the table together.

    ReportBuilder builder = new ReportBuilder(reportDocument);

用五个重载的函数建页眉页脚是非常容易的。下面建了一个简单的页眉,Brithdays Report居左,page#居右。页脚局中。

    // Add a simple page header and footer that is the same on all pages.

    builder.AddPageHeader("Birthdays Report", String.Empty, "page %p");

    builder.AddPageFooter(String.Empty, DateTime.Now.ToLongDateString(),

        String.Empty);

现在,真正有意思的才开始:我们开启垂直行布局,因为所有的后加的元素都依次载先前元素的后面。

builder.StartLinearLayout(Direction.Vertical);

现在:加上两个文本节,第一个元素被Header1所控制,第二个被Nomal控制。这两个样式在前面已经定义过了。

    // Add text sections

    builder.AddTextSection("Birthdays", TextStyle.Heading1);

    builder.AddTextSection("The following are various birthdays of people " +

        "who are considered important in history.");

接下来:我们加一个DataTable的元素,第一行是可见的标题行,设定三列,如下所示一次是LastNameFirstNameBirthdate,

AddColumna方法的第一个参数是是绑定DataTable的列名,第二个参数是显示的标题,第三个参数是宽,最大宽度在inches中规定,默认的她的尺寸将遵循表头或者是数据行。在这个case中,false被传递,不会呈现自动尺寸。

    // Add a data section, then add columns

    builder.AddDataSection(dv, true);

    builder.AddColumn ("LastName", "Last Name", 1.5f, false, false);

    builder.AddColumn ("FirstName", "First Name", 1.5f, false, false);

    builder.AddColumn ("Birthdate", "Birthdate", 3.0f, false, false);

我们设置最后一行的格式必须是日期,这里的格式表达是语String.Format是一样的。这里我们定义长日期。

    // Set the format expression to this string.

    builder.CurrentColumn.FormatExpression = "{0:D}";

    最最后的事情就是完成布局设置:

builder.FinishLinearLayout();

Create a form for printing:为打印建一个窗体。

这里只有简单的几个控件:标签,combox,复选框,和一个从ReportPrinting命名控件调用的用户控件PrintControl

这个窗体也包含一个

ReportPrinting.ReportDocument System.Drawing.Printing.PrintDocument的实例,它是System.Drawing.Printing.PrintDocument的子类,如果你是在设计是做这些事情,在构造函数中需要新建一个对象。

private ReportDocument reportDocument;

public ReportPrinting.PrintControl PrintControls;

public System.Windows.Forms.ComboBox cmbOrderBy;

public System.Windows.Forms.CheckBox chkReverse;

public SamplePrintDialog1()

{

    InitializeComponent();

    this.reportDocument = new ReportDocument();

    this.PrintControls.Document = reportDocument;

    SampleReportMaker1 reportMaker = new SampleReportMaker1(this);

    this.reportDocument.ReportMaker = reportMaker;

    this.cmbOrderBy.Items.Clear();

    this.cmbOrderBy.Items.Add("FirstName");

    this.cmbOrderBy.Items.Add("LastName");

    this.cmbOrderBy.Items.Add("Birthdate");

}

在这个构造函数中,一个ReportDocument被建立,它的实例指派给PrintControl.Document属性。上面的SampleMaker1对象实例化后指派给reportDocument.ReportMaker。最后用了一些代码设置了一些Combobox

大功告成

上面的代码就可以打印一份简单清晰的报表了,当然,你也可以用PrintDialog, PrintPreview, PageSettings对话框注意没有用PrintControls用户控件。

这个简单的例子可以在ReportPrint下载的代码中看到,连同一起的还有其他的一些例子。

      

Report Document Classes

ReportPrinting命名空间中引入了几个类,他们一起共同完成了报表的工作。下面的uml图说明了他们之间的关系:三角形表示扩展,黑色菱形表示组合,使用。

 

ReportDocument

ReportDocument 继承自PrintDocument 并且自定义了报表的数据,从一个或多个表的数据。 ReportDocument 对象是一个报表中顶级的容器,包含了报表中的所有元素。

ReportDocument's 的主要工作是打印,靠调用基类的Print()方法实现。 Print() 方法 迭代组成文档的所有ReportSections,并打印。

The strategy design pattern is employed for formatting the report. 一个继承IreportMaker的对象可以关联到ReportDocument. IReportMaker 对象是个专门的应用并且已知的在它上面建立了应用状态和客户设定。这个对象负责建立的节点sections,关联dataView,应用在TextStyles中定义的样式。它一般指派给 ReportBuilder 用于构建负责的报表.

ReportSection

ReportSection是一个代表报表的可打印节点的抽象类,它有几个子类,包括ReportSectionText (代表文本字符串) ReportSectionData (代表 DataView). 也包括容器类节点 (衍生自 SectionContainer , 继承自 ReportSection). 容器类可以包括子报表节点.我们快速的看一个例子来看看他是怎么工作的。

在示例中,这个文章的顶部,又一段文本在表数据的后面(事实上有两处文字,一处在头里嵌入了一个页眉,但是我们现在忽略它)。我们建一个ReportSectionText 对象打印一段文本,并且ReportSectionData 对象打印表数据。在添加的这些ReportSectionsReportDocument时,我们必须建一个容器。我们将会建一个LinearSections容器,容纳者两个节点,

这个容器然后建立到ReportDocument中,在文档打印时,这个节点容器首先打印ReportSectionText,然后是ReportSectionData。简单的一个一个的打印节点在上面的报表程序中,但是有许多其他的方法设定这些类。

SectionContainer

这个抽象类定义了节点的容器。有两种类型: LinearSections LayeredSections.

LinearSections

LinearSections 类是SectionContainer的子类 。也是 ReportSection子类. 所以, LinearSections 能够被作为可打印的节点。同时,它也是一个或多个节点的容器。

象它的名字所暗示的,他是一个直线布局的节点。也就是说,在行或列中。一个名叫Direction 的属性指定了容器是横行或纵向布局。

LayeredSections

LayeredSections 类也是的SectionContainer子类,又是一个ReportSection子类。因此它既可以被打印,有是其他元素的容器。

她的子对象都被画到它上面,第一个加上去的位于低层,后来的在前面的上面依次排队。

ReportSectionText

ReportSectionText 打印一个字符串在页面上。 两个公共的殊相用来开始一个节点。 Text 用来指定打印的字符. TextStyle, 描述了字体,颜色,布局,以及文本的其他属性。

ReportSectionData

ReportSectionData 打印表的数据。它用一个DataView 对象作为数据源 (from the .NET System.Data namespace) 。然后用一些 ReportDataColumns 来表示详细内容。 这些ReportDataColumns DataGridColumnStyle 类似。

ReportDataColumn

 ReportDataColumn 提供了报表行数据格式化的信息。对每一行,都有一个新的ReportDataColumn 被实例并被附加到ReportSection中。立刻,每一行的通过提取数据源描述最宽的显示在页面上。

ReportDataColumn 能够为头和记本行启用一个单独的 TextStyle。因此,每一行数据,都可以被不同的格式化。

TextStyle

TextStyle 允许文本选择样式和字体 ,允许默认的样式,所有的样式都有他们默认的值(除了静态的TextStyle.Normal)。如果属性不被指定,它会一直用默认的值。

例如一个新的样式能够定义用默认,但是它可以设定加粗。

TextStyle paragraphStyle = new TextStyle(TextStyle.Normal);
paragraphStyle.Bold = true;
TextStyle.Normal.Size += 1.0f

ReportBuilder

ReportBuilder 指定了建立一个报表。这个类是你的代码和库的主要接口。在许多cases中,你没有显示的声明许多对象,,通过 ReportBuilder 可以为你建立他们。

对于一个ReportBuilder实例,你必须显式的建立ReportDocument。让后你可以自由的使用它Add某某方法为报告文档添加片段。

我们已经在上面的实例中看到了这些。

Example:

The following example shows the creation of a report using the ReportBuilder. The following methods would be part of a class that implements IReportMaker (this is from example1 in the sample project).

private DataView GetDataView()
{
    DataTable dt = new DataTable("People");
    dt.Columns.Add("FirstName", typeof(string));
    dt.Columns.Add("LastName", typeof(string));
    dt.Columns.Add("Birthdate", typeof(DateTime));
    dt.Rows.Add(new Object[] {"Theodore", "Roosevelt", new DateTime(1858, 11, 27)});
    dt.Rows.Add(new Object[] {"Winston ", "Churchill", new DateTime(1874, 11, 30)});
    dt.Rows.Add(new Object[] {"Pablo", "Picasso", new DateTime(1881, 10, 25)});
    dt.Rows.Add(new Object[] {"Charlie", "Chaplin", new DateTime(1889, 4, 16)});
    dt.Rows.Add(new Object[] {"Steven", "Spielberg", new DateTime(1946, 12, 18)});
    dt.Rows.Add(new Object[] {"Bart", "Simpson", new DateTime(1987, 4, 19)});
    return dt.DefaultView;
    }
public void MakeDocument(ReportDocument reportDocument)
{
    // Clear the document
    reportDocument.ClearSections();
    // create a data table and a default view from it.
    DataView dataView = this.GetDataView();
    // create a builder to help with putting the table together.
    ReportBuilder builder = new ReportBuilder(reportDocument);
   
    // Add a simple page header and footer that is the same on all pages.
    builder.AddPageHeader("Birthdays Report", String.Empty, "page %p");
    builder.AddPageFooter(String.Empty, DateTime.Now.ToLongDateString(), String.Empty);
    builder.StartLinearLayout(Direction.Vertical);
    // Add text sections
    builder.AddTextSection("Birthdays", TextStyle.Heading1);
    builder.AddTextSection("The following are various birthdays of people who "
        + "are considered important in history.");
    // Add a data section, then add columns
    builder.AddDataSection(dataView, true);
    builder.AddColumn ("LastName", "Last Name", 1.5f, false, false);
    builder.AddColumn ("FirstName", "First Name", 1.5f, false, false);
    builder.AddColumn ("Birthdate", "Birthdate", 3.0f, false, false);
    // Set the format expression to this string.
    builder.CurrentColumn.FormatExpression = "{0:D}";
    builder.FinishLinearLayout();
       
}

IReportMaker

IreportMaker 在设计上被所为一个接口来被使用。一个继承它的对象可以添加 ReportDocument. 当一个文档要被打印,它自动的调用单例方法MakeDocument(). 在上面的例子中显示一个继承的方法打印一个报告。

例如,你可以有个应用打印详细和预览。报告的逻辑被分离在单独的类中。每一个都继承IReportMaker的类。你的打印对话框有个打印什么的momboxbox 允许用户选择报告类型, 用它来选择指定的扩展自的ReportDocument

Print Dialogs

打印对话框引导用户使用打印过程。许多应用有许多设置来影响打印什么和怎么打印,许多的windows应用自定义标准的打印对话框,附加一些按钮来做各种各样的操作。有一片文章说怎么用mfc扩展打印对话框,但是我仍然有.net.如果有人做的.net控件看起来象标准的打印对话框,并且很容易的附加到窗体上,或者知道其他的一些办法,请你告诉我。

PrintControl

To make printing easy for my applications, I created this very basic control that can be dropped onto any form. It gives the user options to setup, preview, submit (ok) or cancel. Providing a preview button and a page setup button on a print dialog are not standard in the windows interface, but I wish they were. So this control provides that functionality to your print dialog. Note, you can still provide access via a File menu (File | Print Preview, File | Page Setup).

Figure 3 - PrintControl user control

The control uses the following dialogs associated with printing:

  • PrintPreviewDialog
  • PageSetupDialog
  • PrintDialog

To use the print control, place it on a form. Set the Document property to a valid PrintDocument. (it doesnt have to just be the ReportDocument described earlier). Thats it!

You can customize a few things with the following properties:

  • ShowStatusDialog - The progress of the print job is shown in a status dialog. Default is true.
  • PrintInBackground - Indicates that printing should be done in the background. Default is true.
  • Printing - This event is raised prior to printing. It allows user code to setup for printing. (This is useful for dumping data from the GUI to a helper class, for instance).

Print Dialog with PrintControl

A sample form with a PrintControl is shown below. This dialog allows a user to select tables to print from the Northwind sample database.

Figure 4 - A dialog to prompt user for print settings and give them a chance to preview and setup the page.

布局

Linear layout :流布局

The LinearSections class is used to print a variety of sections, one below (or next to) another. In its simplest form, the linear layout looks like the figure to the right.

Each section may consume a different amount of real-estate. Some may be as wide as the page, others may not. The LinearSections container (shown in color) is a rectangle as wide as the widest section and as tall as all sub-sections combined.

This is the simplest class to use for structuring a report. Simply start a LinearLayout with the ReportBuilder class and then add ReportSections for text and data as needed.

Layered layout

The layered layout is for combing sections together into one region. This is often used for placing varying ReportSectionText objects together in one PageHeader or PageFooter. It could also be used to add a watermark to the body of a ReportDocument.

Column layout

As the name suggests, the column layout is used for creating columns on a page. A picture of a document using columns can be found on the samples page.

The column layout contains a vertical LinearSections container nested within a horizontal LinearRepeatableSections container. The vertical container has a maximum width of the column width. It renders as much of its content as possible within a column, then returns to the horizontal container. The horizontal container advances by the width of the column (plus any margin required in the middle) and repeats the call to the vertical container. This gives the parent container the name LinearRepeatableSections since it will repeat calls to a child section within a single page.

In the example shown, the LinearRepeatableSections has a special divider section assigned to it of a vertical line. This divider is printed between calls to the vertical section.

Box section

The box isn't stricly a container in the class hierarchy sense, since it doesn't inherit from SectionContainer. However, it does contain one section which can be assigned to it. And the ReportBuilder class, when it supports the SectionBox, will treat it as a container by assigning a layered or linear container to the box.

The box follows many of the properties from the W3C box model. I recommend visiting this page to learn more.

(graphic from w3.org)

The margins are set the same as for any other report section. The margins are always clear. The borders are set by specifying a System.Drawing.Pen object to use for each border. The pen object specifies color and width. The padding is specified in inches, again for each side independently.

If a background brush is set (using the System.Drawing.Brushes), it will paint the entire area inside the border (including the padding).

The width and height can each be set independently, or not at all. If the width is not specified, then the width will size to that of the contents. If the width is explictly set using the Width property, that width includes content, padding, border, and margins. The width can also be set as a percentage of the parent (using the WidthPercent property). The same is true for the Height and HeightPercent properties.

An offset can be set which moves the box in relation to the parent and normal flow. This should normally be used in LayeredLayout, as the results in LinearLayout haven't been adequately tested.

结束语

以上总结了ReportPrinting库的使用,了解更多请到网站: http://www.mag37.com/projects/Printing/.

版本

06-9-2003 : 最初版本

11-9-2003 : 修正版本

原创粉丝点击