ラベル BindingSource の投稿を表示しています。 すべての投稿を表示
ラベル BindingSource の投稿を表示しています。 すべての投稿を表示

2010年3月10日水曜日

[.Net ミニメモ] DataGridViewにBindingSourceをバインドしたときに表示がおかしいとき

DataGridViewにBindingSourceをバインドして、同期して表示や編集をしているときに、列やセルの選択の表示がおかしくなっている場合があります。
そのときには、バインドしているBindingSourceを操作しているスレッドがメインスレッドになっているか確認してみてください。通常のUI部品への操作と違って、例外が発生せずにそのまま走りますが、動きが変になるようです。

具体的には、InvokeRequiredでメインスレッドでないかを確認して、必要であればInvoke()を使ってメインのスレッドで動くようにしてください。ありがちなC#のコードは以下ようなものです。

private void exampleHandler(object sender, EventArg e)
{
    if (InvokeRequired) {
        object[] args = { sender, e };
        Invoke(new EventHandler(exampleHandler), args);
        return;
    }
    // 以下に処理したいコードを書く
}

2008年11月26日水曜日

[.NET]バインドされたDataGridViewに追加した項目を選択状態にする

先ほどの記事でDataGridViewとBindingSourceおよびDataTableの関係について書いたが、それを調べるための目的は、DataGridViewにバインドされたDataTableにプログラムで行を追加したときにその項目を選択させるためだったので、その方法を書いてみることにする。

実際にこのような動作をするプログラムを書かないと、状況がつかめないかもしれないが、DataGridViewは、バインドされたDataTableに新しく行を追加しても、選択位置は一番最初のままである。そのため、新しく追加された項目は、スクロールアウトされていると表示されない。しかも、DataGridViewは選択されている項目を表示させようとするため、さらに行が追加されると、選択されている行がスクロールアウトされていても選択行を表示させるためスクロールする。

すなわち、新しい行を表示させるためにはDataGridViewの選択行を変更しなければならない。ここで前提としては、DataGridViewの選択モードは、FullRowSelectになっているものとする。CellSelectでもうまくいくと思われるが試してはいない。

以下のコードはC++で書くことにするが、VC++2008 Express EditionでDataTableやDataSetをデータデザイナを使って定義する方法は、こちらの記事を参照されたい。

まず、データデザイナで定義をするとヘッダファイルにDataSetのサブクラスが作られて、その中にDataTableのサブクラスを定義するようなコードを生成するが、ここでは、"MyDataSet"や"MyDataTable"のように"My"+クラス名というものを定義したとする。

あと、DataTableの定義の前提として、主キーでAutoIncrementされる列(Column)を定義し、カラム名は"ID"とする。あと、定義されたDataSet(すなわち"MyDataSet")は、Formのメンバー変数として、「MyDataSet^ myDataSet;」と定義され、コンストラクタのどこかで、「myDataSet = gcnew MyDataSet();」のようにインスタンスを生成しており、また、フォームデザイナでは、BindingSourceを"myBindingSource"と定義したとする。

とりあえず、コードは以下の通り

//新しい行を生成する
MyDataSet::MyDataTableRow^ newRow = myDataSet->MyDataTable->NewMyDataTableRow();

//ここでnewRowのメンバー(カラム)にデータを代入する
//newRow-><カラム名> = …;
//カラム"ID"は追加時に設定されるので何もしなくてよい

//行をDataTableに追加する
myDataSet->MyDataTable->Rows->Add(newRow);
myDataSet->MyDataTable->AcceptChanges();

//追加した行のBindingSourceの位置を取得する
int position = myBindingSource->Find(L"ID", newRow->ID);

//BindingSourceの現在の位置を変更する
myBindingSource->Position = position;

ここでの最大のポイントは、BindingSourceの現在の位置はDataGridViewの選択行に一致するのだが、項目がソートされているとDataTableの行(Row)のインデックスとは一致しない。そのため、新しく追加した項目がBindingSourceでどの位置にあるかを調べてから設定するということである。

あと、このコードでの"newRow"はAdd();で"MyDataTable"に追加すると、"ID"にAutoIncrementされた値が代入される。そのため、この値を使って"myBindingSource"のFind();を呼び出せば、位置がわかるということになる。

[.NET]DataGridViewのデータについて

前記事でVC++ 2008 ExpressでDataGridViewにDataSetをバインドする方法について書いたが、その後DataGridViewを操作しようとしたところ、多少はまってしまった。ドキュメントを読んでわかったことだが、DataGridViewと表示・編集しようとしているデータとの関係について書いてみることにする。

DataGridViewに表示されるデータがどこにあるのか、という意味で3つのパターンがある。

普通のモード
普通という言い方が正しいかどうかはわからないが、単にDataGridViewのインスタンスを生成した場合の状態で、対象となるデータはDataGridViewの中に存在する。具体的には、DataGridView.RowsプロパティであるDataGridViewRowCollectionの中に行のデータが格納されている。
バインディングモード
これは、前記事で書いた、DataSetなどに存在するデータをDataGridViewに表示・編集する方法で、具体的には、DataGridView.DataSourceプロパティにデータの実体があるオブジェクトを設定する。VC++やVC#のIDEでデータデザイナとフォームデザイナを使った場合には、これらの設定はIDEによってコード化されるので、特にコードを書く必要はない。
仮想モード
このモードについては、MSDNなどのドキュメントにいろいろ書かれているので、詳細はそちらへ譲るが、基本的にはDataGridViewが表示・編集のためにデータの参照・設定が必要になった場合、イベントが発生する。そのイベントの処理でデータを受け渡しなどをする。この場合、データの実体はプログラマー任せとなる。

実際には、編集などではもっと複雑なデータのやりとりをするようだが、特殊なことをしない限り、あまり意識する必要はないもよう。この手のことは、DataGridViewのドキュメントの中に「共有モード」という記述があるので、それを参照されたい。

ここのでポイントは、バインディングや仮想モードになっているときには、DataGridView.Rowsプロパティにデータが存在しないということである。要するにこのプロパティを使ってデータを操作しているドキュメント上のサンプルなどは、普通のモードで動いているDataGridViewでの話であって、バインディングなどの場合には通用しない。

ところで、バインディングの場合、DataSourceは、基本的にはBindingSourceのインスタンスを設定する。このBindingSourceに実際のデータをBindingSource.DataSourceに設定する。このことは、ドキュメントに詳しく書かれているので詳細は割愛するが、BindingSource.DataSourceには、DataSetなどの複雑な構造のデータや、単純なオブジェクトのリスト(System.Collections.Generic.Listなど)を設定することができる。

DataGridViewで編集などをする場合には、行やセルの選択をいう操作が必要になるのだが、BindingSource.DataSourceに設定されるデータは、列挙可能(IListが実装されている)なものでしかないので、選択された位置という情報は持っていない。すなわち、BindingSourceが選択や項目の並び替え(ソート)を担っているということになる。

プログラムで項目を選択したい場合、普通のモードのDataGridViewときには、Rowsプロパティから行に対するデータを取得して、セルや行(Row)に対して選択を設定する。ところが、バインディングモードの場合、DataGridView.Rowsプロパティを使うのではなく、DataSourceプロパティに設定されたBindingSourceを操作する。具体的にはPositionプロパティやMoveFirstメソッドなどで現在の位置を変更する。

DataGridView.DataSourceにBindingSourceを設定している場合、プログラムでBindingSourceの現在の位置を変更すると、DataGridViewの表示上の選択位置が変更され、マウス操作などでDataGridViewの選択位置が変更されるとBindingSourceの現在の位置が変更される、という仕組みになっている。