Chapter 22

ステップ5-4: メインロジックの記述

Apterygiformes-zenn
Apterygiformes-zenn
2022.02.06に更新

通知メッセージの動作確認

追加ボタンと削除ボタンのClickイベントハンドラを用意します。

前回作成した通知画面の動作確認をします。
とりあえず追加ボタンを押したらメッセージが出るようにしてみます。
AddButton_Clickの中に以下を入力してください。

// 動作確認用
var w = new NotifyWindow();
w.Message.Text = "通知テストです";
w.Show();

実行してみます。

追加ボタンを押すとメッセージが表示されました。
マウスクリックするか、キーボードでESCスペースCキーのいずれかを押すとメッセージが閉じます。

プログラムを終了して先ほど入力した動作確認用のコードは削除してください。

通知表示処理作成

では本物の通知表示処理を書いていきます。

private void 通知表示(string timeStr)
{
    // 通知ウィンドウ生成
    var window = new NotifyWindow();
    // 表示するメッセージ設定
    window.Message.Text = $"{timeStr} になりました";

    // 通知ウィンドウ表示
    window.Show();

    // ミュートがOFFか
    if (Mute.IsChecked != true)
    {
        // システムサウンドを鳴らす
        System.Media.SystemSounds.Asterisk.Play();
    }
}

タイマー処理

タイマーを使い、定期的に「現在時刻」と「アラーム一覧の時刻」で一致するものがないかチェックします。
一致するものがあったら通知画面を表示し、アラーム一覧から該当時刻を削除します。
タイマーの使い方はステップ4を思い出しましょう。

MainWindowクラスに以下のタイマー変数を用意します。

/// <summary>
/// アラーム時刻チェック用タイマー
/// </summary>
private readonly DispatcherTimer _timer = new();
📘補足

初期化時に生成したインスタンスをプログラム終了までずっと使うため、readonlyにしています。
これで_timerに誤って別のDispatcherTimerインスタンスを代入してしまうことを防げます。

MainWindowのコンストラクタにタイマー初期化メソッド呼び出しを書きます。

タイマー初期化();

Ctrlキーを押しながら.でメソッド生成します。

タイマー初期化処理に以下を書きます。

タイマー初期化処理
// イベント発生間隔の設定
_timer.Interval = TimeSpan.FromMilliseconds(500);
// イベント登録
_timer.Tick += (_, _) =>
{
    // アラーム一覧の時刻を1つずつ確認していく
    foreach (var item in AlarmList.Items)
    {
        // 時刻を文字列で取得
        string timeStr = item?.ToString() ?? "";

        // 現在時刻と比較
        if (DateTime.Now.ToString("HH:mm") == timeStr)
        {
            // アラーム一覧から該当時刻を削除
            AlarmList.Items.Remove(item);

            通知表示(timeStr);

            break;
        }
    }

    if (AlarmList.Items.IsEmpty)
    {
        // アラーム一覧が空ならタイマー停止
        _timer.Stop();
    }
};

500ミリ秒ごとに以下の処理を行います。

  • 現在時刻とアラーム一覧の時刻で同じものがないか順番に見ていき、見つかったらその時刻はアラーム一覧から削除し、通知画面を表示します。
  • アラーム一覧が空になった場合はタイマーを動かしていても意味がないので止めます。
📘他の書き方
string timeStr = item?.ToString() ?? "";

は以下のようにも書けます。

書き方A
string timeStr = item != null ? item.ToString() : "";
書き方B
string timeStr;
if (item != null)
{
    timeStr = item.ToString();
}
else
{
    timeStr = "";
}

追加ボタンの処理

ボタンを押したときの処理

追加ボタンを押したときの処理に以下を書きます。

追加ボタンの処理
if (!入力時刻の検証(TimeInput.Text))
{
    // 入力内容不正なので処理打ち切り
    return;
}

// アラーム一覧に時刻を追加
AlarmList.Items.Add(TimeInput.Text);
// 時刻コンボボックスの入力値クリア
TimeInput.Text = "";

// タイマーを開始していなければ開始
if (!_timer.IsEnabled)
{
    _timer.Start();
}

コンボボックスに設定されている値を検証してOKならアラーム一覧に追加し、コンボボックス設定値をクリアします。
時刻を追加したときにタイマーがまだ開始していなければ開始します。

