VB6開発者向け:C#で始める.NETプログラミング

第8回オブジェクト指向プログラミングの省力化

はじめに

前回は、オブジェクト指向プログラミングによってサンプルソフトウェアを作成しました。 このサンプルを作成するために、各テーブルのフィールドに応じたクラスや、データにアクセスさせるためのクラスを作成しました。 これによって、オブジェクト指向プログラミングがどのようなものなのかを体感していただけたかと思います。

しかし、実際に業務で使用するフィールドやテーブルの数だけ、同様のクラスを作ることは困難です。

そこで今回はDataSetやComponentという方法を用いて、前回開発したソフトウェアの開発の省力化についてをご説明したいと思います。

DataSetとは?

DataSetは、VB6で提示したサンプルソフトウェアで使用したRecordSetオブジェクトと似ています。 データレコードをDataSetオブジェクトの中のDataTableオブジェクトに格納して、編集した後にデータテーブルに書き戻すという一連の流れを容易に実現できます。

Componentとは?

再利用性の高いオブジェクトを部品化したもの、と表現するのが理解しやすいかと思います。

例えば、TextBoxなどのユーザーコントロールは、ユーザーインターフェイスを持ったコンポーネントです。 一方でタイマーコントロールのようなユーザーインターフェイスを持たないコンポーネントも存在します。 しかし、どちらのコンポーネントもコンテナに配置できます。

作成するにあたっての注意

今回、コンポーネントを多用しますが、個々のコンポーネントを作成した後にビルドを成功させないとツールボックスにコンポーネントが出現しません。

そこで、今回の内容を実践する場合は、必ず新しいプロジェクトを用意してください。 以前に使ったプロジェクトを利用した場合、変更の仮定でエラーが発生する恐れがあります。

DataSetの作成

新規作成

DataSetを新規作成する手順は次の通りです。

画像

最初に、プロジェクト名を選択後に右クリックしてメニューを表示します。 ここから「追加」⁠新しい項目」と順にクリックしてください。

画像

「新しい項目の追加」ダイアログボックスが表示されたら、データセットを選択します。 今回も名前は「DataSet1」のままでよいでしょう。 ⁠追加]ボタンを押すことでDataSetが生成されます。

サーバーエクスプローラーにデータベースを接続

DataSetを編集してテーブルを追加します。

画像

DataSetの編集画面に表示されているメッセージの「サーバーエクスプローラー」のリンク部分をクリックして、サーバーエクスプローラーを開きます。

画像

データ接続を選択後に右クリックしてメニューを表示します。 ここから「接続の追加」をクリックします。

画像

「接続の追加」ダイアログボックスが表示されたら、データベースファイル名を指定します。 ⁠テスト接続]ボタンを押して問題なく接続できることを確認後に[OK]ボタンをクリックしてください。

データテーブルを編集

DataSetにデータテーブルをマッピングします。

画像

サーバーエクスプローラーの画面からMyTableをドラッグして、DataSet1のデザイナ上でドロップします。

画像

これによって、データ本体を格納するMyTableオブジェクトと、データベースにアクセスするためのMyTableAdapterオブジェクトが作成されます。

この状態で、一旦ビルドします。 ビルドに成功することで、フォームエディタ画面が表示されている状態でツールボックスを開いた際に、先ほど作成したオブジェクトがコンポーネントとして追加さます。

Logicの作成

次にLogicコンポーネントを作成します。

新規作成

ソリューションエクスプローラーから、DataSetを登録した方法と同様に、Logicというコンポーネントを追加してください。

画像

上記のように、DataSet1、MyTableTableAdapter、BindingSourceコンポーネントをLogicコンポーネントのコンテナに配置します。 各コンポーネントオブジェクトの名前も、上記と同様になっていることを確認してください。

BindingSource1の設定

BindingSourceは、DataSet1のMyTableオブジェクトとフォームオブジェクト上のユーザーコントロールとのデータバインディングを行う際に便利なコンポーネントです。

画像

上記のように、DataSourceにdataSet1を、DataMemberにMyTableを設定します。 これによって、dataSet1.MyTableオブジェクトはBindingSourceオブジェクトにバインドされたことになります。

BindingSourceオブジェクトとユーザーコントロールのバインディングの方法は、次項でご説明します。

コードの作成

