컨테이너 안도 인쇄 필터를 탄다 — Panel 안 컨트롤이 '인쇄 출력'을 지킨다
미리보기 레거시 호환 계획의 P0-1. 오늘 실제로 틀리고 있던 유일한 항목이다.
BuildPageVisual 은 page.Controls <b>최상위만</b> 훑는데, Panel·GroupBox 템플릿이
ItemsSource="{Binding Children}" 로 자식을 스스로 그린다. 그래서 최상위를 아무리 걸러도
컨테이너 안은 그대로 나왔다 —
① Panel 안 컨트롤은 '인쇄 출력'을 꺼도 인쇄됐다
② 컨테이너 안 MDataTable 은 파란 DB 원통 배지가 종이에 찍혔다
얼마 전 인쇄 필터를 넣으면서 이 구멍을 못 봤다.
<b>먼저 실패시키고 고쳤다.</b> 단정을 넣고 돌리니 요소 17 → 17. 그 red 가 두 번 일했다.
<b>첫 접근이 틀렸고 측정이 잡았다.</b> 컨테이너 VM 에 '인쇄 필터' 플래그를 켰다 끄는 방법을 썼는데
듣지 않았다 — WPF 는 템플릿 자식을 <b>레이아웃 시점</b>에 만드는데 BuildPageVisual 은
프레젠터만 만들어 두고 반환하므로, 자식이 실체화될 때는 이미 finally 가 플래그를 껐다.
플래그 수명을 인쇄 비주얼 전체로 늘리면 그동안 캔버스에서도 자식이 사라진다(VM 공유).
그 접근은 되돌렸다.
<b>택한 방법</b>: 같은 모델로 같은 타입의 VM 을 하나 더 만들고 인쇄될 자식만 담는다.
타입이 같으니 템플릿이 그대로 잡히고, 모델이 같으니 배경·테두리·머리글이 똑같이 나온다.
자식 VM 은 새로 만들지 않고 원본을 담아 해석된 글꼴·색을 유지한다.
<b>캔버스가 쓰는 VM 은 한 글자도 건드리지 않는다</b> — 단정 하나가 그것을 지킨다.
모르는 컨테이너 타입은 투영하지 않고 원본을 그린다(잘못 투영하느니 그 편이 낫다).
중간에 한 번 더 걸렸다: 처음 단정이 2 → 2 를 냈는데 그건 레이아웃을 안 돌려 시각 트리가
아예 없었던 것이다. Measure/Arrange 를 돌리고 나서야 17 → 17 이라는 진짜 실패가 보였다.
또 하나 정정: --db-render P062 는 이 경로를 쓰지 않는다(캔버스 PageView 를 찍는다).
"md5 가 걸린 경로"라고 했던 것은 과했다. 그래도 게이트로 확인했고 md5 는 동일하다.
게이트: 테스트 298/298, --edit-smoke 0실패(신규 2), --dialog-shots 글자 2,649개 FAIL 0 대조군 4/4,
--modal-check 0실패, --cleartype 11/11, --maxrect 0실패, --scale-budget 5/5, 빌드 경고 0,
--db-smoke 1271건 diff 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3af7b3a071
commit
dbb3deeda3
@@ -17,6 +17,21 @@ public static class EditSmoke
|
|||||||
{
|
{
|
||||||
#region Methods
|
#region Methods
|
||||||
/// <summary>스모크 실행 — 결과를 리포트 파일에 기록, 실패 있으면 1 반환</summary>
|
/// <summary>스모크 실행 — 결과를 리포트 파일에 기록, 실패 있으면 1 반환</summary>
|
||||||
|
/// <summary>
|
||||||
|
/// 시각 트리의 요소 수 — 컨테이너 <b>안</b>까지 세야 자식 필터가 들었는지 보인다.
|
||||||
|
/// Canvas 직계만 세면 컨테이너는 늘 1개라 안에서 뭐가 빠졌는지 알 수 없다.
|
||||||
|
/// </summary>
|
||||||
|
private static int CountVisuals(System.Windows.DependencyObject node)
|
||||||
|
{
|
||||||
|
var total = 1;
|
||||||
|
var count = System.Windows.Media.VisualTreeHelper.GetChildrenCount(node);
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
total += CountVisuals(System.Windows.Media.VisualTreeHelper.GetChild(node, i));
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
public static int Run(string reportPath)
|
public static int Run(string reportPath)
|
||||||
{
|
{
|
||||||
var lines = new List<string>();
|
var lines = new List<string>();
|
||||||
@@ -270,6 +285,61 @@ public static class EditSmoke
|
|||||||
$"전 {all} → 후 {afterBox}");
|
$"전 {all} → 후 {afterBox}");
|
||||||
Check("미리보기: 라벨의 대문자 Visible 은 무시한다", afterWrongKey == all - 2,
|
Check("미리보기: 라벨의 대문자 Visible 은 무시한다", afterWrongKey == all - 2,
|
||||||
$"전 {all} → 후 {afterWrongKey} (레거시는 이 라벨을 인쇄한다)");
|
$"전 {all} → 후 {afterWrongKey} (레거시는 이 라벨을 인쇄한다)");
|
||||||
|
|
||||||
|
// 컨테이너 <b>안</b>도 필터를 타는가.
|
||||||
|
// 위 검사는 전부 최상위였다. Panel·GroupBox 템플릿이 자식을 스스로 그리므로
|
||||||
|
// 최상위만 걸러서는 컨테이너 안이 그대로 나왔다 —
|
||||||
|
// 인쇄 출력을 꺼도 인쇄됐고, 컨테이너 안 MDataTable 은 파란 DB 배지가 종이에 찍혔다.
|
||||||
|
var boxed = new FormDocument();
|
||||||
|
var boxedPage = new FormPage { Width = 720, Height = 856 };
|
||||||
|
boxed.Pages.Add(boxedPage);
|
||||||
|
var host = new ControlElement
|
||||||
|
{
|
||||||
|
Type = "Panel", Id = "PanelP",
|
||||||
|
Bounds = new LayoutRect { X = 20, Y = 20, W = 400, H = 200 },
|
||||||
|
};
|
||||||
|
foreach (var (id, y) in new[] { ("보이는라벨", 10.0), ("숨긴라벨", 40.0) })
|
||||||
|
{
|
||||||
|
var inner = new ControlElement
|
||||||
|
{
|
||||||
|
Type = "Label", Id = id,
|
||||||
|
Bounds = new LayoutRect { X = 10, Y = y, W = 200, H = 18 },
|
||||||
|
};
|
||||||
|
inner.Props.SetText("Text", id);
|
||||||
|
host.Children.Add(inner);
|
||||||
|
}
|
||||||
|
host.Children.Add(new ControlElement
|
||||||
|
{
|
||||||
|
Type = "MDataTable", Id = "안쪽데이터소스",
|
||||||
|
Bounds = new LayoutRect { X = 10, Y = 90, W = 120, H = 40 },
|
||||||
|
});
|
||||||
|
boxedPage.Controls.Add(host);
|
||||||
|
|
||||||
|
var boxedVm = new DesignerViewModel(boxed);
|
||||||
|
var boxedPanel = (ContainerViewModel)boxedVm.Pages[0].Controls.First(c => c.Type == "Panel");
|
||||||
|
|
||||||
|
// 레이아웃을 돌려야 템플릿이 실체화된다 — 안 돌리면 Canvas 와 프레젠터만 세고
|
||||||
|
// 컨테이너 안은 존재하지도 않아 늘 같은 수가 나온다(실제로 2 → 2 로 걸렸다).
|
||||||
|
int Rendered()
|
||||||
|
{
|
||||||
|
var visual = (System.Windows.FrameworkElement)
|
||||||
|
Services.PrintService.BuildPageVisual(boxedVm.Pages[0]);
|
||||||
|
visual.Measure(new System.Windows.Size(720, 856));
|
||||||
|
visual.Arrange(new Rect(0, 0, 720, 856));
|
||||||
|
visual.UpdateLayout();
|
||||||
|
return CountVisuals(visual);
|
||||||
|
}
|
||||||
|
var baseline = Rendered();
|
||||||
|
|
||||||
|
boxedPanel.Children.First(c => c.Model.Id == "숨긴라벨")
|
||||||
|
.Model.Props.SetText("PrintOutPut", "False");
|
||||||
|
var afterHide = Rendered();
|
||||||
|
Check("미리보기: 컨테이너 안의 PrintOutPut=False 도 빠진다", afterHide < baseline,
|
||||||
|
$"요소 {baseline} → {afterHide} (안 줄면 최상위만 거르고 있다)");
|
||||||
|
|
||||||
|
// 인쇄 필터가 캔버스로 새면 안 된다 — 편집 중인 것을 못 보면 고칠 수 없다
|
||||||
|
Check("미리보기: 캔버스는 자식을 그대로 다 보여 준다",
|
||||||
|
boxedPanel.Children.Count == 3, $"{boxedPanel.Children.Count} (기대 3)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) 클릭 선택 — 컨트롤 중심(핸들 반경 밖)
|
// 2) 클릭 선택 — 컨트롤 중심(핸들 반경 밖)
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ public static class PrintService
|
|||||||
}
|
}
|
||||||
var presenter = new ContentPresenter
|
var presenter = new ContentPresenter
|
||||||
{
|
{
|
||||||
Content = control,
|
Content = PrintProjection(control),
|
||||||
Width = Math.Max(1, control.Width),
|
Width = Math.Max(1, control.Width),
|
||||||
Height = Math.Max(1, control.Height),
|
Height = Math.Max(1, control.Height),
|
||||||
};
|
};
|
||||||
@@ -90,5 +90,58 @@ public static class PrintService
|
|||||||
}
|
}
|
||||||
return canvas;
|
return canvas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 컨테이너를 <b>인쇄용으로 다시 만든 것</b>으로 바꿔 준다. 컨테이너가 아니면 원본 그대로.
|
||||||
|
///
|
||||||
|
/// <b>왜 필요한가.</b> 위 루프는 최상위만 훑는데 Panel·GroupBox 템플릿이 자식을 스스로 그린다
|
||||||
|
/// (<c>ItemsSource="{Binding Children}"</c>). 그래서 여기서 아무리 걸러도 컨테이너 안은 그대로 나왔다 —
|
||||||
|
/// Panel 안 컨트롤은 '인쇄 출력'을 꺼도 인쇄됐고, 컨테이너 안 MDataTable 은
|
||||||
|
/// 파란 DB 배지가 종이에 찍혔다.
|
||||||
|
///
|
||||||
|
/// <b>왜 플래그가 아닌가.</b> 처음에는 컨테이너 VM 에 '인쇄 필터' 플래그를 켰다 끄는 방법을 썼는데
|
||||||
|
/// 듣지 않았다 — WPF 는 템플릿 자식을 <b>레이아웃 시점</b>에 만드는데,
|
||||||
|
/// 이 함수는 프레젠터만 만들어 두고 반환하므로 자식이 실체화될 때는 이미 플래그가 꺼져 있다.
|
||||||
|
/// 플래그 수명을 인쇄 비주얼 전체로 늘리면 그동안 캔버스에서도 자식이 사라진다(VM 을 공유하므로).
|
||||||
|
///
|
||||||
|
/// 그래서 <b>같은 모델로 같은 타입의 VM 을 하나 더 만들고</b> 인쇄될 자식만 담는다.
|
||||||
|
/// 타입이 같으니 템플릿이 그대로 잡히고, 모델이 같으니 배경·테두리·머리글이 전부 똑같이 나온다.
|
||||||
|
/// 자식 VM 은 새로 만들지 않고 원본을 그대로 담는다 — 해석된 글꼴·색이 유지된다.
|
||||||
|
/// 캔버스가 쓰는 VM 은 <b>한 글자도 건드리지 않는다.</b>
|
||||||
|
/// </summary>
|
||||||
|
private static object PrintProjection(ControlViewModel control)
|
||||||
|
{
|
||||||
|
if (control is not ContainerViewModel container)
|
||||||
|
{
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
ContainerViewModel? copy = container switch
|
||||||
|
{
|
||||||
|
PanelViewModel => new PanelViewModel(container.Model),
|
||||||
|
GroupBoxViewModel => new GroupBoxViewModel(container.Model),
|
||||||
|
// 모르는 컨테이너 타입은 손대지 않는다 — 잘못 투영하느니 원본을 그리는 쪽이 낫다
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
if (copy is null)
|
||||||
|
{
|
||||||
|
return control;
|
||||||
|
}
|
||||||
|
foreach (var child in container.Children)
|
||||||
|
{
|
||||||
|
if (child.Model.Hidden || child is DataTableViewModel
|
||||||
|
|| !SheetMe.Core.Catalog.PrintFilter.IsPrinted(child.Model))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (PrintProjection(child) is ControlViewModel projected)
|
||||||
|
{
|
||||||
|
copy.Children.Add(projected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 사본은 시각 컨텍스트를 아직 못 받았다 — 원본이 해석해 둔 값을 부모 값으로 넘긴다.
|
||||||
|
// 컨테이너에 자기 글꼴 속성이 있으면 그것이 이기고, 없으면 상속값이 그대로 온다 — 양쪽 다 원본과 같다.
|
||||||
|
copy.ResolveVisualContext(container.EffectiveFont, container.Foreground);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user