いつもどこかでデスマーチ♪

不定期に、私の日常を書き込みしていきます。

WPF Prism で カスタムスプラッシュウィンドウ(スクリーン) を表示する

2024年04月08日 17時10分10秒 | .NET系
画像のみのスプラッシュウィンドウを表示する場合はこっち(画像のみだけど表示中にいろいろ処理したい)
https://blog.goo.ne.jp/admin/editentry/?eid=6e22b1c3008b33e8350cf9a02c7e12b0&sc=c2VhcmNoX3R5cGU9MCZsaW1pdD0xMCZzb3J0PWRlc2MmY2F0ZWdvcnlfaWQ9JnltZD0mcD0z


上記でスプラッシュウィンドウを表示したアプリケーションを作っていたのだが…
「起動時間が長いからプログレスバーを出したい!」とかいう訳分からん修正依頼が来た…
(上記記事にしてますが、シリアルケーブル通信をしているのでCOM数が多ければ遅くなるのは当たり前…)

って事で、試行錯誤をした結果たどり着いた内容を記載します。

前提条件:
・Prism を利用していること。
・Reactive を利用していること。(必須ではない)
・IDialogService を利用したダイアログ表示が可能であること。
・スプラッシュウィンドウ用のUserControl がスタートアッププロジェクトに実装済みであること。

App.xaml.cs:
public partial class App : PrismApplication
{
    /// <summary>
    /// メイン画面の起動処理
    /// </summary>
    protected override void OnInitialized()
    {
        if (MainWindow != null)
        {
            // ダイアログサービスを取得して、カスタムスプラッシュウィンドウを表示する
            var dialogService = this.Container.Resolve<IDialogService>();
            var ret = ButtonResult.Cancel;
            dialogService.ShowDialog(nameof(CustomizeSplashScreen), null, r => { ret = r.Result; resultParam = r.Parameters; }, nameof(CustomizeSplashScreen));

            // 問題無ければOK/問題あればそれ以外を返却する処理になってる
            if (ret == ButtonResult.OK)
            {
                // 初期化後の画面構成に必要な通知(必要があれば実装)
                var eventAggregator = this.Container.Resolve<IEventAggregator>();
                eventAggregator.GetEvent<InitScreen>().Publish();

                // 画面を表示
                MainWindow.Show();
            }
            else
            {
                // 起動失敗なのでクローズする
                MainWindow.Close();
            }
        }
    }

    /// <summary>
    /// インスタンスのDI登録(インスタンスの生成を任せる)
    /// </summary>
    /// <param name="containerRegistry">インスタンス登録クラス</param>
    protected override void RegisterTypes(IContainerRegistry containerRegistry)
    {
        … その他いろいろ登録
        containerRegistry.RegisterDialog<CustomizeSplashScreen>();
        … その他いろいろ登録
    }
}


CustomizeSplashScreen.xaml:
<UserControl x:Class="Test.Views.CustomizeSplashScreen"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
             xmlns:prism="http://prismlibrary.com/"
             xmlns:local="clr-namespace:Test.Views"
             xmlns:vm="clr-namespace:Test.ViewModels"
             mc:Ignorable="d"
             prism:ViewModelLocator.AutoWireViewModel="True">

    <prism:Dialog.WindowStyle>
        <Style TargetType="Window">
            <Setter Property="prism:Dialog.WindowStartupLocation" Value="CenterScreen"/>
            <Setter Property="AllowsTransparency" Value="True"/>
            <Setter Property="ResizeMode" Value="NoResize"/>
            <Setter Property="WindowStyle" Value="None"/>
            <Setter Property="SizeToContent" Value="WidthAndHeight"/>
            <Setter Property="ShowInTaskbar" Value="False"/>
        </Style>
    </prism:Dialog.WindowStyle>

    <i:Interaction.Triggers>
        <!-- 画面レンダリング前のイベント発行 -->
        <i:EventTrigger EventName="Loaded">
            <prism:InvokeCommandAction Command="{Binding LoadedCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>

    <Grid>
        <!-- すきにカスタマイズ -->
        <Image Width="{Binding" RelativeSource="{RelativeSource" Self}, Path="Source.PixelWidth}" 
               Height="{Binding" RelativeSource="{RelativeSource" Self}, Path="Source.PixelHeight}"
               Source="/Test;component/Resources/images/Splash.png"/>
    </Grid>
</UserControl>


CustomizeSplashScreenViewModel.cs:
/// <summary>
/// カスタマイズのスプラッシュウィンドウ
/// </summary>
internal class CustomizeSplashScreenViewModel : BindableBase, IDialogAware
{
    private readonly IContainerExtension _containerExtension;
    private readonly IEventAggregator _eventAggregator;
    public string Title => "";
    public event Action<IDialogResult> RequestClose;
    public ReactiveCommand LoadedCommand { get; }
    
