読者です 読者をやめる 読者になる 読者になる

ComponentOne Information

ComponentOne Studio/Wijmo/Xuniの最新情報を公開中

動的に生成されるサンプルデータを利用した帳票デザイン

FlexReportを使用したレポートアプリケーションを作成するとき、多くの場合は付属のレポートデザイナを使用してレポート定義を作成・編集していることと思います。

一般的には、レポート生成元となるデータソース(SQL Serverなど)に接続し、それによって得られるテーブル情報や実際のデータを参照しながら、レポートデザインを進めていくことになります。プレビュー機能により、実際の出力結果を確認しながらレイアウトを調整できるので、非常に効率よくレポートのデザインを編集することができます。

データソースの課題

しかしながら、以下のようなケースでは、どのようにデータソースを用意するかが課題となる場合があります。

  • レポートの生成に使用するデータは静的に用意されたものではなく、アプリケーション内で動的に生成される。
  • 実際のデータは運用後に蓄積されるので、開発時にはデータが存在せず、動作確認用に別途ダミーデータを用意しなければならない。
  • レポートのデザイン時に、さまざまなパターンでのテストデータを使用して出力結果を確認したい。

レポートデザイナに接続するデータソースが動的なデータ生成をサポートしていれば、いずれの問題もすぐに解決できそうですが、はたしてそんなことが簡単に実現できるのでしょうか?



はい、ご安心ください。FlexReportが提供する「カスタムデータソース」と「外部レコードセット」の機能を使用すれば、非常に簡単にそれを実装できます。

カスタムデータソース

カスタムデータソースとは、事実上どのようなデータの集合であっても、それをFlexReportのデータソースとして接続可能にするための機能です。必要な条件は、そのオブジェクトがIC1FlexReportRecordsetインタフェースを実装したクラスのインスタンスであることだけです。

IC1FlexReportRecordsetインタフェースでは、以下のメンバーが定義されています。

メンバー名 説明
Count レコードソース内のレコードの数を取得します。
BOF() カーソルがデータソースの最初のレコードの位置にある場合は true を返します。
EOF() カーソルがデータソースの最後のレコードを過ぎた位置にある場合は true を返します。
GetBookmark() 現在のカーソル位置を返します。
GetFieldNames() データソース内の各フィールドの名前を表す文字列ベクターを取得します。
GetFieldTypes() データソース内の各フィールドの型を表すベクターを取得します。
GetFieldValue(index) 現在のカーソル位置にある特定のフィールドの値を取得します。
MoveFirst() カーソルをデータソースの最初のレコードに移動します。
MoveLast() カーソルをデータソースの最後のレコードに移動します。
MoveNext() カーソルをデータソースの次のレコードに移動します。
MovePrevious() カーソルをデータソースの前のレコードに移動します。
SetBookmark(bkmk) 現在のカーソル位置を指定された値に設定します。

これらのメンバーに対する実装を行うことで、カスタムデータソースとして機能することになります。

以下は、「ID(Int32型)」「Name(String型)」「RegDate(DateTime型)」の各フィールドを持ち、指定された件数のサンプルデータの動的生成機能を持つ、「SampleDataSource」というクラス名によるカスタムデータソースの実装例です。

using C1.Win.FlexReport;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace SampleDataSource
{
    public class SampleDataSource : IC1FlexReportRecordset
    {
        int _position = 0;
        DataTable _data;
        Dictionary<string, Type> _fields;

        public SampleDataSource()
        {
            _fields = new Dictionary<string, Type>()
            {
                { "ID", typeof(int) },
                { "Name", typeof(string) },
                { "RegDate", typeof(DateTime) },
            };

            _data = new DataTable();
            foreach(string name in _fields.Keys)
                _data.Columns.Add(new DataColumn(name, _fields[name]));
        }

        public int Count
        {
            get { return _data.Rows.Count; }
        }

        public bool BOF()
        {
            return (_position <= 0);
        }

        public bool EOF()
        {
            return (_position >= _data.Rows.Count);
        }

        public int GetBookmark()
        {
            return _position;
        }

        public string[] GetFieldNames()
        {
            return _fields.Keys.ToArray();
        }

        public Type[] GetFieldTypes()
        {
            return _fields.Values.ToArray();
        }

        public object GetFieldValue(int fieldIndex)
        {
            return _data.Rows[_position][fieldIndex];
        }

        public void MoveFirst()
        {
            _position = 0;
        }

        public void MoveLast()
        {
            _position = _data.Rows.Count;
        }

        public void MoveNext()
        {
            _position++;
        }

        public void MovePrevious()
        {
            _position--;
        }

        public void SetBookmark(int bkmk)
        {
            _position = bkmk;
        }

        internal void Update(int count)
        {
            _data.Rows.Clear();
            for(int index = 1; index <= count; index++)
            {
                var row = _data.NewRow();
                row.SetField<int>(0, index);
                row.SetField<string>(1, "Item " + index.ToString());
                row.SetField<DateTime>(2, DateTime.Now.AddDays(index));
                _data.Rows.Add(row);
            }
        }
    }
}

行っていることは単純で、内部的なDataTableにデータを生成しておき、各メソッドの呼び出しに対して適切な値を返すだけです。Updateメソッドが呼び出されたときに、指定された件数のサンプルデータが動的に生成されます。

このカスタムデータソースは、C1FlexReportのレコードセットとして直接指定することができます。

var source = new SampleDataSource();
source.Update(30);
flexReport.DataSource.Recordset = source;

ここまでで、カスタムデータソースの実装は完了です。次は、このカスタムデータソースをレポートデザイナから使用できるようにするための外部レコードセット化を行います。

外部レコードセット

