CodeZine(コードジン)

特集ページ一覧

シングルインスタンスをWindowsフォームにデータバインドする方法

.NETにおけるデータバインディングの高度な使い方

  • ブックマーク
  • LINEで送る
  • このエントリーをはてなブックマークに追加
2005/09/21 12:00

.NETプラットフォームのデータバインディング機能は強力な開発ツールですが、私の見る限り、この機能は充分に活用されていません。おそらく、.NETで何ができるかを知らないか、データバインディングの内部処理を理解していないかの、どちらかでしょう。本稿では、こうしたハードルを乗り越え、データバインディングを活用するための解説を行います。

はじめに

 .NETプラットフォームのデータバインディング機能は強力な開発ツールです。データバインディングとは、簡単に説明すれば、「アプリケーション要素のプロパティを、グラフィカルな表示要素の中の特定要素にリンクすること」です。たとえば、カスタムオブジェクトのプロパティ(従業員オブジェクトの名前プロパティなど)を、Windowsフォームのテキストボックスのtextプロパティにリンクするような場合に使用します。

 データバインディングは開発者にとって新しい概念でありません。アプリケーションの基本的な部分であり、アプリケーションのエンドユーザーと処理ロジックの間で情報をやりとりするための、最も基本的な方法です。多くの場合、これは「力仕事」で行われてきました。手作業でコードを書いて、オブジェクトのプロパティとGUI要素(テキストボックス、データグリッド、チェックボックスなど)の間でデータをマーシャリングしてきました。しかし、.NET Frameworkには、これまでにない豊富な機能が備わっています。特に、アプリケーションでデータバインディングを使うことには、大きな魅力があります。

  • 一度設定するだけで、GUIコントロールとオブジェクトのプロパティとの同期を維持できる。
  • 型変換機能により、たとえば、オブジェクトのdecimal型のプロパティを、テキストボックスのstring型のtextプロパティにマッピングすることができる。
  • デリゲートとイベントに基づいた方法で、オブジェクトのプロパティの変更を「監視」できる。
  • オブジェクトにGUIコントロールのプロパティを管理させることで、わずかな手間で表現豊かなUIが構築できる(たとえば、値が変更されない限り[Save]ボタンを有効にしないなど)。
  • 数多くのイベントを利用して、デフォルトの構文解析や値の書式設定よりも、指定したコントロールのプロパティを優先させることができる。
  • コントロールのデータ型検証機能により、たとえば、オブジェクトの日時プロパティにバインドされたテキストボックスには、有効な日付だけが入力されることを保証できる。

 私の見る限り、データバインディング機能は充分に活用されていません。GUIとアプリケーションオブジェクトとのデータのやりとりを実現するために、膨大なコードを書き続けている開発者をまだよく見かけます。おそらく、.NETで何ができるかを知らないか、データバインディングが水面下でデータをどう管理しているのかを理解していないかの、どちらかでしょう。この記事を読むことで、こうしたハードルを乗り越え、アプリケーションでデータバインディングを使うきっかけにしてほしいというのが、私のねらいです。

 データバインディングは広範なテーマなので、この記事で扱うことと扱わないことを、あらかじめ区別しておきます。この記事では、独自のシングルインスタンスオブジェクトをWindowsフォームコントロールにバインディングする方法だけを扱います。Webフォームのバインディングは扱いません。また、独自のコレクションをデータグリッドやドロップダウンボックスといった要素にバインディングする方法も扱いません。ASP.NETのWebフォームに関する開発の大部分が完成しつつあるのは知っていますが、.NET開発者の関心と支持を急速に集めているスマートクライアント開発への準備になるという理由で、Windowsフォームに的を絞ることにしました。

前提

 この記事の説明とコード例では、全体を通して同じクラスとWindowsフォームを使用します。コード例を作成するにあたっては、話をわかりやすくするためにシンプルなもの、かつ、皆さんに興味を持ってもらえる程度には複雑なものにするよう心がけました。そこで、Employeeという1つのクラスと、このオブジェクトのプロパティを読み書き可能な方法で表示するフォームを用意することにしました。

 この記事で使用するクラスは、一般的な企業をモデルとして作ったものです。したがって、複雑なビジネスロジックや、別の層の別のクラスに属するデータアクセスは出てきません。説明上、オブジェクトのプロパティにデータが必要な場合は、値をハードコーディングします。オブジェクトを保存する必要がある場合は、単純にファイルにシリアライズします。これも、例を単純化して、データバインディングに専念するためです。

 EmployeeクラスをUMLで表したのが、図1です。

