별도 창 전수 점검 — 암시 Window 스타일이 파생 클래스에 적용된 적이 없었다

사용자 지적: "다른 창으로(새창으로 열리는) 실행하는 항목들도 모두 점검해줘".

[근본 원인 — 대화상자 전체가 다크에서 흰 창이었다]
WPF 는 암시 스타일을 요소의 **정확한 런타임 타입**으로만 찾는다. 저장소의 모든 창이
Window 를 상속한 클래스(SheetOpenDialogView : Window, QueryEditorWindow : Window …)라
DesignerTheme.xaml 의 <Style TargetType="Window"> 는 **단 한 번도 적용된 적이 없다**.
결과: 창 배경이 WPF 기본 흰색으로 남고, 그 위 글자는 암시 TextBlock 스타일(B.Ink #EDEDED)을
정상적으로 받는다 → **다크 테마에서 흰 바탕에 흰 글자**. 쿼리 편집기의 변수 목록처럼 라벨이
통째로 사라지는 화면이 나왔다.

라이트에서 눈에 덜 띈 이유는 우연이다 — 기본 흰색(#FFFFFF)이 의도값 B.AppBg(#F5F5F5)와
거의 같아서다. 그래서 지금까지 라이트만 보고는 발견되지 않았다.

수정: 스타일에 x:Key="ThemedWindow" 를 주고 창 10종에 직접 걸었다(코드로 짓는
ColorPickerWindow 는 생성자에서 TryFindResource). 순수 Window 인스턴스용 암시 스타일은
BasedOn 으로 남겼다. 앞으로 창을 추가할 때 잊지 않도록 스타일 위에 경고 주석을 붙였다.

[신규 진단 --dialog-shots <출력폴더>]
이 결함은 정적 분석으로는 못 잡았다(앞선 감사에서 "대화상자들은 암시 Window 스타일을 그대로
받는다"고 잘못 결론냈다). 실입력 주입은 화면 잠금·세션 격리 상태에서 OS 가 거부한다
(SetCursorPos 무시, SendKeys "Access is denied" — 이번에도 중간부터 막혔다).
그래서 창을 화면 밖(-10000)에 띄워 RenderTargetBitmap 으로 찍는 진단을 만들었다.
창 8종 × 2테마 = 16장을 한 번에 남기고, 각 창의 **해석된 Background 와 Style 적용 여부를
텍스트로 함께 보고**한다 — 픽셀만 보면 원인을 못 가린다(실제로 이 한 줄이 원인을 확정했다).
표본 데이터는 실제 사용 시와 같은 형태로 넣었다(빈 껍데기를 찍으면 의미가 없다).

함정 2개를 코드에 남겼다.
· 기본 ShutdownMode 가 OnLastWindowClose 라 찍고 닫는 순간 앱이 종료된다 → OnExplicitShutdown.
· Window.Content 만 렌더하면 창 배경이 빠져 투명(=PNG 검정)이 되고, 다크처럼 보이는 착시로
  라이트 결함이 가려진다 → 배경을 먼저 칠하고 그 위에 콘텐츠를 그린다.

[함께 고친 것 — 여러 줄 입력이 세로 가운데 정렬]
쿼리 편집기의 SQL 이 큰 상자 한가운데 떠 있었다. 템플릿 트리거가 ScrollViewer 의
VerticalContentAlignment 만 Stretch 로 바꿨는데, 텍스트를 배치하는 건 TextBox 자신의
VerticalContentAlignment 이라 아무 효과가 없었다. Style.Triggers 로 옮겨 Top 으로 두고
여러 줄일 때 Padding 도 넉넉히 준다. 인스펙터의 여러 줄 속성 행에도 함께 적용된다.

검증: 창 8종 × 2테마 전부 재렌더해 배경 확인(다크 #0E0E0E / 라이트 #F5F5F5,
미리보기만 의도대로 B.CanvasBg). 테스트 124/124, edit-smoke 실패 0,
**종이 렌더 P062 픽셀 대조 차이 0**.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-12 12:54:55 +09:00
co-authored by Claude Opus 5
parent ff87353d0d
commit 308197c15e
14 changed files with 245 additions and 14 deletions
@@ -54,6 +54,11 @@ public sealed class ColorPickerWindow : Window
private ColorPickerWindow(string? initialHex)
{
Title = "색상 선택";
// 암시 Window 스타일은 파생 클래스에 적용되지 않는다 — 직접 걸지 않으면 다크에서 흰 창이 된다
if (Application.Current?.TryFindResource("ThemedWindow") is Style themed)
{
Style = themed;
}
Width = 300;
SizeToContent = SizeToContent.Height;
WindowStartupLocation = WindowStartupLocation.CenterOwner;
@@ -2,7 +2,8 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="폰트 일괄 변경" Width="620" Height="560" MinWidth="520" MinHeight="420"
WindowStartupLocation="CenterOwner" ShowInTaskbar="False">
WindowStartupLocation="CenterOwner" ShowInTaskbar="False"
Style="{StaticResource ThemedWindow}">
<DockPanel Margin="12">
<TextBlock DockPanel.Dock="Top" Foreground="{DynamicResource B.Muted}" TextWrapping="Wrap" Margin="0,0,0,8"
Text="문서 전체의 (타입·글꼴·크기) 조합입니다. 변경할 조합을 선택(다중 가능)하고 새 글꼴을 지정하세요."/>
+2 -1
View File
@@ -16,7 +16,8 @@
Width="1440" Height="920" MinWidth="940" MinHeight="640"
WindowStyle="None" WindowStartupLocation="CenterScreen"
Background="{DynamicResource B.AppBg}" FontFamily="Malgun Gothic" FontSize="13"
Loaded="OnWindowLoaded" StateChanged="OnWindowStateChanged" Closing="OnWindowClosing">
Loaded="OnWindowLoaded" StateChanged="OnWindowStateChanged" Closing="OnWindowClosing"
Style="{StaticResource ThemedWindow}">
<!--
파일/앱 계열만 창 단위로 바인딩한다. 편집 계열(Ctrl+Z/C/V/X/D/A/G)은 CanvasKeyboardBehavior 가
@@ -2,7 +2,8 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="마스크 편집" Width="620" Height="620"
WindowStartupLocation="CenterOwner" ShowInTaskbar="False">
WindowStartupLocation="CenterOwner" ShowInTaskbar="False"
Style="{StaticResource ThemedWindow}">
<DockPanel Margin="12">
<!--
백슬래시를 글리프로 보여주지 않는다 — 한글 글꼴에서 원화 기호(₩)로 렌더되어 오해를 부른다.
@@ -2,7 +2,8 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="미리보기" Width="900" Height="1000"
WindowStartupLocation="CenterOwner" Background="{DynamicResource B.CanvasBg}">
WindowStartupLocation="CenterOwner" Background="{DynamicResource B.CanvasBg}"
Style="{StaticResource ThemedWindow}">
<DockPanel>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
@@ -2,7 +2,8 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="쿼리 편집" Width="960" Height="560" MinWidth="720" MinHeight="400"
WindowStartupLocation="CenterOwner" ShowInTaskbar="False">
WindowStartupLocation="CenterOwner" ShowInTaskbar="False"
Style="{StaticResource ThemedWindow}">
<DockPanel Margin="12">
<TextBlock x:Name="HintText" DockPanel.Dock="Top" Foreground="{DynamicResource B.Muted}" TextWrapping="Wrap" Margin="0,0,0,8"
Text="작성 시점에 &lt;&lt;변수&gt;&gt; 가 실제 값으로 치환됩니다. 변수는 클래스 전체 이름을 포함해야 하므로 우측 목록을 더블클릭해 삽입하세요 — 임의로 줄여 쓰면 치환되지 않고 SQL 에 그대로 남아 오류가 납니다."/>
@@ -2,7 +2,8 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="상용구 관리" Width="560" Height="520" MinWidth="480" MinHeight="380"
WindowStartupLocation="CenterOwner" ShowInTaskbar="False">
WindowStartupLocation="CenterOwner" ShowInTaskbar="False"
Style="{StaticResource ThemedWindow}">
<DockPanel Margin="12">
<TextBlock x:Name="HeaderText" DockPanel.Dock="Top" Foreground="{DynamicResource B.Muted}"
TextWrapping="Wrap" Margin="0,0,0,8"/>
@@ -2,7 +2,8 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="신규 서식 등록" Width="420" SizeToContent="Height"
WindowStartupLocation="CenterOwner" ShowInTaskbar="False" ResizeMode="NoResize">
WindowStartupLocation="CenterOwner" ShowInTaskbar="False" ResizeMode="NoResize"
Style="{StaticResource ThemedWindow}">
<StackPanel Margin="16">
<TextBlock Text="E_ShtMst 에 등록되지 않은 서식입니다. 신규 등록 후 저장합니다."
TextWrapping="Wrap" Foreground="{DynamicResource B.Muted}" Margin="0,0,0,12"/>
@@ -2,7 +2,8 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="서식 수정이력" Width="560" Height="520" MinWidth="480" MinHeight="380"
WindowStartupLocation="CenterOwner" ShowInTaskbar="False">
WindowStartupLocation="CenterOwner" ShowInTaskbar="False"
Style="{StaticResource ThemedWindow}">
<DockPanel Margin="12">
<TextBlock x:Name="HeaderText" DockPanel.Dock="Top" Foreground="{DynamicResource B.Muted}"
TextWrapping="Wrap" Margin="0,0,0,8"/>
@@ -2,7 +2,8 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DB에서 서식 열기" Width="680" Height="560"
WindowStartupLocation="CenterOwner" ShowInTaskbar="False">
WindowStartupLocation="CenterOwner" ShowInTaskbar="False"
Style="{StaticResource ThemedWindow}">
<DockPanel Margin="12">
<DockPanel DockPanel.Dock="Top">
<Button DockPanel.Dock="Right" Content="검색" Padding="14,4" Margin="6,0,0,0" Click="OnSearch" IsDefault="True"/>
@@ -2,7 +2,8 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="태그 선택" Width="480" Height="560"
WindowStartupLocation="CenterOwner" ShowInTaskbar="False">
WindowStartupLocation="CenterOwner" ShowInTaskbar="False"
Style="{StaticResource ThemedWindow}">
<DockPanel Margin="12">
<TextBlock x:Name="TitleText" DockPanel.Dock="Top" Foreground="{DynamicResource B.Muted}"
TextWrapping="Wrap" Margin="0,0,0,8"/>