인스펙터 3탭 분할 — 빈 탭은 그리지 않는다

사용자 요청으로 [200] 식 「디자인 / 데이터 / 동작」 3탭을 넣었다.

[분류] 속성 49종을 레거시 실사용 근거로 배정했다. 이름만 보고 찍으면 사용자가 속성 하나를 찾아
세 탭을 다 뒤지게 되므로, 각 속성이 레거시에서 실제로 어떻게 쓰이는지를 근거로 삼았다.
· GetValue() 가 그 속성을 돌려주면 값이다 — CheckBox/RadioButton 의 Checked 는 IIf(Checked,'Y','')
  이므로 초기값(데이터).
· DateTimePicker.Format/CustomFormat 은 데이터처럼 보이지만 GetValue 가 Format 과 무관하게 항상
  yyyyMMddHHmmss 를 돌려준다 — 그리기 문자열만 바꾸므로 디자인. 확정적.
· Mask 는 WinForms 분류로는 Behavior 지만 실제로는 날짜·시간 입력 형식 제약이라 데이터.
· Score 는 '점수'라는 이름과 달리 체크됐을 때만 값을 내는 데이터 항목이고 CalcBox.Formula 가
  컨트롤명으로 합산한다.
· 상용구 2종(RwdRsvWrdYon/RwdOrderAutYon)은 입력 보조처럼 보이지만 조작 훅이다 — 전자는 포커스
  핸들러 부착 게이트, 후자는 상용구를 고른 결과로 OCS 처방을 전송하는 게이트. **동작**으로 정정했다
  (초안에서는 데이터로 뒀다).
결과: 디자인 21 / 데이터 20 / 동작 8.

[빈 탭은 그리지 않는다 — 이번 결정의 핵심]
분류를 마치고 세어 보니 **동작 속성을 가진 타입은 TextBox·Button 둘뿐이고 나머지 14타입(87.5%)에서
동작 탭이 통째로 빈다.** 데이터 탭도 6타입에서 빈다. 빈 탭을 남겨 두면 대부분의 컨트롤에서
'여긴 없습니다'만 보게 되고 사용자는 빈 탭을 계속 눌러보게 된다 — 탭이 없느니만 못하다.
그래서 개수 0인 탭은 알약을 아예 그리지 않고, 남는 탭이 하나뿐이면 알약 줄 자체를 숨긴다.
결과적으로 라벨·선·패널·그룹상자·날짜선택은 탭 없이 평평하게 보이고, TextBox 만 3개가 뜬다.
현재 탭이 새 선택에서 비면 디자인으로 되돌린다 — 디자인은 공통·글꼴이 있어 절대 비지 않는
안전한 착지점이다.

[유지] 선택한 탭은 컨트롤을 바꿔도 유지된다. 데이터 탭에서 여러 컨트롤의 태그를 잇달아 손보는
작업이 흔한데 매번 디자인으로 돌아가면 계속 다시 눌러야 한다([200] 은 매번 리셋된다 — 따르지 않았다).

[탈출구는 쪼개지 않는다] '전체 속성(고급)'의 레거시 원문 키는 분류 근거가 없고 무엇보다 탈출구다.
탭에 가두면 '고급에도 없네' 상황이 생기므로 모든 탭 하단에 그대로 둔다.

[분류표 위치] Core 가 아니라 Designer 계층에 뒀다(PaletteIconCatalog 선례). 탭 구성은 화면 사정이지
서식 데이터의 성질이 아니다. 모르는 키는 편집기 종류로 추론해 사이트 커스텀 속성에 대비한다.

