本稿の内容を増補改訂したアップデート版が提供されています。詳しくは連載の目次「5分でわかるActiveReports帳票」をご参照ください。
はじめに
ActiveReports for .NET(以下ActiveReports)は、Visual Studioと統合された使いやすいレポートデザイナや、高機能なレポートビューア、多彩な出力形態をサポートする帳票作成コンポーネントです。今回は前回に引き続きActiveReports帳票の改ページ制御と、多段組レイアウト帳票について紹介していきます。
これまでの記事
対象読者
- Visual Basic 2005またはVisual C# 2005を使ってプログラムを作ったことのある方。
- 帳票作成ツールに興味のある方。
必要な環境
- Visual Studio 2005、Visual Studio .NET 2003でプログラムが作れる環境。
本記事のサンプルコードはC# 2.0/Visual Basic 2005で記述しています。
改ページ制御について
最終的な完成イメージ
今回は、改ページの制御について説明していきます。前半は前回のサンプルを少し変えた、以下のような帳票を例に解説を進めていきます。
出力項目は、前回と同じく「月内での注文番号単位の小計・日単位の小計」「月間の注文合計・宅配料金・値引料金の合計」です。
ただし、Detailセクションの高さが低いままだと改ページ制御について特に意識していなくてもうまく用紙に収まってしまうことが多いため、今回はあえてセクションの高さを広げるためにDetailセクションのレイアウトへ「商品コードのバーコード」を追加しています。
セクション単位での改ページ設定
前回はPageBreakコントロールを利用した改ページを主に紹介しましたが、今回はセクションに用意されたプロパティを利用した改ページの制御を中心に解説していきます。
セクション出力前後のタイミングで改ページ制御を行うには、各セクションのNewPageプロパティを利用します。NewPageプロパティには、None、Before、After、BeforeAfterのいずれかを設定できます。
値 | 説明 |
None | セクション出力のタイミングでは改ページしない |
Before | セクション出力前に改ページする |
After | セクション出力後に改ページする |
BeforeAfter | セクション出力前と出力後の両方で改ページする |
セクションがページ区切りで分割されるのを防ぐ
ActiveReportsでは用紙に対して各セクションを順番に出力していくので、ページの最後で残りサイズより大きなセクションを出力すると、セクションが2ページにまたがって出力されてしまいます。
このような場合、Detailセクションまたはグループヘッダ/フッタセクションのKeepTogetherプロパティを設定することで、ページ区切りによってセクション出力が分割されるのを避けることができます。
KeepTogetherプロパティをTrueに設定すると、ActiveReportsはセクションの出力前に残り用紙サイズを判断し、サイズ不足でセクションが切れてしまう場合には改ページしてからセクションを出力します。
なお、ページヘッダとページフッタにはKeepTogetherプロパティが存在しません。ActiveReportsでは、各ページのページヘッダとページフッタの領域が最初に確保され、残りの領域に対して他のセクションが配置されていきます。ページヘッダとページフッタの高さの合計が用紙サイズよりも大きくなってしまうと、実行時にエラーが発生するので注意してください。
グループヘッダを毎ページ出力する
グループヘッダのセクションは通常、グループの出力ごとに1度だけ出力されます。これを毎ページ出力させたい場合は、グループヘッダのRepeatStyleプロパティを「OnPage」または「OnPageIncludeNoDetail」に設定します。
設定値にOnPageを設定すると、そのページに1件以上detailセクションが出力される場合のみグループヘッダが出力されます。OnPageIncludeNoDetailに設定すると、detailセクションが出力されない(=グループフッタのみ出力される)ケースでもグループヘッダが表示されます。
値 | 説明 |
None | グループヘッダは最初の1回だけ出力する |
OnPage | 新しいページごとにグループヘッダを出力する |
OnColumn | 新しいカラムごとにグループヘッダを出力する |
All | 新しいページまたはカラムごとにグループヘッダセクションを繰り返す |
OnPageIncludeNoDetail | 関連するdetailセクションまたはグループフッタを含むページごとにグループヘッダを出力する |
グループ化して改ページする
セクションの大きさによっては、以下のようにグループヘッダだけが前ページに出力され、detailセクションから次のページ出力が始まってしまうことがあります。グループヘッダを、detaliセクションやグループフッタと1つのブロックとして同一ページに出力するためには、グループヘッダのGroupKeepTogetherプロパティを設定します。
値 | 説明 |
None | グループヘッダの直後で改ページ可能 |
FirstDetail | グループヘッダを同じページまたはカラムの最初の詳細セクションと共に出力する |
All | グループヘッダ、詳細、グループフッタの各セクションを同じページに出力する |
このサンプルでは、注文番号グループヘッダセクションのGroupKeepTogetherプロパティをFirstDetailに設定し、ヘッダは必ず明細行と一緒に表示するよう制御しています。
カラムを使った多段組レイアウト 1
ここからは、ActiveReportsの多段組レイアウト機能について紹介します。
多段組レイアウトとは?
これまで紹介してきたサンプル帳票は、各セクションのヘッダ/フッタ定義と、Detailセクションに定義した1行分のレイアウトを下方向へ繰り返し出力するものでした。ActiveReportsではこのような1段組のレイアウトだけでなく、2段組、3段組といった多段組レイアウトも作成可能です。多段組のレイアウトではDetailセクションが分割され、1行=1件のデータではなく、1行に複数件のデータを配置できます。
多段組レイアウトのサンプル帳票
今回サンプルとして紹介するのは、各商品の日別売上をカレンダー風に横方向へ並べて表示した帳票です。
また、取り込むデータ構造は次のようになっています。
このデータを今までのように1行=(商品名+6日分のデータ)の1段組レイアウトで表示させる場合、行と列を入れ替えるための複雑なSQLを書く必要がありますが、ActiveReportsの多段組レイアウト機能をうまく使うと、複雑なSQLを書くことなく簡単に実装することができます。
多段組レイアウトの作成
それでは、多段組レイアウトのデザイン作成について解説していきます。
データ表示部の作成
多段組レイアウトを実現するには、まずDetailセクションのColumnCountプロパティを設定します。ColumnCountプロパティのデフォルト値は1(=1段組)です。ColumnCountプロパティを2以上の値に設定すると、Detailセクションが均等割付で分割されます。今回のサンプルではColumnCountプロパティに7を設定しているので、セクションが横に7分割されたうち、6つ分の背景がグレー表示されています。グレーで表示された部分は繰り返し項目となります。
グループヘッダ/フッタのColumnLayoutプロパティをTrueに設定すると、ヘッダ、フッタも明細と同じ段数で分割されて表示されます。
次に、detailセクションのColumnDirectionプロパティを設定し、データの展開方向を決めます。ColumnDirectionプロパティには、
- AcrossDown(まず横方向へ出力し、その後次の行へ改行する)
- DownAcross(まず縦方向に出力し、その後隣の列へ折り返す)
のどちらかを設定できます。今回はDownAcrossを選択します。

