Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9297

【C#】DataGridViewの活用方法メモ【バインド】【.NET Framework 2.0対応】

$
0
0

C#のWindows フォーム アプリケーションにはDataGridViewという便利なコントロールがあります。
下のように、行と列からなる表を表現するコントロールです。
2020-12-10.png

実行環境

本記事で紹介する実行画面は次の環境のものです。

OS
Windows 10 Home バージョン 20H2
Visual Studio バージョン
Visual Studio 2017
.NET Framework バージョン
.NET Framework 2.0

基本

プロパティのおすすめ設定

DataGridViewのプロパティのおすすめ設定は次のとおりです。
2020-12-10 (1).png
2020-12-10 (2).png
2020-12-10 (3).png

セルの指定方法

DataGridViewのセルの指定方法はいくつかあります。

  • RowsプロパティとそのCellsプロパティを使用する方法
cell1.cs
//dgvListはDataGridViewコントロールthis.dgvList.Rows[0].Cells[0].Value
  • DataGridViewのインデクサを使用する方法
cell2.cs
//dgvListはDataGridViewコントロールthis.dgvList[0,0].Value

よく使うプロパティ

DataGridView関連でよく使用するプロパティの例を挙げます。

cellproperty.cs
//dgvListはDataGridViewコントロール//セルの値を取得したり変更したりするthis.dgvList[0,0].Value//セルの文字色を取得したり変更したりするthis.dgvList[0,0].Style.ForeColor//セルの背景色を取得したり変更したりするthis.dgvList[0,0].Style.BackColor//セルのエラーアイコンとメッセージを取得したり変更したりするthis.dgvList[0,0].ErrorText//セルのフォントを取得したり変更したりするthis.dgvList[0,0].Style.Font//行と紐付いているバインドオブジェクトを取得します。this.dgvList.Rows[0].DataBoundItem

DataGridViewのバインド

DataGridViewには、「バインド」という機能があります。
この機能は、オブジェクトのデータを簡単にコントロールに反映する便利な機能です。
バインドを行うには、DataGridViewに次のように指定します。

Bind1-1.cs
//dgvListはDataGridViewコントロール//カスタムクラスのバインドリストををバインドする場合、AutoGenerateColumnsプロパティをfalseにしないと勝手に列が追加されてしまうthis.dgvList.AutoGenerateColumns=false;this.dgvList.DataSource=[バインドするオブジェクト];

バインドの準備

DataGridViewにオブジェクトをバインドするにはDataTableなどいくつか方法がありますが、今回はカスタムクラスを使用した方法をご紹介します。

  • SortableBindingListクラスを用意する

System.Windows.Forms名前空間にはBindingSourceというクラスがありますが、そのままだとソートを行うことができません。そのため、次のリンクにあるSortableBindingListクラスを作成してください。

https://garafu.blogspot.com/2016/09/cs-sorablebindinglist.html

  • カスタムクラスの基底クラスを用意する

カスタムクラスを使用するには、共通で使用できる基底クラスがあると便利です。
コードの例を次に挙げます。

