페이지 속성 편집 — 레이어의 페이지 행을 고르면 속성 패널이 페이지를 연다
레거시에서 페이지는 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
@@ -479,6 +479,62 @@ public static class EditSmoke
|
|||||||
badgeTarget.Model.Props.SetText("DataTableField", string.Empty);
|
badgeTarget.Model.Props.SetText("DataTableField", string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 20-2-1-2) 페이지 속성 — 레거시 MDesignerHost 루트가 갖는 속성을 편집한다
|
||||||
|
{
|
||||||
|
var pg = new DesignerViewModel(business.CreateNew());
|
||||||
|
pg.AddControlAt("Label", new Point(100, 100));
|
||||||
|
var pageRow = pg.LayerRows.OfType<LayerPageViewModel>().First();
|
||||||
|
|
||||||
|
pg.ActivatePage(pageRow.Page);
|
||||||
|
Check("페이지 속성: 페이지 모드 진입", pg.InspectedPage is not null && pg.Selection.Items.Count == 0);
|
||||||
|
Check("페이지 속성: 요약이 페이지를 가리킴", pg.Inspector.Summary.StartsWith("페이지 1"),
|
||||||
|
$"실제 {pg.Inspector.Summary}");
|
||||||
|
Check("페이지 속성: 행 생성", pg.Inspector.Rows.Count > 0, $"행 {pg.Inspector.Rows.Count}");
|
||||||
|
|
||||||
|
// 용지 크기 — raw Size 가 아니라 Bounds 를 고쳐야 저장 시 되돌아가지 않는다
|
||||||
|
var sizePair = pg.Inspector.Rows.OfType<ViewModels.Inspector.PairRowViewModel>().First();
|
||||||
|
((ViewModels.Inspector.NumberRowViewModel)sizePair.Left).ValueText = "794";
|
||||||
|
((ViewModels.Inspector.NumberRowViewModel)sizePair.Right).ValueText = "1123";
|
||||||
|
Check("페이지 속성: 용지 크기 편집(A4)",
|
||||||
|
pg.Pages[0].WidthDip == 794 && pg.Pages[0].HeightDip == 1123,
|
||||||
|
$"실제 {pg.Pages[0].WidthDip}×{pg.Pages[0].HeightDip}");
|
||||||
|
|
||||||
|
// 저장하면 writer 가 Bounds 로 Size 를 다시 쓴다 — 그 값이 편집 결과와 같아야 한다
|
||||||
|
var pageXml = serializer.Write(pg.Document);
|
||||||
|
Check("페이지 속성: 저장 XML 의 Size 가 편집값", pageXml.Contains("794, 1123"),
|
||||||
|
"Size 가 Bounds 와 어긋남");
|
||||||
|
|
||||||
|
// 배경색
|
||||||
|
var colorRow = pg.Inspector.Rows.OfType<ViewModels.Inspector.ColorRowViewModel>()
|
||||||
|
.FirstOrDefault(r => r.Label == "배경색");
|
||||||
|
Check("페이지 속성: 배경색 행 존재", colorRow is not null);
|
||||||
|
if (colorRow is not null)
|
||||||
|
{
|
||||||
|
colorRow.ValueText = "Silver";
|
||||||
|
Check("페이지 속성: 배경색 편집 → 루트 반영",
|
||||||
|
pg.Pages[0].Model.Root.Props.GetText("BackColor") == "Silver");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 기본 글꼴 — 명시 Font 가 없는 컨트롤이 상속한다
|
||||||
|
var fontRow = pg.Inspector.Rows.OfType<ViewModels.Inspector.TextRowViewModel>()
|
||||||
|
.FirstOrDefault(r => r.Label == "글꼴");
|
||||||
|
Check("페이지 속성: 기본 글꼴 행 존재", fontRow is not null);
|
||||||
|
if (fontRow is not null)
|
||||||
|
{
|
||||||
|
fontRow.ValueText = "돋움";
|
||||||
|
Check("페이지 속성: 기본 글꼴이 루트에 기록",
|
||||||
|
(pg.Pages[0].Model.Root.Props.GetText("Font") ?? string.Empty).StartsWith("돋움"),
|
||||||
|
$"실제 {pg.Pages[0].Model.Root.Props.GetText("Font")}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Check("페이지 속성: 편집이 되돌리기 스텝", pg.Undo.CanUndo);
|
||||||
|
|
||||||
|
// 컨트롤을 고르면 페이지 모드에서 빠진다
|
||||||
|
pg.Selection.SetSingle(pg.Pages[0].Controls[0]);
|
||||||
|
Check("페이지 속성: 컨트롤 선택 시 해제",
|
||||||
|
pg.InspectedPage is null && !pg.Inspector.Summary.StartsWith("페이지"));
|
||||||
|
}
|
||||||
|
|
||||||
// 20-2-2) 알림이 무인 실행을 멈추지 않는다 — 진단 모드에서는 창을 만들지 않고 즉시
|
// 20-2-2) 알림이 무인 실행을 멈추지 않는다 — 진단 모드에서는 창을 만들지 않고 즉시
|
||||||
// 기각 기본값을 돌려줘야 한다. 이 규약이 깨지면 여기서 영원히 대기한다.
|
// 기각 기본값을 돌려줘야 한다. 이 규약이 깨지면 여기서 영원히 대기한다.
|
||||||
Check("알림: 진단 모드에서 창 없이 반환", Services.UserSession.IsDiagnostic);
|
Check("알림: 진단 모드에서 창 없이 반환", Services.UserSession.IsDiagnostic);
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ public sealed class DesignerViewModel : ViewModelBase
|
|||||||
// 레이어 목록 상태 — 접힌 그룹과 타입 필터는 문서가 아니라 편집 세션에 속한다(Undo 대상 아님)
|
// 레이어 목록 상태 — 접힌 그룹과 타입 필터는 문서가 아니라 편집 세션에 속한다(Undo 대상 아님)
|
||||||
private readonly HashSet<string> collapsedGroups = new(StringComparer.Ordinal);
|
private readonly HashSet<string> collapsedGroups = new(StringComparer.Ordinal);
|
||||||
private readonly HashSet<PageViewModel> collapsedPages = new();
|
private readonly HashSet<PageViewModel> collapsedPages = new();
|
||||||
|
private PageViewModel? inspectedPage;
|
||||||
private readonly HashSet<string> layerTypeFilter = new(StringComparer.Ordinal);
|
private readonly HashSet<string> layerTypeFilter = new(StringComparer.Ordinal);
|
||||||
private readonly List<System.Collections.Specialized.INotifyCollectionChanged> watchedControls = new();
|
private readonly List<System.Collections.Specialized.INotifyCollectionChanged> watchedControls = new();
|
||||||
#endregion
|
#endregion
|
||||||
@@ -275,6 +276,9 @@ public sealed class DesignerViewModel : ViewModelBase
|
|||||||
Interaction = new InteractionController(this);
|
Interaction = new InteractionController(this);
|
||||||
Inspector = new InspectorViewModel(this);
|
Inspector = new InspectorViewModel(this);
|
||||||
Selection.Changed += RefreshOverlay;
|
Selection.Changed += RefreshOverlay;
|
||||||
|
// 순서 주의 — 인스펙터가 Rebuild 하기 전에 페이지 모드를 걷어야 한다
|
||||||
|
// (Inspector 는 생성자 안에서 SetChanged 를 구독하므로 여기가 먼저다)
|
||||||
|
Selection.SetChanged += ClearInspectedPageOnSelection;
|
||||||
Selection.SetChanged += LayerSelectionChanged;
|
Selection.SetChanged += LayerSelectionChanged;
|
||||||
|
|
||||||
UndoCommand = new Command((sender, e) => Undo.Undo());
|
UndoCommand = new Command((sender, e) => Undo.Undo());
|
||||||
@@ -502,6 +506,33 @@ public sealed class DesignerViewModel : ViewModelBase
|
|||||||
OnPropertyChanged(nameof(WorldHeight));
|
OnPropertyChanged(nameof(WorldHeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 용지 크기가 바뀐 뒤 — 페이지 스택 오프셋과 월드 크기를 다시 계산한다.
|
||||||
|
/// 페이지가 여러 장이면 한 장의 높이 변화가 아래 페이지 전부를 밀어낸다.
|
||||||
|
/// </summary>
|
||||||
|
public void NotifyPaperResized()
|
||||||
|
{
|
||||||
|
RecalcPageOffsets();
|
||||||
|
OnPropertyChanged(nameof(WorldWidth));
|
||||||
|
OnPropertyChanged(nameof(WorldHeight));
|
||||||
|
RefreshOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 페이지 기본 글꼴이 바뀐 뒤 — 그 페이지 컨트롤의 상속 컨텍스트를 다시 흘린다.
|
||||||
|
/// Font 를 명시하지 않은 컨트롤은 루트 글꼴을 물려받으므로 값 하나가 화면 전체를 바꾼다.
|
||||||
|
/// </summary>
|
||||||
|
public void RefreshPageInheritance(PageViewModel page)
|
||||||
|
{
|
||||||
|
var rootFontText = page.Model.Root.Props.GetText("Font");
|
||||||
|
var rootFont = rootFontText is not null ? LegacyFormat.ParseFont(rootFontText) : new LegacyFont();
|
||||||
|
foreach (var control in page.Controls)
|
||||||
|
{
|
||||||
|
control.ResolveVisualContext(rootFont, control.ParentForeground);
|
||||||
|
}
|
||||||
|
RefreshOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>문서 교체 후 재구성 — Undo 복원 경로</summary>
|
/// <summary>문서 교체 후 재구성 — Undo 복원 경로</summary>
|
||||||
public void ReplaceDocument(FormDocument newDocument)
|
public void ReplaceDocument(FormDocument newDocument)
|
||||||
{
|
{
|
||||||
@@ -1773,13 +1804,20 @@ public sealed class DesignerViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>페이지 헤더 행 클릭 — 활성 페이지로 삼고 캔버스를 그 페이지로 옮긴다</summary>
|
/// <summary>
|
||||||
|
/// 페이지 헤더 행 클릭 — 활성 페이지로 삼고, 캔버스를 그 페이지로 옮기고, 속성 패널을 페이지로 돌린다.
|
||||||
|
///
|
||||||
|
/// 페이지도 레거시에서는 하나의 <c>Object</c>(MDesignerHost)라 자기 속성을 갖는다.
|
||||||
|
/// 컨트롤을 하나도 고르지 않은 상태에서만 페이지 속성을 보여주므로 선택은 비운다.
|
||||||
|
/// </summary>
|
||||||
public void ActivatePage(PageViewModel page)
|
public void ActivatePage(PageViewModel page)
|
||||||
{
|
{
|
||||||
if (!ReferenceEquals(SelectedPage, page))
|
if (!ReferenceEquals(SelectedPage, page))
|
||||||
{
|
{
|
||||||
SelectedPage = page;
|
SelectedPage = page;
|
||||||
}
|
}
|
||||||
|
Selection.Clear();
|
||||||
|
InspectedPage = page;
|
||||||
foreach (var header in LayerRows.OfType<LayerPageViewModel>())
|
foreach (var header in LayerRows.OfType<LayerPageViewModel>())
|
||||||
{
|
{
|
||||||
header.NotifyStateChanged();
|
header.NotifyStateChanged();
|
||||||
@@ -1787,6 +1825,32 @@ public sealed class DesignerViewModel : ViewModelBase
|
|||||||
ScrollToPageRequested?.Invoke(page);
|
ScrollToPageRequested?.Invoke(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 속성 패널이 들여다보는 페이지 — 컨트롤 선택이 없을 때만 유효하다.
|
||||||
|
/// 컨트롤을 고르는 순간 <see cref="SelectionService.SetChanged"/> 경로에서 비워진다.
|
||||||
|
/// </summary>
|
||||||
|
public PageViewModel? InspectedPage
|
||||||
|
{
|
||||||
|
get => inspectedPage;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
if (!ReferenceEquals(inspectedPage, value))
|
||||||
|
{
|
||||||
|
inspectedPage = value;
|
||||||
|
Inspector.Rebuild();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>선택이 생기면 페이지 속성 모드를 벗어난다</summary>
|
||||||
|
private void ClearInspectedPageOnSelection()
|
||||||
|
{
|
||||||
|
if (inspectedPage is not null && Selection.Items.Count > 0)
|
||||||
|
{
|
||||||
|
inspectedPage = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>캔버스를 특정 페이지로 스크롤하라는 요청 — 뷰가 구독한다</summary>
|
/// <summary>캔버스를 특정 페이지로 스크롤하라는 요청 — 뷰가 구독한다</summary>
|
||||||
public event Action<PageViewModel>? ScrollToPageRequested;
|
public event Action<PageViewModel>? ScrollToPageRequested;
|
||||||
|
|
||||||
|
|||||||
@@ -120,12 +120,21 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
|
|
||||||
/// <summary>선택 요약 텍스트</summary>
|
/// <summary>선택 요약 텍스트</summary>
|
||||||
public string Summary
|
public string Summary
|
||||||
=> designer.Selection.Items.Count switch
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
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 => "선택 없음",
|
0 => "선택 없음",
|
||||||
1 => $"{designer.Selection.Primary!.Type} — {designer.Selection.Primary!.Id}",
|
1 => $"{designer.Selection.Primary!.Type} — {designer.Selection.Primary!.Id}",
|
||||||
var n => $"{n}개 선택",
|
var n => $"{n}개 선택",
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
@@ -166,6 +175,11 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
if (items.Count == 0)
|
if (items.Count == 0)
|
||||||
{
|
{
|
||||||
RefreshTabCounts(null);
|
RefreshTabCounts(null);
|
||||||
|
if (designer.InspectedPage is { } page)
|
||||||
|
{
|
||||||
|
BuildPageRows(page);
|
||||||
|
OnPropertyChanged(nameof(IsTabEmpty));
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,6 +279,148 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
OnPropertyChanged(nameof(ShowTabStrip));
|
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>
|
/// <summary>고급 섹션 구성 — 접이식, raw 문자열 편집 + 속성 추가</summary>
|
||||||
private void BuildAdvancedRows(ControlViewModel target, HashSet<string> curatedKeys)
|
private void BuildAdvancedRows(ControlViewModel target, HashSet<string> curatedKeys)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -70,6 +70,9 @@ public sealed class PageViewModel : ViewModelBase
|
|||||||
OnPropertyChanged(nameof(SizeText));
|
OnPropertyChanged(nameof(SizeText));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>용지 배경 변경 통지 — 루트 BackColor 편집 후</summary>
|
||||||
|
public void NotifyPaperChanged() => OnPropertyChanged(nameof(PaperBrush));
|
||||||
|
|
||||||
/// <summary>페이지 패널 표시 텍스트</summary>
|
/// <summary>페이지 패널 표시 텍스트</summary>
|
||||||
public string SizeText => $"{WidthDip:0}×{HeightDip:0}";
|
public string SizeText => $"{WidthDip:0}×{HeightDip:0}";
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
Reference in New Issue
Block a user