초기 커밋: SheetMe 서식생성기 (P0~P5 완료 상태)

레거시 서식생성기(VB.NET WinForms) 대체용 C#/.NET 10 WPF 디자이너.
기준선: 실DB 활성 디자인 1,271건 왕복 의미론 diff 0 / 예외 0, 단위 테스트 49/49.

이 커밋에 함께 포함된 자격증명 분리:
- appsettings.json 을 __HOST__/__PASSWORD__ 플레이스홀더로 전환
- 실접속 정보는 appsettings.Development.json 으로 분리(.gitignore 제외,
  csproj Debug 조건부 복사라 Release 산출물에 실리지 않음)
- ConfigLoader 를 환경변수 > Development > appsettings 순 레이어링으로 변경,
  미치환 플레이스홀더는 '미설정'으로 간주

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-11 17:20:22 +09:00
co-authored by Claude Fable 5
commit 16c07f48dc
102 changed files with 14210 additions and 0 deletions
@@ -0,0 +1,375 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vmc="clr-namespace:SheetMe.Designer.ViewModels.Controls">
<!-- 타입별 렌더 템플릿 — 캔버스/미리보기/인쇄가 공유하는 단일 원천.
레거시 WinForms 렌더 모양의 시각 근사. 콘텐츠는 히트테스트 제외(입력은 월드 파이프라인 단일 수신). -->
<BooleanToVisibilityConverter x:Key="BoolToVisibility"/>
<!-- 라벨 -->
<DataTemplate DataType="{x:Type vmc:LabelViewModel}">
<Grid Background="{Binding Background}" IsHitTestVisible="False">
<TextBlock Text="{Binding Text}"
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
FontWeight="{Binding FontWeight}" FontStyle="{Binding FontStyle}"
Foreground="{Binding Foreground}"
HorizontalAlignment="{Binding TextAlignment}"
VerticalAlignment="Top"
TextWrapping="Wrap"/>
</Grid>
</DataTemplate>
<!-- 텍스트박스 -->
<DataTemplate DataType="{x:Type vmc:TextBoxViewModel}">
<Border Background="{Binding Background}" IsHitTestVisible="False">
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderBrush" Value="#7F9DB9"/>
<Setter Property="BorderThickness" Value="1"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ShowBorder}" Value="False">
<Setter Property="BorderThickness" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{Binding Text}" Margin="2,1"
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
FontWeight="{Binding FontWeight}" FontStyle="{Binding FontStyle}"
Foreground="{Binding Foreground}"
HorizontalAlignment="{Binding TextAlignment}"
VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping" Value="NoWrap"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Multiline}" Value="True">
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="VerticalAlignment" Value="Top"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
</DataTemplate>
<!-- 마스크 입력 -->
<DataTemplate DataType="{x:Type vmc:MaskedTextBoxViewModel}">
<Border Background="{Binding Background}" BorderBrush="#7F9DB9" BorderThickness="1" IsHitTestVisible="False">
<TextBlock Margin="2,1" VerticalAlignment="Center"
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
Foreground="{Binding Foreground}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Text}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text}" Value="">
<Setter Property="Text" Value="{Binding Mask}"/>
<Setter Property="Opacity" Value="0.45"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
</DataTemplate>
<!-- 체크박스 -->
<DataTemplate DataType="{x:Type vmc:CheckBoxViewModel}">
<StackPanel Orientation="Horizontal" Background="{Binding Background}" IsHitTestVisible="False">
<Border Width="13" Height="13" Background="White" BorderBrush="#5A5A5A" BorderThickness="1"
VerticalAlignment="Center" Margin="0,0,4,0">
<Path Data="M1,6 L5,10 L11,2" Stroke="#1E5AA0" StrokeThickness="2"
Visibility="{Binding IsChecked, Converter={StaticResource BoolToVisibility}}"/>
</Border>
<TextBlock Text="{Binding Text}" VerticalAlignment="Center"
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
FontWeight="{Binding FontWeight}" Foreground="{Binding Foreground}"/>
</StackPanel>
</DataTemplate>
<!-- 라디오버튼 -->
<DataTemplate DataType="{x:Type vmc:RadioButtonViewModel}">
<StackPanel Orientation="Horizontal" Background="{Binding Background}" IsHitTestVisible="False">
<Grid Width="13" Height="13" VerticalAlignment="Center" Margin="0,0,4,0">
<Ellipse Fill="White" Stroke="#5A5A5A" StrokeThickness="1"/>
<Ellipse Width="6" Height="6" Fill="#1E5AA0"
Visibility="{Binding IsChecked, Converter={StaticResource BoolToVisibility}}"/>
</Grid>
<TextBlock Text="{Binding Text}" VerticalAlignment="Center"
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
FontWeight="{Binding FontWeight}" Foreground="{Binding Foreground}"/>
</StackPanel>
</DataTemplate>
<!-- 콤보박스 -->
<DataTemplate DataType="{x:Type vmc:ComboBoxViewModel}">
<Border Background="White" BorderBrush="#7F9DB9" BorderThickness="1" IsHitTestVisible="False">
<DockPanel>
<Border DockPanel.Dock="Right" Width="17" Background="#E1E1E1">
<Path Data="M0,0 L8,0 L4,5 Z" Fill="#5A5A5A"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<TextBlock Text="{Binding DisplayText}" Margin="3,1" VerticalAlignment="Center"
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
Foreground="{Binding Foreground}" TextTrimming="CharacterEllipsis"/>
</DockPanel>
</Border>
</DataTemplate>
<!-- 리스트박스 -->
<DataTemplate DataType="{x:Type vmc:ListBoxViewModel}">
<Border Background="White" BorderBrush="#7F9DB9" BorderThickness="1" IsHitTestVisible="False">
<ItemsControl ItemsSource="{Binding Items}" Margin="2">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontSize="12"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</DataTemplate>
<!-- 체크리스트 -->
<DataTemplate DataType="{x:Type vmc:CheckListViewModel}">
<Border Background="White" BorderBrush="#7F9DB9" BorderThickness="1" IsHitTestVisible="False">
<ItemsControl ItemsSource="{Binding Items}" Margin="2">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Border Width="11" Height="11" Background="White" BorderBrush="#5A5A5A"
BorderThickness="1" Margin="0,1,4,1"/>
<TextBlock Text="{Binding}" FontSize="12"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</DataTemplate>
<!-- 날짜선택 -->
<DataTemplate DataType="{x:Type vmc:DateTimePickerViewModel}">
<Border Background="White" BorderBrush="#7F9DB9" BorderThickness="1" IsHitTestVisible="False">
<DockPanel>
<Border DockPanel.Dock="Right" Width="17" Background="#E1E1E1">
<Path Data="M0,0 L8,0 L4,5 Z" Fill="#5A5A5A"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<TextBlock Text="{Binding DisplayText}" Margin="3,1" VerticalAlignment="Center"
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
Foreground="{Binding Foreground}" TextTrimming="CharacterEllipsis"/>
</DockPanel>
</Border>
</DataTemplate>
<!-- 패널 (컨테이너 — 자식 재귀 렌더) -->
<DataTemplate DataType="{x:Type vmc:PanelViewModel}">
<Grid Background="{Binding Background}" IsHitTestVisible="False">
<Border BorderBrush="#A0A0A0">
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderThickness" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ShowBorder}" Value="True">
<Setter Property="BorderThickness" Value="1"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
<ItemsControl ItemsSource="{Binding Children}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
<Setter Property="Width" Value="{Binding Width}"/>
<Setter Property="Height" Value="{Binding Height}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</DataTemplate>
<!-- 그룹박스 (컨테이너) -->
<DataTemplate DataType="{x:Type vmc:GroupBoxViewModel}">
<Grid Background="{Binding Background}" IsHitTestVisible="False">
<Border BorderBrush="#B5B5B5" BorderThickness="1" CornerRadius="3" Margin="0,7,0,0"/>
<TextBlock Text="{Binding Text}" Margin="8,0,0,0" Padding="3,0"
HorizontalAlignment="Left" VerticalAlignment="Top"
Background="{Binding Background}"
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
FontWeight="{Binding FontWeight}" Foreground="{Binding Foreground}"/>
<ItemsControl ItemsSource="{Binding Children}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
<Setter Property="Width" Value="{Binding Width}"/>
<Setter Property="Height" Value="{Binding Height}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</DataTemplate>
<!-- 선 -->
<DataTemplate DataType="{x:Type vmc:LineViewModel}">
<Rectangle Fill="{Binding LineBrush}" IsHitTestVisible="False"/>
</DataTemplate>
<!-- 이미지 -->
<DataTemplate DataType="{x:Type vmc:PictureBoxViewModel}">
<Border Background="#F5F7FA" BorderBrush="#C8D0DA" BorderThickness="1" IsHitTestVisible="False">
<TextBlock Text="{Binding Hint}" FontSize="10" Foreground="#8090A0"
HorizontalAlignment="Center" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"/>
</Border>
</DataTemplate>
<!-- 계산박스 -->
<DataTemplate DataType="{x:Type vmc:CalcBoxViewModel}">
<Border Background="#FFFDF0" BorderBrush="#C9B458" BorderThickness="1" IsHitTestVisible="False">
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="ƒ" Margin="3,0,2,0" Foreground="#A08A2A"
FontWeight="Bold" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Text}" VerticalAlignment="Center"
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
Foreground="{Binding Foreground}"/>
</DockPanel>
</Border>
</DataTemplate>
<!-- 버튼 (레거시 MButton — 표준 버튼 근사) -->
<DataTemplate DataType="{x:Type vmc:ButtonViewModel}">
<Border CornerRadius="3" BorderBrush="#8A9AAB" BorderThickness="1" IsHitTestVisible="False">
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#FDFDFD" Offset="0"/>
<GradientStop Color="#E8ECF0" Offset="0.5"/>
<GradientStop Color="#D8DEE6" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
<Grid>
<TextBlock Text="{Binding Text}"
FontFamily="{Binding FontFamily}" FontSize="{Binding FontSize}"
FontWeight="{Binding FontWeight}" Foreground="{Binding Foreground}"
HorizontalAlignment="Center" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"/>
<TextBlock Text="{Binding ActionHint}" FontSize="8" Foreground="#7A8AA0"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,3,1"
TextTrimming="CharacterEllipsis"/>
</Grid>
</Border>
</DataTemplate>
<!-- 데이터소스 (레거시 MDataTable — 디자인 전용 배지, 런타임/인쇄 비가시) -->
<DataTemplate DataType="{x:Type vmc:DataTableViewModel}">
<Border Background="#EAF2FB" BorderBrush="#5B8DEF" BorderThickness="1" CornerRadius="4"
IsHitTestVisible="False" ToolTip="{Binding QuerySummary}">
<Grid>
<!-- DB 원통 아이콘 -->
<Path Fill="#5B8DEF" Stretch="Uniform" Margin="6"
Data="M4,3 C4,1.9 7.6,1 12,1 C16.4,1 20,1.9 20,3 L20,17 C20,18.1 16.4,19 12,19 C7.6,19 4,18.1 4,17 Z M4,3 C4,4.1 7.6,5 12,5 C16.4,5 20,4.1 20,3 M4,8 C4,9.1 7.6,10 12,10 C16.4,10 20,9.1 20,8 M4,13 C4,14.1 7.6,15 12,15 C16.4,15 20,14.1 20,13"/>
<TextBlock Text="{Binding Id}" FontSize="7" Foreground="#3E6BC4"
HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,1"
TextTrimming="CharacterEllipsis"/>
</Grid>
</Border>
</DataTemplate>
<!-- 표 (레거시 Spread — E_SpdMst 격자 렌더, 읽기 전용) -->
<DataTemplate DataType="{x:Type vmc:SpreadViewModel}">
<Border BorderBrush="#8A9AAB" BorderThickness="1" Background="White" IsHitTestVisible="False">
<Grid>
<!-- 격자선 -->
<ItemsControl ItemsSource="{Binding GridLines}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="{Binding X1}" Y1="{Binding Y1}" X2="{Binding X2}" Y2="{Binding Y2}"
Stroke="#C6CDD6" StrokeThickness="1"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- 셀 텍스트(스팬 반영) -->
<ItemsControl ItemsSource="{Binding CellViews}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Width="{Binding W}" Height="{Binding H}">
<TextBlock Text="{Binding Text}"
FontFamily="{Binding DataContext.FontFamily, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
FontSize="{Binding DataContext.FontSize, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
HorizontalAlignment="Center" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis" TextWrapping="Wrap"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- 격자 정보 없음(파일 열기 등) 안내 -->
<TextBlock Text="표 (Spread — DB에서 열면 격자 표시)" FontSize="10" Foreground="#8090A0"
HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding HasGrid}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Border>
</DataTemplate>
<!-- 미지원 컨트롤 자리표시 (빗금 + 원본 타입명) -->
<DataTemplate DataType="{x:Type vmc:PlaceholderViewModel}">
<Border BorderBrush="#D08080" BorderThickness="1" IsHitTestVisible="False">
<Border.Background>
<DrawingBrush TileMode="Tile" Viewport="0,0,8,8" ViewportUnits="Absolute">
<DrawingBrush.Drawing>
<GeometryDrawing Geometry="M0,8 L8,0">
<GeometryDrawing.Pen>
<Pen Brush="#30D08080" Thickness="1"/>
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Border.Background>
<TextBlock Text="{Binding LegacyClassName}" FontSize="10" Foreground="#B06060"
HorizontalAlignment="Center" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"/>
</Border>
</DataTemplate>
</ResourceDictionary>