From ac36f93a03e6a1f73218d699b0b06f6c28270524 Mon Sep 17 00:00:00 2001 From: Msystech Date: Thu, 13 Aug 2026 10:44:52 +0900 Subject: [PATCH] =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=B0=B0?= =?UTF-8?q?=EA=B2=BD=EC=83=89=20=EA=B8=B0=EB=B3=B8=20=ED=91=9C=EC=8B=9C?= =?UTF-8?q?=EB=A5=BC=20White=20=E2=86=92=20Control=20=EB=A1=9C=20(?= =?UTF-8?q?=EB=A0=88=EA=B1=B0=EC=8B=9C=20PropertyGrid=20=EC=99=80=20?= =?UTF-8?q?=EC=9D=BC=EC=B9=98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 페이지 루트에 BackColor 가 없을 때 내가 임의로 'White' 를 지어내 보여 주고 있었다. 페이지 루트는 WinForms UserControl 이고 그 BackColor 기본값은 SystemColors.Control 이라 레거시 PropertyGrid 는 'Control' 을 표시한다 — 사용자가 짚은 그대로다. 근거를 위해 --db-pageprops 진단을 신설해 전수 집계했다(디자인 1,271건 / 페이지 1,816장). BackColor White 1,487장(81.9%) · (없음) 230장(12.7%) · Window 91장(5.0%) ButtonHighlight/ControlLightLight/MenuBar/Control/ControlLight 8장 Font (없음) 95.6% — 없을 때 굴림 9pt 로 보이는 현행 기본값이 맞다 기타 키 Margin 9장, BorderStyle 6장, DoNotPrintThisPage 2장 … 12.7% 는 예외가 아니라 흔한 경우다. 표시만 고치고 저장은 하지 않는다 — 지어낸 값이 XML 에 새로 생기면 왕복 diff 0 이 조용히 깨진다. 이 규약을 두 겹으로 못 박았다. - 편집 스모크: 값 없는 페이지에서 Control 표시 / 미저장 / 용지는 흰색 - --db-pageprops: BackColor 없는 실제 서식 40건을 열어 XML 이 그대로인지 대조 렌더는 흰색을 유지한다. 사용자 확인 결과 레거시 실행 시에도 흰색으로 나오고, #F0F0F0 종이는 라이트 테마 캔버스 배경(#D8D8D8)과 1.13:1 이라 용지 경계가 사라진다. 대신 값이 없을 때는 라벨 툴팁으로 '저장된 값 없음 / 실행·인쇄는 흰색 / 바꾸면 그때 저장'을 알린다 — 안 그러면 "Control 인데 왜 희게 보이지?" 로 다시 읽힌다. 부수: PropertyRowViewModel 에 Hint 를 추가하고 색상 행 라벨 툴팁에 배선. 회귀: 테스트 124/124, 편집 스모크 실패 0, DB 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 바이트 동일. Co-Authored-By: Claude Opus 5 --- src/SheetMe.Designer/App.xaml.cs | 5 + src/SheetMe.Designer/Diagnostics/DbSmoke.cs | 123 ++++++++++++++++++ src/SheetMe.Designer/Diagnostics/EditSmoke.cs | 19 ++- .../Inspector/InspectorViewModel.cs | 26 +++- .../ViewModels/Inspector/PropertyRows.cs | 9 ++ src/SheetMe.Designer/Views/InspectorView.xaml | 3 +- 6 files changed, 180 insertions(+), 5 deletions(-) diff --git a/src/SheetMe.Designer/App.xaml.cs b/src/SheetMe.Designer/App.xaml.cs index 46327dd..62a18ba 100644 --- a/src/SheetMe.Designer/App.xaml.cs +++ b/src/SheetMe.Designer/App.xaml.cs @@ -157,6 +157,11 @@ public partial class App : Application return Diagnostics.DbSmoke.RunSpreadDump(args[1], args[2]); } + if (args.Length >= 2 && args[0] == "--db-pageprops") + { + return Diagnostics.DbSmoke.RunPagePropsReport(args[1]); + } + if (args.Length >= 3 && args[0] == "--db-xml") { return Diagnostics.DbSmoke.RunDesignXml(args[1], args[2]); diff --git a/src/SheetMe.Designer/Diagnostics/DbSmoke.cs b/src/SheetMe.Designer/Diagnostics/DbSmoke.cs index a719393..dac08e1 100644 --- a/src/SheetMe.Designer/Diagnostics/DbSmoke.cs +++ b/src/SheetMe.Designer/Diagnostics/DbSmoke.cs @@ -104,6 +104,129 @@ public static class DbSmoke /// 권한 게이트 영향 범위(--db-gate) — ShtUsrDesYon 값별 서식 수를 집계한다. /// 게이트 도입 전에 "갑자기 서식이 안 열린다"는 문의 규모를 미리 알기 위한 읽기 전용 진단. /// + /// + /// 페이지 루트(MDesignerHost) 속성 전수 집계 — 페이지 속성 편집기의 기본값 근거. + /// + /// 레거시 PropertyGrid 는 속성이 없으면 WinForms 기본값을 보여 준다(BackColor 는 Control). + /// 우리가 어떤 키를 얼마나 마주치는지, 어떤 값이 실제로 쓰이는지 알아야 기본 표시를 정할 수 있다. + /// + public static int RunPagePropsReport(string reportPath) + { + var lines = new List(); + try + { + var config = ConfigService.Current; + var store = new OracleLegacyFormStore(config.ConnectionString); + var serializer = new LegacyXmlSerializer(); + + var sheets = store.ListSheets(null, max: 3000).Where(s => s.HasDesign).ToList(); + var keyCounts = new Dictionary(StringComparer.Ordinal); + var backColors = new Dictionary(StringComparer.Ordinal); + var fonts = new Dictionary(StringComparer.Ordinal); + var pages = 0; + var docs = 0; + + foreach (var sheet in sheets) + { + try + { + var raw = store.LoadActiveDesignRaw(sheet.ShtCod); + if (raw is null) + { + continue; + } + docs++; + foreach (var page in serializer.Read(raw.Value.Xml).Pages) + { + pages++; + foreach (var key in page.Root.Props.Keys) + { + keyCounts[key] = keyCounts.GetValueOrDefault(key) + 1; + } + var back = page.Root.Props.GetText("BackColor") ?? "(없음)"; + backColors[back] = backColors.GetValueOrDefault(back) + 1; + var font = page.Root.Props.GetText("Font") ?? "(없음)"; + fonts[font] = fonts.GetValueOrDefault(font) + 1; + } + } + catch (Exception ex) + { + lines.Add($" 실패 {sheet.ShtCod}: {ex.Message}"); + } + } + + // 속성 패널을 '여는 것만으로' 문서가 바뀌면 안 된다. + // 값이 없는 페이지에 기본값을 지어내 저장해 버리면 왕복 diff 0 이 조용히 깨진다. + var mutated = new List(); + var checkedDocs = 0; + foreach (var sheet in sheets) + { + if (checkedDocs >= 40) + { + break; + } + try + { + var raw = store.LoadActiveDesignRaw(sheet.ShtCod); + if (raw is null) + { + continue; + } + var document = serializer.Read(raw.Value.Xml); + // 값이 없는 페이지를 가진 서식만 본다 — 지어낸 값이 생기는지가 관심사다 + if (!document.Pages.Any(p => p.Root.Props.GetText("BackColor") is null)) + { + continue; + } + checkedDocs++; + var before = serializer.Write(document); + var designer = new ViewModels.DesignerViewModel(document); + designer.ActivatePage(designer.Pages[0]); + var after = serializer.Write(designer.Document); + if (!string.Equals(before, after, StringComparison.Ordinal)) + { + mutated.Add($" {sheet.ShtCod} {sheet.Name}"); + } + } + catch (Exception ex) + { + mutated.Add($" {sheet.ShtCod} 예외: {ex.Message}"); + } + } + + lines.Insert(0, mutated.Count == 0 + ? $"[열기 무변경] BackColor 없는 서식 {checkedDocs}건 — 속성 패널을 열어도 XML 동일" + : $"[열기 변경 발생] {mutated.Count}건 — 아래 서식은 여는 것만으로 XML 이 바뀐다"); + lines.InsertRange(1, mutated); + lines.Insert(mutated.Count + 1, string.Empty); + lines.Insert(0, $"디자인 {docs}건 / 페이지 {pages}장"); + lines.Add(string.Empty); + lines.Add("[페이지 루트 속성 키 분포]"); + foreach (var (key, count) in keyCounts.OrderByDescending(p => p.Value)) + { + lines.Add($" {key,-24} {count,6}장 ({count * 100.0 / Math.Max(1, pages):0.0}%)"); + } + lines.Add(string.Empty); + lines.Add("[BackColor 값 분포]"); + foreach (var (value, count) in backColors.OrderByDescending(p => p.Value)) + { + lines.Add($" {value,-24} {count,6}장 ({count * 100.0 / Math.Max(1, pages):0.0}%)"); + } + lines.Add(string.Empty); + lines.Add("[Font 값 분포]"); + foreach (var (value, count) in fonts.OrderByDescending(p => p.Value).Take(15)) + { + lines.Add($" {value,-30} {count,6}장 ({count * 100.0 / Math.Max(1, pages):0.0}%)"); + } + } + catch (Exception ex) + { + lines.Add($"예외: {ex}"); + } + File.WriteAllLines(reportPath, lines); + return 0; + } + public static int RunGateReport(string reportPath) { try diff --git a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs index 16fca95..5b337fa 100644 --- a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs +++ b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs @@ -504,7 +504,8 @@ public static class EditSmoke Check("페이지 속성: 저장 XML 의 Size 가 편집값", pageXml.Contains("794, 1123"), "Size 가 Bounds 와 어긋남"); - // 배경색 + // 배경색 — 값이 없으면 레거시 PropertyGrid 와 같은 기본값(Control)을 보여 주되 + // 저장하지는 않는다. 지어낸 값이 XML 에 새로 생기면 왕복 diff 0 이 깨진다. var colorRow = pg.Inspector.Rows.OfType() .FirstOrDefault(r => r.Label == "배경색"); Check("페이지 속성: 배경색 행 존재", colorRow is not null); @@ -515,6 +516,22 @@ public static class EditSmoke pg.Pages[0].Model.Root.Props.GetText("BackColor") == "Silver"); } + // 값이 없는 페이지: 기본값 표시 + 미저장 + { + var bare = new DesignerViewModel(business.CreateNew()); + bare.Pages[0].Model.Root.Props.Remove("BackColor"); + bare.ActivatePage(bare.Pages[0]); + var bareRow = bare.Inspector.Rows.OfType() + .FirstOrDefault(r => r.Label == "배경색"); + Check("페이지 속성: 값 없으면 Control 로 표시", + bareRow?.ValueText == "Control", $"실제 {bareRow?.ValueText}"); + Check("페이지 속성: 값 없으면 설명 표시", bareRow?.Hint is not null); + Check("페이지 속성: 표시만 하고 저장하지 않음", + bare.Pages[0].Model.Root.Props.GetText("BackColor") is null); + Check("페이지 속성: 값 없어도 용지는 흰색", + bare.Pages[0].PaperBrush is System.Windows.Media.SolidColorBrush { Color.R: 255, Color.G: 255, Color.B: 255 }); + } + // 기본 글꼴 — 명시 Font 가 없는 컨트롤이 상속한다 var fontRow = pg.Inspector.Rows.OfType() .FirstOrDefault(r => r.Label == "글꼴"); diff --git a/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs b/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs index 760dfb2..580e554 100644 --- a/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs +++ b/src/SheetMe.Designer/ViewModels/Inspector/InspectorViewModel.cs @@ -287,6 +287,12 @@ public sealed class InspectorViewModel : ViewModelBase /// 저장 시 Size·Name 은 writer 가 Bounds·Id 에서 다시 만들어 덮으므로 /// 그 두 키는 raw 로 편집하게 두면 안 된다 — 고쳐도 저장하면 되돌아간다. /// + /// + /// 페이지 배경색 기본값 — WinForms UserControl 의 BackColor 기본값(SystemColors.Control). + /// 레거시 PropertyGrid 가 속성 없는 페이지에 표시하는 값과 같다. + /// + private const string DefaultPageBackColor = "Control"; + private void BuildPageRows(PageViewModel page) { var root = page.Model.Root; @@ -297,9 +303,23 @@ public sealed class InspectorViewModel : ViewModelBase "너비", () => page.Model.Width, w => page.Model.Width = Math.Max(1, Math.Round(w)), "높이", () => page.Model.Height, h => page.Model.Height = Math.Max(1, Math.Round(h))); - AddPageRow(page, new ColorRowViewModel("배경색"), - () => root.Props.GetText("BackColor") ?? "White", - value => root.Props.SetText("BackColor", value.Length == 0 ? "White" : value), + // 속성이 없을 때 보여줄 값은 'White' 가 아니라 'Control' 이다. + // 페이지 루트는 WinForms UserControl 이고 BackColor 기본값은 SystemColors.Control 이라 + // 레거시 PropertyGrid 가 그렇게 표시한다. 운영 페이지 1,816장 중 230장(12.7%)이 + // 이 키를 갖고 있지 않으므로 적은 사례도 아니다. + // 값을 지어내 표시하기만 하고 저장하지는 않는다 — 손대지 않은 페이지의 XML 은 그대로여야 + // 왕복 diff 0 이 유지된다(편집하면 그때 기록된다). + var backRow = new ColorRowViewModel("배경색"); + if (root.Props.GetText("BackColor") is null) + { + // 값이 없다는 사실과, 그래도 화면·인쇄는 흰색이라는 것을 함께 알린다 — + // 안 그러면 "Control 인데 왜 희게 보이지?" 로 읽힌다 + backRow.Hint = $"배경색 — 저장된 값 없음. 레거시 기본값({DefaultPageBackColor})으로 표시하며, " + + "실행·인쇄는 흰색입니다. 여기서 바꾸면 그때 저장됩니다."; + } + AddPageRow(page, backRow, + () => root.Props.GetText("BackColor") ?? DefaultPageBackColor, + value => root.Props.SetText("BackColor", value.Length == 0 ? DefaultPageBackColor : value), page.NotifyPaperChanged); // 페이지 글꼴은 자식이 상속한다 — 여기를 바꾸면 Font 를 명시하지 않은 컨트롤이 전부 따라온다 diff --git a/src/SheetMe.Designer/ViewModels/Inspector/PropertyRows.cs b/src/SheetMe.Designer/ViewModels/Inspector/PropertyRows.cs index b088765..21dd8f4 100644 --- a/src/SheetMe.Designer/ViewModels/Inspector/PropertyRows.cs +++ b/src/SheetMe.Designer/ViewModels/Inspector/PropertyRows.cs @@ -18,6 +18,15 @@ public abstract class PropertyRowViewModel : ViewModelBase /// 행 라벨(한글) public string Label { get; } + /// + /// 라벨에 붙일 설명 — 값만으로는 오해할 수 있는 행에 쓴다. + /// (예: 저장된 값이 없어 기본값을 보여 주는 중이라는 사실) + /// + public string? Hint { get; set; } + + /// 라벨 툴팁 — 설명이 있으면 그것, 없으면 라벨 자신(잘림 대비) + public string LabelToolTip => Hint ?? Label; + /// 선택 대상들의 값이 서로 다른지 — "여러 값" 표시 public bool IsMixed { get; private set; } diff --git a/src/SheetMe.Designer/Views/InspectorView.xaml b/src/SheetMe.Designer/Views/InspectorView.xaml index 085b75c..b6a45bc 100644 --- a/src/SheetMe.Designer/Views/InspectorView.xaml +++ b/src/SheetMe.Designer/Views/InspectorView.xaml @@ -426,7 +426,8 @@ - +