From 4150ebe1d7c2dfca2ffca96a1001934526ed6f61 Mon Sep 17 00:00:00 2001 From: Msystech Date: Fri, 14 Aug 2026 16:50:24 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=9D=84=20=EA=B2=8C=EC=9D=B4=ED=8A=B8=EC=97=90=20=EB=84=A3?= =?UTF-8?q?=EA=B3=A0,=20=EC=B5=9C=EC=86=8C=20=ED=81=AC=EA=B8=B0=EB=A5=BC?= =?UTF-8?q?=20=EB=8B=A8=EC=9D=BC=20=EC=B6=9C=EC=B2=98=EB=A1=9C,=20?= =?UTF-8?q?=EC=B0=BD=20=ED=81=AC=EA=B8=B0=EB=A5=BC=20=EA=B8=B0=EC=96=B5?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 재설계 1단계 계속. 메인 셸이 게이트에 아예 없었다. 사용자가 하루 종일 보는 화면만 자동 검증 밖이었다. 넣고 나서 글자 31개만 검사되는 것을 보고 빈 껍데기를 찍고 있다는 것을 알았다 — 컨트롤을 놓고 하나를 골라 인스펙터·레이어를 채우니 81~95개가 된다. 세 크기·탭으로 찍는다: 기본 1440x920, 현장 최대화 1920x1032, 도구 상자 탭. Loaded 커맨드(서식 목록 DB 조회)는 진단 모드에서 돌지 않게 막았다 — 게이트 결과가 접속 상태에 따라 달라지면 안 된다. 게이트가 게이트의 오류를 잡았다. 메인 화면을 넣자 세로잘림 30건이 떴는데 전부 오탐이었다. DesiredSize 는 Margin 을 포함하고 RenderSize 는 포함하지 않아서, 세로 Margin 이 있는 TextBlock 이 예외 없이 걸린 것이다(도구 상자 그룹 머리가 "필요 27 > 실제 14" 였고 그 13 이 Margin). Margin 을 빼고 비교하도록 고쳤다. 이 검사기에서 측정이 나를 정정한 세 번째다. --maxrect 가 최소 크기를 베껴 두고 있었다. ProbeMinWidth=940 / ProbeMinHeight=640 / ProbeBorder=6 은 MainView.xaml 의 복사본이라, 최소 크기를 바꾸면 이 검사가 옛 값을 계속 검사하며 계속 통과한다. 실제 MainView 를 만들어 XAML 이 정한 값을 읽는다. 창 크기·위치·상태·두 패널 폭을 기억한다. UserPrefs 가 Dictionary<string,bool> 이라 숫자를 못 담던 것이 원인이었다. JsonElement 로 바꿔 이미 깔린 prefs.json 이 살아남게 했다 — string 이나 double 로 바꾸면 역직렬화가 터져 기존 취향이 통째로 초기화된다. 복원값은 반드시 클램프한다. 이 앱은 WindowStyle=None 이라 제목 표시줄을 직접 그리므로, 저장된 위치가 지금 화면에 없으면(회사에서 두 번째 모니터, 집에서 노트북) 메뉴·저장·닫기가 전부 손에 닿지 않고 마우스로 되돌릴 방법이 없다. 기본 크기로는 이 문제가 안 나지만, 저장을 시작하는 순간 복원되는 것은 사용자가 만든 값이다. 닫기를 물렀을 때는 저장하지 않는다 — 조작하지 않은 값이 남는다. 게이트: 테스트 277/277, --dialog-shots 대조군 4/4 · 글자 2,465개 검사 · 넘침 0건, --maxrect 0실패(MainView 실측 940x640 테두리 6), --cleartype 11/11, --edit-smoke 0실패, --modal-check 0실패, 빌드 경고 0, --db-smoke 1271건 diff 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일. Co-Authored-By: Claude Opus 5 --- src/SheetMe.Designer/App.xaml.cs | 1 + .../Diagnostics/DialogShots.cs | 59 +++++++++++ .../Diagnostics/MaximizeCheck.cs | 28 ++++-- .../Diagnostics/OverflowCheck.cs | 10 +- .../Services/StartupArguments.cs | 8 ++ src/SheetMe.Designer/Services/UserPrefs.cs | 56 +++++++++-- .../Services/WindowPlacement.cs | 99 +++++++++++++++++++ src/SheetMe.Designer/Views/MainView.xaml | 4 +- src/SheetMe.Designer/Views/MainView.xaml.cs | 22 +++++ 9 files changed, 270 insertions(+), 17 deletions(-) create mode 100644 src/SheetMe.Designer/Services/WindowPlacement.cs diff --git a/src/SheetMe.Designer/App.xaml.cs b/src/SheetMe.Designer/App.xaml.cs index 4bf80bf..9f4d246 100644 --- a/src/SheetMe.Designer/App.xaml.cs +++ b/src/SheetMe.Designer/App.xaml.cs @@ -41,6 +41,7 @@ public partial class App : Application if (Services.StartupArguments.IsDiagnostic(e.Args)) { diagnosticMode = true; + Services.StartupArguments.DiagnosticMode = true; // 기본 ShutdownMode 는 OnLastWindowClose 다. 창을 띄웠다 전부 닫는 진단 // (--maxrect, --edit-smoke, --dialog-shots …)은 마지막 창을 닫는 순간 WPF 가 먼저 // Shutdown(0) 을 걸어 버리고, 뒤이은 Shutdown(RunDiagnostic(...)) 인자는 무시된다 — diff --git a/src/SheetMe.Designer/Diagnostics/DialogShots.cs b/src/SheetMe.Designer/Diagnostics/DialogShots.cs index 0df7f3a..c2fcf7d 100644 --- a/src/SheetMe.Designer/Diagnostics/DialogShots.cs +++ b/src/SheetMe.Designer/Diagnostics/DialogShots.cs @@ -120,6 +120,13 @@ public static class DialogShots var designer = SampleDesigner(); return new List<(string, Func)> { + // 메인 셸 — 이 게이트에 아예 없었다. 사용자가 하루 종일 보는 화면만 자동 검증 밖이었다. + // 두 크기로 찍는다: 기본 창(1440×920)과 현장 단말 최대화 크기(1920×1032). + // 현장이 1920×1080 이상뿐이라는 답을 받았으므로 그 사각형이 실제 기준이다. + ("00-main-default", () => NewMainShell(1440, 920)), + ("00b-main-1920", () => NewMainShell(1920, 1032)), + // 도구 상자 탭은 타일 라벨이 좁은 2열에 들어가므로 잘림이 가장 잘 나는 자리다 + ("00c-main-toolbox", () => NewMainShell(1440, 920, leftTab: 2)), ("01-sheet-open", () => new Views.SheetOpenDialogView(_ => SampleSheets())), ("02-sheet-history", () => new Views.SheetHistoryDialogView("S999", "표본 서식", SampleVersions())), ("03-query-editor", () => NewQueryEditor(designer, @@ -203,6 +210,58 @@ public static class DialogShots /// MainViewModel 을 통째로 만들면 DB 조회가 딸려 오므로, 패널이 읽는 CurrentDesigner 하나만 /// 들고 있는 최소 DataContext 를 세워 준다. /// + /// + /// 메인 셸을 주어진 크기로 만든다. + /// + /// Loaded 커맨드(서식 목록 DB 조회)는 때문에 돌지 않으므로 + /// 접속 없이도 같은 결과가 나온다. 문서 한 장을 넣어 캔버스·레이어·인스펙터가 빈 상태가 아니게 한다 — + /// 빈 껍데기를 찍으면 잘림을 볼 수 없다. + /// + private static Window NewMainShell(double width, double height, int leftTab = 1) + { + var window = new Views.MainView + { + Width = width, + Height = height, + Left = -10000, + Top = -10000, + WindowStartupLocation = WindowStartupLocation.Manual, + }; + if (window.DataContext is not MainViewModel shell) + { + return window; + } + shell.NewFileCommand?.Execute(null); + shell.SelectedLeftTabIndex = leftTab; + + // 컨트롤을 놓고 하나를 고른다 — 이걸 안 하면 인스펙터가 비어 있고 레이어에 행이 없어서 + // 검사할 글자가 31개밖에 안 나온다(빈 껍데기를 찍으면 잘림을 볼 수 없다). + // 이름이 긴 컨트롤을 섞는다 — 좁은 칸에서 잘리는 것은 짧은 이름이 아니라 긴 이름이다. + if (shell.CurrentDesigner is { } designer) + { + foreach (var type in new[] { "Label", "TextBox", "CheckBox", "MDataTable", "MPictureBox" }) + { + designer.AddPaletteItemAtCenter(type); + } + foreach (var control in designer.Pages[0].Controls) + { + if (control.Type == "Label") + { + control.Model.Id = "아주긴이름을가진라벨컨트롤_LAB_00123"; + control.Model.Props.SetText("Text", + "잘림을 보려면 충분히 긴 문구가 필요하다 — 간호·간병통합서비스 병동"); + control.NotifyAllChanged(); + break; + } + } + if (designer.Pages[0].Controls.FirstOrDefault() is { } first) + { + designer.Selection.SetSingle(first); + } + } + return window; + } + private static Window HostLayerPanel(DesignerViewModel designer, LayerShotMode mode) { designer.Selection.Clear(); diff --git a/src/SheetMe.Designer/Diagnostics/MaximizeCheck.cs b/src/SheetMe.Designer/Diagnostics/MaximizeCheck.cs index 6aeee43..ca9f5d5 100644 --- a/src/SheetMe.Designer/Diagnostics/MaximizeCheck.cs +++ b/src/SheetMe.Designer/Diagnostics/MaximizeCheck.cs @@ -36,9 +36,20 @@ public static class MaximizeCheck private const uint AbmGetAutoHideBarEx = 0x0000000B; private const int AbsAutoHide = 0x00000001; - private const int ProbeMinWidth = 940; - private const int ProbeMinHeight = 640; - private const double ProbeBorder = 6; + /// + /// 검사에 쓰는 최소 크기·테두리는 실제 MainView 에서 읽는다. + /// + /// 전에는 940 / 640 / 6 을 상수로 베껴 두었다. 그러면 MainView 의 최소 크기를 바꿨을 때 + /// 이 검사가 옛 값을 계속 검사하며 계속 통과한다 — 게이트가 있는데 아무것도 지키지 않는 상태다. + /// 창을 하나 만들어(띄우지 않는다) XAML 이 정한 값을 그대로 가져온다. + /// + private static (double MinWidth, double MinHeight, double Border) Real() + { + var main = new Views.MainView(); + var chrome = WindowChrome.GetWindowChrome(main); + var border = chrome?.ResizeBorderThickness.Left ?? 6; + return (main.MinWidth, main.MinHeight, border); + } [StructLayout(LayoutKind.Sequential)] private struct RECT @@ -152,7 +163,9 @@ public static class MaximizeCheck var failures = 0; // 프로브는 WindowStyle=None 이어야 한다 — 기본값이면 Attach 의 가드에 걸려 아무 일도 안 일어난다 - var probe = NewProbe(ProbeMinWidth, ProbeMinHeight); + var real = Real(); + report.AppendLine($"MainView 실측 — 최소 {real.MinWidth}x{real.MinHeight}, 리사이즈 테두리 {real.Border}"); + var probe = NewProbe(real.MinWidth, real.MinHeight); var handle = new WindowInteropHelper(probe).EnsureHandle(); Services.MaximizeToWorkArea.Attach(probe); @@ -226,8 +239,9 @@ public static class MaximizeCheck private static int Live(StringBuilder report, MonitorFacts monitor) { var failures = 0; - var chrome = new WindowChrome { CaptionHeight = 40, ResizeBorderThickness = new Thickness(ProbeBorder) }; - var probe = NewProbe(ProbeMinWidth, ProbeMinHeight); + var real = Real(); + var chrome = new WindowChrome { CaptionHeight = 40, ResizeBorderThickness = new Thickness(real.Border) }; + var probe = NewProbe(real.MinWidth, real.MinHeight); WindowChrome.SetWindowChrome(probe, chrome); probe.WindowStartupLocation = WindowStartupLocation.Manual; probe.Left = monitor.Bounds.Left + 40; @@ -263,7 +277,7 @@ public static class MaximizeCheck var handle = new WindowInteropHelper(probe).Handle; probe.MinHeight = workHeight + 200; var cramped = Ask(handle); - probe.MinHeight = ProbeMinHeight; + probe.MinHeight = real.MinHeight; probe.Close(); report.AppendLine($"좁은 화면 흉내 — 최소 높이 {workHeight + 200} 요구, 작업영역 높이 {workHeight}"); diff --git a/src/SheetMe.Designer/Diagnostics/OverflowCheck.cs b/src/SheetMe.Designer/Diagnostics/OverflowCheck.cs index 2c6c748..6637e70 100644 --- a/src/SheetMe.Designer/Diagnostics/OverflowCheck.cs +++ b/src/SheetMe.Designer/Diagnostics/OverflowCheck.cs @@ -181,10 +181,16 @@ internal static class OverflowCheck } // ② 세로로 잘림 — 줄바꿈이 일어났는데 높이를 안 준 경우다. - if (text.DesiredSize.Height - text.RenderSize.Height > Slack) + // + // Margin 을 빼야 한다. DesiredSize 는 Margin 을 포함하고 RenderSize 는 포함하지 않는다. + // 그래서 그냥 비교하면 세로 Margin 이 있는 TextBlock 이 예외 없이 걸린다 — + // 처음에 그렇게 만들어 30건이 떴고, 전부 오탐이었다 + // (도구 상자 그룹 머리가 "필요 27 > 실제 14" 로 나왔는데 그 13 이 Margin 이었다). + var needHeight = text.DesiredSize.Height - text.Margin.Top - text.Margin.Bottom; + if (needHeight - text.RenderSize.Height > Slack) { found.Add(new Violation("세로잘림", label, - $"{owner} 필요 {text.DesiredSize.Height:0.#} > 실제 {text.RenderSize.Height:0.#}")); + $"{owner} 필요 {needHeight:0.#} > 실제 {text.RenderSize.Height:0.#}")); } // ③ 자기를 자르는 조상 밖으로 삐져나갔는가. diff --git a/src/SheetMe.Designer/Services/StartupArguments.cs b/src/SheetMe.Designer/Services/StartupArguments.cs index ae855a5..54e41fd 100644 --- a/src/SheetMe.Designer/Services/StartupArguments.cs +++ b/src/SheetMe.Designer/Services/StartupArguments.cs @@ -16,6 +16,14 @@ public static class StartupArguments { #region Methods /// 진단 플래그 기동인지 — 첫 인자가 "--" 로 시작 + /// + /// 이번 실행이 진단 모드인가. App 이 기동 때 한 번 정하고, 화면 쪽에서 읽는다. + /// + /// 화면이 이것을 봐야 하는 이유: 게이트가 MainView 를 띄울 때 Loaded 커맨드가 DB 를 치면 + /// 검사 결과가 접속 상태에 따라 달라진다. 게이트는 DB 없이 같은 답을 내야 한다. + /// + public static bool DiagnosticMode { get; set; } + public static bool IsDiagnostic(string[] args) => args.Length > 0 && args[0].StartsWith("--", StringComparison.Ordinal); diff --git a/src/SheetMe.Designer/Services/UserPrefs.cs b/src/SheetMe.Designer/Services/UserPrefs.cs index 16f11dd..29e89ba 100644 --- a/src/SheetMe.Designer/Services/UserPrefs.cs +++ b/src/SheetMe.Designer/Services/UserPrefs.cs @@ -16,7 +16,14 @@ public static class UserPrefs private static readonly string SettingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "SheetMe", "prefs.json"); - private static Dictionary values = new(StringComparer.Ordinal); + /// + /// 값 형식이 인 이유: 전에는 Dictionary<string, bool> 였고 + /// 그래서 숫자를 담을 수 없었다(창 크기·패널 폭을 저장하지 못한 원인이다). + /// 여기서 형식을 string 이나 double 로 바꾸면 이미 깔린 prefs.json 이 + /// 역직렬화에서 터져 기존 취향이 통째로 초기화된다. + /// JsonElement 는 false640 을 모두 그대로 받으므로 옛 파일이 살아남는다. + /// + private static Dictionary values = new(StringComparer.Ordinal); private static bool loaded; #endregion @@ -25,21 +32,58 @@ public static class UserPrefs public static bool GetBool(string key, bool fallback) { EnsureLoaded(); - return values.TryGetValue(key, out var value) ? value : fallback; + return values.TryGetValue(key, out var value) + && value.ValueKind is JsonValueKind.True or JsonValueKind.False + ? value.GetBoolean() + : fallback; } /// 불리언 취향 저장 public static void SetBool(string key, bool value) { EnsureLoaded(); - if (values.TryGetValue(key, out var current) && current == value) + if (GetBool(key, !value) == value && values.ContainsKey(key)) { return; } - values[key] = value; + values[key] = JsonSerializer.SerializeToElement(value); Save(); } + /// 숫자 취향 읽기 — 미저장이거나 형식이 다르면 기본값 + public static double GetDouble(string key, double fallback) + { + EnsureLoaded(); + return values.TryGetValue(key, out var value) + && value.ValueKind == JsonValueKind.Number + && value.TryGetDouble(out var number) + && double.IsFinite(number) + ? number + : fallback; + } + + /// + /// 숫자 취향 여러 개를 한 번에 저장 — 창을 닫을 때 6개를 각각 저장하면 파일을 6번 쓴다. + /// + public static void SetDoubles(params (string Key, double Value)[] entries) + { + EnsureLoaded(); + var changed = false; + foreach (var (key, value) in entries) + { + if (!double.IsFinite(value) || Math.Abs(GetDouble(key, double.NaN) - value) < 0.5) + { + continue; + } + values[key] = JsonSerializer.SerializeToElement(Math.Round(value, 1)); + changed = true; + } + if (changed) + { + Save(); + } + } + private static void EnsureLoaded() { if (loaded) @@ -51,8 +95,8 @@ public static class UserPrefs { if (File.Exists(SettingsPath)) { - values = JsonSerializer.Deserialize>(File.ReadAllText(SettingsPath)) - ?? new Dictionary(StringComparer.Ordinal); + values = JsonSerializer.Deserialize>(File.ReadAllText(SettingsPath)) + ?? new Dictionary(StringComparer.Ordinal); } } catch diff --git a/src/SheetMe.Designer/Services/WindowPlacement.cs b/src/SheetMe.Designer/Services/WindowPlacement.cs new file mode 100644 index 0000000..14a2561 --- /dev/null +++ b/src/SheetMe.Designer/Services/WindowPlacement.cs @@ -0,0 +1,99 @@ +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 +} diff --git a/src/SheetMe.Designer/Views/MainView.xaml b/src/SheetMe.Designer/Views/MainView.xaml index 03bf271..fd777b5 100644 --- a/src/SheetMe.Designer/Views/MainView.xaml +++ b/src/SheetMe.Designer/Views/MainView.xaml @@ -314,13 +314,13 @@ - + - + diff --git a/src/SheetMe.Designer/Views/MainView.xaml.cs b/src/SheetMe.Designer/Views/MainView.xaml.cs index 9ebc3a4..4712f0d 100644 --- a/src/SheetMe.Designer/Views/MainView.xaml.cs +++ b/src/SheetMe.Designer/Views/MainView.xaml.cs @@ -17,6 +17,16 @@ public partial class MainView : Window // StaysOpen=False 팝업은 바깥 클릭(버튼 포함)으로 먼저 닫힌다 — 토글 버튼 재클릭이 곧바로 다시 열지 않게 닫힌 시각 기록 AddControlPopup.Closed += (_, _) => addControlPopupClosedAt = Environment.TickCount; ZoomPopup.Closed += (_, _) => zoomPopupClosedAt = Environment.TickCount; + + // 진단(게이트)은 창 크기를 스스로 정한다 — 저장된 취향이 끼어들면 샷 크기가 PC 마다 달라진다 + if (StartupArguments.DiagnosticMode) + { + return; + } + // Show 전에 복원해야 한 프레임 번쩍이지 않는다 + WindowPlacement.Restore(this); + LeftColumn.Width = new GridLength(WindowPlacement.LeftPanelWidth(LeftColumn.Width.Value)); + RightColumn.Width = new GridLength(WindowPlacement.RightPanelWidth(RightColumn.Width.Value)); } /// @@ -36,6 +46,12 @@ public partial class MainView : Window private void OnWindowLoaded(object sender, RoutedEventArgs e) { ThemeMenuItem.IsChecked = !ThemeManager.IsLight; + // 진단(게이트) 실행에서는 Loaded 커맨드를 돌리지 않는다 — 서식 목록 DB 조회가 붙어 있어서 + // 검사 결과가 접속 상태와 조회 결과에 따라 달라진다. 게이트는 DB 없이 같은 답을 내야 한다. + if (StartupArguments.DiagnosticMode) + { + return; + } if (DataContext is MainViewModel viewModel && viewModel.LoadedCommand?.CanExecute(null) == true) { viewModel.LoadedCommand.Execute(null); @@ -127,6 +143,12 @@ public partial class MainView : Window if (DataContext is MainViewModel viewModel && !viewModel.IsShuttingDown && !viewModel.ConfirmCloseAll()) { e.Cancel = true; + return; + } + // 취소되지 않은 뒤에만 저장한다 — 닫기를 물렀는데 크기가 기억되면 조작하지 않은 값이 남는다 + if (!StartupArguments.DiagnosticMode) + { + WindowPlacement.Save(this, LeftColumn.ActualWidth, RightColumn.ActualWidth); } }