図1
図1

 このクラスのすべてのソースコードを見る場合は、この記事の先頭のダウンロード用リンクを使用してください。ダウンロードファイルには3つのソリューションが含まれており、それぞれこの記事で説明するデータバインディングの種類に対応しています。

 具体的なデータバインディングの話に進む前に、ソースコードについていくつか説明しておきます。まず、ダーティフラグ(dirty flag)とその実装方法についてです。ダーティフラグは、オブジェクトのプロパティが違う値に変更されたかどうかを示します。オブジェクトのプロパティに値が設定されるたびに、図2のように、CheckDirtyというプライベート関数が呼び出されます。

図2
図2

 呼び出されたCheckDirtyが、プロパティの現在値と提示された値が異なると判定しない限り、プロパティには新しい値が設定されません。

 CheckDirtyのロジックは図3のとおりです。後述の「上級レベルのデータバインディング」では、このダーティフラグを使ってユーザーインターフェースを改良します。

図3
図3

 この記事で使用するフォームが図4です。

図4
図4

 このフォームには、従業員の名前(First Name)、名字(Last Name)、雇用日(Hire Date)、給与(Salary)を表示するテキストボックスがあります。.NET Framework内でデータバインディングがどのようにキャストと変換を行うのかを見てもらうため、decimalDateTimeなど、さまざまなデータ型を選びました。さらに、Boolean型のisActiveプロパティの値を示すチェックボックスと、[Save]と[Load New Values]の2つのボタンを用意しました。

 これから説明するアプリケーションの要素について基本的な説明が終わったところで、データバインディングがどのように動作するのかを見ていきましょう。

力仕事のデータバインディング

 最初に説明するデータバインディングの方法は、私が「力仕事」と名づけた方法です。この方法では、オブジェクトと表示の連結部をすべてハードコーディングし、.NET Frameworkのデータバインディング機能はまったく使用しません。これを基準にすると、.NETが実際にどれくらいのことを行ってくれるのかがよくわかります。この例の全ソースコードは、ダウンロードサンプルの「ManualDataBindingSolution」フォルダの中にあります。

 フォームの中には、フォームで使用するEmployeeクラスのインスタンスを保存するため、図5のとおり、メンバ変数を作成しました。

図5
図5

 フォームのコンストラクタの中では、図6のとおり従業員オブジェクトをインスタンス化し、データを設定します。説明の便宜上、値はハードコーディングしています。実際のアプリケーションでは、こうした値はほとんどの場合、ビジネスロジックやデータクラスによって設定されます。

図6
図6

 フォームのLoadイベントハンドラは、図7のようにしてフォームの値をマーシャリングします。このとき、データバインディングコードをカプセル化したLoadValuesToFormというプライベートメソッド(図8)を呼び出します。これを別々のメソッドに分けたことには、2つの理由があります。フォームの別の領域からコードを再利用できるようにするためと、フォームのイベントハンドラからできるだけ処理ロジックを切り離すためです。これにより、コードがすっきりし、メンテナンスがしやすくなります。

図7
図7
図8
図8

 コードを見てわかるとおり、decimal型とDateTime型のフィールドの書式を手作業で設定する必要があります。

 オブジェクトの値がどのように設定され、その値がどういった経路でオブジェクトからフォームのコントロールに渡されるかを理解したところで、次は、[Save]ボタンがクリックされたときなどに、フォームコントロールの値がオブジェクトにどのようにマーシャリングされるのかを見てみましょう。フォームの値をオブジェクトにマーシャリングするための実際のロジックは図9のとおりです。

図9
図9

 ここでも、データをテキストベースのプロパティからdecimal型やDateTime型の値にキャストするのは、開発者の仕事です。なお実際のアプリケーションでは、詳しいエラーメッセージを出力するために、238~241行目(decimal型、DateTime型の値をマーシャリングしている部分)で値をチェックするロジックを追加するとよいでしょう。たとえば、[Hire Date]テキストボックスに文字列でない日付を入力すると、この部分のコードにより、図10のようなエラーダイアログが表示されます。

図10
図10

 すべての値がフォームからオブジェクトに正常にマーシャリングされ、少なくとも1つのプロパティの値が変更されていた場合は、そのオブジェクトがアプリケーションのカレントディレクトリにあるXMLファイルにシリアライズされます。

 [Load New Values]ボタンは、データソースから新しい値を入力するなど、現在のEmployeeオブジェクトの値を別の値に変更する処理を行います。注意してほしいのは、オブジェクトのプロパティが変更されたときに、手作業でLoadValuesToFormメソッドを呼び出して、オブジェクトの値をフォームに読み込む必要があるという点です。オブジェクトのプロパティが変更されたかどうかを手作業で確認する代わりに、フォームへの変更が自動的に行われればよいとは思いませんか。実はそれが、.NET Frameworkのデータバインディング機能の一面です。

