SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

5分でわかるActiveReports帳票

5分でわかるActiveReports帳票-改ページ制御と多段組レイアウト(2007年度版)

ActiveReports for .NET 3.0Jで作るサンプル帳票

  • このエントリーをはてなブックマークに追加

ダウンロード 使用データ (1013.0 B)

 本連載では帳票作成コンポーネント「ActiveReports for .NET 3.0J」を使って帳票アプリケーションを作成していきます。今回は改ページ時のセクション制御方法や、多段組レイアウトについて解説していきます。

  • このエントリーをはてなブックマークに追加
編集部注

 本稿の内容を増補改訂したアップデート版が提供されています。詳しくは連載の目次「5分でわかるActiveReports帳票」をご参照ください。

はじめに

 ActiveReports for .NET(以下ActiveReports)は、Visual Studioと統合された使いやすいレポートデザイナや、高機能なレポートビューア、多彩な出力形態をサポートする帳票作成コンポーネントです。今回は前回に引き続きActiveReports帳票の改ページ制御と、多段組レイアウト帳票について紹介していきます。

これまでの記事

対象読者

  • Visual Basic 2005またはVisual C# 2005を使ってプログラムを作ったことのある方。
  • 帳票作成ツールに興味のある方。

必要な環境

(注:Express EditionではActiveReportsをインストールできません)
  • Visual Studio 2005、Visual Studio .NET 2003でプログラムが作れる環境。

 本記事のサンプルコードはC# 2.0/Visual Basic 2005で記述しています。

改ページ制御について

最終的な完成イメージ

 今回は、改ページの制御について説明していきます。前半は前回のサンプルを少し変えた、以下のような帳票を例に解説を進めていきます。

「注文集計表2」帳票
「注文集計表2」帳票

 出力項目は、前回と同じく「月内での注文番号単位の小計・日単位の小計」「月間の注文合計・宅配料金・値引料金の合計」です。

 ただし、Detailセクションの高さが低いままだと改ページ制御について特に意識していなくてもうまく用紙に収まってしまうことが多いため、今回はあえてセクションの高さを広げるためにDetailセクションのレイアウトへ「商品コードのバーコード」を追加しています。

セクション単位での改ページ設定

 前回はPageBreakコントロールを利用した改ページを主に紹介しましたが、今回はセクションに用意されたプロパティを利用した改ページの制御を中心に解説していきます。

 セクション出力前後のタイミングで改ページ制御を行うには、各セクションのNewPageプロパティを利用します。NewPageプロパティには、None、Before、After、BeforeAfterのいずれかを設定できます。

NewPageプロパティに設定可能な値
説明
None セクション出力のタイミングでは改ページしない
Before セクション出力前に改ページする
After セクション出力後に改ページする
BeforeAfter セクション出力前と出力後の両方で改ページする

セクションがページ区切りで分割されるのを防ぐ

 ActiveReportsでは用紙に対して各セクションを順番に出力していくので、ページの最後で残りサイズより大きなセクションを出力すると、セクションが2ページにまたがって出力されてしまいます。

明細が切れた注文集計表
明細が切れた注文集計表

 このような場合、Detailセクションまたはグループヘッダ/フッタセクションのKeepTogetherプロパティを設定することで、ページ区切りによってセクション出力が分割されるのを避けることができます。

 KeepTogetherプロパティをTrueに設定すると、ActiveReportsはセクションの出力前に残り用紙サイズを判断し、サイズ不足でセクションが切れてしまう場合には改ページしてからセクションを出力します。

 なお、ページヘッダとページフッタにはKeepTogetherプロパティが存在しません。ActiveReportsでは、各ページのページヘッダとページフッタの領域が最初に確保され、残りの領域に対して他のセクションが配置されていきます。ページヘッダとページフッタの高さの合計が用紙サイズよりも大きくなってしまうと、実行時にエラーが発生するので注意してください。

グループヘッダを毎ページ出力する

 グループヘッダのセクションは通常、グループの出力ごとに1度だけ出力されます。これを毎ページ出力させたい場合は、グループヘッダのRepeatStyleプロパティを「OnPage」または「OnPageIncludeNoDetail」に設定します。

 設定値にOnPageを設定すると、そのページに1件以上detailセクションが出力される場合のみグループヘッダが出力されます。OnPageIncludeNoDetailに設定すると、detailセクションが出力されない(=グループフッタのみ出力される)ケースでもグループヘッダが表示されます。

