single.php

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

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

元のサイズに戻した時のサイズを保存

先回、「C# WinUI 3 アンパッケージ化したアプリで前回終了時の位置にフォームを復帰する方法」で、Xml形式のファイルに保存して読み込むようにした方法では最大化(または最小化)で終了した場合に、通常ウィンドウ「元のサイズに戻す」にした際のサイズが正しく保存されない部分があったので、改良しました。

WinUI3アプリを単体で起動できるようにアンパッケージ化した際に、Windows.Storage アセンブリなどアプリの設定関連を簡単に保存できるメソッドなどが実行時に例外が発生するために、こんな面倒な手間をかけています。

ウィンドウのサイズ変更時に保存

先回の方法では、アプリ終了時にウィンドウのサイズを保存していたので、ウィンドウを最大化したまま終了してしまうと、元に戻した際のサイズが最大化したウィンドウのサイズになってしまいました。

最大化する直前のウィンドウのサイズが必要なので、ウィンドウのサイズ変更時に常にサイズを保存するように改良します。

まず、ウィンドウのサイズを保持するためのRect型とOverlappedPresenterState 型の変数を2つ作ります。(Rectは幅と高さ、左上の座標を保存し、OverlappedPresenterState はウィンドウの状態を保存します)

private Rect m_normalAppRect;
private OverlappedPresenterState m_stateApp;

インスタンス部分にウィンドウのサイズ変更時のイベント(Window_Changed)`を追加します。

private Microsoft.UI.Windowing.AppWindow m_AppWindow;

public MainWindow()
{
  this.InitializeComponent();
  IntPtr hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
  Microsoft.UI.WindowId windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);
  m_AppWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);

  m_AppWindow.Changed += Window_Changed;

}

実体のイベントハンドラーを追加してウィンドウのサイズ変更時にウィンドウのサイズを変数に保存するように処理を追加します。

private void Window_Changed(Microsoft.UI.Windowing.AppWindow sender, Microsoft.UI.Windowing.AppWindowChangedEventArgs e)
{
  if (e.DidSizeChange || e.DidPositionChange)
  {
    if (((OverlappedPresenter)m_AppWindow.Presenter).State == OverlappedPresenterState.Restored)
    {
      IntPtr hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
      double dpiRasio = GetDpiForWindow(hWnd) / 96;

      m_normalAppRect.Y = m_AppWindow.Position.Y;
      m_normalAppRect.X = m_AppWindow.Position.X;
      m_normalAppRect.Width = (int)(m_AppWindow.Size.Width / dpiRasio);
      m_normalAppRect.Height = (int)(m_AppWindow.Size.Height / dpiRasio);
    }
  }
}

GetDpiForWindow関数はWin32を利用してディスプレイの拡大率を取得しています。詳しい内容は別記事をご覧ください。

これでサイズ変更前のウィンドウサイズが変数に保存されるようになります。

ウィンドウが閉じる際にXML形式に保存

XML形式に保存する部分は先回とほとんど同じでウィンドウのサイズを、保存した変数に置き換えます。今回は、StoreMainWindowRect関数として書いています。AppSettingクラスは、ウィンドウの幅や高さなどのデータを受け取りやすくするためにクラス化しています。

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

private void StoreMainWindowRect()
{
  string appfolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);

  System.Diagnostics.FileVersionInfo fvinfo = FileVersionInfo.GetVersionInfo(System.Reflection.Assembly.GetExecutingAssembly().Location);
    
  string appdatafolder = appfolder + @"\" + fvinfo.ProductName;
  string appdatafile = appdatafolder + @"\data.xml";
  System.IO.Directory.CreateDirectory(appdatafolder);

  AppSetting aps = new AppSetting();
  aps.Top = (int)m_normalAppRect.Y;
  aps.Left = (int)m_normalAppRect.X;
  aps.Width = (int)m_normalAppRect.Width;
  aps.Height = (int)m_normalAppRect.Height;

  aps.State = ((OverlappedPresenter)m_AppWindow.Presenter).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();
}

ウィンドウが閉じるイベントで、StoreMainWindowRect関数を呼び出します。これで「元のサイズに戻す」操作で戻すウィンドウのサイズが保存されるようになりました。

アプリ起動時にXML形式から読み出し

XML形式から読み出す部分も、先回とほとんど同じでウィンドウのサイズを、読みだしたデータから変数に置き換えます。今回は、LoadMainWindowRect関数として書いています。AppSettingクラスは、XML保存時に記載したクラスです。

private void LoadMainWindowRect()
{
  string appfolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
  string appdatafolder = appfolder + @"\WinUI3ImageViewer";
  string appdatafile = appdatafolder + @"\data.xml";

  if(System.IO.File.Exists(appdatafile) == true)
  {
    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();

    m_normalAppRect.Width = aps.Width;
    m_normalAppRect.Height = aps.Height;
    m_normalAppRect.Y = aps.Top;
    m_normalAppRect.X = aps.Left;
    m_stateApp = aps.State;
  }
}

アプリ起動時(インスタンス化する際のイベント)にLoadMainWindowRect関数を呼び出します。

public MainWindow()
{
  this.InitializeComponent();
  //...

  LoadMainWindowRect();
}

ウィンドウがアクティブ化した際に状態を戻す

保存したXMLから終了時のウィンドウサイズを読みだして、ウィンドウに適用します。

まずはウィンドウがアクティブになった際のイベントを追加します。Window_Activatedイベントの作り方は前回の記事を参照してください。

ちょっと複雑ですが、Window_Activatedイベントはウィンドウがアクティブになった際に、何度も呼び出されるので、フラグ用の変数(m_bRestoredWindow)を使って、フォームが可視状態になった最初の1回のみ処理されるようにします。

private bool m_bRestoredWindow;

private void Window_Activated(object sender, Microsoft.UI.Xaml.WindowActivatedEventArgs e)
{
  if(m_AppWindow.IsVisible == true)
  {
    if (m_bRestoredWindow == false)
    {
      Windows.Graphics.RectInt32 rect = new Windows.Graphics.RectInt32((int)m_normalAppRect.X, (int)m_normalAppRect.Y, (int)m_normalAppRect.Width, (int)m_normalAppRect.Height);
      m_AppWindow.MoveAndResize(rect);

      if (m_stateApp == OverlappedPresenterState.Maximized)
      {
        ((OverlappedPresenter)m_AppWindow.Presenter).Maximize();
      }

      if (m_stateApp == OverlappedPresenterState.Minimized)
      {
        ((OverlappedPresenter)m_AppWindow.Presenter).Minimize();
      }

      m_bRestoredWindow = true;
    }
  }
}

ここまでの処理で、アプリが終了する際にウィンドウが最大化(または最小化)した状態の場合には、元に戻す際のサイズに変更してから最大化(または最小化)されるため「元にサイズに戻す」操作をした際に、直前のウィンドウサイズに戻すことができるようになります。

まとめ

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

先回紹介した方法では、アプリの終了時にウィンドウが最大化(最小化)していた場合に、「元のサイズに戻す」操作でウィンドウのサイズが戻らない状態になるため処理を変更して、最大化(最小化)する直前のウィンドウサイズを保存するように改良を行いました。

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

スポンサーリンク

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

コメントを残す

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