Chapter 41

ステップ11-4: 続き3

Apterygiformes-zenn
Apterygiformes-zenn
2022.01.27に更新

プロジェクトの作成

1)

同等のものをWinUI 3で作成します。
以前のWinUIのステップを読み飛ばした方はこのステップ11-4も読み飛ばしてください。

2)

新しいプロジェクトの作成で、最近使用したプロジェクトテンプレートから
空のアプリ、パッケージ化 (デスクトップのWinUI 3)を選択して次へボタンを押します。

プロジェクト名にDarkLightMode_2を入力して作成します。

3)

ソリューションエクスプローラーでプロジェクトをダブルクリックし、csprojを開き、PropertyGroupに以下を追加します。

<Nullable>enable</Nullable>
<WindowsPackageType>None</WindowsPackageType>

プロジェクト直下のPackage.appxmanifestを削除します。

4)

ソリューションエクスプローラーでソリューションを右クリックし、ソリューションの NuGet パッケージの管理を選択します。
更新があればアップデートしておきます。

5)

App.xaml.csを開き、OnLaunchedメソッドのm_windowActivateメソッド呼び出しに?を付け、
m_window定義の型のWindow?を付けます。

6)

ソリューションエクスプローラーでPropertiesフォルダの中のlaunchSettings.jsonを開き、
(Package)の項目を削除します。

launchSettings.jsonを保存します。
デバッグ実行のボタンが Unpackaged になっていることを確認します。

画面作成

1)

WPFのDarkLightMode_1も開いておきます。

WPF側のMainWindow.xamlNavigationViewを丸々コピーします。

そしてWinUI側のMainWindow.xamlStackPanelに上書き貼り付けし、
Ctrl + Hで置換ダイアログを表示し、ui:を空欄に全置換します。

ScrollViewerMargin10に修正します。
FramePaddingプロパティを削除します。

2)

プロジェクト直下にPagesフォルダを作成します。
Pagesフォルダを右クリックし、追加 > 新しい項目を選択します。
新しい項目の追加ダイアログで左のツリーからWinUIを選択し、空白のページ (WinUI 3)を選択し、
名前にHomePageを入力して追加ボタンを押します。

同様に以下のページを追加します。

  • EditPage
  • SettingsPage
  • BlankPage

Pagesフォルダを右クリックし、追加 > クラスNaviPagesクラスを追加します。

WPF側のNaviPages.csを開き、usingディレクティブとNaviPageId列挙定義、NaviPagesクラスをWinUI側にコピー&貼り付けします。
名前空間の部分(namespace DarkLightMode_1.Pages)までコピーしないよう注意してください。

3)

HomePage.xamlEditPage.xamlTextBlockをコピーしてきます。

4)

MainWindow.xamlWindowxmlns:pg=と入力し、pageと入力します。
そうすると候補が1つに絞り込まれるのでEnterで確定します。

HomeNavigationViewItemTag指定を以下のように修正します。

修正前
<NavigationViewItem Icon="Home"
                        Content="ホーム"
                        Tag="{x:Static pg:NaviPageId.Home}"
                        IsSelected="True" />
修正後
<NavigationViewItem Icon="Home"
                    Content="ホーム"
                    IsSelected="True">
    <NavigationViewItem.Tag>
        <pg:NaviPageId>Home</pg:NaviPageId>
    </NavigationViewItem.Tag>
</NavigationViewItem>

EditNavigationViewItemTag指定も同様に修正します。

修正後
<NavigationViewItem Icon="Edit"
                    Content="編集">
    <NavigationViewItem.Tag>
        <pg:NaviPageId>Edit</pg:NaviPageId>
    </NavigationViewItem.Tag>
</NavigationViewItem>

5)

NavigationViewNaviView_SelectionChangedにカーソルを置いてF12キーを押します。

myButton_Clickメソッドは削除します。

6)

プロジェクト直下にDialogフォルダを作成し、その中にMsgDialogクラスを作成します。

MsgDialog.cs
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Threading.Tasks;

namespace DarkLightMode_2.Dialog
{
    /// <summary>
    /// メッセージダイアログ
    /// </summary>
    internal static class MsgDialog
    {
        /// <summary>
        /// エラー表示
        /// </summary>
        /// <param name="root"></param>
        /// <param name="msg"></param>
        /// <returns></returns>
        public static async Task ErrorAsync(XamlRoot root, string msg)
        {
            var dlg = new ContentDialog
            {
                Title = "エラー",
                Content = msg,
                CloseButtonText = "OK",
            };
            dlg.XamlRoot = root;

            // ダイアログ表示
            await dlg.ShowAsync();
        }
    }
}

7)

MainWindow.xamlNaviView_SelectionChangedasyncを付け、中身に以下を書きます。

try
{
    var item = (NavigationViewItem)args.SelectedItem;
    NaviView.Header = item?.Content.ToString();

    if (args.IsSettingsSelected)
    {
        // 設定ページ表示
        PageNavigate(NaviPageId.Settings);

        return;
    }
    string? tag = item?.Tag?.ToString();
    if (Enum.TryParse(tag, out NaviPageId id))
    {
        // 対応するページ表示
        PageNavigate(id);
    }
    else
    {
        // 空ページ表示
        PageNavigate(NaviPageId.None);
    }
}
catch (Exception ex)
{
    await MsgDialog.ErrorAsync(Content.XamlRoot, ex.ToString());
}

PageNavigateメソッドを追加します。

/// <summary>
/// ページ切り替え
/// </summary>
/// <param name="id">ページID</param>
private void PageNavigate(NaviPageId id) =>
    ContentFrame.Navigate(NaviPages.Index[id]);

エラー箇所はいつもの対処です。