外部レコードセットは、それが実装されたアセンブリの外部からアクセス可能なレコードセットです。必要な条件は、そのオブジェクトがIC1FlexReportExternalRecordsetインタフェースを実装したクラスのインスタンスであることだけです。

IC1FlexReportExternalRecordsetインタフェースでは、以下のメンバーが定義されています。

メンバー名 説明
Caption 現在のレコードセットのユーザーフレンドリな名前を取得します。
Params 現在のレコードセットのパラメータを取得または設定します。
EditParams() このメソッドは、現在のレコードセットのパラメータを設定するために使用されます。 たとえば、このメソッドを実装して、ユーザーがパラメータを 編集するためのダイアログを表示できます。
GetRecordset() データにアクセスするために使用される IC1FlexReportRecordsetインタフェースを返します。

このうち、最低限実装が必要なメンバーはCaptionプロパティとGetRecordsetメソッドの2つです。エンドユーザーがパラメータを変更でき、そのパラメータに基づいてレコードセットの内容を更新する必要がある場合は、残りの2つについても実装します。

以下は、前述のSampleDataSourceカスタムデータソース用の外部レコードセットの実装例です。

using C1.Win.FlexReport;
using System.Windows.Forms;

namespace SampleDataSource
{
    public class ExternalSampleDataSource : IC1FlexReportExternalRecordset
    {
        SampleDataSource _data = new SampleDataSource();
        int _count = 10;

        public string Caption
        {
            get { return "SampleDataSource"; }
        }

        public string Params
        {
            get { return _count.ToString(); }
            set 
            {
                var old = _count;
                if (!int.TryParse(value, out _count))
                    _count = old;
            }
        }

        public void EditParams()
        {
            using (var dialog = new frmEditor(_count))
            {
                if (dialog.ShowDialog() == DialogResult.OK)
                    _count = dialog.SampleDataCount;
            }
        }

        public IC1FlexReportRecordset GetRecordset()
        {
            if (_data == null)
                _data = new SampleDataSource();
            _data.Update(_count);
            return _data;
        }
    }
}

この例では、動的に生成するデータ数をエンドユーザーが指定できるようにするため、ParamsプロパティとEditParams()メソッドも実装しています。EditParamsメソッドが呼び出されたときには、NumericUpDownと2つのボタン(OK、キャンセル)が配置されたダイアログを表示し、エンドユーザーが生成するデータ件数を指定できるようにしています。

f:id:ComponentOne_JP:20170421143429p:plain

このダイアログの実装コードは以下の通りです。

using System;
using System.Windows.Forms;

namespace SampleDataSource
{
    public partial class frmEditor : Form
    {
        int _count = 1;

        public frmEditor(int count)
        {
            InitializeComponent();

            nudCount.Value = _count = Math.Max(count, 1);
        }

        public int SampleDataCount
        {
            get { return _count; }
        }

        private void btnOK_Click(object sender, EventArgs e)
        {
            _count = (int)nudCount.Value;
        }
    }
}

ここまでで、外部レコードセットの実装は完了です。

外部レコードセット用アセンブリの作成

クラスライブラリプロジェクトなどにて、前述のカスタムデータソース、外部レコードセットの2つ(場合によりパラメータ編集用のフォームなど)を実装して、*.dllファイルとしてビルドすれば、レポートデザイナから参照可能なアセンブリの完成です。

レポートデザイナから参照する

最後に、レポートデザイナから外部レコードセットを使用する方法を紹介します。以下は、レポート定義の新規作成時に参照する手順を示します。

  1. レポートデザイナを起動して、レポートの新規作成を実行し、新規作成ウィザードを起動します。
  2. データソースの選択画面では、データプロバイダとして「外部アセンブリ内のオブジェクトへ接続」を選択します。
    f:id:ComponentOne_JP:20170421150458p:plain
  3. 接続文字列フィールドの右側にある[...]ボタンをクリックし、作成したアセンブリファイル(*.dll)を選択します。カスタムデータソースおよび外部レコードセットが正しく実装されていれば、「データソース」タブ内に参照可能なカスタムデータソースが表示されます(Captionプロパティで指定した名称が表示されます)。
    f:id:ComponentOne_JP:20170421151528p:plain
    このとき、カスタムデータソースが選択された状態で、エンドユーザーが「プロパティ」ボタンをクリックすると、EditParams()メソッドが呼び出され、メソッド内に実装された処理が実行されます。前述のコード例の場合、パラメータの設定ダイアログが表示され、生成するデータ件数を指定することができます。
    f:id:ComponentOne_JP:20170421152150p:plain
  4. ウィザードの次の画面に移動すると、利用可能なフィールド一覧が表示され、使用するフィールドを選択できます。このとき、カスタムデータソースで定義したフィールドが一覧に表示されていることにご注目ください。
    f:id:ComponentOne_JP:20170421152347p:plain
  5. あとは通常通りウィザードの設定を進めます。すべての設定が完了し、レポートのプレビューが作成されると、カスタムデータソースにより動的に生成されたデータでレポートが作成されたことが確認できます。
    f:id:ComponentOne_JP:20170421152746p:plain]

以上により、カスタムデータソースの実装、およびそれを外部レコードセットとしてレポートデザイナで使用するのが非常に簡単であることがお分かりいただけたかと思います。

カスタムデータソースは構造がシンプルですが、例えば計測機器から受信したデータを直接データソースとしてレポートに渡したり、C1Excelと組み合わせてxlsxファイルをデータソースとして利用できるようにするなど、その活用方法はさまざまです。また、そのカスタムデータソースをレポートデザイナで利用できることは、レポートアプリケーションにおいて最も重要であるレポートのデザインおよびチェックにかかる手間を大幅に削減する大きなメリットとなります。

レポートアプリケーションの作成の際には、ぜひともこれらの機能をご活用いただければと思います。

ComponentOne