single.php

C# WinUI 3 アンパッケージ化したアプリで前回終了時の位置にフォームを復帰する方法

C# WinUI 3 プロジェクトで、アンパッケージ化した際にXamlで構成されたフォームを終了時の位置で復帰する方法を備忘録的に投稿します。

アンパッケージ化するデメリット

C#のWinUI 3プロジェクトで、配布を必要とせずに実行ファイルから起動できるようにする「アンパッケージ化」ができます。

自己実行形式(EXE)を開いてアプリが起動するのは便利なのですが、特定のアセンブリ内のメソッドやプロパティに制約がかかるデメリットがあります。

Windows.Storage アセンブリなどアプリの設定関連を簡単に保存できるメソッドなどは実行時に例外が発生するために利用することができなくなります。

汎用的なアセンブリを使ってXML形式で保存

色々試してみましたが、アンパッケージ化したWinUI3プロジェクトでも制約を受けないアセンブリやWin32のAPIを使うことで、フォームの位置をXML形式で保存して読み出すことで、前回終了時の位置を復元することができました。

フォームの終了時と表示時のイベント追加

具体的には次の処理を追加します。

まずは「フォームが閉じる(Closed)」と「フォームが表示される(Activated)」イベントを作成してハンドラーを追加します。

XamlファイルのWindowタグに、ClosedとActivatedを追加します。

<Window
  x:Class="WinUI3ImageViewer.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:WinUI3ImageViewer"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="d"
  Closed="Window_Closed"
  Activated="Window_Activated"
  >

  <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
  </StackPanel>
</Window>

Xaml.csファイルに、対応したイベントハンドラーを追加します。

private void Window_Activated(object sender, Microsoft.UI.Xaml.WindowActivatedEventArgs e)
{
  //ウィンドウが表示された際のイベント
}

private void Window_Closed(object sender, WindowEventArgs e)
{
  //ウィンドウが閉じた際のイベント
}

ウィンドウが閉じた際と表示された際に処理の追加ができます。詳細な実装手順は別記事をご覧ください。

XML形式で保存

追加したイベントハンドラーに[System.Xml.Serialization.XmlSerializer]アセンブリでXML形式のファイルで保存と読み込みを行う処理を追加します。

設定ファイルに保存するデータ用の新しいクラスを準備して

public class AppSetting
{
  public int Width = 0;
  public int Height = 0;
  public int Top = 0;
  public int Left = 0;
  public OverlappedPresenterState State = OverlappedPresenterState.Restored;
}

クラスを使って、XML形式で保存

AppSetting aps = new AppSetting();
aps.Top = nTop;
aps.Left = nLeft;
aps.Width = nWidth;
aps.Height = nHeight;
aps.State = State;

System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(AppSetting));
System.IO.StreamWriter sw = new System.IO.StreamWriter(appdatafile, false, new System.Text.UTF8Encoding(false));
serializer.Serialize(sw, aps);
sw.Close();

読み込みのそれぞれの処理を追加します。

System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(AppSetting));
System.IO.StreamReader sr = new System.IO.StreamReader(appdatafile, new System.Text.UTF8Encoding(false));
AppSetting aps = (AppSetting)serializer.Deserialize(sr);
sr.Close();

int nWidth = aps.Width;
int nHeight = aps.Height;
int nTop = aps.Top;
int nLeft = aps.Left;

OverlappedPresenterState State = aps.State;

XML形式でデータを保存する手順については別記事をご覧ください。

マルチディスプレイへの対応

ここまでの処理で、Xamlで作成されたフォームの位置を次回起動時に復元することできますが、高解像度(4Kモニターなど)設定でWindows11/10が自動的に設定を行う[拡大/縮小]が有効(150%など)になっている場合に、画面サイズが正しく行われない現象になります。

Win32 のGetDpiForWindowを使って解像度を取得して調整を追加します。

[DllImport("user32.dll")]
static extern int GetDpiForWindow(IntPtr hWnd);
IntPtr hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
double dpi = GetDpiForWindow(hWnd);

GetDpiForWindow関数では、モニターで設定されている実際の解像度が取得されるため、100%での解像度96を使って、拡大率を求めることができます。

4Kモニターなど拡大/縮小設定を取得してウィンドウのサイズ変更を行う手順の詳細については別記事をご覧ください。

まとめ

今回は、WinUI 3 プロジェクトでアンパッケージ化したアプリの終了時のフォームの位置を保存して次回起動時に復帰させる方法を書きました。

アンパッケージ化することで、実行ファイルから直接アプリを実行できるメリットがありますが、特定のアセンブリが実行時に例外で停止するデメリットもあるため、可能ならアンパッケージ化せずに実行した方が良いです。

しかし、どうしてもアンパッケージ化が必要な場合には、動作する既存のアセンブリを組み合わせてXML形式のファイルで保存することで、画面の位置を保存することができます。

C#のWinUI 3プロジェクトのアンパッケージ化したアプリでフォームの位置を保存したい場合の参考になれば幸いです。

スポンサーリンク

最後までご覧いただき、ありがとうございます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です