페이지 배경색 기본 표시를 White → Control 로 (레거시 PropertyGrid 와 일치)
페이지 루트에 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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
38093691ad
commit
ac36f93a03
@@ -157,6 +157,11 @@ public partial class App : Application
|
|||||||
return Diagnostics.DbSmoke.RunSpreadDump(args[1], args[2]);
|
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")
|
if (args.Length >= 3 && args[0] == "--db-xml")
|
||||||
{
|
{
|
||||||
return Diagnostics.DbSmoke.RunDesignXml(args[1], args[2]);
|
return Diagnostics.DbSmoke.RunDesignXml(args[1], args[2]);
|
||||||
|
|||||||
@@ -104,6 +104,129 @@ public static class DbSmoke
|
|||||||
/// 권한 게이트 영향 범위(--db-gate) — ShtUsrDesYon 값별 서식 수를 집계한다.
|
/// 권한 게이트 영향 범위(--db-gate) — ShtUsrDesYon 값별 서식 수를 집계한다.
|
||||||
/// 게이트 도입 전에 "갑자기 서식이 안 열린다"는 문의 규모를 미리 알기 위한 읽기 전용 진단.
|
/// 게이트 도입 전에 "갑자기 서식이 안 열린다"는 문의 규모를 미리 알기 위한 읽기 전용 진단.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <summary>
|
||||||
|
/// 페이지 루트(MDesignerHost) 속성 전수 집계 — 페이지 속성 편집기의 기본값 근거.
|
||||||
|
///
|
||||||
|
/// 레거시 PropertyGrid 는 <b>속성이 없으면 WinForms 기본값</b>을 보여 준다(BackColor 는 Control).
|
||||||
|
/// 우리가 어떤 키를 얼마나 마주치는지, 어떤 값이 실제로 쓰이는지 알아야 기본 표시를 정할 수 있다.
|
||||||
|
/// </summary>
|
||||||
|
public static int RunPagePropsReport(string reportPath)
|
||||||
|
{
|
||||||
|
var lines = new List<string>();
|
||||||
|
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<string, int>(StringComparer.Ordinal);
|
||||||
|
var backColors = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||||
|
var fonts = new Dictionary<string, int>(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<string>();
|
||||||
|
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)
|
public static int RunGateReport(string reportPath)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -504,7 +504,8 @@ public static class EditSmoke
|
|||||||
Check("페이지 속성: 저장 XML 의 Size 가 편집값", pageXml.Contains("794, 1123"),
|
Check("페이지 속성: 저장 XML 의 Size 가 편집값", pageXml.Contains("794, 1123"),
|
||||||
"Size 가 Bounds 와 어긋남");
|
"Size 가 Bounds 와 어긋남");
|
||||||
|
|
||||||
// 배경색
|
// 배경색 — 값이 없으면 레거시 PropertyGrid 와 같은 기본값(Control)을 보여 주되
|
||||||
|
// 저장하지는 않는다. 지어낸 값이 XML 에 새로 생기면 왕복 diff 0 이 깨진다.
|
||||||
var colorRow = pg.Inspector.Rows.OfType<ViewModels.Inspector.ColorRowViewModel>()
|
var colorRow = pg.Inspector.Rows.OfType<ViewModels.Inspector.ColorRowViewModel>()
|
||||||
.FirstOrDefault(r => r.Label == "배경색");
|
.FirstOrDefault(r => r.Label == "배경색");
|
||||||
Check("페이지 속성: 배경색 행 존재", colorRow is not null);
|
Check("페이지 속성: 배경색 행 존재", colorRow is not null);
|
||||||
@@ -515,6 +516,22 @@ public static class EditSmoke
|
|||||||
pg.Pages[0].Model.Root.Props.GetText("BackColor") == "Silver");
|
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<ViewModels.Inspector.ColorRowViewModel>()
|
||||||
|
.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 가 없는 컨트롤이 상속한다
|
// 기본 글꼴 — 명시 Font 가 없는 컨트롤이 상속한다
|
||||||
var fontRow = pg.Inspector.Rows.OfType<ViewModels.Inspector.TextRowViewModel>()
|
var fontRow = pg.Inspector.Rows.OfType<ViewModels.Inspector.TextRowViewModel>()
|
||||||
.FirstOrDefault(r => r.Label == "글꼴");
|
.FirstOrDefault(r => r.Label == "글꼴");
|
||||||
|
|||||||
@@ -287,6 +287,12 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
/// 저장 시 <c>Size</c>·<c>Name</c> 은 writer 가 Bounds·Id 에서 다시 만들어 덮으므로
|
/// 저장 시 <c>Size</c>·<c>Name</c> 은 writer 가 Bounds·Id 에서 다시 만들어 덮으므로
|
||||||
/// 그 두 키는 raw 로 편집하게 두면 안 된다 — 고쳐도 저장하면 되돌아간다.
|
/// 그 두 키는 raw 로 편집하게 두면 안 된다 — 고쳐도 저장하면 되돌아간다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <summary>
|
||||||
|
/// 페이지 배경색 기본값 — WinForms UserControl 의 BackColor 기본값(SystemColors.Control).
|
||||||
|
/// 레거시 PropertyGrid 가 속성 없는 페이지에 표시하는 값과 같다.
|
||||||
|
/// </summary>
|
||||||
|
private const string DefaultPageBackColor = "Control";
|
||||||
|
|
||||||
private void BuildPageRows(PageViewModel page)
|
private void BuildPageRows(PageViewModel page)
|
||||||
{
|
{
|
||||||
var root = page.Model.Root;
|
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.Width, w => page.Model.Width = Math.Max(1, Math.Round(w)),
|
||||||
"높이", () => page.Model.Height, h => page.Model.Height = Math.Max(1, Math.Round(h)));
|
"높이", () => page.Model.Height, h => page.Model.Height = Math.Max(1, Math.Round(h)));
|
||||||
|
|
||||||
AddPageRow(page, new ColorRowViewModel("배경색"),
|
// 속성이 없을 때 보여줄 값은 'White' 가 아니라 'Control' 이다.
|
||||||
() => root.Props.GetText("BackColor") ?? "White",
|
// 페이지 루트는 WinForms UserControl 이고 BackColor 기본값은 SystemColors.Control 이라
|
||||||
value => root.Props.SetText("BackColor", value.Length == 0 ? "White" : value),
|
// 레거시 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);
|
page.NotifyPaperChanged);
|
||||||
|
|
||||||
// 페이지 글꼴은 자식이 상속한다 — 여기를 바꾸면 Font 를 명시하지 않은 컨트롤이 전부 따라온다
|
// 페이지 글꼴은 자식이 상속한다 — 여기를 바꾸면 Font 를 명시하지 않은 컨트롤이 전부 따라온다
|
||||||
|
|||||||
@@ -18,6 +18,15 @@ public abstract class PropertyRowViewModel : ViewModelBase
|
|||||||
/// <summary>행 라벨(한글)</summary>
|
/// <summary>행 라벨(한글)</summary>
|
||||||
public string Label { get; }
|
public string Label { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 라벨에 붙일 설명 — 값만으로는 오해할 수 있는 행에 쓴다.
|
||||||
|
/// (예: 저장된 값이 없어 기본값을 보여 주는 중이라는 사실)
|
||||||
|
/// </summary>
|
||||||
|
public string? Hint { get; set; }
|
||||||
|
|
||||||
|
/// <summary>라벨 툴팁 — 설명이 있으면 그것, 없으면 라벨 자신(잘림 대비)</summary>
|
||||||
|
public string LabelToolTip => Hint ?? Label;
|
||||||
|
|
||||||
/// <summary>선택 대상들의 값이 서로 다른지 — "여러 값" 표시</summary>
|
/// <summary>선택 대상들의 값이 서로 다른지 — "여러 값" 표시</summary>
|
||||||
public bool IsMixed { get; private set; }
|
public bool IsMixed { get; private set; }
|
||||||
|
|
||||||
|
|||||||
@@ -426,7 +426,8 @@
|
|||||||
|
|
||||||
<DataTemplate DataType="{x:Type ins:ColorRowViewModel}">
|
<DataTemplate DataType="{x:Type ins:ColorRowViewModel}">
|
||||||
<DockPanel Margin="0,2">
|
<DockPanel Margin="0,2">
|
||||||
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
|
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"
|
||||||
|
ToolTip="{Binding LabelToolTip}"/>
|
||||||
<!-- 스와치 = 피커 버튼(클릭 → 색상 선택 대화상자), 직접 입력도 병행 -->
|
<!-- 스와치 = 피커 버튼(클릭 → 색상 선택 대화상자), 직접 입력도 병행 -->
|
||||||
<Button DockPanel.Dock="Right" Width="30" Height="26" Margin="4,0,0,0" Padding="0"
|
<Button DockPanel.Dock="Right" Width="30" Height="26" Margin="4,0,0,0" Padding="0"
|
||||||
Command="{Binding PickCommand}" Cursor="Hand"
|
Command="{Binding PickCommand}" Cursor="Hand"
|
||||||
|
|||||||
Reference in New Issue
Block a user