グループヘッダ/フッタの設定
次に、改ページする際のNewPageプロパティの設定と同様、改列のためのプロパティを設定します。
今回は売上日単位で列を移動するので、見出しグループヘッダのDataFieldプロパティには日付(SlipDate)を設定します。また、1番左の列は商品名を表示するために空けておく必要があるため、NewColumnプロパティはBeforeに設定します。

これでデータ表示部分の設定は完了です。
カラムを使った多段組レイアウト 2
SubReportコントロールで行ヘッダを作成する
今回の帳票で1番左側に表示する商品名列は、表示される値が決まっていれば直接Labelコントロールなどに書いて並べてもよいのですが、ここではSubReportコントロールを使って、表示される値が可変の商品名列を作成する方法を紹介します。
ActiveReportsのSubReportコントロールを使うと、レポート内に別のレポート(子レポート)を埋め込んで使うことができます。子レポートの出力は親レポートのセクションが出力されるたびに実行されます。
子レポートは、普通のActiveReports帳票を作成するのと同じ方法で作成します。プロジェクトに新しいActiveReports 3.0ファイルを追加し、親レポートと同様にグループヘッダ、detail、グループフッタセクションをもつ小さな帳票を作成します。

次に、作成した子レポートを親レポートに埋め込みます。親レポートの商品名グループヘッダに、ツールボックスからSubReportコントロールをドロップし、商品名グループヘッダのFormatイベントで、SubReportコントロールのReportプロパティに新しい子レポートのインスタンスを設定します。