コンボボックス設定値を検証するための入力時刻の検証メソッドがまだ未定義なのでエラーになります。
Ctrl + .でメソッドを生成しましょう。

エラー表示共通処理

エラーメッセージを表示する共通処理を作ります。
プロジェクトを右クリックし、追加 > クラスを選択します。

MsgBoxクラスを作成します。

以下のメソッドを作成します。

public static void ShowErr(string msg)
{

}

中にMessageBoxと入力します。
エラーになったらCtrl + .でusingを追加します。

MessageBoxクラスのShowメソッドを呼び出します。
Show(msg, "エラー", MessageBoxButton.OK, MessageBoxImage.Error);

検証処理

入力時刻の検証メソッドに以下を書きます。

入力時刻の検証処理
#region ローカル関数定義

void エラー表示して入力項目にフォーカス設定(string msg)
{
    // エラーメッセージ表示
    MsgBox.ShowErr(msg);
    // 時刻入力にフォーカス設定
    TimeInput.Focus();
}

#endregion

// 入力されているか確認
if (string.IsNullOrEmpty(text))
{
    エラー表示して入力項目にフォーカス設定("追加する時刻を設定してください。");
    return false;
}

// 時刻として正しいか確認
if (!Regex.IsMatch(text, "^[0-9]{2}:[0-9]{2}$") ||
    !DateTime.TryParse(text, out _))
{
    エラー表示して入力項目にフォーカス設定("正しい時刻を設定してください。");
    return false;
}

// 重複確認
if (AlarmList.Items
    .Cast<string>()
    .Any(item => item == text))
{
    エラー表示して入力項目にフォーカス設定("追加済みの時刻です。");
    return false;
}

// チェックOK
return true;

検証処理はまず時刻が入力されているかチェックします。
入力がなかった場合、入力時刻の検証メソッドの最初で定義しているローカル関数を呼び出して終了させます。

https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/local-functions

次に時刻として正しい形式かチェックします。
if文の1つ目の条件は正規表現を使用して「0~9の数字2つ」で始まり、次に:が来て、「0~9の数字2つ」で終わるか調べています。
これだけだと99:99などがOKになってしまうのでDateTime.TryParseで時刻に変換できるか調べています。
変換できるか知りたいだけなので変換結果の時刻は_で捨てています。

最後に、追加しようとしている時刻がアラーム一覧にすでに存在しないかチェックします。
Anyメソッドはデータがあるかないかを調べるときに使います。
()内の条件を満たすとtrueを返します。

📘他の書き方

重複判定は以下のようにも書けます。

foreach (var item in AlarmList.Items)
{
    if (item.ToString() == text)
    {
        // 重複あり!
    }
}

削除ボタンの処理

削除ボタンを押したときの処理に以下を書きます。

削除ボタンの処理
// アラーム一覧で項目が選択されているか確認
if (AlarmList.SelectedIndex == -1)
{
    MsgBox.ShowErr("削除するアイテムを選択してください。");
}
else
{
    // 一覧から選択項目を削除
    AlarmList.Items.RemoveAt(AlarmList.SelectedIndex);

    // 一覧にまだ項目があるか確認
    if (AlarmList.Items.IsEmpty)
    {
        // 一覧が空なのでタイマー停止
        _timer.Stop();
    }
    else
    {
        // 一覧の先頭項目を選択
        AlarmList.SelectedIndex = 0;
    }
}

削除してアラーム一覧が空になった場合はタイマーを止めておきます。

これで完成です。

実行

実行してみます。

コンボボックスのドロップダウンを開くと時間の候補が表示されます。

12:00を選択しました。

追加ボタンを押すとアラーム一覧に12:00が追加され、コンボボックスの方はクリアされました。

コンボボックスに現在時刻を入力します。
入力したらEnterキーを押しても追加ボタンを押したことになります。

現在時刻なので即座にメッセージが表示されました。

アラーム一覧で時刻を選択し、

削除ボタンを押すことで、選択していた時刻が削除されます。

コンボボックスに不正な時刻を入力して追加しようとするとエラーメッセージが表示されます。

復習ポイント

  • 別ウィンドウの表示
  • コードからのリストボックスアイテム追加・削除