C#

【C#】DataGridViewにExcel風のフィルター機能を実装するメモ

C#のアプリで「Excelのようにフィルター機能を使いたいっ!」といった お話をされることがあり、「そんなのこっちがやり方を教えてほしいよっ!」となっていましたが、実装する必要が出てきたので 調査してみました。

Visual Studioのフォームアプリの機能としては公開されていませんが、Microsoft様のWebサイトに実装方法が載っていました。とりあえず調査した結果をメモしておきます。

フィルター機能について

フィルター機能の実装は、Microsoft様のWebサイトを見ながら、試していきます。

DataGridViewフィルターリスト表示

Webサイトの概要

以下のサイトに実装方法が記載されていました。詳しくは、こちらを参照して下さい。

DataGridView 列ヘッダー セルのDrop-Down フィルター リストの作成

上記サイトの概要としては、Microsoft Visual Studio の DataGridViewコントロールでは、Excelの列をフィルターする様な ドロップダウンのオートフィルター機能がありません。

なので、BindingSourceコンポーネントのフィルター機能を利用して、ドロップダウンのフィルター機能を持つ、カスタムDataGridView の作り方を教えます、といった感じです。

ただし、以下の注意書きがあるように、独自の実装を変更、またはガイドするために使用できる基礎が提供されています とのことです。注意が必要そうです。

  • 完全な機能を備えているわけではない。
  • バグがないわけではない。
  • Microsoftはサポートしていない。

フィルタ機能のついた、カスタム DataGridView のコードサンプルは、以下からダウンロードできます。

Download Building a Drop-Down Filter List for a DataGridView Column Header Cell Sample Code from Official Microsoft Download Center

コードサンプルについて

ダウンロードしたファイルを解凍してみると、「C#」と「VB」のプロジェクト、Webサイトと同じ内容の英語取説、テストデータが格納されています。

「CS」フォルダの中にある、「DataGridViewAutoFilter.sln」を起動して、プログラムを確認してみると、以下の3つのプロジェクトが入っています。

  • DataGridViewAutoFilter・・・カスタムDataGridViewのプロジェクト
  • DesignerSetupDemo・・・デモ その1
  • ProgrammaticSetupDemo・・・デモ その2

とりあえず「DesignerSetUpDemo」を起動してみます。

まず、Visual Studioの上部にある「スタートアップ プロジェクト」で「DesignerSetUpDemo」を選択して、デバッグを開始します。

Visual Studio 使用プロジェクト切替

DataGridViewにデータが表示された状態で、ウィンドウが表示されます。

そして今回の目的であるExcelのドロップダウンのフィルターが表示されています。すごいです。一番上の行にある下向き矢印の箱が、フィルタになります。

DataGridViewフィルター デモ1

実際にフィルターをかけてみます。カラムの脇にあるボタンを押すと、ドロップダウンリストが表示されます。

DataGridViewフィルター デモ2

表示されたリストから、1つ選択するとフィルターがかかったリストが表示されます。Excelのフィルターみたいに、簡単にデータを絞りこむことが出来ました。

DataGridViewフィルター デモ3

今のExcelは、フィルターの機能が充実しているため、全く同じとはいきませんが、ほぼ 同じようにデータの絞り込みが行えるのは魅力的です。

デモ プログラムについて

コードサンプルの「DesignerSetupDemo」で行っている処理を簡単に確認してみたいと思います。

プロジェクトの参照設定を確認すると、「DataGridViewAutoFilter」が参照設定されています。

Visual Studio 参照設定

実際のプログラム「DesignerSetupForm.cs」では、ファイルの先頭に「DataGridViewAutoFilter」の usingステートメントを追加しています。

using DataGridViewAutoFilter;

