using System.Windows; namespace SheetMe.Designer.Services; /// /// 창 크기·위치·패널 폭을 실행 사이에 기억한다. /// /// 왜 필요한가. 전에는 아무것도 기억하지 않아서, 창을 옮기거나 패널 폭을 조절해도 /// 다음 실행에 원래대로 돌아갔다. 하루 종일 쓰는 사람에게 그 조작은 매일 반복하는 일이 된다. /// /// 복원값을 반드시 클램프한다. 저장된 사각형이 지금 화면에 없을 수 있다 — /// 회사에서 두 번째 모니터를 쓰다 집에서 노트북만 켜면, 저장된 위치가 존재하지 않는 화면을 가리킨다. /// 이 앱은 WindowStyle="None" 이라 OS 캡션이 없고 제목 표시줄을 직접 그리므로, /// 창이 화면 위쪽으로 나가면 메뉴·저장·닫기가 전부 손에 닿지 않는다(마우스로 되돌릴 방법이 없다). /// 그래서 복원 전에 작업영역과 교차 검사를 한다. /// /// 현장 단말이 1920x1080 이상이라 기본 크기(1440x920)로는 이 문제가 나지 않는다. /// 그래도 클램프를 두는 이유는, 저장을 시작하는 순간 사용자가 만든 값이 복원되기 때문이다 — /// 기본값이 안전한 것과 저장된 값이 안전한 것은 다른 문제다. /// public static class WindowPlacement { #region Member Fields private const string KeyLeft = "Window.Left"; private const string KeyTop = "Window.Top"; private const string KeyWidth = "Window.Width"; private const string KeyHeight = "Window.Height"; private const string KeyMaximized = "Window.Maximized"; private const string KeyLeftPanel = "Panel.Left.Width"; private const string KeyRightPanel = "Panel.Right.Width"; /// 제목 표시줄을 잡을 수 있어야 하는 최소 노출 폭·높이 private const double Grip = 120; #endregion #region Methods /// /// 저장된 사각형을 복원한다 — 화면 밖이면 버리고 기본값을 쓴다. /// Show() 전에 불러야 한다(보인 뒤에 옮기면 한 프레임 번쩍인다). /// public static void Restore(Window window) { var width = UserPrefs.GetDouble(KeyWidth, window.Width); var height = UserPrefs.GetDouble(KeyHeight, window.Height); var left = UserPrefs.GetDouble(KeyLeft, double.NaN); var top = UserPrefs.GetDouble(KeyTop, double.NaN); window.Width = Math.Max(window.MinWidth, width); window.Height = Math.Max(window.MinHeight, height); if (double.IsNaN(left) || double.IsNaN(top)) { return; } var work = SystemParameters.WorkArea; var rect = new Rect(left, top, window.Width, window.Height); // 어느 화면에도 충분히 걸치지 않으면 저장값을 버린다. 여러 모니터를 정확히 다루려면 // 모니터 열거가 필요하지만, 주 화면 작업영역과의 교차만으로도 '손에 닿지 않는 창'은 막힌다. if (rect.Right < work.Left + Grip || rect.Left > work.Right - Grip || rect.Bottom < work.Top + Grip || rect.Top > work.Bottom - Grip) { return; } window.WindowStartupLocation = WindowStartupLocation.Manual; window.Left = left; // 위로는 절대 넘기지 않는다 — 제목 표시줄이 화면 밖으로 나가면 되돌릴 수 없다 window.Top = Math.Max(work.Top, top); if (UserPrefs.GetBool(KeyMaximized, false)) { window.WindowState = WindowState.Maximized; } } /// 지금 상태를 저장한다 — 창을 닫을 때 한 번 부른다 public static void Save(Window window, double leftPanel, double rightPanel) { UserPrefs.SetBool(KeyMaximized, window.WindowState == WindowState.Maximized); // 최대화 중이면 Left/Top/Width/Height 는 '복원했을 때의 값'이라 그대로 쓰는 것이 맞다. // 최소화 중이면 그 값이 쓰레기이므로 건드리지 않는다. if (window.WindowState == WindowState.Minimized) { return; } var bounds = window.WindowState == WindowState.Maximized ? window.RestoreBounds : new Rect(window.Left, window.Top, window.ActualWidth, window.ActualHeight); UserPrefs.SetDoubles( (KeyLeft, bounds.Left), (KeyTop, bounds.Top), (KeyWidth, bounds.Width), (KeyHeight, bounds.Height), (KeyLeftPanel, leftPanel), (KeyRightPanel, rightPanel)); } /// 패널 폭 복원 — 0 이면 XAML 기본값을 그대로 둔다는 뜻 public static double LeftPanelWidth(double fallback) => UserPrefs.GetDouble(KeyLeftPanel, fallback); public static double RightPanelWidth(double fallback) => UserPrefs.GetDouble(KeyRightPanel, fallback); #endregion }