페이지 속성 편집 — 레이어의 페이지 행을 고르면 속성 패널이 페이지를 연다
레거시에서 페이지는 MDesignerHost 라는 하나의 Object 이고 자기 속성을 갖는다. SheetMe 도 그 루트 노드를 FormPage.Root 로 통째로 보존해 왔고(그래서 운영 1,271건 왕복 diff 가 0이다), 값도 일부는 이미 쓰고 있었다 — BackColor 는 용지 배경으로 렌더하고 Font 는 자식이 상속한다. 없던 것은 편집 화면뿐이었다. 그 결과 용지 크기조차 바꿀 수 없는 상태였다. ApplyPaperSize 는 구현돼 있었지만 호출처가 편집 스모크뿐이고 메뉴·XAML 어디에도 배선이 없었다 — 새 서식은 720×856 고정으로만 시작했다. 레이어 아웃라인의 페이지 헤더 행을 누르면 선택을 비우고 속성 패널이 페이지 모드로 바뀐다. 컨트롤을 하나라도 고르면 자동으로 빠져나온다. 용지 너비·높이(한 줄), 배경색 기본 글꼴 글꼴·크기 — 여기를 바꾸면 Font 를 명시하지 않은 컨트롤이 전부 따라온다 전체 속성 나머지 원본 속성(레거시 PropertyGrid 등가) 저장 규약을 지켰다. writer 는 저장할 때 Size 를 Bounds 에서, Name 을 Id 에서 다시 만들어 덮으므로 그 두 키는 raw 편집 대상에서 뺐다 — 열어 두면 고쳐도 저장하면 되돌아간다. 크기는 raw Size 가 아니라 Bounds 를 고치고, 편집 결과가 저장 XML 의 Size 와 같은지 스모크로 못 박았다. 용지 크기가 바뀌면 페이지 스택 오프셋과 월드 크기를 다시 계산한다(여러 장이면 한 장의 높이 변화가 아래 페이지를 전부 밀어낸다). 기본 글꼴이 바뀌면 그 페이지 컨트롤의 상속 컨텍스트를 다시 흘린다. 편집 스모크에 페이지 속성 검사 11건 추가. 회귀: 테스트 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
f5cce00019
commit
38093691ad
@@ -120,12 +120,21 @@ public sealed class InspectorViewModel : ViewModelBase
|
||||
|
||||
/// <summary>선택 요약 텍스트</summary>
|
||||
public string Summary
|
||||
=> designer.Selection.Items.Count switch
|
||||
{
|
||||
get
|
||||
{
|
||||
0 => "선택 없음",
|
||||
1 => $"{designer.Selection.Primary!.Type} — {designer.Selection.Primary!.Id}",
|
||||
var n => $"{n}개 선택",
|
||||
};
|
||||
if (designer.Selection.Items.Count == 0 && designer.InspectedPage is { } page)
|
||||
{
|
||||
return $"페이지 {page.Index + 1} — {page.Model.Root.Id}";
|
||||
}
|
||||
return designer.Selection.Items.Count switch
|
||||
{
|
||||
0 => "선택 없음",
|
||||
1 => $"{designer.Selection.Primary!.Type} — {designer.Selection.Primary!.Id}",
|
||||
var n => $"{n}개 선택",
|
||||
};
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
@@ -166,6 +175,11 @@ public sealed class InspectorViewModel : ViewModelBase
|
||||
if (items.Count == 0)
|
||||
{
|
||||
RefreshTabCounts(null);
|
||||
if (designer.InspectedPage is { } page)
|
||||
{
|
||||
BuildPageRows(page);
|
||||
OnPropertyChanged(nameof(IsTabEmpty));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -265,6 +279,148 @@ public sealed class InspectorViewModel : ViewModelBase
|
||||
OnPropertyChanged(nameof(ShowTabStrip));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 페이지 속성 — 레거시에서 페이지는 <c>MDesignerHost</c> 라는 하나의 Object 이고 자기 속성을 갖는다.
|
||||
/// SheetMe 는 그 루트 노드를 <c>FormPage.Root</c> 로 통째로 보존해 왔지만(그래서 왕복 diff 가 0이다)
|
||||
/// 편집 화면이 없어 용지 크기조차 바꿀 수 없었다.
|
||||
///
|
||||
/// 저장 시 <c>Size</c>·<c>Name</c> 은 writer 가 Bounds·Id 에서 다시 만들어 덮으므로
|
||||
/// 그 두 키는 raw 로 편집하게 두면 안 된다 — 고쳐도 저장하면 되돌아간다.
|
||||
/// </summary>
|
||||
private void BuildPageRows(PageViewModel page)
|
||||
{
|
||||
var root = page.Model.Root;
|
||||
|
||||
AddSection("용지");
|
||||
// 크기는 raw Size 가 아니라 Bounds 를 고친다 — writer 가 Bounds 로 Size 를 다시 쓰기 때문
|
||||
AddPagePair(page,
|
||||
"너비", () => 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),
|
||||
page.NotifyPaperChanged);
|
||||
|
||||
// 페이지 글꼴은 자식이 상속한다 — 여기를 바꾸면 Font 를 명시하지 않은 컨트롤이 전부 따라온다
|
||||
AddSection("기본 글꼴 (컨트롤이 상속)");
|
||||
AddPageFontRow(page, "글꼴", f => f.Family, (f, v) => f.Family = v.Length == 0 ? f.Family : v);
|
||||
AddPageFontRow(page, "크기(pt)",
|
||||
f => f.SizePt.ToString("0.##", CultureInfo.InvariantCulture),
|
||||
(f, v) => f.SizePt = double.TryParse(v, NumberStyles.Number, CultureInfo.InvariantCulture, out var size) && size > 0
|
||||
? size
|
||||
: f.SizePt);
|
||||
|
||||
// 저장 시 writer 가 다시 쓰는 키는 raw 편집 대상에서 제외한다
|
||||
var handled = new HashSet<string>(StringComparer.Ordinal) { "Size", "Name", "BackColor", "Font" };
|
||||
BuildPageAdvancedRows(page, handled);
|
||||
}
|
||||
|
||||
/// <summary>페이지 루트의 나머지 원본 속성 — 레거시 PropertyGrid 등가의 탈출구</summary>
|
||||
private void BuildPageAdvancedRows(PageViewModel page, HashSet<string> handled)
|
||||
{
|
||||
var root = page.Model.Root;
|
||||
var keys = root.Props.Keys.Where(k => !handled.Contains(k)).ToList();
|
||||
|
||||
if (!showAdvanced)
|
||||
{
|
||||
currentSection = null;
|
||||
AddPlain(new ToggleAdvancedRowViewModel($"전체 속성 표시 ▾ ({keys.Count}개)", () =>
|
||||
{
|
||||
showAdvanced = true;
|
||||
Rebuild();
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
AddSection("전체 속성 (고급 — 레거시 원문)");
|
||||
AddPlain(new ToggleAdvancedRowViewModel("전체 속성 숨기기 ▴", () =>
|
||||
{
|
||||
showAdvanced = false;
|
||||
Rebuild();
|
||||
}));
|
||||
|
||||
foreach (var key in keys)
|
||||
{
|
||||
// 페이지 루트에 목록·중첩·바이너리 속성이 오는 사례는 없지만, 왔을 때 편집으로
|
||||
// 원문을 깨뜨리지 않도록 문자열만 편집을 허용한다
|
||||
if (root.Props.Get(key) is LegacyPropValue.TextValue or LegacyPropValue.NullValue or null)
|
||||
{
|
||||
AddPageRow(page, new TextRowViewModel(key),
|
||||
() => root.Props.GetText(key) ?? string.Empty,
|
||||
value => root.Props.SetText(key, value));
|
||||
continue;
|
||||
}
|
||||
AddPlain(new ReadOnlyRowViewModel(key, "(원문 보존됨)"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>페이지 속성 행 배선 — 대상이 선택 집합이 아니라 페이지 루트 하나다</summary>
|
||||
private void AddPageRow(PageViewModel page, PropertyRowViewModel row,
|
||||
Func<string> get, Action<string> set, Action? afterCommit = null)
|
||||
{
|
||||
row.Initialize(get(), isMixed: false);
|
||||
row.Commit = value =>
|
||||
{
|
||||
designer.Undo.Snapshot();
|
||||
set(value);
|
||||
afterCommit?.Invoke();
|
||||
};
|
||||
currentSection?.Adopt(row);
|
||||
Rows.Add(row);
|
||||
}
|
||||
|
||||
/// <summary>용지 너비·높이 — 한 줄에 둘. 값이 바뀌면 페이지 오프셋까지 다시 계산해야 한다</summary>
|
||||
private void AddPagePair(PageViewModel page,
|
||||
string leftLabel, Func<double> getLeft, Action<double> setLeft,
|
||||
string rightLabel, Func<double> getRight, Action<double> setRight)
|
||||
{
|
||||
var pair = new PairRowViewModel(
|
||||
leftLabel, NewPageNumber(page, leftLabel, getLeft, setLeft),
|
||||
rightLabel, NewPageNumber(page, rightLabel, getRight, setRight));
|
||||
currentSection?.Adopt(pair);
|
||||
Rows.Add(pair);
|
||||
}
|
||||
|
||||
private NumberRowViewModel NewPageNumber(PageViewModel page, string label,
|
||||
Func<double> get, Action<double> set)
|
||||
{
|
||||
var row = new NumberRowViewModel(label) { CoalesceUndo = true };
|
||||
row.Initialize(get().ToString("0.##", CultureInfo.InvariantCulture), isMixed: false);
|
||||
row.Commit = value =>
|
||||
{
|
||||
if (!double.TryParse(value, NumberStyles.Number, CultureInfo.InvariantCulture, out var parsed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
designer.Undo.SnapshotForNudge();
|
||||
set(parsed);
|
||||
page.Model.Root.HasSize = true;
|
||||
page.NotifySizeChanged();
|
||||
designer.NotifyPaperResized();
|
||||
};
|
||||
return row;
|
||||
}
|
||||
|
||||
/// <summary>페이지 기본 글꼴 — 자식 상속 컨텍스트가 바뀌므로 문서 전체 시각을 다시 해석한다</summary>
|
||||
private void AddPageFontRow(PageViewModel page, string label,
|
||||
Func<LegacyFont, string> get, Action<LegacyFont, string> set)
|
||||
{
|
||||
var root = page.Model.Root;
|
||||
LegacyFont Current()
|
||||
{
|
||||
var text = root.Props.GetText("Font");
|
||||
return text is not null ? LegacyFormat.ParseFont(text) : new LegacyFont();
|
||||
}
|
||||
|
||||
AddPageRow(page, new TextRowViewModel(label), () => get(Current()), value =>
|
||||
{
|
||||
var font = Current();
|
||||
set(font, value);
|
||||
root.Props.SetText("Font", LegacyFormat.FormatFont(font));
|
||||
}, () => designer.RefreshPageInheritance(page));
|
||||
}
|
||||
|
||||
/// <summary>고급 섹션 구성 — 접이식, raw 문자열 편집 + 속성 추가</summary>
|
||||
private void BuildAdvancedRows(ControlViewModel target, HashSet<string> curatedKeys)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user