次に、フォームロードイベントで、DataGridViewに表示するデータの読み込みと、DataGridViewのカラム幅の自動リサイズを設定しているようです。

    private void DesignerSetupForm_Load(object sender, EventArgs e)
    {
        // Load the sample data and resize the columns based on their contents.
        this.newDataSet.ReadXml(@"..\..\..\..\..\TestData.xml");
        this.dataGridView1.AutoResizeColumns();
    }

基本的に、これだけで準備ができているみたいです。カスタムDataGridViewを使用するだけであれば、めちゃくちゃ簡単です。

その他の部分も使えそうな処理なので、確認しておきます。

まず、DataGridViewのキー押下イベントで、「Alt」+「↑」、または、「Alt」+「↓」が押された場合は、ドロップダウンリストを表示するように設定されています。

    private void dataGridView1_KeyDown(object sender, KeyEventArgs e)
    {
        if (e.Alt && (e.KeyCode == Keys.Down || e.KeyCode == Keys.Up))
        {
            DataGridViewAutoFilterColumnHeaderCell filterCell =
                dataGridView1.CurrentCell.OwningColumn.HeaderCell as
                DataGridViewAutoFilterColumnHeaderCell;
            if (filterCell != null)
            {
                filterCell.ShowDropDownList();
                e.Handled = true;
            }
        }
    }

次に、フィルター処理をして、データのバインドが完了した後に発生するイベントの設定をしています。

この処理で、アプリケーションのウィンドウ下部にあるステータスラベルに、フィルターの状態と、絞り込みを全て解除するリンクラベルの表示切替を行っているようです。

DataGridViewフィルター リンクラベル表示
        private void dataGridView1_DataBindingComplete(object sender,
            DataGridViewBindingCompleteEventArgs e)
        {
            String filterStatus = DataGridViewAutoFilterColumnHeaderCell
                .GetFilterStatus(dataGridView1);
            if (String.IsNullOrEmpty(filterStatus))
            {
                showAllLabel.Visible = false;
                filterStatusLabel.Visible = false;
            }
            else
            {
                showAllLabel.Visible = true;
                filterStatusLabel.Visible = true;
                filterStatusLabel.Text = filterStatus;
            }
        }

最後にフィルターの絞り込みを全て解除する処理をリンクラベルのクリック処理に設定しています。

    private void showAllLabel_Click(object sender, EventArgs e)
    {
        DataGridViewAutoFilterColumnHeaderCell.RemoveFilter(dataGridView1);
    }

データフィルター機能としては、十分な実装な気がします。しかも お手軽に実装出来てしまうので、すごいです。

プログラムの実装

実際にフィルター機能を使ってみたいと思います。

DataGridViewAutoFilterを使用してみる

1.「DataGridViewAutoFilter」を「Release」ビルドして、DLL(DataGridViewAutoFilter.dll)を作成します。

 DLLは、プロジェクトフォルダ配下に格納されています。

 格納フォルダパス\DataGridViewAutoFilter\DataGridViewAutoFilter\bin\Release

2.Windowsフォームアプリケーションのプロジェクトを作成して、「DataGridViewAutoFilter.dll」を参照設定します。

3.フォームにDataGridViewのコントロールを貼り付けます。

Visual Studio フォームのコントロール設定

4.下記プログラムを記載します。

using DataGridViewAutoFilter;
using System;
using System.Data;
using System.Windows.Forms;

namespace FilterTest
{
	public partial class Form1 : Form
	{
		public Form1()
		{
			InitializeComponent();
		}

		private void Form1_Load(object sender, EventArgs e)
		{
			// DataGridViewプロパティ値 変化時イベント処理
			dataGridView1.BindingContextChanged += new EventHandler(dataGridView1_BindingContextChanged);

			// データ設定
			SetDataTable();
		}

		/// <summary>
		/// カラム名
		/// </summary>
		private string[] aryColumn = new string[] { "品名", "値段", "購入場所", "調査日" };