Logic クラスのコードは次のようになります。 なお、今回はあえて、Fillメソッドによって全レコードをDataSetオブジェクトに格納してみたいと思います。


using System;
using System.Data;
using System.ComponentModel;
namespace WindowsFormsApplication1
{
    //*************************************************************
    /// <summary>電話帳サンプル</summary>
    //*************************************************************
    public partial class Logic : 
        Component, INotifyPropertyChanged
    {
        //*************************************************************
        /// <summary>コンストラクタ</summary>
        //*************************************************************
        public Logic() {
            this.InitializeComponent();
        }

        public Logic(IContainer container) {
            if (container != null) container.Add(this);
            this.InitializeComponent();
        }

        //*************************************************************
        /// <summary>PropertyChanged イベント</summary>
        //*************************************************************
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName) {
            if (this.PropertyChanged == null) return;
            this.PropertyChanged(
                this, new PropertyChangedEventArgs(propertyName));
        }       

        //*************************************************************
        /// <summary>Code プロパティ</summary>
        //*************************************************************
        private int _Code;
        public int Code {
            get { return this._Code; }
            set {
                if (this._Code == value) return;
                this._Code = value;
                this.OnPropertyChanged("Code");
            }
        }

        //*************************************************************
        ///<summary>BindingSource プロパティ</summary>
        //*************************************************************
        public System.Windows.Forms.BindingSource BindingSource {
            get { return this.bindingSource1; }
        }

        //*************************************************************
        /// <summary>DataSet オブジェクトにテーブルデータを格納します
        /// </summary>
        //*************************************************************
        public void Fill() {
            this.myTableTableAdapter.Fill(this.dataSet1.MyTable);
            this.SetCode();
        }

        //*************************************************************
        /// <summary>追加処理を実行します</summary>
        //*************************************************************
        public void Add() {
            this.InvalidCode();
            if (this.bindingSource1.Find("番号", this.Code) >= 0) {
                throw new Exception("既に登録されている番号です");
            }

            object newRow = this.bindingSource1.AddNew();
            DataSet1.MyTableRow row = this.GetMyTableRow(newRow);
            if (row == null) throw new Exception("追加に失敗しました");
            row.番号 = this.Code;

            this.bindingSource1.EndEdit();
            myTableTableAdapter.Update(this.dataSet1.MyTable);

            this.Read();
        }

        //*************************************************************
        /// <summary>更新処理を実行します</summary>
        //*************************************************************
        public void Update() {
            this.InvalidCode();
            this.bindingSource1.EndEdit();
            this.myTableTableAdapter.Update(this.dataSet1.MyTable);
        }

        //*************************************************************
        /// <summary>削除処理を実行します</summary>
        //*************************************************************
        public void Delete() {
            this.InvalidCode();
            this.bindingSource1.RemoveCurrent();
            this.bindingSource1.EndEdit();
            this.myTableTableAdapter.Update(this.dataSet1.MyTable);
            this.SetCode();
        }

        //*************************************************************
        /// <summary>読込み処理を実行します</summary>
        //*************************************************************
        public void Read() {
            int i = this.bindingSource1.Find("番号", this.Code);
            if (i < 0) throw new Exception("番号が見つかりません");
            this.bindingSource1.Position = i;
            this.SetCode();
        }

        //*************************************************************
        /// <summary>番号値が有効かどうかを検証した結果を取得します
        /// </summary>
        //*************************************************************
        private void InvalidCode() {
            if (this.Code > 0 || this.Code < 100000000) return;
            throw new Exception("番号が有効な値ではありません");
        }

        //*************************************************************
        /// <summary>現在行のコード値をテキストボックスに設定します
        /// </summary>
        /// <remarks>現在行が null ならゼロに設定します</remarks>
        //*************************************************************
        private void SetCode() {
            DataSet1.MyTableRow row = 
                this.GetMyTableRow(this.bindingSource1.Current);
            if (row == null) {
                this.Code = 0;
                return;
            }
            this.Code = row.番号;
        }

        //*************************************************************
        /// <summary>DataSet1.MyTableRow 型に変換した結果を取得します
        /// </summary>
        /// <param name="value">変換したい値</param>
        /// <returns>DataSet1.MyTableRow 型に変換した結果</returns>
        /// <remarks>変換できない場合は null 値を返します</remarks>
        //*************************************************************
        private DataSet1.MyTableRow GetMyTableRow(object value) {
            DataRowView v = value as DataRowView;
            if (v == null) return null;

            return v.Row as DataSet1.MyTableRow;
        }
    }
}

