From dbb3deeda3dc6fbca598ecd0686d158562eeeb62 Mon Sep 17 00:00:00 2001 From: Msystech Date: Tue, 18 Aug 2026 09:32:27 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BB=A8=ED=85=8C=EC=9D=B4=EB=84=88=20?= =?UTF-8?q?=EC=95=88=EB=8F=84=20=EC=9D=B8=EC=87=84=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=EB=A5=BC=20=ED=83=84=EB=8B=A4=20=E2=80=94=20Panel=20=EC=95=88?= =?UTF-8?q?=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EC=9D=B4=20'=EC=9D=B8=EC=87=84?= =?UTF-8?q?=20=EC=B6=9C=EB=A0=A5'=EC=9D=84=20=EC=A7=80=ED=82=A8=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 미리보기 레거시 호환 계획의 P0-1. 오늘 실제로 틀리고 있던 유일한 항목이다. BuildPageVisual 은 page.Controls 최상위만 훑는데, Panel·GroupBox 템플릿이 ItemsSource="{Binding Children}" 로 자식을 스스로 그린다. 그래서 최상위를 아무리 걸러도 컨테이너 안은 그대로 나왔다 — ① Panel 안 컨트롤은 '인쇄 출력'을 꺼도 인쇄됐다 ② 컨테이너 안 MDataTable 은 파란 DB 원통 배지가 종이에 찍혔다 얼마 전 인쇄 필터를 넣으면서 이 구멍을 못 봤다. 먼저 실패시키고 고쳤다. 단정을 넣고 돌리니 요소 17 → 17. 그 red 가 두 번 일했다. 첫 접근이 틀렸고 측정이 잡았다. 컨테이너 VM 에 '인쇄 필터' 플래그를 켰다 끄는 방법을 썼는데 듣지 않았다 — WPF 는 템플릿 자식을 레이아웃 시점에 만드는데 BuildPageVisual 은 프레젠터만 만들어 두고 반환하므로, 자식이 실체화될 때는 이미 finally 가 플래그를 껐다. 플래그 수명을 인쇄 비주얼 전체로 늘리면 그동안 캔버스에서도 자식이 사라진다(VM 공유). 그 접근은 되돌렸다. 택한 방법: 같은 모델로 같은 타입의 VM 을 하나 더 만들고 인쇄될 자식만 담는다. 타입이 같으니 템플릿이 그대로 잡히고, 모델이 같으니 배경·테두리·머리글이 똑같이 나온다. 자식 VM 은 새로 만들지 않고 원본을 담아 해석된 글꼴·색을 유지한다. 캔버스가 쓰는 VM 은 한 글자도 건드리지 않는다 — 단정 하나가 그것을 지킨다. 모르는 컨테이너 타입은 투영하지 않고 원본을 그린다(잘못 투영하느니 그 편이 낫다). 중간에 한 번 더 걸렸다: 처음 단정이 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 --- src/SheetMe.Designer/Diagnostics/EditSmoke.cs | 70 +++++++++++++++++++ src/SheetMe.Designer/Services/PrintService.cs | 55 ++++++++++++++- 2 files changed, 124 insertions(+), 1 deletion(-) diff --git a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs index 8d4fb90..dab4990 100644 --- a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs +++ b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs @@ -17,6 +17,21 @@ public static class EditSmoke { #region Methods /// 스모크 실행 — 결과를 리포트 파일에 기록, 실패 있으면 1 반환 + /// + /// 시각 트리의 요소 수 — 컨테이너 까지 세야 자식 필터가 들었는지 보인다. + /// Canvas 직계만 세면 컨테이너는 늘 1개라 안에서 뭐가 빠졌는지 알 수 없다. + /// + 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) { var lines = new List(); @@ -270,6 +285,61 @@ public static class EditSmoke $"전 {all} → 후 {afterBox}"); Check("미리보기: 라벨의 대문자 Visible 은 무시한다", afterWrongKey == all - 2, $"전 {all} → 후 {afterWrongKey} (레거시는 이 라벨을 인쇄한다)"); + + // 컨테이너 도 필터를 타는가. + // 위 검사는 전부 최상위였다. 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) 클릭 선택 — 컨트롤 중심(핸들 반경 밖) diff --git a/src/SheetMe.Designer/Services/PrintService.cs b/src/SheetMe.Designer/Services/PrintService.cs index 56d51fa..30ae448 100644 --- a/src/SheetMe.Designer/Services/PrintService.cs +++ b/src/SheetMe.Designer/Services/PrintService.cs @@ -80,7 +80,7 @@ public static class PrintService } var presenter = new ContentPresenter { - Content = control, + Content = PrintProjection(control), Width = Math.Max(1, control.Width), Height = Math.Max(1, control.Height), }; @@ -90,5 +90,58 @@ public static class PrintService } return canvas; } + + /// + /// 컨테이너를 인쇄용으로 다시 만든 것으로 바꿔 준다. 컨테이너가 아니면 원본 그대로. + /// + /// 왜 필요한가. 위 루프는 최상위만 훑는데 Panel·GroupBox 템플릿이 자식을 스스로 그린다 + /// (ItemsSource="{Binding Children}"). 그래서 여기서 아무리 걸러도 컨테이너 안은 그대로 나왔다 — + /// Panel 안 컨트롤은 '인쇄 출력'을 꺼도 인쇄됐고, 컨테이너 안 MDataTable 은 + /// 파란 DB 배지가 종이에 찍혔다. + /// + /// 왜 플래그가 아닌가. 처음에는 컨테이너 VM 에 '인쇄 필터' 플래그를 켰다 끄는 방법을 썼는데 + /// 듣지 않았다 — WPF 는 템플릿 자식을 레이아웃 시점에 만드는데, + /// 이 함수는 프레젠터만 만들어 두고 반환하므로 자식이 실체화될 때는 이미 플래그가 꺼져 있다. + /// 플래그 수명을 인쇄 비주얼 전체로 늘리면 그동안 캔버스에서도 자식이 사라진다(VM 을 공유하므로). + /// + /// 그래서 같은 모델로 같은 타입의 VM 을 하나 더 만들고 인쇄될 자식만 담는다. + /// 타입이 같으니 템플릿이 그대로 잡히고, 모델이 같으니 배경·테두리·머리글이 전부 똑같이 나온다. + /// 자식 VM 은 새로 만들지 않고 원본을 그대로 담는다 — 해석된 글꼴·색이 유지된다. + /// 캔버스가 쓰는 VM 은 한 글자도 건드리지 않는다. + /// + 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 }