검증: 테스트 124/124, edit-smoke 실패 0, 왕복 1,271건 diff 0/예외 0, 종이 렌더 P062 픽셀 차이 0.
dialog-shots 진단에 데이터·동작 탭 스냅샷을 추가해 세 탭 전부를 양 테마로 확인.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-12 14:12:29 +09:00
co-authored by Claude Opus 5
parent abd7eb7d0e
commit 394807f8df
4 changed files with 359 additions and 31 deletions
@@ -10,6 +10,53 @@
<UserControl.Resources>
<ctl:IconNameToVisualConverter x:Key="IconName"/>
<BooleanToVisibilityConverter x:Key="BoolToVisibility"/>
<!-- 3탭 알약 — 좌측 패널 탭과 같은 결(선택만 채움) -->
<Style x:Key="TabPill" TargetType="ToggleButton">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border x:Name="bd" Background="Transparent" CornerRadius="6" Padding="0,5" Margin="1">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center">
<ContentPresenter.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{Binding Foreground, RelativeSource={RelativeSource AncestorType=ToggleButton}}"/>
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bd" Property="Background" Value="{DynamicResource B.Hover}"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="bd" Property="Background" Value="{DynamicResource B.Surface}"/>
<Setter Property="Foreground" Value="{DynamicResource B.Ink}"/>
<Setter Property="FontWeight" Value="Bold"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 탭 배지 — 0 이면 그 탭에 볼 게 없다는 뜻이라 흐리게 -->
<Style x:Key="TabBadge" TargetType="TextBlock">
<Setter Property="FontSize" Value="10.5"/>
<Setter Property="FontWeight" Value="Normal"/>
<Setter Property="Margin" Value="4,1,0,0"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
<Style.Triggers>
<Trigger Property="Text" Value="0">
<Setter Property="Opacity" Value="0.45"/>
</Trigger>
</Style.Triggers>
</Style>
<!--
라벨 폭. 실측상 가장 긴 라벨이 11자("입력 파라미터 컨트롤", "선택지(줄바꿈 구분)")라
@@ -348,6 +395,42 @@
<DockPanel>
<TextBlock DockPanel.Dock="Top" Text="{Binding Summary}" FontWeight="Bold"
Margin="10,10,10,2" Foreground="{DynamicResource B.Muted}" TextTrimming="CharacterEllipsis"/>
<!--
디자인 / 데이터 / 동작 3탭 알약([200] 인스펙터 구성).
배지는 눌러보지 않아도 볼 게 있는지 알려준다 — 컨트롤에 따라 데이터·동작 탭이
통째로 비는 경우가 흔해서(라벨·선 등), 배지가 없으면 빈 탭을 계속 눌러보게 된다.
-->
<Border DockPanel.Dock="Top" Margin="10,4,10,8" Padding="2" CornerRadius="8"
Background="{DynamicResource B.Chip}"
Visibility="{Binding ShowTabStrip, Converter={StaticResource BoolToVisibility}}">
<UniformGrid Rows="1">
<ToggleButton Style="{StaticResource TabPill}" IsChecked="{Binding IsDesignTab, Mode=TwoWay}"
ToolTip="글자·색·정렬·크기 등 보이는 방식">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="디자인"/>
<TextBlock Text="{Binding DesignCount}" Style="{StaticResource TabBadge}"/>
</StackPanel>
</ToggleButton>
<ToggleButton Style="{StaticResource TabPill}" IsChecked="{Binding IsDataTab, Mode=TwoWay}"
Visibility="{Binding ShowDataTab, Converter={StaticResource BoolToVisibility}}"
ToolTip="자동 채움·조회·초기값·입력 형식 등 값에 관한 것">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="데이터"/>
<TextBlock Text="{Binding DataCount}" Style="{StaticResource TabBadge}"/>
</StackPanel>
</ToggleButton>
<ToggleButton Style="{StaticResource TabPill}" IsChecked="{Binding IsBehaviorTab, Mode=TwoWay}"
Visibility="{Binding ShowBehaviorTab, Converter={StaticResource BoolToVisibility}}"
ToolTip="액션 태그·대상 컨트롤 등 조작했을 때 벌어지는 일">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="동작"/>
<TextBlock Text="{Binding BehaviorCount}" Style="{StaticResource TabBadge}"/>
</StackPanel>
</ToggleButton>
</UniformGrid>
</Border>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Rows}" Margin="10,0,10,10">
<!-- 접힌 섹션의 행은 컨테이너만 숨긴다 — 행 VM 을 재생성하지 않아 편집 상태가 유지된다 -->
@@ -364,6 +447,22 @@
</ScrollViewer>
</DockPanel>
<!-- 선택은 있는데 이 탭이 빈 경우 — 빈 화면은 고장으로 읽힌다 -->
<TextBlock Text="{Binding EmptyTabText}" FontSize="12" TextAlignment="Center" TextWrapping="Wrap"
Foreground="{DynamicResource B.Muted}" HorizontalAlignment="Center"
VerticalAlignment="Center" Margin="24,0">
<TextBlock.Style>
<Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsTabEmpty}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<!-- 빈 상태 — 선택이 없으면 "선택 없음" 한 줄만 떠 있어 패널이 고장난 것처럼 보였다 -->
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Margin="24,0">
<StackPanel.Style>