Window位置の保存と復元

2020-03-25

ぐらばく氏の「WPF でウィンドウ位置とサイズを保存・復元しよう」を元に
ApplicationSettingsBaseのGradeUp対応を含んだカスタマイズを行った。

RestorableWindow.cs

using System;
using System.ComponentModel;
using System.Configuration;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

namespace UserControls
{
    public class RestorableWindow : Window
    {
        #region Win32API

        [DllImport("user32.dll")]
        public static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);

        [DllImport("user32.dll")]
        public static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);

        [Serializable]
        [StructLayout(LayoutKind.Sequential)]
        public struct WINDOWPLACEMENT
        {
            public int length;
            public int flags;
            public SW showCmd;
            public POINT minPosition;
            public POINT maxPosition;
            public RECT normalPosition;
        }

        [Serializable]
        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int X;
            public int Y;

            public POINT(int x, int y)
            {
                this.X = x;
                this.Y = y;
            }
        }

        [Serializable]
        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;

            public RECT(int left, int top, int right, int bottom)
            {
                this.Left = left;
                this.Top = top;
                this.Right = right;
                this.Bottom = bottom;
            }
        }

        public enum SW
        {
            HIDE = 0,
            SHOWNORMAL = 1,
            SHOWMINIMIZED = 2,
            SHOWMAXIMIZED = 3,
            SHOWNOACTIVATE = 4,
            SHOW = 5,
            MINIMIZE = 6,
            SHOWMINNOACTIVE = 7,
            SHOWNA = 8,
            RESTORE = 9,
            SHOWDEFAULT = 10,
        }

        #endregion

        /// <summary>
        /// 画面表示時処理
        /// </summary>
        /// <param name="e"></param>
        protected override void OnSourceInitialized(EventArgs e)
        {
            base.OnSourceInitialized(e);

            WindowPosition windowposition = new WindowPosition(this);
            windowposition.Reload();
            if (windowposition.Upgraded == false)
            {
                // アップグレード処理
                windowposition.Upgrade();
                windowposition.Upgraded = true;
                windowposition.Save();
            }

            if (windowposition.Placement.HasValue)
            {
                IntPtr hwnd = new WindowInteropHelper(this).Handle;
                WINDOWPLACEMENT placement = windowposition.Placement.Value;
                placement.length = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
                placement.flags = 0;
                placement.showCmd = (placement.showCmd == SW.SHOWMINIMIZED) ? SW.SHOWNORMAL : placement.showCmd;

                SetWindowPlacement(hwnd, ref placement);
            }
        }

        /// <summary>
        /// 画面終了時処理
        /// </summary>
        /// <param name="e"></param>
        protected override void OnClosing(CancelEventArgs e)
        {
            base.OnClosing(e);

            if (!e.Cancel)
            {
                WINDOWPLACEMENT placement;
                IntPtr hwnd = new WindowInteropHelper(this).Handle;
                GetWindowPlacement(hwnd, out placement);

                WindowPosition windowposition = new WindowPosition(this);
                windowposition.Placement = placement;
                windowposition.Save();
            }
        }

        /// <summary>
        /// 設定情報クラス
        /// </summary>
        private class WindowPosition : ApplicationSettingsBase
        {
            public WindowPosition(Window window) : base(window.Title) { }

            /// <summary>
            /// ウィンドウ位置情報
            /// </summary>
            [UserScopedSetting()]
            public WINDOWPLACEMENT? Placement
            {
                get
                {
                    return this["Placement"] != null ? (WINDOWPLACEMENT?)this["Placement"] : null;
                }
                set
                {
                    this["Placement"] = value;
                }
            }

            /// <summary>
            /// アップグレードフラグ
            /// </summary>
            [UserScopedSetting()]
            public bool Upgraded
            {
                get
                {
                    return this["Upgraded"] != null ? (bool)this["Upgraded"] : false;
                }
                set
                {
                    this["Upgraded"] = value;
                }
            }
        }

    }
}

MainWindow.xaml

<UserControls:RestorableWindow x:Class="RestorableWindowTest.MainWindow"
        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:local="clr-namespace:RestorableWindowTest"
        xmlns:UserControls="clr-namespace:UserControls"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        
    </Grid>
</UserControls:RestorableWindow>

MainWindow.xaml.cs

namespace RestorableWindowTest
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : UserControls.RestorableWindow
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

この状態で、一度起動→終了を行うと、
%USERPROFILE%appDataLocal {会社名}{exe名+ハッシュ値}{アセンブリバージョン}user.config
が作成されている。

バージョンアップを行うと、別ディレクトリにuser.configが生成されることになるが、上記コードでは「アップグレードフラグ」がfalseの時は、前のバージョンから情報を引き継ぐようにしてある。