設定ページコントロール記述

SettingsPage.xamlGridを以下に置き換えます。

<Page.Resources>
    <Style TargetType="ToggleSwitch">
        <Setter Property="Margin"
                Value="0,5" />
    </Style>

    <!-- テーマラジオボタンスタイル -->
    <Style x:Key="ThemeRadioStyle"
            TargetType="RadioButton"
            BasedOn="{StaticResource DefaultRadioButtonStyle}">
        <Setter Property="GroupName"
                Value="Theme" />
    </Style>
</Page.Resources>

<StackPanel>
    <TextBlock Text="テーマ"
                Style="{StaticResource TitleTextBlockStyle}" />
    <RadioButton x:Name="ThemeSystem"
                    Content="Windows テーマを使用"
                    Style="{StaticResource ThemeRadioStyle}" />
    <RadioButton x:Name="ThemeLight"
                    Content="ライト"
                    Style="{StaticResource ThemeRadioStyle}" />
    <RadioButton x:Name="ThemeDark"
                    Content="ダーク"
                    Style="{StaticResource ThemeRadioStyle}" />
    <TextBlock Text="※アプリ再起動後に有効"
                Margin="0,10,0,0"/>

    <TextBlock Text="その他"
                Style="{StaticResource TitleTextBlockStyle}"
                Margin="0,20,0,0" />
    <ToggleSwitch x:Name="Option1"
                        Header="何かのオプション1" />
    <ToggleSwitch x:Name="Option2"
                        Header="何かのオプション2" />
</StackPanel>

設定読み書き

1)

App.xaml.csを開き、以下を追加します。

/// <summary>
/// アプリケーション設定ファイル名
/// </summary>
public const string SettingsFileName = "settings.json";
/// <summary>
/// アプリケーション設定ファイルパス取得
/// </summary>
/// <returns></returns>
/// <exception cref="DirectoryNotFoundException"></exception>
public static string GetSettingsFilePath()
{
    string? appPath = AppContext.BaseDirectory;
    if (appPath is null)
    {
        throw new DirectoryNotFoundException("実行ファイルのパス取得失敗");
    }

    return System.IO.Path.Combine(appPath, SettingsFileName);
}

2)

WPFのプロジェクトからSettingsフォルダをコピーし、WinUIのプロジェクトに貼り付けます。

WinUIプロジェクトに貼り付けたSettings.cs, SettingsReader.cs, SettingsWriter.csの名前空間を修正します。

namespace DarkLightMode_2.Settings

3)

App.xaml.csを編集します。
コンストラクタにSetAppThemeメソッド呼び出しを追加します。

 public App()
 {
     this.InitializeComponent();
+    SetAppTheme();
 }

SetAppThemeメソッドは以下のように書きます。

/// <summary>
/// 設定ファイルを読み込みテーマ適用
/// </summary>
private void SetAppTheme()
{
    try
    {
        var sr = new Settings.SettingsReader(App.GetSettingsFilePath());
        var settings = sr.ReadFromFile();
        if (settings.Theme == "Dark")
        {
            App.Current.RequestedTheme = ApplicationTheme.Dark;
        }
        else if (settings.Theme == "Light")
        {
            App.Current.RequestedTheme = ApplicationTheme.Light;
        }
    }
    catch
    {
    }
}

設定ページのイベント処理

1)

Settings.xamlを開き、
PageLoaded, LostFocus, Unloadedイベントハンドラを作成します。

Loadedの処理にasyncを付け足して中に以下を書きます。

Loadedの処理
try
{
    ReadSettings();
}
catch (Exception ex)
{
    await MsgDialog.ErrorAsync(Content.XamlRoot, ex.ToString());
}

ReadSettingsメソッドは以下のように編集します。

/// <summary>
/// 設定読み込み
/// </summary>
private void ReadSettings()
{
    var sr = new Settings.SettingsReader(App.GetSettingsFilePath());
    var settings = sr.ReadFromFile();

    SetThemeSettings(settings.Theme);
    Option1.IsOn = settings.Option1;
    Option2.IsOn = settings.Option2;
}
/// <summary>
/// テーマ文字列を画面の設定に反映
/// </summary>
/// <param name="theme"></param>
private void SetThemeSettings(string? theme)
{
    if (theme == "Dark")
    {
        ThemeDark.IsChecked = true;
    }
    else if (theme == "Light")
    {
        ThemeLight.IsChecked = true;
    }
    else
    {
        ThemeSystem.IsChecked = true;
    }
}

2)

LostFocusの処理にasyncを付け足して中に以下を書きます。

LostFocusの処理
try
{
    WriteSettings();
}
catch (Exception ex)
{
    await MsgDialog.ErrorAsync(Content.XamlRoot, ex.ToString());
}

WriteSettingsメソッドは以下のように編集します。

/// <summary>
/// 設定書き込み
/// </summary>
var settings = new Settings.Settings
{
    Option1 = Option1.IsOn,
    Option2 = Option2.IsOn,
    Theme = GetThemeString(),
};

var sw = new Settings.SettingsWriter(App.GetSettingsFilePath());
sw.WriteToFile(settings);
/// <summary>
/// テーマ設定の文字列取得
/// </summary>
/// <returns></returns>
private string? GetThemeString()
{
    if (ThemeDark.IsChecked == true)
    {
        return "Dark";
    }
    else if (ThemeLight.IsChecked == true)
    {
        return "Light";
    }
    else
    {
        return null;
    }
}

3)

Unloadedの処理にasyncを付け足して中に以下を書きます。

Unloadedの処理
try
{
    WriteSettings();
}
catch (Exception ex)
{
    await MsgDialog.ErrorAsync(Content.XamlRoot, ex.ToString());
}

実行