前回はCSVファイルから社員データを読み込み、DataGridViewに一覧表示する基本機能を実装しました。
今回は、ユーザが目的の情報をすばやく見つけられるように、データの検索/フィルタリング機能を実装していきます。
- CSVファイルの読み込み(←前回作った)
- データの一覧表示(←前回作った)
- データ検索/フィルタリング(←今回作る)
- CSVファイルへの保存
- Excel出力
以下について学びます。
- LINQとDataGridViewを活用してデータ検索と表示
- ユーザ入力をリアルタイムに検索へ反映
- DateTimePickerの使い方
次のような方に役立つ内容となっています。
- 表形式データを動的に検索・絞り込みたい方
- BindingSourceやLINQを利用したフィルタ処理の実装方法に興味がある初心者の方
前回、以下のようにCSVファイルを読み込み表示する機能を作りましたね。

今回は以下の2つの機能を作ります。
- 検索ボックスでキーワード入力して社員名・部署名でフィルタリングして表示
(演習1:基本の部分一致検索) - 入社日の範囲も指定可能にし、複数条件でフィルタリングして表示
(演習2:複数条件での絞り込み)
業務アプリでは、ユーザが膨大なデータから目的の情報を探し出すことが求められますね。
今回の実装で、基本の部分一致検索に加え、複数条件での柔軟なフィルタ機能を実装する方法を学んでいきましょう!
演習のコード一式はGitHubで公開しています。
解説動画は以下です。
演習1:基本の部分一致検索
演習1では前回演習のコードをベースとして、次の機能を作ってみましょう。
- 画面上部の検索ボックスに入力された文字列が、「氏名」または「部署」に部分一致する社員データのみをDataGridViewに表示
実装のポイントは以下です。
- DataGridViewのBindingSourceとLINQを利用して、社員リストから条件に合致するデータのみを抽出
- ユーザが入力するたびにリアルタイムで結果を更新
LINQはC#のデータ操作機能で、コレクションに対して簡潔なクエリ構文を使って検索・フィルタリング・並べ替えなどできます。LINQの基本は以下を参考にしてください。

