WinForms

【C#/WinForms実践入門編(6)】社員管理アプリを作る ~ユーザ入力でリアルタイムキーワード検索~【LINQ応用】

前回はCSVファイルから社員データを読み込み、DataGridViewに一覧表示する基本機能を実装しました。

今回は、ユーザが目的の情報をすばやく見つけられるように、データの検索/フィルタリング機能を実装していきます。

  1. CSVファイルの読み込み(←前回作った)
  2. データの一覧表示(←前回作った)
  3. データ検索/フィルタリング(←今回作る)
  4. CSVファイルへの保存
  5. Excel出力

以下について学びます。

  • LINQとDataGridViewを活用してデータ検索と表示
  • ユーザ入力をリアルタイムに検索へ反映
  • DateTimePickerの使い方

次のような方に役立つ内容となっています。

  • 表形式データを動的に検索・絞り込みたい方
  • BindingSourceやLINQを利用したフィルタ処理の実装方法に興味がある初心者の方

前回、以下のようにCSVファイルを読み込み表示する機能を作りましたね。

今回は以下の2つの機能を作ります。

  • 検索ボックスでキーワード入力して社員名・部署名でフィルタリングして表示
    演習1:基本の部分一致検索
  • 入社日の範囲も指定可能にし、複数条件でフィルタリングして表示
    演習2:複数条件での絞り込み
プロ太

業務アプリでは、ユーザが膨大なデータから目的の情報を探し出すことが求められますね。

今回の実装で、基本の部分一致検索に加え、複数条件での柔軟なフィルタ機能を実装する方法を学んでいきましょう!

演習のコード一式はGitHubで公開しています。

解説動画は以下です。

演習1:基本の部分一致検索

演習1では前回演習のコードをベースとして、次の機能を作ってみましょう。

  • 画面上部の検索ボックスに入力された文字列が、「氏名」または「部署」に部分一致する社員データのみをDataGridViewに表示

実装のポイントは以下です。

  • DataGridViewのBindingSourceLINQを利用して、社員リストから条件に合致するデータのみを抽出
  • ユーザが入力するたびにリアルタイムで結果を更新

LINQはC#のデータ操作機能で、コレクションに対して簡潔なクエリ構文を使って検索・フィルタリング・並べ替えなどできます。LINQの基本は以下を参考にしてください。

C#入門編(16)LINQ ~データ操作を効率的に行う~ 今回は「LINQ (Language Integrated Query)」について解説します。LINQは、C#でデータの操作や検索を効...

手順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#におけるコードの共通化の基本的な考え方は以下の記事も参考にしてください。

C#入門編(7)クラス、メソッドによるコードの部品化 ~オブジェクト指向の土台を学ぶ~ 今回は、コードを部品化する演習を行います。 汚いコードを題材として用意して、そのコードを修正して綺麗にしながら、部品化について学...

講義:イベント削除時のエラー対策

プロ美

イベントを削除したらデザイナでエラーが!

プロ太

コントロールに設定しているイベントのコードを削除すると、「コードが見つからないよ!」と、このエラーがでます。

このようなときは、あわてず、「エラー一覧」を確認してデザイナのコードを修正しましょう。

以下のように「データが失われる可能性を防ぐため、デザイナーの読み込み前に以下のエラーを解決する必要があります。」とデザイナでエラー画面が表示されます。

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

例えば、「searchBox.TextChanged += searchBox_TextChanged;」の行を削除すればエラーが解消します。

プロ太

このエラーはWinFormsアプリ開発をしていると必ず一度は見るのではないでしょうか。(通過儀礼のようなものですかね。。。)

上述したように落ち着いて対処すれば大丈夫です。

まとめ

本記事では、WinFormsアプリでの検索/フィルタリング機能の実装について学びました。

主な実装内容は以下の通りです:

  • 基本の部分一致検索では、TextBoxとLINQを組み合わせて、社員名や部署名でのリアルタイム検索機能を実装しました。
  • 複数条件でのフィルタリングでは、DateTimePickerを活用して日付範囲の指定機能を追加し、テキスト検索と組み合わせた複合的な検索を可能にしました。
  • イベントハンドラの共通化では、複数のコントロールで重複していたイベントハンドラを1つにまとめ、コードをよりシンプルに整理する方法を学びました。

また、WinFormsアプリ開発でよく遭遇するイベントハンドラ削除時のデザイナエラーへの対処方法についても理解を深めました。

この実装により、ユーザーは大量のデータの中から必要な情報を素早く見つけ出すことが可能になりました。

今後は、CSVファイルへの保存機能やExcel出力機能の実装へと進んでいきます。

プロ太

引き続き、一緒にC# WinFormsアプリ開発を学んでいきましょう!

ABOUT ME
プロ太
●仕事:現在は個人事業主(メンター・情報発信等)、大手IT企業で技術者・マネージャ(15年以上)、大学の外部講師、学生時代は学習塾で非常勤講師(約4年間) ●博士(工学)の学位取得 ●高校生の頃に独学で始め、プログラミング歴20年以上 ●言語:C# 、Java、C/C++、Python、JavaScript/TypeScript等 ●分野:Webアプリ、テスト自動化、生成AI、デバッガ、コード解析、ドメイン特化言語

ご依頼・ご相談について

プログラミング学習のご相談、お仕事のご依頼については、
こちらのお問い合わせページをご確認ください。