    public CustomizeSplashScreenViewModel(IContainerExtension containerExtension,
                                          IEventAggregator eventAggregator)
    {
        _containerExtension = containerExtension;
        _eventAggregator = eventAggregator;
        LoadedCommand = new ReactiveCommand().WithSubscribe(Loaded);
    }

    /// <summary>
    /// スプラッシュウィンドウのロードが完了した場合に呼び出す
    /// </summary>
    private async void Loaded()
    {
        // プログレスバーを利用する場合は別スレッド化しないと止まる
        await Task.Run(() =>
        {
            // 直接サービス呼び出しでも良いし、イベント発行してViewModel経由で処理しても良い
            // 今回は再接続・再チェック用のダイアログを出すので、イベント発行しViewModel経由で処理させてます
            // 別スレッドになるため、ダイアログを出す場合は「Application.Current.Dispatcher.Invoke(new Action(() =>」を使いUIスレッドに移行させる必要があります。

            // 今回は既存処理の変更が出来ないためイベント呼び出しで対応する。
            _eventAggregator.GetEvent<DoStartup>().Publish();
        });

        // 初期化OKの場合はOKを返し画面を閉じる
        RequestClose?.Invoke(new DialogResult(ButtonResult.OK));
    }

    /// <summary>
    /// ダイアログを閉じても良いかどうか
    /// </summary>
    /// <returns></returns>
    public bool CanCloseDialog()
    {
        return true;
    }

    /// <summary>
    /// ダイアログを閉じた後
    /// </summary>
    public void OnDialogClosed()
    {
        // ダイアログを閉じる
        Dispose();
    }

    /// <summary>
    /// ダイアログを表示した後
    /// </summary>
    /// <param name="parameters"></param>
    public void OnDialogOpened(IDialogParameters parameters)
    {
    }
}



簡単な解説:(Prism でやってるけど、Prism使わなくてもほぼ一緒だと思う)
1.App.xaml.cs について
・スプラッシュウィンドウをダイアログとして表示させてしまう。
・App.xaml.csの「OnInitialized」メソッドは「onSetup」メソッドの一番最後に呼び出されます。
  そのため、DIやMainWindowの初期化完了後です。
・「Container.Resolve」でDIに登録されたクラスを取得できるので「各ViewModel」と同じことができるようになる。
・「this.Container.Resolve<IDialogService>()」でダイアログクラスを取得
・「dialogService.ShowDialog」で処理を止めつつスプラッシュウィンドウを表示する
・「eventAggregator.GetEvent<PubSubEvent<XXXX>>().Publish();」で初期化処理完了後の画面反映処理を呼び出す(必要な場合)
 → 「CustomizeSplashScreenViewModel.cs」側で行えば?と思いますが、「イベント→イベント」でやると私の環境では画面が反映されなかった

2.CustomizeSplashScreen.xaml について
・「prism:Dialog.WindowStyle」でタイトルなし、リサイズ無し、タスクバー表示なし等々スプラッシュウィンドウ用の設定
・「i:Interaction.Triggers」で画面ロード完了後のイベント発行をサポート
・「Image」タグで指定の画像を表示する。Width/Height は画像サイズをそのまま表示する記載方法

3.CustomizeSplashScreenViewModel.cs について
・「LoadedCommand = new ReactiveCommand().WithSubscribe(Loaded);」画面ロード後に自動で処理を継続
・「Loadedメソッド」の「await Task.Run(() =>」でプログレスバーの停止を回避
・別スレッドにて初期化・通信処理・ダイアログ表示によるリトライ等々の処理を行う。
 →サンプルではイベントを発行しているが、スプラッシュウィンドウ内ですべて行っても良いと思う(普通のViewModelなので…)
・初期化処理の戻り値が欲しければ、一般的なやり方と同じで「Task.Run内でreturn」し受け取ればいい。
 → awaitしてるから処理は止まっており、Task.Runが終わり次第処理が継続する
・「RequestClose?.Invoke(new DialogResult(ButtonResult.OK));」でOKを返却しているので、
 「App.xaml.cs」の呼び出しもとで、「ButtonResult」の値をもとに判定が出来るようになる。
・その他はDialogServiceに必要な処理のはず…


という流れになるはずです。
処理の内容は、最初のURLと同じく、スプラッシュウィンドウ表示中に、エラーメッセージ表示、リトライダイアログ表示等々が出来ます。

もっと楽な方法はないものか…

わかりにくい箇所があれば書き直すのでコメントしてください
コメント    この記事についてブログを書く
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする
« postgresql の 数値キャスト... | トップ | ヨドバシカメラの店舗購入履... »

コメントを投稿

ブログ作成者から承認されるまでコメントは反映されません。

.NET系」カテゴリの最新記事