中で使用するオブジェクトが異なるため、前回作成したLogicクラスとは少々違っています。

クラスの継承について

public partial class Logic : Component, INotifyPropertyChanged

基本クラスがComponentになっています。 C#では継承できるクラスは1つだけになるので、前回のようにNotifyPropertyChangedクラスを自作して実装を書いたものを利用することはできません。 ただし、インターフェイスは複数継承できます。

そこで、INotifyPropertyChangedインターフェイスを継承してNotifyPropertyChangedクラスの実装をLogicクラスに持たせています。

コンストラクタ

今回のコンストラクタは、引数のないコンストラクタと、IContainer型引数を受け取るコンストラクタの2つ存在します。 これらのコンストラクタは自動生成されたものです。

public Logic() {
    InitializeComponent();
}

public Logic(IContainer container) {
    container.Add(this);
    InitializeComponent();
}

C#では、コンストラクタに限らず、引数の型や数が異なる場合に同じメソッド名を複数持つことができます。 これをOverloadと呼びます。

BindingSourceプロパティ

VB6では、フォームオブジェクトに配置されたコントロールへ外部からアクセスすることができました。 しかしC#では、カプセル化という意味から、必要に応じて公開するという意味で非公開になっています。

public System.Windows.Forms.BindingSource BindingSource {
    get { return this.bindingSource1; }
}

BindingSourceはフォーム上のユーザーコントロールとバインドさせる必要があるため、このコントロールのオブジェクトをプロパティとして公開します。

DataSetのデータを充填する

フォームが開いたタイミングで、DataSetにデータを充填できるようにFillメソッドを作成して公開します。

public void Fill() {
    this.myTableTableAdapter.Fill(this.dataSet1.MyTable);
    this.SetCode();
}

this.myTableTableAdapter.Fillによって、全レコードがthis.dataSet1.MyTableオブジェクトに充填されます。 また、読みこまれたレコードは複数あるため、現在どのレコードを指しているのかに応じてCodeプロパティの値をセットするためのメソッドを呼び出します。

追加処理

bindingSource1のFindメソッドによって、番号列の値がthis.Codeプロパティの値を探して、その要素番号を取得しています。 bindingSource1オブジェクトはdataSet1.MyTableオブジェクトとバインドされているため、Fillメソッドによって充填したデータを参照できます。

要素番号が0以上なら、既に存在している値を追加しようとしていることになるため、例外エラーを発生させます。

public void Add() {
    this.InvalidCode();
    if (this.bindingSource1.Find("番号", this.Code) >= 0) {
        throw new Exception("既に登録されている番号です");
    }

    object newRow = this.bindingSource1.AddNew();
    DataSet1.MyTableRow row = this.GetMyTableRow(newRow);
    if (row == null) throw new Exception("追加に失敗しました");
    row.番号 = this.Code;

    this.bindingSource1.EndEdit();
    myTableTableAdapter.Update(this.dataSet1.MyTable);

    this.Read();
}

その後、新しい行をbindingSource1.AddNewメソッドから作成します。 作成された行オブジェクトはobject型なので、これをGetMyTableRowメソッドによってDataSet1.MyTableRow型に変換します。

最後に、bindingSource1のEndEditメソッドで編集を終了させて、dataSet1.MyTableオブジェクトの内容をデータベースに反映させます。

読み込み

this.bindingSource1.Position = i;

Positionプロパティに要素番号値を格納することで、現在指しているレコードを変更できます。

Codeプロパティに値をセットする

bindingSource1オブジェクトのCurrentプロパティは、現在指しているレコードオブジェクトを格納しています。

DataSet1.MyTableRow row = 
    this.GetMyTableRow(this.bindingSource1.Current);
if (row == null) {
    this.Code = 0;
    return;
}
this.Code = row.番号;

このレコードの番号の値をCodeプロパティにセットしています。

型の変換

以下は、指定されたオブジェクト型の値をDataSet1.MyTableRow型に変換した結果を返すためのメソッドです。 変換できない場合はnull値を返します。

private DataSet1.MyTableRow GetMyTableRow(object value) {
    DataRowView v = value as DataRowView;
    if (v == null) return null;

    return v.Row as DataSet1.MyTableRow;
}

