Chapter 28

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

Apterygiformes-zenn
Apterygiformes-zenn
2021.12.23に更新

終了確認

何か入力があるときに終了しようとしたら確認するようにします。

WindowClosingイベントに以下の処理を書きます。

if (!string.IsNullOrEmpty(Memo.Text))
{
    if (MessageBox.Show(
        "入力がありますが、終了しますか?",
        "確認",
        MessageBoxButton.YesNo,
        MessageBoxImage.Question) != MessageBoxResult.Yes)
    {
        // 終了をキャンセル
        e.Cancel = true;
    }

    // 「はい」が押されたら普通に終了
}

非同期対応

ファイルを開くときと保存するときの処理を非同期で実行するようにします。

ファイル読み込み処理

OpenMenu_Clickの戻り値voidの前にasyncを付け足します。
StreamReaderのメソッド呼び出しを同期版ReadToEndから非同期版ReadToEndAsyncに変更します。
ReadToEndAsync呼び出しにawaitを付けます。

修正前(同期版)はReadToEndが呼ばれたら終わるまで他の処理をせず待ち状態になります。
時間がかかる場合、応答なしで画面が固まってしまいます。

修正後(非同期版)では、ReadToEndAsyncが別スレッドで呼び出されるため、応答なしを防げます。
awaitが付いているのでReadToEndAsyncが終わるまでOpenMenu_Clickの処理は先に進まず待機します。
ReadToEndAsyncが完了するとawaitの左側、Memo.Textに結果を代入します。

ファイル読み込みの部分を別メソッドに切り出してみます。

以下のメソッドを定義し、await LoadFileToMemoAsync(dialog.FileName);で呼び出します。

/// <summary>
/// ファイルを読み込んで内容を画面のTextBoxに設定
/// </summary>
/// <param name="filePath"></param>
/// <returns></returns>
private async Task LoadFileToMemoAsync(string filePath)
{
    using var reader = new StreamReader(
        filePath,
        encoding: Encoding.UTF8);

    // 読み込んだ内容をTextBoxに設定
    Memo.Text = await reader.ReadToEndAsync();
}

awaitを使用するメソッドにはasyncを付けます。
非同期メソッド名は慣習的に末尾にAsyncを付けます。
非同期メソッドは戻り値がない場合はvoidではなくTaskにします(イベントハンドラだけ例外的に戻り値voidのままにします)。
戻り値がある場合はTask<戻り値の型>にします。

ファイル保存処理

SaveAsMenu_Clickの戻り値voidの前にasyncを付け足します。
StreamWriterのメソッド呼び出しを同期版Writeから非同期版WriteAsyncに変更します。
WriteAsync呼び出しにawaitを付けます。

https://docs.microsoft.com/ja-jp/dotnet/csharp/programming-guide/concepts/async/

設定ファイル

概要

初期値を設定ファイルから読み込むようにします。

NuGetパッケージのインストール

ソリューションエクスプローラーでプロジェクトを右クリックし、NuGetパッケージの管理を選択します。

参照を選択し、検索ボックスにextensions.configを入力します。
Microsoft.Extensions.Configuration.Jsonをインストールします。

もう1つ、Microsoft.Extensions.Configuration.Binderもインストールします。

2つのパッケージがインストールされました。

設定ファイルの追加

ソリューションエクスプローラーでプロジェクトを右クリックし、追加 > 新しい項目を選択します。

左のツリーからWebを選択し、JavaScript JSON 構成ファイルを選択します。
名前appsettings.jsonを入力し、追加ボタンを押します。

↓↓↓↓↓

中身を以下のように書き換えます。
ウィンドウのタイトルと、起動時のウィンドウ高さ・幅です。

appsettings.json
{
  "Window": {
    "Title": "簡易メモ帳",
    "InitHeight": 300,
    "InitWidth": 400
  }
}

メニューのファイル > 名前を付けて appsettings.json を保存を選択します。

上書き保存ボタン右のからエンコード付きで保存を選択します。

上書き確認ではいを選択します。

エンコードUnicode (UTF-8シグネチャなし) - コードページ 65001を選択します。

OKを選択します。

appsettings.jsonのプロパティで出力ディレクトリにコピー新しい場合はコピーするに設定します。
これでプロジェクト直下のappsettings.json(元ファイル)がbinフォルダ配下にもコピーされるようになります。

ロジックの記述

App.xaml.csに定数を追加します。

App.xaml.cs
public partial class App : Application
{
    /// <summary>
    /// 設定ファイル名
    /// </summary>
    public const string AppSettingsFile = "appsettings.json";
}

WindowSourceInitializedイベントに以下を書きます。

try
{
    LoadAppSettings();
}
catch (Exception ex)
{
    MessageBox.Show(ex.ToString());
}

設定ファイルから取得したウィンドウのサイズを適用するため、Loadedより前の段階で発生するSourceInitializedイベントを使用します。

LoadAppSettingsのメソッドの生成をし、以下を書きます。

const double MinWindowSize = 150;
const double MaxWindowSize = 800;
const double DefWindowSize = 400;

IConfigurationRoot config = new ConfigurationBuilder()
    .SetBasePath(AppContext.BaseDirectory)
    .AddJsonFile(App.AppSettingsFile, optional: true, reloadOnChange: false)
    .Build();

// 設定読み込み
IConfigurationSection section = config.GetSection("Window");
Title = section.GetValue<string>("Title", "メモ");
double h = section.GetValue<double>("InitHeight", DefWindowSize);
double w = section.GetValue<double>("InitWidth", DefWindowSize);

// ウィンドウ高さ設定
Height = (h is < MinWindowSize or > MaxWindowSize)
    ? DefWindowSize
    : h;

// ウィンドウ幅設定
Width = (w is < MinWindowSize or > MaxWindowSize)
    ? DefWindowSize
    : w;

AddJsonFileの引数optionaltrueを設定すると、設定ファイルが存在しなくても例外が発生しません。
設定値読み込み処理のGetValueの1番目の引数は設定項目のキーで、2番目の引数は読み込めなかったときの初期値です。

📘他の書き方(config)

IConfigurationRootconfigを作るまでの処理は以下のようにも書けます。

IConfigurationBuilder cfgBuilder = new ConfigurationBuilder();
cfgBuilder.SetBasePath(AppContext.BaseDirectory);
cfgBuilder.AddJsonFile(App.AppSettingsFile, optional: true, reloadOnChange: false);

IConfigurationRoot config = cfgBuilder.Build();
📘他の書き方(Height, Width)

Height, Widthの設定は以下のようにも書けます。

// ウィンドウ高さ設定
if (h < MinWindowSize ||
    h > MaxWindowSize)
{
    Height = DefWindowSize;
}
else
{
    Height = h;
}

// ウィンドウ幅設定
if (w < MinWindowSize ||
    w > MaxWindowSize)
{
    Width = DefWindowSize;
}
else
{
    Width = w;
}

実行

実行してみます。
タイトルのテキストが設定ファイルのものに変わりました。

いったん終了し、実行ファイルのある場所のappsettings.jsonを書き換え保存し、Memo.exeを起動してみます。

復習ポイント

  • 終了確認
  • async/await
  • NuGet
  • 設定ファイルの読み込み