프로그래밍_Programing/C#

WPF에서 Window 창 위치의 보존&복원을 해보자

NineTIN 2017. 1. 17. 18:01



데스크톱 응용 프로그램에서 창 위치를 저장하고 싶다는 클라이언트의 요구가 있었습니다.

그러나 구현하는 것이 큰 골칫거리 입니다. 

예를들어 최대화를 하면 최대화하기 직전의 윈도우의 위치와 크기도 보존 해두지 않으면 안된다거나

세세한 동작까지 신경쓰면 여간 일이 많은게 아닙니다.

(+귀찮음)


조금 찾아 본 결과 MSDN에 해당 소스 코드가 있었기에 


SetWindowPlacement

GetWindowPlacement


이를 기초로 

Window창 위치의 보존&복원 기능을 구현 해보고자 합니다.


그전에 WPF도 어찌되었든 Window에서 움직이므로

.NetF FrameWork의 네이티브 라이브러리를 참조할 필요가 있습니다.

네이티브 라이브러리 기능을 호출하는 이것을 P/Invoke (Platform Invoke : 플랫폼 호출)이라고합니다.



    public class NativeMethods
    {
        [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,
    }

WINDOWPLACEMENT structure 구조체에 윈도우의 창 위치, 사이즈, 상태를 넣어 둘 수 있게 합니다.

그럼 이제 저장을 해야 하는데

MSDN 샘플 에서는 Settings.settings를 쓰고 있습니다.




근데 이러면 재미 없으니까 ^^;

보존 방법을 외부에서 설정 할 수 있도록 하겠습니다.


public interface IWindowSettings
{
    WINDOWPLACEMENT? Placement { get; set; }
    void Reload();
    void Save();
}
 
public class WindowSettings : ApplicationSettingsBase, IWindowSettings
{
    public WindowSettings(Window window) : base(window.GetType().FullName) { }
 
    [UserScopedSetting]
    public WINDOWPLACEMENT? Placement
    {
        get { return this["Placement"] != null ? (WINDOWPLACEMENT?)(WINDOWPLACEMENT)this["Placement"] : null; }
        set { this["Placement"] = value; }
    }
}

그럼 이제 대략 준비는 끝났고




Window창이 자동적으로 보존・복원 되도록 합니다.


public class RestorableWindow : Window
{
    #region WindowSettings 의존관계 프로퍼티
 
    public IWindowSettings WindowSettings
    {
        get { return (IWindowSettings)this.GetValue(WindowSettingsProperty); }
        set { this.SetValue(WindowSettingsProperty, value); }
    }
    public static readonly DependencyProperty WindowSettingsProperty =
        DependencyProperty.Register("WindowSettings", typeof(IWindowSettings), typeof(RestorableWindow), new UIPropertyMetadata(null));
 
    #endregion
 
 
    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);
 
        // 외부에서 윈도의 복원 설정이 없을 경우 기본 구현을 사용
        if (this.WindowSettings == null)
        {
            this.WindowSettings = new WindowSettings(this);
        }
 
        this.WindowSettings.Reload();
 
        if (this.WindowSettings.Placement.HasValue)
        {
            var hwnd = new WindowInteropHelper(this).Handle;
            var placement = this.WindowSettings.Placement.Value;
            placement.length = Marshal.SizeOf(typeof(WINDOWPLACEMENT));
            placement.flags = 0;
            placement.showCmd = (placement.showCmd == SW.SHOWMINIMIZED) ? SW.SHOWNORMAL : placement.showCmd;
 
            NativeMethods.SetWindowPlacement(hwnd, ref placement);
        }
    }
 
    protected override void OnClosing(CancelEventArgs e)
    {
        base.OnClosing(e);
 
        if (!e.Cancel)
        {
            WINDOWPLACEMENT placement;
            var hwnd = new WindowInteropHelper(this).Handle;
            NativeMethods.GetWindowPlacement(hwnd, out placement);
 
            this.WindowSettings.Placement = placement;
            this.WindowSettings.Save();
        }
    }
}


WindowSettings 의존관계 프로퍼티는 창 위치의 보존・복원 수단을 외부에서주기위한 것입니다. 

아무것도 지정하지 않으면 (= null) ApplicationSettingsBase를 사용한 기본 구현됩니다.


그리고 P/Invoke시는 SourceInitiazlized이벤트(창을 핸들링 할 수 있는 타이밍)에서 

구성 정보를 복원하고 SetWindowPlacement 함수에서 윈도우의 위치를 ​​설정합니다.


WINDOWPLACEMENT의 length 멤버는 반드시 sizeof(WINDOWPLACEMENT)가 설정되어 있어야합니다 (안 그러면 실패합니다). 


flags는 딱히 0으로 해도 상관없습니다. 


showCmd에는 복원 된 값이 SW_SHOWMINIMIZED (최소화 상태) 인 경우, 

즉 지난번 시작할 때 최소화 된 채 종료 한 경우, SW_SHOWNORMAL (표준 상태)에서 표시하는 처리를 추가합니다.


마찬가지로, 윈도우가 닫히기 직전에하고 취소되지 않은 경우 GetWindowPlacement 함수에서 현재 윈도우 상태를 검색하고 저장합니다.



설정 파일은 %UserProfile%\AppData\Local\프로젝트명user.config파일에 저장됩니다.