C#はタイプセーフな言語です。

objectはすべての型から継承されている型なので、すべての変数はobject型に格納できます。

一方で、一度object型に格納したオブジェクトを元の型に代入するような場合には、キャスト(型変換)が必要です。

ただし、object型には何でも代入できてしまうため、確実にキャストできる保証はありません。 キャストに失敗すると、実行時に例外エラーが発生してしまいます。

DataRowView v = value as DataRowView;

上記のコードはas演算子を使って、valueをDataRowView型にキャストした結果をDataRowView型のvという変数に格納します。 この時、キャストに失敗した場合、vにはnullが格納されます。

Form1の作成

Form1は、プロジェクトを作成した際に自動的に作られています。

Logicコンポーネントの配置

Form1のフォームエディタを開いて、TextBoxやButtonコントロールを配置する方法と同様に、LogicコンポーネントをツールボックスからForm1に配置します。 配置は、Form1のイメージ上ではなく、エディタの下部に表示されます。

なお、Logicコンポーネントをツールボックスに存在させるためには、Logicクラスを作成した後に、一度ビルドを成功させる必要があります。

コードの作成

コードは以下の通りです。

using System;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
    //*************************************************************
    /// <summary>電話帳サンプルソフトフォーム</summary>
    //*************************************************************
    public partial class Form1 : Form
    {
        //*************************************************************
        ///<summary>コンストラクタ</summary>
        //*************************************************************
        public Form1() {
            this.InitializeComponent();
            this.codeTextBox.DataBindings.Add(
                "Text", this.logic, "Code");
            this.nameTextBox.DataBindings.Add(
                "Text", this.logic.BindingSource, "名前");
            this.telephoneNumberTextBox.DataBindings.Add(
                "Text", this.logic.BindingSource, "電話番号");
        }

        //*************************************************************
        // イベント処理用メソッド
        //*************************************************************
        private void addButton_Click(object sender, EventArgs e) {
            try {
                this.logic.Add();
            }
            catch(Exception ex) {
                MessageBox.Show(ex.Message);
            }
        }

        private void closeButton_Click(object sender, EventArgs e) {
            this.Close();
        }

        private void Form1_Load(object sender, EventArgs e) {
            this.logic.Fill();
        }

        private void readButton_Click(object sender, EventArgs e) {
            try {
                this.logic.Read();
            }
            catch(Exception ex) {
                MessageBox.Show(ex.Message);
            }
        }

        private void updateButton_Click(object sender, EventArgs e) {
            try {
                this.logic.Update();
            }
            catch(Exception ex) {
                MessageBox.Show(ex.Message);
            }
        }

        private void deleteButton_Click(object sender, EventArgs e) {
            try {
                this.logic.Delete();
            }
            catch(Exception ex) {
                MessageBox.Show(ex.Message);
            }
        }
    }
}

基本的には、以前作成したForm1と同様ですが、logicフィールド変数はフォームエディタ側でコンポーネントとして設定されているためコードで記述していません。

コンストラクタ

今回も、各TextBoxへのバインディングをコンストラクタで指定しています。

this.nameTextBox.DataBindings.Add(
    "Text", this.logic.BindingSource, "名前");
this.telephoneNumberTextBox.DataBindings.Add(
    "Text", this.logic.BindingSource, "電話番号");

バインディングするオブジェクトは、RowではなくBindingSourceになります。

フォームロードイベント時の処理

以下のコードによって、フォームロードイベントによって、DataSetにデータが充填されます。

private void Form1_Load(object sender, EventArgs e) {
    this.logic.Fill();
}

最後に

今回は、DataSetを使う方法をご紹介しました。 ここまで理解できれば、実際にC#を使って仕事が行えるだけの下準備は十分にできていると言えるでしょう。

本連載は、今回が最後になります。

VB6で実際に活躍されているプログラマの方に向けた内容ということもあり、基本的な入門書という形に捕らわれることなく、サンプルコードを多用した説明をさせていただくことで、多くの内容を盛り込むことができました。

本連載を読んでくださった多くの方がC#によるプログラミングを楽んでいただけることを願っています。

最後に、本連載を読んでくださった皆さん、連載の機会を与えてくださった技術評論社と担当の方々を始めとする多くの皆さんに心からお礼を申し上げます。

ありがとうございました。

おすすめ記事

記事・ニュース一覧