private void groupHeader1_Format(object sender, EventArgs e) { //子レポートのインスタンスを作り、SubReportコントロールへ設定する SubReport1 sub1 = new SubReport1(); this.subReport1.Report = sub1; }
Private Sub GroupHeader1_Format(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles GroupHeader1.Format '子レポートのインスタンスを作り、SubReportコントロールへ設定する Dim sub1 As New SubReport1() Me.SubReport1.Report = sub1 End Sub
Formatイベントの記述が終わったら、商品名グループヘッダのRepeatStyleプロパティに「OnPageIncludeNoDetail」を設定し、商品名グループフッタのNewColumnプロパティには「After」を設定します。


テキストファイルからデータを読み込む
これまで紹介した帳票サンプルでは、detailセクション作成時にデータソースの設定をすることで製品付属の「NWind.mdb」にアクセスしていましたが、今回は実行時にテキストファイルを直接読み取ってレポートに出力する方法を紹介します。このように、レポートに対してデータソースを指定せず、コードを記述して実行時にデータをロードする帳票を「アンバウンドレポート」と呼びます。
今回の帳票では、親レポートで使う売上データと子レポートで使う商品名データを別々に用意しています。売上データは、「通し番号」「商品名」「売上日」「売り上げた数量」がカンマ区切りで入っている、いわゆるCSVファイルです。

一方、子レポート側で必要なのは商品名だけなので、データはカンマ区切りのない単純なテキストファイルです。

アンバウンドレポートでは、DataInitializeイベントとFetchDataイベントにデータの初期化と読み取りを記述します。また、今回はファイルからデータを読み取るため、ReportEndイベントで文字ストリームを閉じる必要があります。
以下に各イベントで実装するコードを掲載します。便宜のため例外処理を省略していますが、実際の開発では適切な例外処理を追加してください。また、ファイル名もハードコーディングせず、パラメータとして外部から与えられるようにしておくべきでしょう。
private StreamReader reader; //DataInitializeイベントの処理 private void DailySalesList_DataInitialize(object sender, System.EventArgs eArgs) { // データフィールドを追加 this.Fields.Add("SlipNo"); this.Fields.Add("ProductName"); this.Fields.Add("SlipDate"); this.Fields.Add("Quantity"); // テキストファイルをオープン reader = System.IO.File.OpenText( @"C:\Codezine\ActiveReports\SalesData.txt"); } //FetchDataイベントの処理 private void DailySalesList_FetchData(object sender, DataDynamics.ActiveReports.ActiveReport3.FetchEventArgs eArgs) { //ファイルよりデータを読み込み string line = reader.ReadLine(); if (line != null) { //CSVデータを配列に変換し、フィールドに取り込む string[] arrayValues = line.Split(",".ToCharArray()); Fields["SlipNo"].Value = arrayValues[0]; Fields["ProductName"].Value = arrayValues[1]; Fields["SlipDate"].Value = arrayValues[2]; Fields["Quantity"].Value = arrayValues[3]; eArgs.EOF = false; } else { eArgs.EOF = true; } } //ReportEndイベントの処理 private void DailySalesList_ReportEnd(object sender, EventArgs e) { reader.Close(); }
Private reader As System.IO.StreamReader 'DataInitializeイベントの処理 Private Sub DailySalesList_DataInitialize(_ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.DataInitialize 'データフィールドを追加 Me.Fields.Add("SlipNo") Me.Fields.Add("ProductName") Me.Fields.Add("SlipDate") Me.Fields.Add("Quantity") 'テキストファイルをオープン reader = System.IO.File.OpenText(_ "C:\\Codezine\\ActiveReports\\SalesData.txt") End Sub 'FetchDataイベントの処理 Private Sub DailySalesList_FetchData(ByVal sender As System.Object, _ ByVal eArgs As _ DataDynamics.ActiveReports.ActiveReport3.FetchEventArgs) _ Handles MyBase.FetchData Dim line As String If line Is Nothing Then eArgs.EOF = True Else Dim arrayValues As String() 'CSVデータを配列に変換し、フィールドに取り込む arrayValues = line.Split(",".ToCharArray()) Me.Fields("SlipNo").Value = arrayValues(0) Me.Fields("ProductName").Value = arrayValues(1) Me.Fields("SlipDate").Value = arrayValues(2) Me.Fields("Quantity").Value = arrayValues(3) eArgs.EOF = False End If End Sub 'ReportEndイベントの処理 Private Sub DailySalesList_ReportEnd(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.ReportEnd reader.Close() End Sub
同様に、サブレポートに対してもイベント処理コードを実装します。CSVの変換処理がないところ以外は、親レポートとほとんど同じです。
private StreamReader reader; //DataInitializeイベントの処理 private void SubReport1_DataInitialize(object sender, System.EventArgs eArgs) { // データフィールドを追加 this.Fields.Add("ProductName"); // テキストファイルをオープン reader = System.IO.File.OpenText( @"C:\Codezine\ActiveReports\ProductName.txt"); } //FetchDataイベントの処理 private void SubReport11_FetchData(object sender, DataDynamics.ActiveReports.ActiveReport3.FetchEventArgs eArgs) { // ファイルよりデータを読み込み string productName = reader.ReadLine(); if (productName != null) { //読み込んだデータをフィールドに設定します。 Fields["ProductName"].Value = productName; eArgs.EOF = false; } else { eArgs.EOF = true; } } //ReportEndイベントの処理 private void SubReport1_ReportEnd(object sender, EventArgs e) { reader.Close(); }
Private reader As System.IO.StreamReader 'DataInitializeイベントの処理 Private Sub SubReport1_DataInitialize(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.DataInitialize 'データフィールドを追加 Me.Fields.Add("ProductName") 'テキストファイルをオープン reader = System.IO.File.OpenText(_ "C:\\Codezine\\ActiveReports\\ProductName.txt") End Sub 'FetchDataイベントの処理 Private Sub SubReport1_FetchData(ByVal sender As System.Object, _ ByVal eArgs As _ DataDynamics.ActiveReports.ActiveReport3.FetchEventArgs) _ Handles MyBase.FetchData Dim productName As String productName = reader.ReadLine() If productName Is Nothing Then eArgs.EOF = True Else Me.Fields("ProductName").Value = productName eArgs.EOF = False End If End Sub 'ReportEndイベントの処理 Private Sub SubReport1_ReportEnd(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.ReportEnd reader.Close() End Sub
注意事項
今回作成した帳票では、「商品名」行と「売上日」列で構成されるマトリックスに対して端から順番にデータを配置していくため、「売上データ、商品名データが順序保証されていること」「すべての組み合わせのデータが存在し、抜けがないこと」を前提としています。データの順序が決まっていない場合や、実際に売上のあった日のデータのみが取り込まれる場合は、必要な処理を追加していくことになります。
まとめ
今回は改ページ制御と多段組レイアウトについて紹介しました。スクロールによって描画領域を(ほぼ無限に)広げられるWebページと異なり、「用紙サイズ」という制約のある帳票開発においては、改ページ制御1つ取ってもいろいろと細かい設定が必要になることがおわかりいただけたかと思います。
これまで3回にわたって、帳票アプリケーション開発の基本である「表示、集計、改ページ」についてひと通り紹介しました。次回以降も、実際の帳票アプリケーション開発に役立つテクニックを紹介していきますので、お楽しみに。