HOME > ソフテックだより > 第353号(2020年5月6日発行) 技術レポート「DataGridViewコントロールのVirtualMode(仮想モード)について」

「ソフテックだより」では、ソフトウェア開発に関する情報や開発現場における社員の取り組みなどを定期的にお知らせしています。
さまざまなテーマを取り上げていますので、他のソフテックだよりも、ぜひご覧下さい。

ソフテックだより(発行日順)のページへ
ソフテックだより 技術レポート(技術分野別)のページへ
ソフテックだより 現場の声(シーン別)のページへ


ソフテックだより 第353号(2020年5月6日発行)
技術レポート

「DataGridViewコントロールのVirtualMode(仮想モード)について」

1. はじめに

私が最近携わったWindowsフォームアプリケーション開発でのお話になりますが、DataGridViewコントロール(表形式でデータの表示、編集を可能とするコントロール)を使用して大量のデータを処理する必要がありました。そのとき先輩社員から頂いたアドバイスでVirtualMode(仮想モード)を使用することになりました。今回のソフテックだよりでは、そのVirtualModeについてご紹介いたします。

2. VirtualMode(仮想モード)とは

VirtualModeはDataGridViewコントロールのモードで、プロパティから設定可能です。(設定方法については、後ほど説明します)
VirtualModeプロパティの説明をVisualStudio上で確認すると、「独自のデータ管理操作をDataGridViewコントロールに対して指定したかどうかを示します。」と表示されます。 この説明だけでは少々わかりにくいですが、「DataGridViewコントロールのVirtualModeプロパティをtrueにすることで、セルに表示するデータを設定する処理と、セルの値が変更された際にデータを格納する処理を自分で実装することができます。」と言い換えることができます。

メリットとして、VirtualModeは大規模なデータを使用するためにデザインされており、大量のデータの取り扱いにおいて、高速かつメモリ使用量の少ないコンパクトな処理にすることができます。
デメリットとして、セルに表示する値の設定や、セルへの入力値の処理などを自分で作成する必要があります。

3. VirtualMode使用時/未使用時の比較