		/// <summary>
		/// 設定データ
		/// </summary>
		private string[][] aryRow = new string[15][] {
			new string[]{"みかん", "150", "スーパー", "10月4日"},
			new string[]{"りんご", "200", "スーパー", "10月4日"},
			new string[]{"いちご", "500", "スーパー", "10月4日"},
			new string[]{"すいか", "1000", "スーパー", "10月4日"},
			new string[]{"なし", "200", "スーパー", "10月4日"},
			new string[]{"みかん", "100", "八百屋", "10月10日"},
			new string[]{"りんご", "150", "八百屋", "10月10日"},
			new string[]{"いちご", "400", "八百屋", "10月10日"},
			new string[]{"すいか", "800", "八百屋", "10月10日"},
			new string[]{"なし", "130", "八百屋", "10月10日"},
			new string[]{"みかん", "100", "道の駅", "10月11日"},
			new string[]{"りんご", "160", "道の駅", "10月11日"},
			new string[]{"いちご", "300", "道の駅", "10月11日"},
			new string[]{"すいか", "900", "道の駅", "10月11日"},
			new string[]{"なし", "140", "道の駅", "10月11日"},
		};

		/// <summary>
		/// データ設定
		/// </summary>
		private void SetDataTable()
		{
			// データテーブル
			var table = new DataTable();

			// カラム追加
			foreach (var array in aryColumn)
			{
				table.Columns.Add(array);
			}

			// データ追加
			foreach (var array in aryRow)
			{
				table.Rows.Add(array);
			}

			// データをバインドする
			var bindSrc = new BindingSource();
			bindSrc.DataSource = table;
			dataGridView1.DataSource = bindSrc;
		}

		/// <summary>
		/// DataGridViewプロパティ値 変化時イベント処理
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void dataGridView1_BindingContextChanged(object sender, EventArgs e)
		{
			if (dataGridView1.DataSource == null)
			{
				return;
			}

			// 各列にフィルタを設定する
			foreach (DataGridViewColumn col in dataGridView1.Columns)
			{
				col.HeaderCell = new
					DataGridViewAutoFilterColumnHeaderCell(col.HeaderCell);
			}
		}
	}
}

簡単にプログラムについて、説明を記載します。

まず、1行目で「DataGridViewAutoFilter」の usingステートメントを追加しています。

次に、フォームロードの18行目で、DataGridViewのプロパティ値が変化した時に発生するイベントを追加しています。その後、21行目で データ設定を行う関数を呼び出しています。

データ設定を行う「SetDataTable()」関数では、データテーブルを作成して、DataGridViewのデータソースに設定しています。

59~62行目で、データテーブルのカラムに表示する名称を設定しています。

65~68行目で、データテーブルに実際のデータを追加しています。

71~73行目で、作成したデータテーブルをDataGridViewのデータソースに設定しています。

最後に、81~94行目の「dataGridView1_BindingContextChanged()」関数で各列にオートフィルターの追加設定を行っています。

これは、デモ その2「ProgrammaticSetupDemo」プロジェクトに記述されている処理になります。DataGridViewの設定値が変更された場合に、オートフィルタの設定値を変更するのは、カラムの個数が変わった場合にも対応できるようになのでしょうか。

フィルターの追加として重要な部分は、以下の2カ所になります。

  • 71~73行目:DataGridViewのソースにデータ設定
  • 89~93行目:DataGridViewの各列カラムのヘッダーに、フィルタを設定

とりあえず、最低限のプログラムは以上になります。なかなか簡単にできました。

フィルター機能のテスト

それでは、実際に作成したプログラムをテストしてみたいと思います。

プログラムを起動すると、設定したデータがDataGridViewに表示され、オートフィルターのボタンがカラムに表示されているのが確認できました。よかった、よかった。

DataGridViewフィルター機能 実装後確認1

フィルタボタンを押すと、ドロップダウンリストが表示され、リストから項目を選択すると、ちゃんとフィルターがかかったデータが表示されました。

DataGridViewフィルター機能 実装後確認2

DataGridViewでExcel風のフィルター機能を実装することが出来ました。