RepeatStyleプロパティに設定可能な値
説明
None グループヘッダは最初の1回だけ出力する
OnPage 新しいページごとにグループヘッダを出力する
OnColumn 新しいカラムごとにグループヘッダを出力する
All 新しいページまたはカラムごとにグループヘッダセクションを繰り返す
OnPageIncludeNoDetail 関連するdetailセクションまたはグループフッタを含むページごとにグループヘッダを出力する

グループ化して改ページする

 セクションの大きさによっては、以下のようにグループヘッダだけが前ページに出力され、detailセクションから次のページ出力が始まってしまうことがあります。グループヘッダを、detaliセクションやグループフッタと1つのブロックとして同一ページに出力するためには、グループヘッダのGroupKeepTogetherプロパティを設定します。

グループヘッダだけが最後の行に来てしまった注文集計表
グループヘッダだけが最後の行に来てしまった注文集計表
GroupKeepTogetherプロパティに設定可能な値
説明
None グループヘッダの直後で改ページ可能
FirstDetail グループヘッダを同じページまたはカラムの最初の詳細セクションと共に出力する
All グループヘッダ、詳細、グループフッタの各セクションを同じページに出力する
グループヘッダが次ページへ移動された注文集計表
グループヘッダが次ページへ移動された注文集計表

 このサンプルでは、注文番号グループヘッダセクションのGroupKeepTogetherプロパティをFirstDetailに設定し、ヘッダは必ず明細行と一緒に表示するよう制御しています。

カラムを使った多段組レイアウト 1

 ここからは、ActiveReportsの多段組レイアウト機能について紹介します。

多段組レイアウトとは?

 これまで紹介してきたサンプル帳票は、各セクションのヘッダ/フッタ定義と、Detailセクションに定義した1行分のレイアウトを下方向へ繰り返し出力するものでした。ActiveReportsではこのような1段組のレイアウトだけでなく、2段組、3段組といった多段組レイアウトも作成可能です。多段組のレイアウトではDetailセクションが分割され、1行=1件のデータではなく、1行に複数件のデータを配置できます。

1段組みレイアウトと多段組(3段組)レイアウト
1段組みレイアウトと多段組(3段組)レイアウト

多段組レイアウトのサンプル帳票

 今回サンプルとして紹介するのは、各商品の日別売上をカレンダー風に横方向へ並べて表示した帳票です。

カラム帳票できあがりイメージ
カラム帳票できあがりイメージ

 また、取り込むデータ構造は次のようになっています。

データ構造
データ構造

 このデータを今までのように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プロパティに新しい子レポートのインスタンスを設定します。

SubReportコントロールの貼り付け
SubReportコントロールの貼り付け
商品名グループヘッダのFormatイベント設定(C#)
private void groupHeader1_Format(object sender, EventArgs e)
{
  //子レポートのインスタンスを作り、SubReportコントロールへ設定する
  SubReport1 sub1 = new SubReport1();
  this.subReport1.Report = sub1;
}
商品名グループヘッダのFormatイベント設定(VB2005)
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ファイルです。

売上データ(SalesData.txt)
売上データ(SalesData.txt)

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

商品名データ(ProductList.txt)
商品名データ(ProductList.txt)

 アンバウンドレポートでは、DataInitializeイベントとFetchDataイベントにデータの初期化と読み取りを記述します。また、今回はファイルからデータを読み取るため、ReportEndイベントで文字ストリームを閉じる必要があります。

 以下に各イベントで実装するコードを掲載します。便宜のため例外処理を省略していますが、実際の開発では適切な例外処理を追加してください。また、ファイル名もハードコーディングせず、パラメータとして外部から与えられるようにしておくべきでしょう。

親レポート(DailySalesList)のデータ取込処理(C#)
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();
}
親レポート(DailySalesList)のデータ取込処理(VB2005)
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の変換処理がないところ以外は、親レポートとほとんど同じです。

サブレポート(SubReport1)のイベント処理コード(C#)
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();
}
サブレポート(SubReport1)のイベント処理コード(VB2005)
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回にわたって、帳票アプリケーション開発の基本である「表示、集計、改ページ」についてひと通り紹介しました。次回以降も、実際の帳票アプリケーション開発に役立つテクニックを紹介していきますので、お楽しみに。

この記事は参考になりましたか?

  • このエントリーをはてなブックマークに追加

この記事は参考になりましたか?

この記事をシェア

  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/1752 2014/02/06 11:25

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング