本稿の内容を増補改訂したアップデート版が提供されています。詳しくは連載の目次「5分でわかるActiveReports帳票」をご参照ください。
はじめに
ActiveReports for .NET(以下ActiveReports)はVisual Studioと統合された使いやすいレポートデザイナや高機能なレポートビューア、多彩な出力形態をサポートする帳票作成コンポーネントです。前回はActiveReportsの最新版3.0Jを利用した帳票アプリケーション開発の流れを簡単に紹介しましたが、今回はActiveReportsを利用した集計処理機能を中心に紹介していきます。
前回の記事
対象読者
- Visual Basic 2005またはVisual C# 2005を使ってプログラムを作ったことのある方。
- 帳票作成ツールに興味のある方。
必要な環境
- Visual Studio 2005、Visual Studio .NET 2003でプログラムが作れる環境。
本記事のサンプルコードは、C# 2.0/Visual Basic 2005で記述しています。
集計を含む帳票の作成
ActiveReportsが提供する各種の集計機能を利用すると、データソースやSQLの構造を複雑にすることなくデータの総合計やグループごとの小計、ページ数といった集計データを簡単に表示させることができます。
それでは、帳票側でデータを加工して表示する帳票を作成していきましょう。
最終的な完成イメージ
今回作成する帳票は、お店の注文伝票を月ごとに集計した「注文集計表」です。
各月の受注額を注文単位の小計、日単位の小計、月単位の合計3つで集計して表示します。また、月単位に宅配料金、値引料金の合計も表示します。
データの説明
今回使用するデータは、注文が入ると商品を複数まとめて宅配便で送るお店の受注データです。データベースは前回と同じく、ActiveReports 3.0J付属のNwind.mdbを使用し、注文伝票(Orders)、注文伝票明細(Order Details)、商品(Products)の3テーブルにアクセスします。
- 注文伝票テーブル(注文日、注文番号、宅配料金)
- 注文伝票詳細テーブル(注文番号、商品番号、単価、個数、割引率)
- 商品テーブル(商品番号、商品名)
次に、レポート出力するデータを取得するためのSQLを記述します。今回は「注文日」「注文番号」「宅配料金」「商品番号」「商品名」「単価」「数量」「割引額」「注文金額」を取得する、次のようなクエリを設定します(便宜のため、小数点以下の値を含む列はRound関数で丸めています)。
なお、クエリではSUMやCOUNTといった集計関数やGROUP BY句による集計処理は行いません。
SELECT YEAR(Orders.OrderDate) AS OrderYear, MONTH(Orders.OrderDate) AS OrderMonth, Orders.OrderDate, Orders.OrderID, Round(Orders.Freight, 0) As Freight, Products.ProductID, Products.ProductName, Round([Order Details].UnitPrice, 0) As UnitPrice, [Order Details].Quantity, Round(Round([Order Details].UnitPrice,0) * [Quantity] * [Discount], 0) AS DiscountPrice, Round(Round([Order Details].UnitPrice,0) * [Quantity] * (1 - [Discount]), 0) AS Price FROM Orders INNER JOIN (Products INNER JOIN [Order Details] ON Products.ProductID = [Order Details].ProductID) ON Orders.OrderID = [Order Details].OrderID ORDER BY Orders.OrderDate, Orders.OrderID,Products.ProductID ;
帳票レイアウトの作成
1.セクションの設定
Visual Studioのプロジェクトに「ActiveReports 3.0ファイル」を追加し、レポートデザイナで帳票のレイアウトを作成します。
前回と同様、detailセクションのアイコンをダブルクリックして、レポートデータソースにクエリを設定します。レポートデータソース設定の詳細については前回の記事を参照ください。
また、今回は取得したデータをグループ単位で集計・表示するために[グループヘッダ/フッタ]を設定します。右クリックメニューの[挿入]から「グループヘッダ/フッタ」を追加します。
今回の集計単位は、「注文月」と「注文日」「注文番号」の3つになるので、上位グループ(外側)から順に「注文月(OrderYear, OrderMonth)」「注文日(OrderDate)」「注文番号(OrderID)」単位のグループヘッダ/フッタを設定します。グループヘッダ/フッタを追加したら、それぞれキーになるデータソースの列名を、グループヘッダのDataFieldプロパティに設定します。このうち、注文月だけは「注文年+月」の単位で集計する必要があるため、DataFieldプロパティには計算式の=OrderYear*100 + OrderMonth
を入力します。
2.コントロールの追加
次に、ツールボックスからコントロールをドロップして、帳票レイアウトに貼り付けていきます。帳票タイトルなどの固定値表示や集計のないデータ項目表示はLabelコントロールとTextBoxコントロールのどちらを使用しても構いませんが、Labelは集計設定のためのSummaryプロパティを持たないため、集計値の表示欄にはTextBoxを使う必要があります。
3.集計項目のプロパティ設定
集計データを設定するためのSummaryプロパティには、以下の4つがあります(表1)。このうち、SummaryGroupはグループ単位で合計するときのみ使用する項目で、SubTotal/PageCountの場合に作用します。また、SummaryRunningはNoneに設定すると、値の初期化が行われません。
プロパティ名 | 内容 |
SummaryFunc | 集計関数の種類 |
SummaryGroup | どのグループを対象とするかの設定 |
SummaryRunning | 値の初期化のタイミング。None/Group/Allのいずれかを設定する |
SummaryType | 合計する単位。None/GrandTotal/PageTotal/SubTotal/PageCountのいずれかを設定する |
例えば、グループ単位で、合計値を表示したい場合は次のように設定します(合計単位とグループを設定してあるので、値の初期化はGROUPに設定する必要はありません)。
プロパティ名 | 設定値 |
SummaryFunc | Sum(合計関数) |
SummaryGroup | [集計したいグループ] |
SummaryRunning | None |
SummaryType | SubTotal(グループ単位の小計) |
DataField | [集計したい値] |
Summaryプロパティを使うと、アプリケーション側で集計値を加算していく処理や複雑なSQLの記述が不要になるだけでなく、グループヘッダに明細の合計値欄を配置させることもできます。
今回作成する帳票のコントロール設定
続いて、具体的に今回作成する帳票の集計値の設定について説明します。
A.ページ番号を表示する
帳票にページ番号を表示するには、SummaryTypeプロパティにSummaryType.PageCountを設定します。
ページ | プロパティ | 設定値 |
現在ページ | SummaryRunning | All |
SummaryType | PageCount | |
総ページ | SummaryRunning | None |
SummaryType | PageCount |
現在ページ番号と総ページ数の切り替えは、SummaryRunningプロパティの設定で行います。SummaryFuncは何を設定しても同じ値が表示されます。なお、グループごとにページ数を設定する場合や、2ページ目からページを数えたいなどの場合については、後ほど説明します。
B.総合計値を表示する
明細の全データを集計する場合は、SummaryType.GrandTotalを設定します。
C.グループごとの小計値を表示する。
前項で説明した集計項目のプロパティ設定例と同様に設定します。商品小計項目は次のように設定します。
プロパティ | 設定例 |
SummaryFunc | Sum |
SummaryGroup | [注文番号グループヘッダ] |
SummaryRunning | None |
SummaryType | SubTotal(グループ単位の小計) |
DataField | [Price] |
どのセクションでも小計の取得は可能ですが、位置によってはうまく合計されないこともあります。
特に、グループヘッダ/フッタを複数もつ帳票の場合、グループの順序に気をつけないと集計の単位が大きく変わってしまい、正しい集計結果を表示させることができません。今回はグループの順序が「注文日→注文番号→商品」の順序で入れ子構造になるようにグループを作成する必要があります。
グループの順序を確認・変更するには、右クリックメニューの[グループ順]を選択すると表示されるグループ順ダイアログで変更することができます。
D.重複を排除した小計値を表示する
宅配料金は注文ごとに発生するデータなので、明細単位ですべて合計するとデータが重複し、正しい値を得られなくなってしまいます。このような場合はSum関数の代わりに、Dsum関数を使用します。Dsum関数は、指定されたキー値をもとに重複を除いた値の合計を計算します。Dsum関数がキーとして使用する値は、DistinctDataFieldプロパティに設定します。
今回は宅配料金を注文番号(OrderID)ごとに集計したいので、Summaryプロパティを次のように設定します。
プロパティ | 設定値 |
SummaryFunc | DSum |
SummaryGroup | [注文月グループヘッダ] |
SummaryRunning | None |
SummaryType | SubTotal(グループ単位の小計) |
DistinctDataField | OrderID(注文番号) |
DataField | Freight(宅配料金) |
ページ数の取得について
グループごとの総ページ数・現在ページ数の設置
グループごとの総ページ数と現在ページ数を取得する場合のプロパティ設定について説明します。TextBoxコントロールのSummaryGroupプロパティを設定すると、グループごとにページカウントが行われます。
グループ内の現在ページ番号を取得するときはSummaryRunningプロパティをGroupに設定します。総ページ数を取得するときは帳票全体のときと同じく、Noneに設定します。
ページ | プロパティ | 設定値 |
現在ページ | SummaryGroup | [集計したいグループ] |
SummaryRunning | Group | |
SummaryType | PageCount | |
総ページ | SummaryGroup | [集計したいグループ] |
SummaryRunning | None | |
SummaryType | PageCount |
ページ数のスタートを1以外に設定したい場合
PageCountの最終的な値は描画が終了したReport_Endイベント内で捕捉することはできますが、ページヘッダ・ページフッタのSummaryプロパティからは直接取得することはできません。また、ActiveReports帳票では現在ページ数を取得するためにPageNumberプロパティを使用しますが、このプロパティは読み取り専用なので直接変更することはできません。
よって、ページ番号をアプリケーション側で制御する場合は、別の変数に保持しておいたページ番号を表示させる必要があります。
ページ番号をカスタマイズするにはページ番号を保持するフィールドを追加し、ページヘッダのFormatイベントで現在のページ番号と処理を追加します。
private int anotherPageCount = 0; protected const int AddNumber = 4; //ページ数を5から開始する場合 //Formatイベント private void pageHeader_Format(object sender, EventArgs e) { //現在ページ数を計算 anotherPageCount = PageNumber + AddNumber; //TextBoxに代入 this.currentPage.Text = anotherPageCount.ToString(); }
Dim anotherPageCount = 0 Protected Const addNumber = 4 'ページ数を5から開始する Private Sub PageHeader_Format(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles PageHeader.Format '現在ページ数を計算 anotherPageCount = Me.PageNumber + addNumber 'TextBoxに代入 Me.currentPage.Text = anotherPageCount.ToString() End Sub
カスタマイズした総ページ数を取得する場合も、PageCountプロパティは使わず現在ページ数をMAX関数で集計した値を表示させます。
今回は、アンバウンドフィールドを追加して表示させる例を紹介します。
private void NewActiveReport1_DataInitialize(object sender, System.EventArgs e) { Fields.Add("GetCount"); }
Private Sub NewActiveReport1_DataInitialize( _ ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.DataInitialize Fields.Add("GetCount") End Sub
private void NewActiveReport1_FetchData(object sender, DataDynamics.ActiveReports.ActiveReport3.FetchEventArgs eArgs) { try { Fields["GetCount"].Value = anotherPageCount; } catch { eArgs.EOF = true; } }
Private Sub NewActiveReport1_FetchData(ByVal sender As System.Object, _ ByVal eArgs As DataDynamics.ActiveReports.ActiveReport3.FetchEventArgs) _ Handles MyBase.FetchData Try Fields("GetCount").Value = anotherPageCount Catch ex As Exception eArgs.EOF = True End Try End Sub
総ページ数Textboxのプロパティを以下のように設定し、現在ページの最大値をとるようにします。
プロパティ | 設定値 |
SummaryFunc | Max |
SummaryGroup | なし |
SummaryRunning | None |
SummaryType | GrandTotal(総合計) |
DataField | GetCount(現在ページ数) |
重複する値を表示させないようにする
今回のサンプル帳票レイアウトでは、注文番号と注文単位の小計値を明細の1行目に表示させ、2行目以降は何も表示していません。重複値の表示、非表示を切り替えるには、detailセクションのFormatイベントでTextBoxコントロールのVisibleプロパティを切り替えればよいのですが、以下のようにすることでコード追加が不要になります。
重複する項目を次の行に表示させないようにするには、初回だけ表示させたい列のTextBoxコントロールをグループヘッダに設定し、UnderlayNextプロパティにTrueを設定します。UnderlayNextプロパティをTrueに設定したセクションは、次のセクションの開始位置に重なって表示されるため、結果として1行目だけ集計値が表示されることになります。
なお、コードを追加して表示、非表示を切り替える方法はActiveReportsのコードスニペットに含まれているので、ぜひ試してみてください。
改ページ処理について
最後に、改ページの処理について簡単に説明します。出力結果が設定した用紙サイズに収まらない場合、ActiveReportsでは自動的に改ページが行われますが、帳票レイアウト側で明示的に改ページ位置を指定することもできます。
改ページを指定するにはPageBreakコントロールを使用する方法と、各レポートセクションのNewPageプロパティを設定する方法の2つがあります。
PageBreakコントロールを使った改ページ
PageBreakコントロールはMicrosoft Wordの[改ページの挿入]とほぼ同様の機能です。コントロールを配置した位置で、毎回改ページを行います。
PageBreakコントロールはセクション途中の改ページに使用するコントロールです。例えば、ページヘッダに印刷する画像サイズが大きいので、2ページに分けたい場合などに使用します。
ただ、最終ページであっても指定位置で改ページしてしまうため「セクションが終了したところで改ページする」といったケースにはあまり向いていません。
セクション単位で改ページを行うには後述するNewPageプロパティを使用するのが便利ですが、PageBreakコントロールを使う場合はFetchDataイベントにコードを追加してEnabledプロパティを制御するなどの方法があります。以下のコードでは、最終行だった場合(eArgs.EOFプロパティの値がTrue)、改ページを起こさないように制御しています。
private void NewActiveReport1_FetchData(object sender, DataDynamics.ActiveReports.ActiveReport.FetchEventArgs eArgs) { if (!eArgs.EOF) { this.pageBreak1.Enabled = true; } else { this.pageBreak1.Enabled = false; } }
Private Sub NewActiveReport1_FetchData(ByVal sender As System.Object, _ ByVal eArgs As DataDynamics.ActiveReports.ActiveReport3.FetchEventArgs) _ Handles MyBase.FetchData If Not eArgs.EOF Then PageBreak1.Enabled = True Else PageBreak1.Enabled = False End If End Sub
集計グループ単位での改ページ
グループヘッダ・グループフッタのNewPageプロパティを設定すると、集計グループの単位で改ページが行われます。NewPageプロパティにはNone、Before、After、BeforeAfterのいずれかを設定できます。
例えば、グループフッタのNewPageプロパティを「After」に設定すると、グループフッタが印刷されたあとで改ページしますが、PageBreakコントロールのように最後に空白ページが入ることはありません。
まとめ
ActiveReportsの集計機能はデータソースの変更をすることなく、Summaryプロパティを設定するだけで高度な集計処理を簡単に行うことができます。また、必要に応じてコードを追加することで、より柔軟なカスタマイズが可能となっています。
改ページ制御については簡単な機能紹介にとどまりましたが、次回は、より掘り下げたページ制御のノウハウや実際の帳票印刷などのテーマを取り上げる予定です。