手順1で「コントロール配置とプロパティ設定」、手順2で「コード作成」と進めていきましょう。
WinFormsのいつもの開発の流れだね!
手順1:コントロール配置とプロパティ設定
以下を配置してプロパティの設定をしましょう。
- 検索ボックス(TextBox)
- Name:searchBox
手順2:コード作成
以下のイベントを作成します。
- searchBox
- TextChanged:searchBox_TextChanged
MainForm.csのコードを以下のように修正します。
using EmployeeManager.Models;
using EmployeeManager.Services;
using System.Windows.Forms;
namespace EmployeeManager.Forms
{
public partial class MainForm : Form
{
private readonly CsvService _csvService;
//★(a1)読み込んだ社員リストを保持しておくためのフィールド
private List<Employee> _employees = new List<Employee>();
public MainForm()
{
InitializeComponent();
_csvService = new CsvService();
}
private void loadCsvButton_Click(object sender, EventArgs e)
{
using (OpenFileDialog dialog = new OpenFileDialog())
{
dialog.Filter = "CSVファイル|*.csv";
if (dialog.ShowDialog() == DialogResult.OK)
{
try
{
//★(a2)読み込んだ社員リストをフィールドで保持しておく
_employees = _csvService.ReadCsv(dialog.FileName);
employeeDataGridView.DataSource = _employees;
MessageBox.Show("CSVファイルを読み込みました。", "成功",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show($"エラーが発生しました:{ex.Message}", "エラー",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
private void searchBox_TextChanged(object sender, EventArgs e)
{//(b)検索ボックスで変更があったときに呼び出される
ApplyFilter();
}
private void ApplyFilter()
{
// 検索キーワード(小文字・前後の空白はトリム)
string searchText = searchBox.Text.ToLower().Trim();
//★(c)LINQで、検索キーワードが氏名または部署に含まれているかチェック
var filteredList = _employees.Where(emp =>
emp.Name.ToLower().Contains(searchText) ||
emp.Department.ToLower().Contains(searchText)
).ToList();
//★(d)データソースにフィルタリングしたリストをセット
employeeDataGridView.DataSource = filteredList;
}
}
}
コードの要点を解説します。
(a)では、フィールド_employees
に読み込み時のオリジナルの社員リストを保持しておき、これに対して様々なフィルタリングを行います。
(b)では、ユーザが検索ボックスに文字を入力したり削除したりするたびに、イベントハンドラが自動的にApplyFilter()
メソッドを呼び出して検索を実行します。
(c)では、入力された検索キーワードが社員の氏名または部署名に含まれているかをLINQで検索し、条件に合う社員だけを抽出します。
(d) フィルタリングした結果をDataGridViewのデータソースに設定することで、画面に表示する社員リストを更新しています。
アプリを実行
アプリを実行すると、以下のようになります。

検索ボックスへ入力したキーワードで、リアルタイムにフィルタリングが行われるね!
演習2:複数条件での絞り込み
引き続き、以下の機能を実装しましょう。
- 演習1の基本フィルタに加え、入社日の範囲を指定する日付フィルタを追加
具体的には、画面に「開始日」「終了日」を選択できるDateTimePickerを配置し、指定した期間内に入社した社員のみを表示するようにします。
「キーワード一致」と「入社日の範囲」という複数の条件でフィルタリングをできるようにするんだね。
手順1:コントロール配置とプロパティ設定
以下を配置してプロパティの設定をしましょう。
- 日付範囲の始点(DateTimePicker)
- Name:fromDatePicker
- 日付範囲の終点(DateTimePicker)
- Name:toDatePicker
DateTimePickerは視覚的に日付等を選択できる便利なコントロールです。
配置すると以下のような構成になります。

手順2:コード作成
以下のイベントを作成します。
- fromDatePicker
- ValueChanged:fromDatePicker_ValueChanged
- toDatePicker
- ValueChanged:toDatePicker_ValueChanged
MainForm.csを以下のように修正します。
…
private void fromDatePicker_ValueChanged(object sender, EventArgs e)
{
//★(a)日付範囲の始点が変更されたときにフィルタを適用する
ApplyFilter();
}
private void toDatePicker_ValueChanged(object sender, EventArgs e)
{
//★(b)日付範囲の終点が変更されたときにフィルタを適用する
ApplyFilter();
}
private void ApplyFilter()
{
string searchText = searchBox.Text.ToLower().Trim();
//★(c)日付フィルタ:開始日と終了日(DateTimePickerコントロールから取得)
DateTime fromDate = fromDatePicker.Value.Date;
DateTime toDate = toDatePicker.Value.Date;
//★(d)LINQで複数条件を適用
var filteredList = _employees.Where(emp =>
// 検索キーワードが空の場合はこの条件を無視、入力がある場合は氏名または部署に部分一致
(emp.Name.ToLower().Contains(searchText) ||
emp.Department.ToLower().Contains(searchText))
&&
// 入社日が指定範囲内にあるかチェック
(emp.JoinDate.Date >= fromDate &&
emp.JoinDate.Date <= toDate)
).ToList();
employeeDataGridView.DataSource = filteredList;
}
…
変更部分の要点を説明します。
(a) fromDatePicker_ValueChanged の追加: DateTimePickerで開始日が変更された時に自動的にフィルタ処理を実行し、データを更新します。
(b) toDatePicker_ValueChanged の追加: DateTimePickerで終了日が変更された時に自動的にフィルタ処理を実行し、データを更新します。
(c) 日付フィルタの変数追加: DateTimePickerから選択された開始日と終了日の値を取得し、日付でフィルタリングするための準備をします。
(d) LINQ条件式の追加: 検索テキストによる氏名・部署の部分一致検索と、入社日の日付範囲チェックを組み合わせて、条件に合う社員データだけを抽出します。
アプリを実行
アプリを実行してみましょう。
以下のように、キーワードと日付範囲でフィルタリングができるはずです。

これで複数の条件を組み合わせた絞り込みができるようになったね!
これで基本ができたね!
他の様々な条件へ対応させるなどして機能を拡張してみると勉強になるかと思います!
演習3:イベントハンドラの共通化
最後の演習として、少しコードを整理しましょう。
演習2で作成したコードですが、以下の部分が冗長ですね。
…
private void searchBox_TextChanged(object sender, EventArgs e)
{
ApplyFilter();
}
private void fromDatePicker_ValueChanged(object sender, EventArgs e)
{
//★(a)日付範囲の始点が変更されたときにフィルタを適用する
ApplyFilter();
}
private void toDatePicker_ValueChanged(object sender, EventArgs e)
{
//★(b)日付範囲の終点が変更されたときにフィルタを適用する
ApplyFilter();
}
…
イベントハンドラは複数のコントロールで共用して使うこともできます。
以下の共通イベントハンドラを1つ用意してみましょう。
private void searchCondition_Changed(object sender, EventArgs e)
{
ApplyFilter();
}
そして、各コントロールのイベントを以下のように設定し直しましょう。
- searchBox
- TextChanged:searchCondition_Changed
- fromDatePicker
- ValueChanged:searchCondition_Changed
- toDatePicker
- ValueChanged:searchCondition_Changed
プロパティウィンドウのイベントタブで、以下のように「searchCondition_Changed」を選んでそれぞれ設定します。

全て設定が終わったらもともとあったsearchBox_TextChanged、formDatePicker_ValueChanged、toDatePicker_ValueChangedを削除しましょう。
これでコードが共通化されて整理されました。(アプリの動作は変わっていません)
開発を続けていくと、だんだんとコードが汚くなりがちですね。時々、見直して修正(リファクタリング)するとよいです。
C#におけるコードの共通化の基本的な考え方は以下の記事も参考にしてください。

講義:イベント削除時のエラー対策
イベントを削除したらデザイナでエラーが!
コントロールに設定しているイベントのコードを削除すると、「コードが見つからないよ!」と、このエラーがでます。
このようなときは、あわてず、「エラー一覧」を確認してデザイナのコードを修正しましょう。
以下のように「データが失われる可能性を防ぐため、デザイナーの読み込み前に以下のエラーを解決する必要があります。」とデザイナでエラー画面が表示されます。

エラーをクリックしてエラー位置へジャンプすると、searchBox_TextChangedが存在しないのでエラーになっていることがわかります。

例えば、「searchBox.TextChanged += searchBox_TextChanged;」の行を削除すればエラーが解消します。
このエラーはWinFormsアプリ開発をしていると必ず一度は見るのではないでしょうか。(通過儀礼のようなものですかね。。。)
上述したように落ち着いて対処すれば大丈夫です。
まとめ
本記事では、WinFormsアプリでの検索/フィルタリング機能の実装について学びました。
主な実装内容は以下の通りです:
- 基本の部分一致検索では、TextBoxとLINQを組み合わせて、社員名や部署名でのリアルタイム検索機能を実装しました。
- 複数条件でのフィルタリングでは、DateTimePickerを活用して日付範囲の指定機能を追加し、テキスト検索と組み合わせた複合的な検索を可能にしました。
- イベントハンドラの共通化では、複数のコントロールで重複していたイベントハンドラを1つにまとめ、コードをよりシンプルに整理する方法を学びました。
また、WinFormsアプリ開発でよく遭遇するイベントハンドラ削除時のデザイナエラーへの対処方法についても理解を深めました。
この実装により、ユーザーは大量のデータの中から必要な情報を素早く見つけ出すことが可能になりました。
今後は、CSVファイルへの保存機能やExcel出力機能の実装へと進んでいきます。
引き続き、一緒にC# WinFormsアプリ開発を学んでいきましょう!