以下の簡単なデータ(Data1〜5)を20万行表示するWindowsフォームアプリケーション(開発言語C#)において、VirtualModeを使用したDataGridViewコントロールと、使用していないもので、それぞれ表示にかかる時間と使用メモリを比較してみました。
計測環境とデータは以下の通りです。

■計測環境
CPU
Intel(R) Core(TM) i5-4570 CPU @ 3.20GHz 3.20GHz
メモリ
8.00GB
OS
WIndows 10 Pro(64bit)
■データ
Data1:
Int(1、2、3、…)
Data2:
string(あ、い、う、え、お、か、…)
Data3:
string(A、B、C、D、…)
Data4:
DateTime(2020/04/01 00:00、2020/04/01 00:01、…)
Data5:
byte(1、2、3、…)
■速度(20万行表示にかかる時間)
VirtualMode使用時 : 0.4626515秒

速度 VirtualMode使用時

VirtualMode未使用時 : 7.6341076秒

速度 VirtualMode未使用時

■メモリ使用量
VirtualMode使用時 : 30.3MB

メモリ使用量 VirtualMode使用時

VirtualMode未使用時 : 170.1MB

メモリ使用量 VirtualMode未使用時

上記例では、VirtualModeで実装することで高速かつ省メモリで動作するようになったことが分かります。

4. VirtualModeの実装方法

以下に開発言語C#でDataGridViewコントロールにVirtualModeを実装する方法について、簡単に説明します。

(1)プロパティを設定
VirtualModeはDataGridViewコントロールのVirtualModeプロパティをtrueに設定することで有効化します。

プロパティ

(2) 必要なイベントの実装
VirtualModeを有効にする場合、いくつかのイベントを適切に実装する必要があります。

■CellValueNeededイベント
CellValueNeededはセルを書式設定して表示するためにDataGridViewコントロールがセルの値を必要とする場合に発生するイベントです。このイベントによって、セルにデータを表示します。

実装例
以下に比較に使用したアプリケーションの例を示します。この例では、1行分のデータを持つ簡単なクラスのリスト(DataList)から値を表示し、ユーザーによる行の追加や削除を考慮していません。


       private void VirtualModeDataGridView_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
        {
            if(e.RowIndex > DataList.Count - 1)
            {
                return;
            }

            switch(e.ColumnIndex)
            {
                case 0:
                    e.Value = DataList[e.RowIndex].Data1;
                    break;
                case 1:
                    e.Value = DataList[e.RowIndex].Data2;
                    break;
                case 2:
                    e.Value = DataList[e.RowIndex].Data3;
                    break;
                case 3:
                    e.Value = DataList[e.RowIndex].Data4;
                    break;
                case 4:
                    e.Value = DataList[e.RowIndex].Data5;
                    break;

                default:
                    e.Value = null;
                    break;
            }
        }


■CellValuePushedイベント
CellValuePushedはセルの値が変更された場合に発生するイベントです。
これにより、DataGridViewコントロールに表示しているデータの実体に変更を反映します。

実装例
以下に比較に使用したアプリケーションの例を示します。この例では、1行分のデータを持つ簡単なクラスのリスト(DataList)の値を変更しています。CellValueNeededイベントハンドラと同様に、ユーザーによる行の追加や削除を考慮していません。


        private void VirtualModeDataGridView_CellValuePushed(object sender, DataGridViewCellValueEventArgs e)
        {
            switch (e.ColumnIndex)
            {
                case 0:
                    int nVal;
                    if (int.TryParse(e.Value.ToString(),out nVal))
                    {
                        DataList[e.RowIndex].Data1 = nVal;
                    }
                    break;
                case 1:
                    DataList[e.RowIndex].Data2  = e.Value.ToString();
                    break;
                case 2:
                    DataList[e.RowIndex].Data3 = e.Value.ToString();
                    break;
                case 3:
                    DateTime DateVal;
                    if (DateTime.TryParse(e.Value.ToString(), out DateVal))
                    {                        
                        DataList[e.RowIndex].Data4 = DateVal;
                    }
                    break;
                case 4:
                    Byte bVal;
                    if (Byte.TryParse(e.Value.ToString(), out bVal))
                    {
                        DataList[e.RowIndex].Data5 = bVal;
                    }
                    break;

                default:
                    break;
            }
        }


■NewRowNeededイベント
NewRowNeededはユーザーがDataGridViewコントロールの新しい行に移動したときに発生するイベントです。
ユーザーに行の追加を許可する場合に実装が必要となります。

注意点として、ユーザーが新しい行に移動するたび発生するため、このイベントでDataGridViewコントロールに表示しているデータのListに新しい行用のデータをAddすると、ユーザーが新しい行に移動⇒編集せずにほかの行に移動などの操作をした場合に余計なデータを追加してしまいます。
そのため、ユーザーに行の追加を許可する場合は、編集中の行のデータを全体のデータと別に持ち、RowValidatedイベントで編集を反映するタイミングで編集を反映、追加するなどの工夫が必要です。
これについては、Microsoftから公開されている、VirtualMode実装のチュートリアル(※1)で紹介されています。

■RowDirtyStateNeeded/CancelRowEditイベント
RowDirtyStateNeededは現在の行がコミットされていない変更を含んでいるかどうかを決定しなければならないときに発生するイベントです。
具体的には、セルの選択が別の行に移った時や、行ヘッダーにカーソルが移動した時などです。
CancelRowEditは行の編集をキャンセルした場合(セルの編集中にESCキーを押下等)に発生するイベントです。
これらは、編集をキャンセルする場合に特別な動作をする必要がある場合に実装が必要となります。たとえば、NewRowNeeded実装のため編集中の行のデータを全体のデータと別に持っている場合、編集をキャンセルした場合に編集中の行のデータを破棄する処理を実装することになります。
実装しない場合でも、セル編集中にESCキーを押下すればそのセルの編集はキャンセルされます。

5. まとめ

今回のソフテックだよりでは、DataGridViewコントロールのVirtualMode(仮想モード)について簡単にご紹介させていただきました。VirtualModeを使用することによって実装が必要なイベントは増えますが、高速化や省メモリといったメリットがあることを理解いただけたと思います。DataGridViewコントロールで大量のデータを表示する必要がある場合には選択肢となるかと思います。
今回ご紹介させていただいた内容が、皆様のWindowsフォームアプリケーション開発の手助けになれば幸いです。

(A.F.)

[参照]
※1
チュートリアル : Windows フォーム DataGridView コントロールでの仮想モードの実装

関連ページへのリンク

関連するソフテックだより

ページTOPへ