一方向のデータバインディング

 このシナリオでは、.NET Framworkの機能を使用して、Employeeクラスに変更を加えずにデータバインディングを実現し、フォームのロジックを実際に削減します。このシナリオのコードは「OneWayDataBindingSolution」の中にあります。

 このソリューションでは、Employeeを表示するフォームから、2つのマーシャリング関数(LoadValuesToFormLoadValuesFromForm)を削除します。この2つの関数を、フォームのLoadイベントハンドラから呼び出されるSetDataBindingsに置き換えます。ここでも、データバインディングを設定するロジックをLoadイベントハンドラ内に直接記述することもできますが、ロジックを独立した関数にまとめた方が、コードの再利用性が高まり、すっきりしたものになります。SetDataBindingsメソッドのコードは図11のとおりです。

図11
図11

 このメソッドでは単純に、GUI要素とローカルオブジェクトのプロパティとのデータバインディングを定義しています。バインディングは、コードの243~244行目のような方法で、それぞれ個別に設定する必要があります。構文は以下のとおりです。

図12
図12

 この構文から、各コントロールが自分用のデータバインディングのコレクションを持っていることがわかります。このコレクションの中で、コントロールの各プロパティがデータバインディングを持つことができます。重要なのは、プロパティはデータバインディングを1つしか持てないという点です。複数割り当てることもできますが、システム引数例外になります。このため、バインディング処理の前に、目的のプロパティにバインディングがあるかどうかをチェックし、あれば削除します。このコードでは239~241行目でその処理を行っています。

図13
図13

 バインディングを作成する構文(図12の242~244行目)を見てみましょう。コントロール側のプロパティ("Text")と、データソース(_oEmployee)と、データソース側のプロパティ("firstName")を指定しています。面白いことに、C#の世界では通常は大文字と小文字が区別されますが、コントロールとデータソースに指定されたプロパティの名前については、大文字と小文字の区別がありません。

 バインディングに使用するコントロール側のプロパティは、バインディングの構文の中で指定するので、他のプロパティにバインディングするのも簡単です。例えば271~272行目では、チェックボックスの"Checked"プロパティにバインディングしています。

 もう1つ注目すべきは、データバインディング機能は自動的に変換を行ってくれることです。これで、decimal型やDateTime型のデータを、文字列から、あるいは文字列へ変換するコードを書く必要はなくなりました。この機能の利点は、開発者がこうした低水準な細かい連結処理の心配をしなくてよくなったことです。また、入力データをフィルタする機能もあります。プロジェクトを起動し、[Hire Date]テキストボックスにdate型以外の値を入力した場合、または[Salary]テキストボックスに数値以外の値を入力した場合、入力は無効になり元の値に戻されます。

 ここで注意してほしいのは、アプリケーションを起動したときに、デフォルトの書式が使用されるという点です。[Hire Date]テキストボックスの場合は、日付と時間が表示されますが、これは意図とは違います。手作業によるデータバインディングの例では、DateTime型のToShortDateTime()メソッドを使って値を書式設定しました。Microsoftは、この問題を解決するために、イベントデリゲートを割り当てておき、データバインディング機能がコントロールに表示するデータを書式設定するときにそのイベントデリゲートが呼び出されるようにするという方法を採用しました。今回の例では、バインディング機能がバインド先コントロールの値の書式設定を準備するときに発生するFormatイベントにイベントデリゲートを割り当てる必要があります。そこで、データバインディングを定義するためのもう1つのメソッドとして、SetDataBindingsWithFormatというメソッドをフォーム内に作成しました。このメソッドは、表示の書式設定を制御するのに必要なイベントを追加します。さらに、デリゲートとして機能するDateTimeToShortDateStringメソッドを用意しました。サンプルコードを実行するときに、SetDataBindingsWithFormatSetDataBindingsのどちらかを呼び出すことで、違いを確認することができます。イベントデリゲートを追加する新しいコードを図14に示します。

