Chapter 23

ステップ5-5: もうひと手間

Apterygiformes-zenn
Apterygiformes-zenn
2022.02.24に更新

概要

毎日決まった時間に通知を出したい場合、アプリを起動するたびに時刻を登録するのは面倒です。
アラーム一覧をファイル保存できるようにし、アプリ起動時にファイルから時刻を読み込むようにしてみましょう。

画面作成

デザイナーの画面左側のStackPanelにツールボックスからButtonをドラッグ&ドロップします。
画面のStackPanelを選択し、ツールボックスのButtonをダブルクリックしても配置できます。

Buttonの名前をSaveButtonContentアラーム一覧を保存(_S)に設定します。

ボタン幅いっぱいにテキストが埋まっていて見づらいので少し余裕を持たせます。
ボタンの既定スタイル定義にPaddingプロパティ、値10,0を追加します。
Paddingって何だっけ?という人はステップ3を読み返しましょう。

↓↓↓↓↓

コンボボックスのHorizontalAlignmentLeftに設定します。

初期フォーカス設定

ついでにアプリ起動時の初期フォーカスを時刻コンボボックス(TimeInput)に設定しておきます。
WindowFocusManager.FocusedElement="{Binding ElementName=TimeInput}"を追加してください。

ロジック作成

アラームのファイルパス関連

App.xaml.csAppクラスの中に以下の処理を書きます。

App.xaml.cs
private const string AlarmInitFileName = "時刻初期データ.txt";

/// <summary>
/// 時刻初期データファイルパス
/// </summary>
public static string AlarmInitFilePath
{
    get
    {
        // アプリケーションのパス取得
        string? appPath = AppContext.BaseDirectory;
        if (appPath is null)
        {
            throw new DirectoryNotFoundException("実行ファイルのパス取得失敗");
        }

        // パスとファイル名を結合して返す
        return Path.Combine(appPath, AlarmInitFileName);
    }
}

アラーム一覧保存処理

SaveButton_Clickメソッドに以下を書きます。

try
{
    SaveButton.IsEnabled = false;
    アラーム一覧保存(App.AlarmInitFilePath);
}
catch (Exception ex)
{
    MsgBox.ShowErr(ex.ToString());
}
finally
{
    SaveButton.IsEnabled = true;
}

アラーム一覧保存メソッドに以下を書きます。

if (AlarmList.Items.IsEmpty)
{
    MsgBox.ShowErr("保存する時刻がありません。");
    return;
}

using var writer = new StreamWriter(alarmInitFilePath, append: false, encoding: Encoding.UTF8);

foreach (var item in AlarmList.Items)
{
    writer.WriteLine(item);
}

MsgBox.ShowInfo($"{App.AlarmInitFilePath}を保存しました。");
📘他の書き方

using var writer = ....の部分は以下のようにも書けます。

書き方A
using (var writer = new StreamWriter(alarmInitFilePath, append: false, encoding: Encoding.UTF8))
{
    foreach (var item in AlarmList.Items)
    {
        writer.WriteLine(item);
    }
}
書き方B
var writer = new StreamWriter(alarmInitFilePath, append: false, encoding: Encoding.UTF8);
try
{
    foreach (var item in AlarmList.Items)
    {
        writer.WriteLine(item);
    }
}
finally
{
    writer.Close();
}

情報メッセージ表示の共通処理追加

MsgBoxクラスに以下のメソッドを追加します。

/// <summary>
/// 情報メッセージ表示
/// </summary>
/// <param name="msg"></param>
public static void ShowInfo(string msg)
{
    MessageBox.Show(msg, "情報", MessageBoxButton.OK, MessageBoxImage.Information);
}

起動時のアラーム一覧読み込み処理1

WindowLoadedイベントに処理を書きます。

try
{
    アラーム一覧読み込み(App.AlarmInitFilePath);
}
catch (Exception ex)
{
    MsgBox.ShowErr(ex.ToString());
}

アラーム一覧読み込みメソッドはとりあえず定義だけ生成しておきます。

検証メソッドの修正

追加ボタンを押すときに使用していた検証メソッドをファイル読み込み時にも使えるよう改造します。

入力時刻の検証メソッドに引数を1つ追加し、ローカル関数の部分で判定に使用します。
ファイル読み込み時の検証はisSilenttrueにして呼び出すようにします。
そうするとエラー時でもメッセージ表示とフォーカス設定をしなくなります。
isSilentに初期値falseを指定しているため、既存の入力時刻の検証メソッド呼び出しは変更不要(引数isSilentの指定を省略)です。

https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments#optional-arguments

起動時のアラーム一覧読み込み処理2

アラーム一覧読み込みメソッドに戻って以下を書きます。

if (!File.Exists(alarmInitFilePath))
{
    // ファイルがなければ何もしない
    return;
}

AlarmList.Items.Clear();

using var reader = new StreamReader(alarmInitFilePath, Encoding.UTF8);

while (!reader.EndOfStream)
{
    string timeStr = reader.ReadLine() ?? "";

    if (入力時刻の検証(timeStr, isSilent: true))
    {
        // アラーム一覧に追加
        AlarmList.Items.Add(timeStr);
    }
}

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

ファイルから1行ずつ時刻を読み込み検証し、OKならアラーム一覧のリストボックスに追加します。

アプリケーションアイコンを設定

アイコンを設定します。
今回はフリーのアイコンを利用させてもらいます。

https://icon-icons.com/

キーワードを入力し、合いそうなアイコンを探します。

ICO形式を選択し、サイズを選んでダウンロードします。

ソリューションエクスプローラーでプロジェクトを右クリック、プロパティを選択します。
リソースカテゴリのアイコン参照ボタンを押して、ダウンロードしたicoファイルを指定します。

実行

実行してみます。

タイトルバーの左端のアイコンが変わりました。
時刻のコンボボックスに初期フォーカスが設定されています。

適当に時刻をいくつか追加し、アラーム一覧を保存ボタンを押します。

プログラムをいったん終了します。

実行プログラムのある場所にファイル出力されました。

プログラムを起動します。
アラーム一覧にファイルの内容が読み込まれました。

復習ポイント

  • 初期フォーカス
  • タブオーダー
  • ファイル存在チェック
  • ファイル読み書き
  • アプリケーションアイコン