優秀開源項目介紹:Printing Reports in .NET

  
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)。如果屬性不被指定,它會一直用默認的值。

例如一個新的樣式能夠定義用默認,但是它可以設定加粗。

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 : 修正版本

作者:Mike Mayer

發佈了43 篇原創文章 · 獲贊 1 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章