Bind2-1.cs
usingSystem;usingSystem.Collections.Generic;usingSystem.Text;usingSystem.ComponentModel;namespaceDataGridViewBindingSample.Datas.DataGridView.Rows{/// <summary>///     DataGridViewのカスタムクラスの基底クラスです。/// </summary>publicclassDataGridViewDataClassBase:INotifyPropertyChanged{/// <summary>///     プロパティ変更イベント/// </summary>publiceventPropertyChangedEventHandlerPropertyChanged;/// <summary>///     プロパティ変更イベントを発生させます。/// </summary>/// <param name="name"></param>publicvoidOnPropertyChanged(stringname){if(this.PropertyChanged!=null){this.PropertyChanged(this,newPropertyChangedEventArgs(name));}}/// <summary>///     プロパティ変更イベントを発生させながらプロパティに関連するフィールドに値をセットします。/// </summary>/// <typeparam name="T">データ型</typeparam>/// <param name="field">フィールド変数</param>/// <param name="value">セットする値</param>/// <param name="propName">プロパティ名(イベントの発生に必要)</param>publicvoidSetValue<T>(outTfield,Tvalue,stringpropName){field=value;this.OnPropertyChanged(propName);}/// <summary>///     入力操作をした場合プロパティ変更イベントを発生させながらプロパティに関連するフィールドを整数に変換して値をセットします。/// </summary>/// <typeparam name="T">データ型</typeparam>/// <param name="field">フィールド変数</param>/// <param name="value">セットする値</param>/// <param name="propName">プロパティ名(イベントの発生に必要)</param>publicvoidSetValueInputInt(refint?field,stringvalue,stringpropName){intintValue;if(int.TryParse(value,outintValue)){//整数を入力した場合field=intValue;}elseif(value==null||value.Trim().Length==0){//何も入力しない場合field=null;}else{//文字や記号を含めて入力した場合//何もしない}this.OnPropertyChanged(propName);}}}

実は、カスタムクラスといっても、プロパティに値をセットしてもすぐにはDataGridViewに反映されません。
そのため、DataGridViewに通知をする仕組みが必要です。
その仕組みが、INotifyPropertyChangedインターフェイス・PropertyChangedイベント・OnPropertyChangedメソッドです。
PropertyChangedイベントをプロパティ名を指定して実行すれば、DataGridViewに通知され、表示が反映されるという仕組みになっています。

カスタムクラスの実装

先述で紹介したカスタムクラスの基底クラスを継承して実装します。
各プロパティがDataGridViewの列の1つに紐付けされます。

Bind3-1.cs
usingSystem;usingSystem.Collections.Generic;usingSystem.Text;namespaceDataGridViewBindingSample.Datas.DataGridView.Rows.Sample{/// <summary>///     カスタムクラスサンプル1/// </summary>publicclassDgvRowSample1:DataGridViewDataClassBase{privatestringstrName;privateint?intKyoka1Point;privateint?intKyoka2Point;privateint?intKyoka3Point;privateint?intKyoka4Point;privateint?intKyoka5Point;/// <summary>///     名前/// </summary>publicstringName{get{returnthis.strName;}set{this.SetValue(outthis.strName,value,"Name");}}/// <summary>///     国語の点数/// </summary>publicint?Kyoka1Point{get{returnthis.intKyoka1Point;}set{this.SetValue(outthis.intKyoka1Point,value,"Kyoka1Point");}}/// <summary>///     数学の点数/// </summary>publicint?Kyoka2Point{get{returnthis.intKyoka2Point;}set{this.SetValue(outthis.intKyoka2Point,value,"Kyoka2Point");}}/// <summary>///     理科の点数/// </summary>publicint?Kyoka3Point{get{returnthis.intKyoka3Point;}set{this.SetValue(outthis.intKyoka3Point,value,"Kyoka3Point");}}/// <summary>///     社会の点数/// </summary>publicint?Kyoka4Point{get{returnthis.intKyoka4Point;}set{this.SetValue(outthis.intKyoka4Point,value,"Kyoka4Point");}}/// <summary>///     英語の点数/// </summary>publicint?Kyoka5Point{get{returnthis.intKyoka5Point;}set{this.SetValue(outthis.intKyoka5Point,value,"Kyoka5Point");}}/// <summary>///     国語の点数(表示・入力用)/// </summary>publicstringKyoka1PointDisplay{get{returnthis.intKyoka1Point.ToString();}set{this.SetValueInputInt(refthis.intKyoka1Point,value,"Kyoka1PointDisplay");}}/// <summary>///     数学の点数(表示・入力用)/// </summary>publicstringKyoka2PointDisplay{get{returnthis.intKyoka2Point.ToString();}set{this.SetValueInputInt(refthis.intKyoka2Point,value,"Kyoka2PointDisplay");}}/// <summary>///     理科の点数(表示・入力用)/// </summary>publicstringKyoka3PointDisplay{get{returnthis.intKyoka3Point.ToString();}set{this.SetValueInputInt(refthis.intKyoka3Point,value,"Kyoka3PointDisplay");}}/// <summary>///     社会の点数(表示・入力用)/// </summary>publicstringKyoka4PointDisplay{get{returnthis.intKyoka4Point.ToString();}set{this.SetValueInputInt(refthis.intKyoka4Point,value,"Kyoka4PointDisplay");}}/// <summary>///     英語の点数(表示・入力用)/// </summary>publicstringKyoka5PointDisplay{get{returnthis.intKyoka5Point.ToString();}set{this.SetValueInputInt(refthis.intKyoka5Point,value,"Kyoka5PointDisplay");}}}}

列とカスタムクラスのプロパティの紐付け

カスタムクラスをバインドしても、列にどのプロパティを適用するかわからないため、そのままでは表示することができません。
そのため、バインドするプロパティを設定する必要があります。
2020-12-10 (4).png
デザイナ画面のDataGridViewの右上の三角をクリックして、「DataGridView タスク」を開き、「列の編集…」を開きます。
2020-12-10 (5).png
DataPropertyNameプロパティに表示対象のプロパティ名を設定し、OKを押します。

バインドリスト・カスタムクラスの基本操作

先ほど紹介したSortableBindingListListクラスとほぼ同じように(一部使えないメソッドもありますが)追加・挿入・削除・クリア操作ができます。

コードの例を次に挙げます。

Bind4-1.cs
//※sbListはSortableBindingList//DataGridViewの行を追加するthis.sbList.Add(【カスタムクラス】);//DataGridViewの1行目に行を挿入するthis.sbList.Insert(1,【カスタムクラス】);//DataGridViewの1行目を削除するthis.sbList.RemoveAt(1);//DataGridViewの行をすべてクリアするthis.sbList.Clear();//DataGridViewのセルの値は基本的に次のように参照することができます。this.dgvList[0,0].Value//ですが、カスタムクラスを使用すると、次のようにも参照することができます。//※rowBoundはカスタムクラスrowBound.Name

応用

入力可能列の制御

整数を入力する場合、単純にカスタムクラスのプロパティの型を整数にしてしまうと、文字列にしたときにエラーが発生してしまいます。
それを防ぐためのコードの例を挙げます。

Bind5-1.cs
/// <summary>///     入力操作をした場合プロパティ変更イベントを発生させながらプロパティに関連するフィールドを整数に変換して値をセットします。/// </summary>/// <typeparam name="T">データ型</typeparam>/// <param name="field">フィールド変数</param>/// <param name="value">セットする値</param>/// <param name="propName">プロパティ名(イベントの発生に必要)</param>publicvoidSetValueInputInt(refint?field,stringvalue,stringpropName){intintValue;if(int.TryParse(value,outintValue)){//整数を入力した場合field=intValue;}elseif(value==null||value.Trim().Length==0){//何も入力しない場合field=null;}else{//文字や記号を含めて入力した場合//何もしない}this.OnPropertyChanged(propName);}

カスタムクラスの基底クラスの例にある上のコードは派生クラスのプロパティのsetで呼び出します。

Bind5-2.cs
/// <summary>///     国語の点数(表示・入力用)/// </summary>publicstringKyoka1PointDisplay{get{returnthis.intKyoka1Point.ToString();}set{this.SetValueInputInt(refthis.intKyoka1Point,value,"Kyoka1PointDisplay");}}

実はDataGridViewでデータを入力すると、プロパティのsetが呼び出されます。
その性質を利用して、文字列で入力したものを整数に変換できるか確かめて、変換出来たら整数として格納し、空文字列の場合はnullを格納し(null許容型でフィールド変数を作成)、文字が入力されていたら格納しないようにします。
また、そのままではデータの表示が反映されないため、OnPropertyChangedメソッドを呼び出してPropertyChangedイベントを発生させます。

文字色・背景色の制御

2020-12-10 (6).png

DataGridViewのカスタムクラスでは直接色を紐付けすることはできません。
ただし、工夫次第でカスタムクラスで色をコントロールすることができます。

背景色を変更するコードの例を挙げます。

Bind6-2.cs
/// <summary>///     DataGridViewセルフォーマット時/// </summary>/// <param name="sender"></param>/// <param name="e"></param>privatevoiddgvList_CellFormatting(objectsender,DataGridViewCellFormattingEventArgse){DataGridViewdgv=senderasDataGridView;if(dgv==null)return;if(e.RowIndex<0||e.ColumnIndex<0||e.RowIndex>=dgv.Rows.Count||e.ColumnIndex>=dgv.Columns.Count)return;DataGridViewCellcell=dgv[e.ColumnIndex,e.RowIndex];if(cell==null)return;DataGridViewRowrow=cell.OwningRow;if(row==null)return;DataGridViewColumncol=cell.OwningColumn;if(col==null)return;DgvRowSample1rowBound=row.DataBoundItemasDgvRowSample1;if(rowBound==null)return;if(e.ColumnIndex==this.colName.Index){//名前if(rowBound.Name==null||rowBound.Name.Trim().Length==0){//未入力cell.Style.BackColor=Color.LightPink;}else{//入力有cell.Style.BackColor=Color.White;}}elseif(e.ColumnIndex==this.colKyoka1.Index){//国語の点数if(rowBound.Kyoka1Point!=null&&rowBound.Kyoka1Point.Value<30){//赤点cell.Style.BackColor=Color.LightPink;}else{//合格点cell.Style.BackColor=Color.White;}}elseif(e.ColumnIndex==this.colKyoka2.Index){//数学の点数if(rowBound.Kyoka2Point!=null&&rowBound.Kyoka2Point.Value<30){//赤点cell.Style.BackColor=Color.LightPink;}else{//合格点cell.Style.BackColor=Color.White;}}elseif(e.ColumnIndex==this.colKyoka3.Index){//理科の点数if(rowBound.Kyoka3Point!=null&&rowBound.Kyoka3Point.Value<30){//赤点cell.Style.BackColor=Color.LightPink;}else{//合格点cell.Style.BackColor=Color.White;}}elseif(e.ColumnIndex==this.colKyoka4.Index){//社会の点数if(rowBound.Kyoka4Point!=null&&rowBound.Kyoka4Point.Value<30){//赤点cell.Style.BackColor=Color.LightPink;}else{//合格点cell.Style.BackColor=Color.White;}}elseif(e.ColumnIndex==this.colKyoka5.Index){//英語の点数if(rowBound.Kyoka5Point!=null&&rowBound.Kyoka5Point.Value<30){//赤点cell.Style.BackColor=Color.LightPink;}else{//合格点cell.Style.BackColor=Color.White;}}}

DataGridViewにはCellFormattingというイベントがあります。
そのイベントを使用して、上のようにプロパティを設定すると背景色を変更できます。
文字色を変更する場合は、ForeColorプロパティを使用します。

エラーアイコン・メッセージの制御

2020-12-10 (7).png

少しトリッキーな方法ですが、Timerクラスを利用する方法があります。
一定間隔でTickイベントを発生させて、エラーチェックをし、エラーの場合はエラーアイコンとメッセージを表示させます。

コードの例を次に挙げます。

Bind7-1.cs
/// <summary>///     表示更新用タイマー/// </summary>/// <param name="sender"></param>/// <param name="e"></param>privatevoidtmUpdateDisplay_Tick(objectsender,EventArgse){foreach(DataGridViewRowrowinthis.dgvList.Rows){DgvRowSample1rowBound=row.DataBoundItemasDgvRowSample1;if(rowBound==null)continue;foreach(DataGridViewColumncolinthis.dgvList.Columns){DataGridViewCellcell=this.dgvList[col.Index,row.Index];if(col.Index==this.colName.Index){//名前if(rowBound.Name==null||rowBound.Name.Trim().Length==0){//未入力cell.ErrorText="名前が未入力です。";}else{//入力有cell.ErrorText="";}}elseif(col.Index==this.colKyoka1.Index){//国語の点数if(rowBound.Kyoka1Point!=null&&rowBound.Kyoka1Point.Value<30){//赤点cell.ErrorText="国語が赤点です。";}else{//合格点cell.ErrorText="";}}elseif(col.Index==this.colKyoka2.Index){if(rowBound.Kyoka2Point!=null&&rowBound.Kyoka2Point.Value<30){//赤点cell.ErrorText="数学が赤点です。";}else{//合格点cell.ErrorText="";}}elseif(col.Index==this.colKyoka3.Index){//理科の点数if(rowBound.Kyoka3Point!=null&&rowBound.Kyoka3Point.Value<30){//赤点cell.ErrorText="理科が赤点です。";}else{//合格点cell.ErrorText="";}}elseif(col.Index==this.colKyoka4.Index){//社会の点数if(rowBound.Kyoka4Point!=null&&rowBound.Kyoka4Point.Value<30){//赤点cell.ErrorText="社会が赤点です。";}else{//合格点cell.ErrorText="";}}elseif(col.Index==this.colKyoka5.Index){//英語の点数if(rowBound.Kyoka5Point!=null&&rowBound.Kyoka5Point.Value<30){//赤点cell.ErrorText="英語が赤点です。";}else{//合格点cell.ErrorText="";}}}}}

ただし、重い処理やデータベースにアクセスするような処理はTimerイベントに記述することは望ましくないでしょう。

カスタムクラスの階層化

2020-12-10 (8).png

カスタムクラスを使用すればリストを選択すると子リストも変わる処理を簡単に実装することができます。

まず最初に親リストのカスタムクラスを作成します。

Bind8-1.cs
usingSystem;usingSystem.Collections.Generic;usingSystem.Text;namespaceDataGridViewBindingSample.Datas.DataGridView.Rows.Sample{/// <summary>///     カスタムクラスサンプル2-1/// </summary>publicclassDgvRowSample21:DataGridViewDataClassBase{privatestringstrName;privateSortableBindingList<DgvRowSample22>sbChild=newSortableBindingList<DgvRowSample22>();/// <summary>///     名前/// </summary>publicstringName{get{returnthis.strName;}set{this.SetValue(outthis.strName,value,"Name");}}/// <summary>///     教科の点数リスト(子バインドリスト)/// </summary>publicSortableBindingList<DgvRowSample22>ChildList{get{returnthis.sbChild;}set{this.sbChild=value;}}}}

注目すべき点は、親リストのカスタムクラスにバインドリスト(SortableBindingList)がある点です。
このバインドリストの使い方は後述で述べます。

次に、子リストのカスタムクラスを作成します。

Bind8-2.cs
usingSystem;usingSystem.Collections.Generic;usingSystem.Text;namespaceDataGridViewBindingSample.Datas.DataGridView.Rows.Sample{/// <summary>///     カスタムクラスサンプル2-2/// </summary>publicclassDgvRowSample22:DataGridViewDataClassBase{privatestringstrKyoka;privateint?intPoint;/// <summary>///     教科名/// </summary>publicstringKyoka{get{returnthis.strKyoka;}set{this.SetValue(outthis.strKyoka,value,"Kyoka");}}/// <summary>///     点数/// </summary>publicint?Point{get{returnthis.intPoint;}set{this.SetValue(outthis.intPoint,value,"Point");}}/// <summary>///     点数/// </summary>publicstringPointDisplay{get{returnthis.intPoint.ToString();}set{this.SetValueInputInt(refthis.intPoint,value,"PointDisplay");}}}}

フォーム上では、最初にDataGridViewに次のようにプロパティに設定します。

Bind8-3.cs
//親リストにデータソースを追加したとき勝手に列が追加されないようにするthis.dgvList.AutoGenerateColumns=false;//子リストにデータソースを追加したとき勝手に列が追加されないようにするthis.dgvListChild.AutoGenerateColumns=false;//親リストのデータソースを設定するthis.dgvList.DataSource=this.sbList;

DataGridViewにはセルの選択変更時に発生するSelectionChangedというイベントがあります。
親リストのセルを選択変更したときにこのバインドリストを子リストのデータソースに設定します。

Bind8-4.cs
/// <summary>///     DataGridView選択変更時/// </summary>/// <param name="sender"></param>/// <param name="e"></param>privatevoiddgvList_SelectionChanged(objectsender,EventArgse){this.dgvListChild.DataSource=null;DataGridViewdgv=senderasDataGridView;if(dgv==null)return;if(dgv.SelectedCells.Count==0)return;DataGridViewCellcell=dgv.SelectedCells[0];if(cell==null)return;DataGridViewRowrow=cell.OwningRow;if(row==null)return;DataGridViewColumncol=cell.OwningColumn;if(col==null)return;DgvRowSample21rowBound=row.DataBoundItemasDgvRowSample21;if(rowBound==null)return;//子リストのデータソースをセットする。this.dgvListChild.DataSource=rowBound.ChildList;}

最後に

DataGridViewのカスタムクラスは「クラス」なので、いろいろな処理を実装することができます。

メソッドを追加して計算処理をしたり、文字列の加工処理をしたり、型の変換処理をすることもできます。
また、入力に不備がないかチェックするメソッドを実装することもできます。

本記事では色を挙げましたが、画像(Imageクラスなど)などいろいろ格納することができます。

バインドにカスタムクラスを使用すれば、実装の可能性が広がります。

いろいろ試行錯誤をして、DataGridViewを使用した実装をしてみてはいかがでしょうか?

最後までお読みいただきありがとうございました。


Viewing all articles
Browse latest Browse all 9297

Trending Articles