本稿はActiveReportsの旧バージョンを用いた内容となっています。最新版に基づいた記事は連載の目次「5分でわかるActiveReports帳票」をご参照ください。
はじめに
ActiveReports for .NET(以下ActiveReports)はVisual Studioと統合された使いやすいレポートデザイナや高機能なレポートビューワ、多彩な出力形態をサポートする帳票作成コンポーネントです。ActiveReportsは2008年3月下旬にリリースされたService Pack1でVisual Studio 2008に対応しました。今回はVisual Studio 2008に対応したActiveReportsの新機能について紹介します。
前回の記事
過去の連載記事
- 第1回:5分でわかるActiveReports帳票-らくらく始める帳票作成
- 第2回:5分でわかるActiveReports帳票-集計処理と改ページ
- 第3回:5分でわかるActiveReports帳票-改ページ制御と多段組レイアウト
- 第4回:5分でわかるActiveReports帳票-罫線・折り返しとプレビュー・印刷
- 第5回:5分でわかるActiveReports帳票-さまざまなデータソースの利用とデータのグラフ表示
対象読者
- Visual Basic 2008またはVisual C# 2008を使ってプログラムを作ったことのある方。
- 帳票作成ツールに興味のある方。
- 今回のサンプルではLINQ(Language INtegrated Query:言語統合クエリ)や匿名クラスなどの新機能を使用しています。各機能の詳細については他のCodeZine記事を参照ください。
必要な環境
開発ツール
- Visual Studio 2008(Express EditionではActiveReportsをインストールできません)
開発言語
本記事のサンプルコードはC# 3.0/Visual Basic 9.0で記述しています。
Visual Studio 2008に対応したActiveReports 3.0
ActiveReports 3.0は、2008年3月下旬にリリースされたService Pack1でVisual Studio 2008(以下VS2008)に対応しました。このService PackにはVS2008のIDE対応および追加の新機能、改訂されたヘルプが含まれています。また、製品付属のサンプルコードにも、VS2008対応版が追加されました。この他、以前のバージョンで作成したレポートファイルを変換するためのインポートウィザードも、VS2008に対応したものに更新されています。
今回は、ActiveReportsの新機能として「ActiveReports用プロジェクトテンプレート」と「データソースとしてLINQを使ったクエリ結果を扱う方法」の2つを紹介します。
ActiveReports用のプロジェクトテンプレート
VS2008では、.NET Frameworkの機能追加に合わせて「新しいプロジェクト」ダイアログで選択可能なプロジェクトテンプレートが大幅に追加され、帳票アプリケーションのための「Reporting」というグループと、帳票アプリケーション用のプロジェクトテンプレートが追加されました。VS2008にActiveReportsをインストールすると、「新しいプロジェクト」ダイアログのReportingグループに「ActiveReportsアプリケーション」というプロジェクトテンプレートが追加されます。
これまではActiveReportsを利用した帳票アプリケーションを開発するのに、いったん「Windows Formsアプリケーション」のプロジェクトテンプレートを作ってからActiveReportsのレポートファイルを追加する、という手順を取っていましたが、このプロジェクトテンプレートを使うことで、すぐに帳票アプリケーションの開発をスタートすることができます。
「新しいプロジェクト」ダイアログから「ActiveReportsアプリケーション」テンプレートを選択してプロジェクトを作成すると、Viewerコントロールが追加されたWindowsフォームと、空のActiveReportsファイルを1つ含むプロジェクトが作成されます。
このFormコントロールには以下のように、デフォルトのLoadイベントが実装されています。
Form1.cs(VBの場合はForm1.vb)を右クリックしてメニューから「コードの表示」を選択すると、Loadイベントに最初から実装されているコードを見ることができます。
private void Form1_Load(object sender, EventArgs e) { NewActiveReport1 rpt = new NewActiveReport1(); rpt.Run(); this.viewer1.Document = rpt.Document; }
Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Dim rpt As New NewActiveReport1 rpt.Run() Me.Viewer1.Document = rpt.Document End Sub
デフォルトのLoadイベントが実行されると、ActiveReportsのレポートインスタンスが生成され、Formに関連付けられたViewerコントロールにプレビューが表示されます。
このプロジェクトテンプレートを使えば、F5キーを押してデバッグ起動することですぐに帳票のプレビューを確認できます。今回は、このプロジェクトテンプレートを使用して以後のサンプルコードを説明していきます。
LINQを使ったクエリの結果を帳票に表示する
.NET Framework 3.5に対応したVisual Studio 2008の目玉機能と言えば、なんといってもLINQ(Language INtegrated Query:言語統合クエリ)ではないでしょうか。
これまではRDBMSのテーブルに格納されたデータやオブジェクトのコレクション、XMLドキュメントなど、データソースの種類ごとに異なったデータアクセスコードを記述する必要がありました。.NET Framework 3.5では新しいLINQの構文を活用することで、データソースの種類を気にせず、統一的な問い合わせコードでデータを操作できるようになります。
ActiveReportsも、VS2008への対応と合わせてLINQを使ったデータの取り扱いに対応しました。今後は帳票として出力するデータの操作にLINQを活用するシーンも増えてくるのではないかと思います。
LINQを使うための準備
これから紹介するサンプルコードでは、冒頭で紹介した「ActiveReportsアプリケーション」のプロジェクトテンプレートを使用しますが、ここで1つ、気をつけたいポイントがあります。
このテンプレートで作成したプロジェクトのForm.csやProgram.csには、LINQを使用するための名前空間が設定されていません。プロジェクトをLINQのコードを記述するソースファイルを開いて、先頭にusingディレクティブ(VBの場合はImportsステートメント)を追加して、LINQを使えるようにしましょう。
using System.Linq;
Imports System.Linq
参照を追加したところで、さっそくLINQを利用した帳票作成を始めたいと思います。
ActiveReportsでLINQを使うときの原則
ActiveReportsでは、帳票の出力元となるデータソースを、DataDynamics.ActiveReports.ActiveReport3オブジェクトのDataSourceプロパティに設定します。DataSourceプロパティの型はobject型になっているので、一見どんな型のオブジェクトを指定してもよさそうに見えますが、LINQによるクエリの結果をそのままデータソースとして使うことはできません。
LINQのクエリ結果はSystem.Collections.Generics.IEnumerable<T>インターフェースを実装した型で返されます。ActiveReportsでLINQの問い合わせ結果をデータソースとして使うためには、問い合わせ結果をToListメソッドでリストの型(System.Collections.Generics.List<T>)に変換して渡します。
LINQにはオブジェクトコレクションを操作するための「LINQ to Object」、データベースに格納されたデータにアクセスするための「LINQ to SQL」、XMLドキュメント用の「LINQ to XML」の3種類がありますが、どのタイプのLINQでもこのルールは同じです。
Enumerable.Rangeで作った列挙を表示させる
それでは、まず最初は簡単な例でLINQを試してみましょう。
System.Linq.Enumerableクラスには、IEnumerable<T>インターフェースを実装した各種データソースを操作するためのさまざまなメソッドが提供されています。Enumerable.Rangeメソッドは、指定された範囲の整数のシーケンスを返します。
例えばEnumerable.Range(1, 10)と書くと、1から10までの数値の列挙がIEnumerableインターフェースで返されます。今回は、このデータの集まりに対して「4より大きな値」という検索条件を指定し、結果として「Countプロパティに数値を含む匿名クラス」を生成してその集合を返すクエリを書いてみましょう。
VS2008で、冒頭に紹介した「ActiveReportsアプリケーション」のプロジェクトを新規作成し、デフォルトで作成されるフォームのLoadイベントに、以下のようにLINQのコードを追加してみましょう。
private void Form1_Load(object sender, EventArgs e) { NewActiveReport1 rpt = new NewActiveReport1(); //クエリ元のデータを指定 var query = from x in Enumerable.Range(1, 10) where x > 4 //検索条件を指定 select new {Count = x}; //匿名クラスを生成 rpt.DataSource = query.ToList(); rpt.Run(); this.viewer1.Document = rpt.Document; }
Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Dim rpt As New NewActiveReport1 ' クエリ元のデータを指定 Dim query = From x In Enumerable.Range(1, 10) _ Where x > 4 _ ' 検索条件を指定 Select New With {.Count = x} ' 匿名クラスを生成 rpt.DataSource = query.ToList() rpt.Run() Me.Viewer1.Document = rpt.Document End Sub
なお、このコードの問い合わせ部分はLINQで記述する以外にも、IEnumerableインターフェースに定義されたメソッドを連結する形(メソッドチェイン)で書くことができます。また、条件式の部分にはC#/Visual Basicの新機能「ラムダ式」を使用することもできます。
以下は、今紹介したLINQのクエリ部分を、メソッドチェインとラムダ式で書きかえたものです。見た目は少し違いますが、どちらも同じ意味になります。
private void Form1_Load(object sender, EventArgs e) { NewActiveReport1 rpt = new NewActiveReport1(); //クエリ元のデータを指定 var query = Enumerable.Range(1, 10) .Where(x => x > 4) .Select(x => new {Count = x}); rpt.DataSource = query.ToList(); rpt.Run(); this.viewer1.Document = rpt.Document; }
Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Dim rpt As New NewActiveReport1 ' クエリ元のデータを指定 Dim query = Enumerable.Range(1, 10) _ .Where (Function(x) x > 4) _ .Select(Function(x) New With {.Count = x}) rpt.DataSource = query.ToList() rpt.Run() Me.Viewer1.Document = rpt.Document End Sub
帳票レイアウト側の設定と実行
レポートファイル側では、LINQによって返される予定のオブジェクトを表示するために、Labelコントロールを1つ配置します(以下の帳票レイアウトの水色の部分)。LabelコントロールのDataFieldプロパティには、クエリの中で作成した匿名クラスで定義した「Count」プロパティを設定してください。
コードの記述と帳票レイアウトの設定が終わったら準備完了です。
F5キーを押してプロジェクトを実行すると、フォームのViewerコントロールには次のような結果が表示されます。
1~10の整数のうち、where句で記述した検索条件(4より大きい)にマッチする6件のデータだけが表示されているのがわかります。
帳票出力にLINQを活用する(CSVファイルに含まれたデータの並べ替え・抽出)
ここまではウォーミングアップということで、LINQを使った簡単なサンプルを紹介しました。ここからは、次はもう少し実用的なサンプルを紹介したいと思います。
LINQを使うと、これまでSQLでやっていたようなデータの並べ替えや抽出、複数データソースの結合といった操作を、データソースの種類を問わず簡潔に記述できます。
CSVファイルに含まれたデータの並べ替え・抽出
まずは、帳票のデータソースとして使用することも多いCSVファイルを、LINQで操作するサンプルを紹介します。
以前の記事では、CSVファイルをデータソースにして帳票を出力する方法について紹介しましたが、そのときはStreamReaderを利用してファイルを一気に読み取るだけの形だったため、ファイル内のデータ順序は決まっているものとして説明していました。
これまではファイルに格納されたデータを並べ替えたり、条件に一致したデータだけを抽出するためにファイルの各行を何らかのオブジェクトに変換し、それをいったんListに格納してから並べ替えたりといったコードを記述する必要がありましたが、LINQを使うとファイルに格納されたデータの並べ替えや抽出を簡単に書くことができます。
以下のデータは今回使用するCSVファイルの内容です。このファイルには商品情報が含まれていて、1件分のデータは「商品コード」「商品名称」「単価」の3要素で構成されています。ここでは、CSVファイルが「C:\ActiveReports3」というフォルダに保存されているものとします。
A12358847,USBフラッシュメモリ(2GB) ,4980 A12318181,USBフラッシュメモリ(8GB) ,9980 A22175175,ブロードバンドルータ,24800 A71351582,ストレートLANケーブル(10m),1650 A47005782,20インチワイド液晶ディスプレイ,39800 A99157000,外付型DVDドライブ,13800 A72002487,A3対応インクジェットプリンタ,49800 A35365587,光学式5ボタンマウス,6500 A23548783,IEEE802.11g/b対応 無線LANアクセスポイント,7800 A55487527,A4対応モノクロレーザープリンター,29800
このCSVファイルに対し「ファイルを読み込んで、単価の高いものから5件ぶんを降順で表示する」というコードを作成してみましょう。
Detailセクションの、商品コードと商品名称を表示する位置にLabelコントロールを2つ配置し、DataFieldプロパティにはそれぞれ「ProductCode」「ProductName」を設定します。単価の部分は金額の書式表示としたいのでTextBoxコントロールを使用し、DataFieldプロパティには「UnitPrice」を、OutputFormatプロパティには「##,##0」を設定します。
以下のコードはAcviteReportsプロジェクトのForm_Loadメソッドに、CSVファイルへアクセスするLINQのクエリを追加したものです。
ファイルをStreamReaderで読み出して文字列のリストに変換する部分はサブルーチンとして別メソッドに切り出しています。取り込んだ各行はカンマで区切られた複数のデータを表す文字列なので、これを配列の形に変換します。LINQクエリの中でletキーワードを使い文字列をSplitメソッドで分解した結果を配列の形で保持しておくと、続きの部分を簡潔に書くことができます。
private void Form1_Load(object sender, EventArgs e) { NewActiveReport1 rpt = new NewActiveReport1(); string fileName = @"C:\ActiveReports3\Products.csv"; using (StreamReader reader = File.OpenText(fileName)) { var query = from line in GetLines(reader) // 各行をカンマで区切る let items = line.Split(',') // 単価の高い順に並べ替え orderby Convert.ToInt32(items[2]) descending select new { ProductCode = items[0], ProductName = items[1], UnitPrice = items[2] }; query = query.Take(5); // 結果から先頭5件分だけ取得 rpt.DataSource = result.ToList(); reader.Close(); } rpt.Run(); this.viewer1.Document = rpt.Document; } //StreamReaderで取得したデータをリストに変換するサブルーチン private IEnumerable<string> GetLines(StreamReader r) { while (!r.EndOfStream) { string line = r.ReadLine(); yield return line; } }
Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Dim rpt As New NewActiveReport1 Dim fileName = "C:\ActiveReports3\Products.csv" Using reader As New StreamReader(fileName) Dim query = From lines In GetLines(reader) _ Let items = lines.Split(",") _ ' 各行をカンマで区切る Order By Convert.ToInt32(items(2)) Descending _ ' 単価の高い順に並べ替え Select New With{ _ .ProductCode = items[0], _ .ProductName = items[1], _ .UnitPrice = items[2] _ } query = query.Take(5) ' 結果から先頭5件分だけ取得 rpt.DataSource = query.ToList() reader.Close() End Using rpt.Run() Me.Viewer1.Document = rpt.Document End Sub 'StreamReaderで取得したデータをリストに変換するサブルーチン Private Function GetLines(ByRef Reader As StreamReader) _ As IEnumerable(Of String) Dim result As New List(Of String) While Reader.EndOfStream <> True Dim line = Reader.ReadLine() Debug.WriteLine(line) result.Add(line) End While Return result End Function
コードの記述が終わったら、さっそくアプリケーションを実行してみましょう。以下のように、単価の高い商品5件だけが表示されるはずです。
帳票出力にLINQを活用する(複数のXMLファイルを組み合わせて出力する)
複数のXMLファイルを組み合わせて出力する
次は、複数のXMLファイルに含まれるデータを組み合わせて1つの帳票に出力する例を紹介します。
「データの集合同士を結合する」という操作は、SQLに専用の構文が用意されているデータベースの世界では特に難しい操作ではありません。しかし、オブジェクトのコレクションやXMLドキュメントを結合する処理を結合したい場合、これまでの.NET言語ではコレクションをループで1つ1つ処理していくというやり方が主流でした。コレクションを処理するコードはループを多用するため、ネスト(入れ子構造)が深く、わかりにくいコードになってしまうことも少なくありません。
このようなデータソースの結合操作も、LINQを使うとすっきりしたコードにまとめることができます。
以下のデータはアドレス帳のデータと、アドレス帳に含まれるデータをグループ分けしたデータです。アドレス帳の各要素には番号、氏名、メールアドレスとグループ番号が、アドレスグループにはグループ番号とグループ名がそれぞれ定義されています。ここでも先ほどと同じように、「C:\ActiveReports3」フォルダにこれらの2ファイルが配置されているものとします。
<?xml version="1.0" encoding="UTF-8"?> <AddressBook> <Address> <AddressNo>1</AddressNo> <Name>山口</Name> <Email>yamaguchi@example.com</Email> <GroupNo>01</GroupNo> </Address> <Address> <AddressNo>2</AddressNo> <Name>新井</Name> <Email>arai@example.com</Email> <GroupNo>02</GroupNo> </Address> <Address> <AddressNo>3</AddressNo> <Name>中村</Name> <Email>nakamura@example.com</Email> <GroupNo>01</GroupNo> </Address> <Address> <AddressNo>4</AddressNo> <Name>松田</Name> <Email>matsuda@example.com</Email> <GroupNo>03</GroupNo> </Address> <Address> <AddressNo>5</AddressNo> <Name>高島</Name> <Email>takashima@example.com</Email> <GroupNo>02</GroupNo> </Address> </AddressBook>
<?xml version="1.0" encoding="UTF-8"?> <AddressGroups> <AddressGroup> <GroupNo>01</GroupNo> <GroupName>友人</GroupName> </AddressGroup> <AddressGroup> <GroupNo>02</GroupNo> <GroupName>職場</GroupName> </AddressGroup> <AddressGroup> <GroupNo>03</GroupNo> <GroupName>趣味</GroupName> </AddressGroup> </AddressGroups>
今回はこの2つを組み合わせて、次のような帳票を出力します。
この帳票を出力するために、次のような帳票レイアウトを作成します。
Detailセクションには番号、グループ、氏名、メールアドレスの各列にLabelコントロールを配置します。各LabelのDataFieldプロパティにはそれぞれ「AddressNo」「GroupName」「Name」「Email」を設定します。
このサンプルは、LINQのデータソースとしてXMLファイルを使用しますが、XMLを操作するためのLINQ(LINQ to XML)を使用するには、System.LinqのほかにSystem.Xml.Linq名前空間の参照を追加する必要があります。usingディレクティブ(Visual Basicの場合はImportsステートメント)を使って、ソースファイルの先頭に以下のコードを追加してください。
using System.Xml.Linq;
Imports System.Xml.Linq
問い合わせコードの記述
以下のコードは、2つのXMLファイルをデータソースとして使用し、結合結果をデータソースとして指定するコードです。LINQでXMLドキュメントを操作するには、主にSystem.Xml.Linq.XElementクラスのメソッドを使用することになります。
具体的には、XElementクラスのLoad()メソッドでXMLファイルを読み出し、Elements()メソッドでコレクションを指定します。そして、Element()メソッドとValueプロパティを組み合わせて、XMLドキュメントから値を取得します。
LINQで2つのデータを結合(内部結合)する場合は、fromクエリ句に続けて「join コレクション on 結合条件」の形で記述します。結合条件は「値1 equals 値2」の形で記述します。クエリが完成したら、これまでの例と同じようにToList()メソッドでListの形に変換します。
private void Form1_Load(object sender, EventArgs e) { NewActiveReport1 rpt = new NewActiveReport1(); string xmlFile1 = @"C:\ActiveReports3\AddressBook.xml"; string xmlFile2 = @"C:\ActiveReports3\AddressGroup.xml"; IEnumerable<XElement> xml1 = XElement.Load(xmlFile1).Elements("Address"); IEnumerable<XElement> xml2 = XElement.Load(xmlFile2).Elements("AddressGroup"); var query = from doc1 in xml1 join doc2 in xml2 on doc1.Element("GroupNo").Value equalsdoc2.Element("GroupNo").Value select new { AddressNo = doc1.Element("AddressNo").Value, Name = doc1.Element("Name").Value, Email = doc1.Element("Email").Value, GroupName = doc2.Element("GroupName").Value }; rpt.DataSource = query.ToList(); rpt.Run(); this.viewer1.Document = rpt.Document; }
Private Sub Form1_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load Dim rpt As New NewActiveReport1 Dim xmlFile1 = @"C:\ActiveReports3\AddressBook.xml"; Dim xmlFile2 = @"C:\ActiveReports3\AddressGroup.xml"; Dim xml1 = XElement.Load(xmlFile1).Elements("Address"); Dim xml1 = XElement.Load(xmlFile2).Elements("AddressGroup"); Dim query = From doc1 In xml1 Join doc2 In xml2 On doc1.Element("GroupNo").ValueEquals doc2.Element("GroupNo").Value _ Select New With { _ .AddressNo = doc1.Element("AddressNo").Value, _ .Name = doc1.Element("Name").Value, _ .Email = doc1.Element("Email").Value, _ .GroupName = doc2.Element("GroupName").Value _ } rpt.DataSource = query.ToList() rpt.Run() Me.Viewer1.Document = rpt.Document End Sub
コードを書き終えたらアプリケーションを起動して、帳票のプレビューを表示してみましょう。2つのXMLファイルに格納されたデータがLINQによって結合され、1つの帳票として出力できました。
まとめ
今回は、Visual Studio 2008に対応したActiveReports 3.0の新機能として、ActiveReports用の新しいプロジェクトテンプレートとLINQの活用法を中心に紹介しました。
次回は「帳票アプリケーション開発におけるテスト」というテーマでお送りする予定です。お楽しみに。