図14
図14

 図14が示すとおり、ここでは[Hire Date]テキストボックスのバインディング(イベントハンドラの登録も含む)を定義してから、そのバインディングを実際のコントロールに割り当てています。これは、バインディングがコントロールに割り当てられた時点で、バインディング機能が自動的にデータソースから値を取得するためです。したがって、図15のように、バインディングの定義と割り当てを一気に行ってからハンドラを割り当てた場合は、書式設定が適用されません。

図15
図15

 データバインディングの動作は、アプリケーションを実行したり、テキストボックスの値を変更したり、[Save]ボタンをクリックしたりすることで確認できます。[Save]のclickイベントハンドラか、値を調べたいオブジェクト内にブレークポイントを設定すれば、オブジェクトのプロパティが新しい値に更新されることを確認できます。または、「Employees.xml」ファイルでシリアライズされた出力を見ることができます。

 しかし、この方法ではデータフローは一方向(フォームからオブジェクト)のみです。オブジェクトのプロパティの値が変更された場合、フォームには変更が反映されません。このことは[Load New Values]ボタンをクリックすると確認できます。このボタンのclickイベントハンドラによってオブジェクトの値が変更されますが、フォームの値は変更されません。このとき[Save]ボタンをクリックすると、オブジェクトはフォームに表示された値とは別の値で保存されることになります。

 データバインディングが一方向だけの機能であるならば、「なぜフォームに初期値が読み込まれたのか」と疑問に思うでしょう。答えはこうです。データバインディングが割り当てられると、データバインディング機能はプロパティの現在の値を参照しますが、ある方法を使わない限り、コントロールがオブジェクトから値を取得するのはこの1回限りになります。以降も継続的に値が取得されるようにするには、次の「上級レベルのデータバインディング」で説明する変更を加えます。

上級レベルのデータバインディング

 ここでは、データバインディングの学習の総仕上げとして、オブジェクトとフォームの間で双方向の通信を可能にし、どちらかを変更するともう一方も変更される方法を説明します。この方法は、オブジェクトとGUIコントロールの間で双方向の通信を行うので、ほとんどの場合で、この方法を使用することになるでしょう。前出の例で見たとおり、一方向(基本的にはフォームからオブジェクト)だけの通信を使用すると、オブジェクトがフォーム以外から変更されたときに矛盾が起こります。双方向の通信を行うためには、データバインディング機能に、オブジェクトのプロパティがいつ変更されたのかを知らせる必要があります。このソリューションのソースコードは、ダウンロードサンプルの「TwoWayDataBindingSolution」フォルダに収録されています。

 コントロールのプロパティとデータソースのプロパティとの間でバインディングを定義すると、データバインディング機能は自動的にデータソース上で「プロパティ名+Changed」という名前のイベントを探します。イベントが見つかると、データバインディング機能はこのイベントのハンドラを用意し、該当プロパティの値が変更されたときに通知を受け取るようにします。したがって、Employeeのプロパティに対して、プロパティ変更を検出するイベントを実装すれば、簡単に双方向のデータバインディングを有効にすることができます。

 図16のコードでは、必要なイベントを宣言するため、Employeeクラスの変数宣言部を変更しています。図17は、プロパティのセッターでイベントを発生させるコードを示しています。

図16
図16
図17
図17

 オブジェクトがフォームに変更を通知できるようになったので、[Save]ボタンのEnabledプロパティをオブジェクトのisDirtyプロパティに連結することができます。これで、オブジェクトのデータが変更されて保存が必要になったときにだけ、[Save]ボタンを有効にできます。それが図18のコードです。

図18
図18

まとめ

 .NET Frameworkのデータバインディングには、多くの機能があります。初期のMicrosoftのツールセットからはずいぶん進歩しました。この記事では、完全な手作業から、コントロールとカスタムオブジェクトのプロパティとの間の双方向通信まで、さまざまなレベルでのデータバインディングを取り上げました。また、.NET Frameworkのデータバインディング機能のイベントモデルを利用して、デフォルトの書式設定処理を変更する方法も示しました。



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

あなたにオススメ

著者プロフィール

  • Luther Stanton(Luther Stanton)

    Intellinet Corporation(本社:ジョージア州アトランタ)のプリンシパルコンサルタント。Intellinet社は、米国南東部では唯一、5種類のMicrosoft Gold Certified Partnerに認定されており、アプリケーションの開発およびインフラストラクチャのコンサル...

  • japan.internet.com(ジャパンインターネットコム)

    japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.com や EarthWeb.c...

バックナンバー

連載:japan.internet.com翻訳記事

もっと読む

All contents copyright © 2005-2021 Shoeisha Co., Ltd. All rights reserved. ver.1.5