[200] 디자인 동일화 2/2 — 좌측 패널·타이틀바·인스펙터 정보 밀도
1/2 가 껍데기(스크롤바·창 기본값)를 맞췄다면 이번은 그 안에 든 정보다. [200] 과의 실제 격차는
장식이 아니라 "한 화면에서 얼마나 읽히는가"였고, 그중 둘은 데이터 손상 경로이기도 했다.
[레이어] 행 라벨을 Model.Id → 신규 LayerName(Text 우선, 없으면 Id)으로. 운영 서식의 Id 는
대부분 Label12 꼴 자동 이름이라 291개짜리 목록이 아무것도 알려주지 않았다. Id 는 외부 참조
키이므로(DataTableField 대상명, EventHandlerMappingTag 의 ShtCod_컨트롤명 규약) 버리지 않고
툴팁 + 검색 대상으로 유지한다. 앞에 타입 아이콘(Accent), 뒤 숨김/잠금을 MiniToggle(eye/eye-off,
lock/unlock)로 — 사각 체크박스로는 상태가 안 읽혔다.
검색칩 신설(LayerFilter, 이름·Id 부분일치). 컨트롤 최대 1,067개 서식에서 스크롤만으로는 대상을
찾을 수 없다. 칩 안 TextBox 에 MinHeight=0 필수 — 전역 TextBox MinHeight 30 이 32px 칩에서
글자를 자른다.
[인스펙터·여러 값] IsMixed 가 설정만 되고 화면 어디에도 바인딩돼 있지 않았다. 다중선택에서 값이
갈리면 편집기가 빈 칸이 되는데 그 빈 칸은 "값 없음"과 구별되지 않는다. 무심코 입력하면 서로
달랐던 값이 전부 한 값으로 덮인다. 편집기 위에 비파괴 오버레이("여러 값")를 얹고, 포커스가
들어오면 감춘다 — 값은 LostFocus 에만 올라가므로 타이핑 중 ValueText 는 계속 "" 이고 안내가
방금 친 글자 위에 남는다.
토글 행은 IsOn 을 bool? 로 바꿔 3상태로. false 로 내리면 "전부 꺼짐"으로 보이고, 사용자가 한 번
켰다 끄면 전부 False 가 되어 조용히 뭉개진다.
[인스펙터·접이식] 섹션 헤더를 클릭 가능한 접기/펴기로(셰브런 + 소속 행 수). 고급 속성까지 켜면
수십 행이 된다. Rows 는 평면 컬렉션을 유지하고 섹션이 자기 행을 들고 있다가 컨테이너
Visibility 만 토글한다 — 행 VM 을 재생성하지 않아 편집 중 값·포커스가 보존되고, 다중선택
재구성이 잦은 이 화면에서 트리 재구축 비용도 피한다.
[타이틀바] 로고 메뉴 루트(셰브런) 아래로 5개 메뉴를 흡수하고, 편집 아이콘 스트립(실행취소·다시
실행 | 복사·삭제·그룹·해제)과 우측 액션(테마·저장·미리보기)을 배치. 중앙 제목은 뺐다 — 문서 탭이
같은 정보를 이미 들고 있다.
[빈 상태] 문서 0개 → 회색 허공 대신 다음 행동 3개(새 서식 / DB에서 열기 / 파일 열기).
선택 0개 → 인스펙터에 "선택 없음" 한 줄만 떠 패널이 고장 난 것처럼 보이던 것을 안내로 교체.
[그 외] 서식 목록에 하단 상태 한 줄(로딩·건수·DB 미접속) — 종전에는 목록 위에 "불러오는 중"
오버레이가 덮여 목록이 가려졌고 건수는 창 하단 전역 상태줄에만 있었다. 활성 문서 탭 아이콘을
Accent 로. 플로팅 바의 ±버튼 제거([200] 동일 — Ctrl+휠이 주 조작이고 배율 버튼이 플라이아웃을
겸한다), 대신 "화면 맞춤" 추가. ZoomFit 은 문서 전체가 아니라 활성 페이지 한 장 기준이다
(7페이지 서식을 통째로 맞추면 14% 가 되어 아무것도 안 보인다). 뷰포트 크기는 캔버스 뷰가
SizeChanged 에서 VM 에 통지 — VM 이 시각 트리를 몰라도 되게 한다.
열 기본 폭 232→248 / 280→300.
검증: 테스트 124/124, edit-smoke 실패 0, 왕복 1,271건 diff 0/예외 0.
**종이 렌더 P062 픽셀 대조 차이 0**(4,802바이트 전부 일치).
실 UI 확인: P062 레이어 행·인스펙터 접기, P093 291컨트롤에서 레이어 검색("알러지" → 10건),
116개 선택 시 X/Y/너비/높이 "여러 값" 표시.
남은 배치: E(선택 오버레이) · F(캔버스 크롬 — 종이에 닿는 유일한 배치) · G(토스트·ThemedDialog).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7591fbed74
commit
9d20cc2971
@@ -113,6 +113,7 @@ public abstract class ControlViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
Model.Locked = value;
|
Model.Locked = value;
|
||||||
OnPropertyChanged(nameof(IsLockedFlag));
|
OnPropertyChanged(nameof(IsLockedFlag));
|
||||||
|
OnPropertyChanged(nameof(LockIcon));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,12 +125,39 @@ public abstract class ControlViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
Model.Hidden = value;
|
Model.Hidden = value;
|
||||||
OnPropertyChanged(nameof(IsHiddenFlag));
|
OnPropertyChanged(nameof(IsHiddenFlag));
|
||||||
|
OnPropertyChanged(nameof(HiddenIcon));
|
||||||
OnPropertyChanged(nameof(DesignOpacity));
|
OnPropertyChanged(nameof(DesignOpacity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>디자인 표시 불투명도 — 숨김이면 0.25</summary>
|
/// <summary>디자인 표시 불투명도 — 숨김이면 0.25</summary>
|
||||||
public double DesignOpacity => Model.Hidden ? 0.25 : 1.0;
|
public double DesignOpacity => Model.Hidden ? 0.25 : 1.0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 레이어 목록 표시명 — Text 가 있으면 그것, 없으면 Id.
|
||||||
|
///
|
||||||
|
/// Id 는 대부분 <c>Label12</c> 처럼 자동 생성된 이름이라 목록에서 아무것도 알려주지 않는다.
|
||||||
|
/// 다만 Id 는 외부 참조 키이기도 하므로(DataTableField 대상명, EventHandlerMappingTag 의
|
||||||
|
/// <c>ShtCod_컨트롤명</c> 규약) 버리지 않고 툴팁·검색 대상으로 함께 유지한다.
|
||||||
|
/// </summary>
|
||||||
|
public string LayerName
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var text = Text.Replace('\r', ' ').Replace('\n', ' ').Trim();
|
||||||
|
if (text.Length == 0)
|
||||||
|
{
|
||||||
|
return Id;
|
||||||
|
}
|
||||||
|
return text.Length <= 40 ? text : string.Concat(text.AsSpan(0, 39), "…");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>레이어 잠금 토글 아이콘명</summary>
|
||||||
|
public string LockIcon => Model.Locked ? "lock" : "unlock";
|
||||||
|
|
||||||
|
/// <summary>레이어 숨김 토글 아이콘명</summary>
|
||||||
|
public string HiddenIcon => Model.Hidden ? "eye-off" : "eye";
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Constructors
|
#region Constructors
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ public sealed class DesignerViewModel : ViewModelBase
|
|||||||
private double zoom = 1.0;
|
private double zoom = 1.0;
|
||||||
private FormDocument document;
|
private FormDocument document;
|
||||||
private PageViewModel? selectedPage;
|
private PageViewModel? selectedPage;
|
||||||
|
private string layerFilter = string.Empty;
|
||||||
|
private double viewportWidth;
|
||||||
|
private double viewportHeight;
|
||||||
private bool isTabOrderMode;
|
private bool isTabOrderMode;
|
||||||
private readonly List<ControlViewModel> tabOrderSequence = new();
|
private readonly List<ControlViewModel> tabOrderSequence = new();
|
||||||
private readonly Dictionary<string, SpreadGridInfo>? spreadDesigns;
|
private readonly Dictionary<string, SpreadGridInfo>? spreadDesigns;
|
||||||
@@ -138,10 +141,50 @@ public sealed class DesignerViewModel : ViewModelBase
|
|||||||
get => selectedPage;
|
get => selectedPage;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (SetProperty(ref selectedPage, value) && value is not null)
|
if (SetProperty(ref selectedPage, value))
|
||||||
|
{
|
||||||
|
if (value is not null)
|
||||||
{
|
{
|
||||||
Selection.ActivePage = value;
|
Selection.ActivePage = value;
|
||||||
}
|
}
|
||||||
|
OnPropertyChanged(nameof(VisibleLayers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 레이어 검색어 — 이름(LayerName)·Id 부분일치. 서식당 컨트롤이 수백 개인 경우가 흔해
|
||||||
|
/// (운영 최대 1,067개) 스크롤만으로는 대상을 찾을 수 없다.
|
||||||
|
/// </summary>
|
||||||
|
public string LayerFilter
|
||||||
|
{
|
||||||
|
get => layerFilter;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref layerFilter, value ?? string.Empty))
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(VisibleLayers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 레이어 목록에 실제로 표시할 항목 — 활성 페이지의 컨트롤에 검색어를 적용한 결과.
|
||||||
|
/// 검색어가 비면 원본 컬렉션을 그대로 돌려준다(항목 추가·삭제가 즉시 반영되도록).
|
||||||
|
/// </summary>
|
||||||
|
public System.Collections.IEnumerable? VisibleLayers
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var source = SelectedPage?.Controls;
|
||||||
|
if (source is null || layerFilter.Length == 0)
|
||||||
|
{
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
return source
|
||||||
|
.Where(c => c.LayerName.Contains(layerFilter, StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| c.Id.Contains(layerFilter, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,6 +444,38 @@ public sealed class DesignerViewModel : ViewModelBase
|
|||||||
|
|
||||||
/// <summary>줌 100%</summary>
|
/// <summary>줌 100%</summary>
|
||||||
public void ZoomReset() => Zoom = 1.0;
|
public void ZoomReset() => Zoom = 1.0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 캔버스 뷰포트 크기 통지 — DesignerCanvasView 가 SizeChanged 에서 호출한다.
|
||||||
|
/// ZoomFit 은 뷰포트를 알아야 하는데 VM 은 시각 트리를 모르므로 값만 받아 둔다.
|
||||||
|
/// </summary>
|
||||||
|
public void ReportViewport(double width, double height)
|
||||||
|
{
|
||||||
|
viewportWidth = width;
|
||||||
|
viewportHeight = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 화면 맞춤 — 활성 페이지 한 장이 뷰포트에 들어오는 배율.
|
||||||
|
/// 문서 전체(WorldHeight)가 아니라 페이지 한 장 기준이다. 7페이지 서식을 통째로 맞추면
|
||||||
|
/// 14% 가 되어 아무것도 안 보인다(운영 최대 7페이지).
|
||||||
|
/// </summary>
|
||||||
|
public void ZoomFit()
|
||||||
|
{
|
||||||
|
var page = SelectedPage ?? (Pages.Count > 0 ? Pages[0] : null);
|
||||||
|
if (page is null || viewportWidth <= 0 || viewportHeight <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Border Padding 24 양쪽 + 스크롤바 여유
|
||||||
|
var availableWidth = viewportWidth - 64;
|
||||||
|
var availableHeight = viewportHeight - 64;
|
||||||
|
if (availableWidth <= 0 || availableHeight <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Zoom = Math.Round(Math.Min(availableWidth / page.WidthDip, availableHeight / page.HeightDip), 2);
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Methods - 좌표/히트테스트
|
#region Methods - 좌표/히트테스트
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
"System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
|
"System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
|
||||||
private readonly DesignerViewModel designer;
|
private readonly DesignerViewModel designer;
|
||||||
private readonly List<(PropertyRowViewModel Row, RowBinding Binding)> boundsRows = new();
|
private readonly List<(PropertyRowViewModel Row, RowBinding Binding)> boundsRows = new();
|
||||||
|
private SectionRowViewModel? currentSection;
|
||||||
private bool showAdvanced;
|
private bool showAdvanced;
|
||||||
|
|
||||||
/// <summary>공통/글꼴 섹션이 이미 다루는 키 — 고급 목록에서 제외</summary>
|
/// <summary>공통/글꼴 섹션이 이미 다루는 키 — 고급 목록에서 제외</summary>
|
||||||
@@ -33,6 +34,9 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
/// <summary>행 목록(섹션 헤더 포함)</summary>
|
/// <summary>행 목록(섹션 헤더 포함)</summary>
|
||||||
public ObservableCollection<PropertyRowViewModel> Rows { get; } = new();
|
public ObservableCollection<PropertyRowViewModel> Rows { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>선택된 컨트롤이 있는지 — 인스펙터 빈 상태 판정</summary>
|
||||||
|
public bool HasSelection => designer.Selection.Items.Count > 0;
|
||||||
|
|
||||||
/// <summary>선택 요약 텍스트</summary>
|
/// <summary>선택 요약 텍스트</summary>
|
||||||
public string Summary
|
public string Summary
|
||||||
=> designer.Selection.Items.Count switch
|
=> designer.Selection.Items.Count switch
|
||||||
@@ -73,7 +77,9 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
Rows.Clear();
|
Rows.Clear();
|
||||||
boundsRows.Clear();
|
boundsRows.Clear();
|
||||||
|
currentSection = null;
|
||||||
OnPropertyChanged(nameof(Summary));
|
OnPropertyChanged(nameof(Summary));
|
||||||
|
OnPropertyChanged(nameof(HasSelection));
|
||||||
|
|
||||||
var items = designer.Selection.Items;
|
var items = designer.Selection.Items;
|
||||||
if (items.Count == 0)
|
if (items.Count == 0)
|
||||||
@@ -82,7 +88,7 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 공통: 이름/위치/크기
|
// 공통: 이름/위치/크기
|
||||||
Rows.Add(new SectionRowViewModel("공통"));
|
AddSection("공통");
|
||||||
if (items.Count == 1)
|
if (items.Count == 1)
|
||||||
{
|
{
|
||||||
AddRow(new TextRowViewModel("이름"), new RowBinding
|
AddRow(new TextRowViewModel("이름"), new RowBinding
|
||||||
@@ -98,7 +104,7 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
AddBoundsRow("높이", vm => vm.Height, (vm, v) => vm.Height = Math.Max(1, v));
|
AddBoundsRow("높이", vm => vm.Height, (vm, v) => vm.Height = Math.Max(1, v));
|
||||||
|
|
||||||
// 폰트(공통) — Font Property(상속 시 빈 값)
|
// 폰트(공통) — Font Property(상속 시 빈 값)
|
||||||
Rows.Add(new SectionRowViewModel("글꼴"));
|
AddSection("글꼴");
|
||||||
AddFontRow("글꼴", f => f.Family, (f, v) => f.Family = v.Length == 0 ? f.Family : v);
|
AddFontRow("글꼴", f => f.Family, (f, v) => f.Family = v.Length == 0 ? f.Family : v);
|
||||||
AddFontRow("크기(pt)", f => f.SizePt.ToString("0.##", CultureInfo.InvariantCulture),
|
AddFontRow("크기(pt)", f => f.SizePt.ToString("0.##", CultureInfo.InvariantCulture),
|
||||||
(f, v) => f.SizePt = double.TryParse(v, NumberStyles.Number, CultureInfo.InvariantCulture, out var size) && size > 0 ? size : f.SizePt);
|
(f, v) => f.SizePt = double.TryParse(v, NumberStyles.Number, CultureInfo.InvariantCulture, out var size) && size > 0 ? size : f.SizePt);
|
||||||
@@ -110,7 +116,7 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
var curatedKeys = new HashSet<string>(HandledKeys, StringComparer.Ordinal);
|
var curatedKeys = new HashSet<string>(HandledKeys, StringComparer.Ordinal);
|
||||||
if (items.All(i => i.Type == type) && ControlRegistry.Find(type) is { } descriptor && descriptor.Properties.Count > 0)
|
if (items.All(i => i.Type == type) && ControlRegistry.Find(type) is { } descriptor && descriptor.Properties.Count > 0)
|
||||||
{
|
{
|
||||||
Rows.Add(new SectionRowViewModel(descriptor.DisplayName));
|
AddSection(descriptor.DisplayName);
|
||||||
foreach (var def in descriptor.Properties)
|
foreach (var def in descriptor.Properties)
|
||||||
{
|
{
|
||||||
AddDefRow(def);
|
AddDefRow(def);
|
||||||
@@ -134,7 +140,8 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
|
|
||||||
if (!showAdvanced)
|
if (!showAdvanced)
|
||||||
{
|
{
|
||||||
Rows.Add(new ToggleAdvancedRowViewModel($"전체 속성 표시 ▾ ({advancedKeys.Count}개)", () =>
|
currentSection = null;
|
||||||
|
AddPlain(new ToggleAdvancedRowViewModel($"전체 속성 표시 ▾ ({advancedKeys.Count}개)", () =>
|
||||||
{
|
{
|
||||||
showAdvanced = true;
|
showAdvanced = true;
|
||||||
Rebuild();
|
Rebuild();
|
||||||
@@ -142,8 +149,8 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Rows.Add(new SectionRowViewModel("전체 속성 (고급 — 레거시 원문)"));
|
AddSection("전체 속성 (고급 — 레거시 원문)");
|
||||||
Rows.Add(new ToggleAdvancedRowViewModel("전체 속성 숨기기 ▴", () =>
|
AddPlain(new ToggleAdvancedRowViewModel("전체 속성 숨기기 ▴", () =>
|
||||||
{
|
{
|
||||||
showAdvanced = false;
|
showAdvanced = false;
|
||||||
Rebuild();
|
Rebuild();
|
||||||
@@ -182,21 +189,21 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case LegacyPropValue.NestedValue nested:
|
case LegacyPropValue.NestedValue nested:
|
||||||
Rows.Add(new ReadOnlyRowViewModel(key, $"(중첩 속성 {nested.Children.Count}개 — 보존됨)"));
|
AddPlain(new ReadOnlyRowViewModel(key, $"(중첩 속성 {nested.Children.Count}개 — 보존됨)"));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LegacyPropValue.BinaryValue:
|
case LegacyPropValue.BinaryValue:
|
||||||
Rows.Add(new ReadOnlyRowViewModel(key, "(바이너리 — 원문 보존됨)"));
|
AddPlain(new ReadOnlyRowViewModel(key, "(바이너리 — 원문 보존됨)"));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LegacyPropValue.ReferenceValue reference:
|
case LegacyPropValue.ReferenceValue reference:
|
||||||
Rows.Add(new ReadOnlyRowViewModel(key, $"(참조: {reference.Name})"));
|
AddPlain(new ReadOnlyRowViewModel(key, $"(참조: {reference.Name})"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 속성 추가 — 레거시 컨트롤의 임의 속성 지정(오타 주의: 로더가 모르는 키는 경고 처리)
|
// 속성 추가 — 레거시 컨트롤의 임의 속성 지정(오타 주의: 로더가 모르는 키는 경고 처리)
|
||||||
Rows.Add(new AddPropertyRowViewModel(key =>
|
AddPlain(new AddPropertyRowViewModel(key =>
|
||||||
{
|
{
|
||||||
if (target.Model.Props.Contains(key))
|
if (target.Model.Props.Contains(key))
|
||||||
{
|
{
|
||||||
@@ -330,6 +337,20 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>섹션 헤더 시작 — 이후 추가되는 행은 이 섹션에 편입된다</summary>
|
||||||
|
private void AddSection(string label)
|
||||||
|
{
|
||||||
|
currentSection = new SectionRowViewModel(label);
|
||||||
|
Rows.Add(currentSection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>바인딩 없는 행 추가(읽기 전용·버튼 행) — 섹션 편입만 처리</summary>
|
||||||
|
private void AddPlain(PropertyRowViewModel row)
|
||||||
|
{
|
||||||
|
currentSection?.Adopt(row);
|
||||||
|
Rows.Add(row);
|
||||||
|
}
|
||||||
|
|
||||||
private void AddRow(PropertyRowViewModel row, RowBinding binding, Action? afterCommit = null)
|
private void AddRow(PropertyRowViewModel row, RowBinding binding, Action? afterCommit = null)
|
||||||
{
|
{
|
||||||
var items = designer.Selection.Items;
|
var items = designer.Selection.Items;
|
||||||
@@ -363,6 +384,7 @@ public sealed class InspectorViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
afterCommit?.Invoke();
|
afterCommit?.Invoke();
|
||||||
};
|
};
|
||||||
|
currentSection?.Adopt(row);
|
||||||
Rows.Add(row);
|
Rows.Add(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,15 @@ public abstract class PropertyRowViewModel : ViewModelBase
|
|||||||
/// <summary>선택 대상들의 값이 서로 다른지 — "여러 값" 표시</summary>
|
/// <summary>선택 대상들의 값이 서로 다른지 — "여러 값" 표시</summary>
|
||||||
public bool IsMixed { get; private set; }
|
public bool IsMixed { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>소속 섹션 — null 이면 섹션 밖(항상 표시)</summary>
|
||||||
|
public SectionRowViewModel? Section { get; set; }
|
||||||
|
|
||||||
|
/// <summary>접힌 섹션에 속하면 숨긴다 — ItemContainerStyle 이 바인딩</summary>
|
||||||
|
public bool IsRowVisible => Section is null || Section.IsExpanded;
|
||||||
|
|
||||||
|
/// <summary>표시 여부 통지(섹션 접기/펴기)</summary>
|
||||||
|
public void NotifyVisibilityChanged() => OnPropertyChanged(nameof(IsRowVisible));
|
||||||
|
|
||||||
/// <summary>정규화 문자열 값 — 파생 편집기가 형 변환</summary>
|
/// <summary>정규화 문자열 값 — 파생 편집기가 형 변환</summary>
|
||||||
public string ValueText
|
public string ValueText
|
||||||
{
|
{
|
||||||
@@ -59,6 +68,9 @@ public abstract class PropertyRowViewModel : ViewModelBase
|
|||||||
OnPropertyChanged(nameof(ValueText));
|
OnPropertyChanged(nameof(ValueText));
|
||||||
OnPropertyChanged(nameof(IsMixed));
|
OnPropertyChanged(nameof(IsMixed));
|
||||||
building = false;
|
building = false;
|
||||||
|
// 파생 표시(토글 3상태·미리보기·요약)도 초기값 기준으로 맞춘다.
|
||||||
|
// 드래그 중 RefreshBoundsRows 가 Initialize 만 호출하는 경로가 있어 여기서 하지 않으면 파생이 뒤처진다.
|
||||||
|
OnValueApplied();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>값 적용 후 파생 갱신 지점</summary>
|
/// <summary>값 적용 후 파생 갱신 지점</summary>
|
||||||
@@ -66,10 +78,59 @@ public abstract class PropertyRowViewModel : ViewModelBase
|
|||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>구분 헤더 행</summary>
|
/// <summary>
|
||||||
|
/// 구분 헤더 행 — 클릭으로 접기/펴기.
|
||||||
|
///
|
||||||
|
/// Rows 는 평면 컬렉션을 유지하고(다중선택 재구성이 잦아 트리 재구축 비용을 피한다) 섹션이 자기
|
||||||
|
/// 소속 행을 들고 있다가 IsRowVisible 만 갱신한다. 컨테이너 Visibility 만 바뀌므로 편집 중이던
|
||||||
|
/// 값·포커스 상태가 보존된다.
|
||||||
|
/// </summary>
|
||||||
public sealed class SectionRowViewModel : PropertyRowViewModel
|
public sealed class SectionRowViewModel : PropertyRowViewModel
|
||||||
{
|
{
|
||||||
public SectionRowViewModel(string label) : base(label) { }
|
private bool isExpanded = true;
|
||||||
|
|
||||||
|
/// <summary>이 섹션에 속한 행들</summary>
|
||||||
|
public List<PropertyRowViewModel> Children { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>펼침 상태</summary>
|
||||||
|
public bool IsExpanded
|
||||||
|
{
|
||||||
|
get => isExpanded;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!SetProperty(ref isExpanded, value))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
OnPropertyChanged(nameof(ChevronIcon));
|
||||||
|
foreach (var child in Children)
|
||||||
|
{
|
||||||
|
child.NotifyVisibilityChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>펼침 표시 아이콘</summary>
|
||||||
|
public string ChevronIcon => isExpanded ? "chevron-down" : "chevron-right";
|
||||||
|
|
||||||
|
/// <summary>접힌 상태에서 몇 개가 숨어 있는지</summary>
|
||||||
|
public string CountText => Children.Count == 0 ? string.Empty : Children.Count.ToString();
|
||||||
|
|
||||||
|
/// <summary>헤더 클릭 — 접기/펴기</summary>
|
||||||
|
public M.Framework.WPF.ICustomCommand? ToggleCommand { get; }
|
||||||
|
|
||||||
|
public SectionRowViewModel(string label) : base(label)
|
||||||
|
{
|
||||||
|
ToggleCommand = new M.Framework.WPF.Command((sender, e) => IsExpanded = !IsExpanded);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>행 편입 — InspectorViewModel 이 Rows.Add 와 함께 호출</summary>
|
||||||
|
public void Adopt(PropertyRowViewModel row)
|
||||||
|
{
|
||||||
|
row.Section = this;
|
||||||
|
Children.Add(row);
|
||||||
|
OnPropertyChanged(nameof(CountText));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>한 줄 문자열 행</summary>
|
/// <summary>한 줄 문자열 행</summary>
|
||||||
@@ -93,11 +154,21 @@ public sealed class NumberRowViewModel : PropertyRowViewModel
|
|||||||
/// <summary>토글(참/거짓) 행</summary>
|
/// <summary>토글(참/거짓) 행</summary>
|
||||||
public sealed class ToggleRowViewModel : PropertyRowViewModel
|
public sealed class ToggleRowViewModel : PropertyRowViewModel
|
||||||
{
|
{
|
||||||
/// <summary>체크 상태 — "True"/"False" 문자열과 동기</summary>
|
/// <summary>
|
||||||
public bool IsOn
|
/// 체크 상태 — "True"/"False" 문자열과 동기. 다중선택에서 값이 갈리면 null(불확정)이다.
|
||||||
|
/// false 로 내리면 "전부 꺼짐"으로 보이고, 사용자가 한 번 눌러 켰다 끄면 전부 False 가 되어
|
||||||
|
/// 조용히 값이 뭉개진다.
|
||||||
|
/// </summary>
|
||||||
|
public bool? IsOn
|
||||||
{
|
{
|
||||||
get => ValueText == "True";
|
get => IsMixed ? null : ValueText == "True";
|
||||||
set => ValueText = value ? "True" : "False";
|
set
|
||||||
|
{
|
||||||
|
if (value is bool on)
|
||||||
|
{
|
||||||
|
ValueText = on ? "True" : "False";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ToggleRowViewModel(string label) : base(label) { }
|
public ToggleRowViewModel(string label) : base(label) { }
|
||||||
|
|||||||
@@ -78,8 +78,38 @@ internal sealed class MainViewModel : ViewModelBase
|
|||||||
public bool IsSheetListLoading
|
public bool IsSheetListLoading
|
||||||
{
|
{
|
||||||
get => isSheetListLoading;
|
get => isSheetListLoading;
|
||||||
set => SetProperty(ref isSheetListLoading, value);
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref isSheetListLoading, value))
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(SheetListStatusText));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 서식 목록 하단 상태 한 줄 — 로딩·건수·미접속을 목록 바로 아래에서 알린다.
|
||||||
|
/// 종전에는 목록 위에 "불러오는 중..." 오버레이를 덮어 목록이 가려졌고, 건수는 창 하단
|
||||||
|
/// 전역 상태줄에만 떠서 목록과 시선 거리가 멀었다.
|
||||||
|
/// </summary>
|
||||||
|
public string SheetListStatusText
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!CanUseDb)
|
||||||
|
{
|
||||||
|
return "DB 미접속 — 파일 모드";
|
||||||
|
}
|
||||||
|
if (IsSheetListLoading)
|
||||||
|
{
|
||||||
|
return "불러오는 중…";
|
||||||
|
}
|
||||||
|
return SheetList.Count == 0 ? "결과 없음" : $"{SheetList.Count}건";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>열린 문서 존재 여부 — 빈 상태 오버레이 표시 판정</summary>
|
||||||
|
public bool HasOpenDocuments => OpenDesigners.Count > 0;
|
||||||
|
|
||||||
/// <summary>DB 사용 가능 여부(서식 목록 패널 안내용)</summary>
|
/// <summary>DB 사용 가능 여부(서식 목록 패널 안내용)</summary>
|
||||||
public bool CanUseDb => dataBusiness.CanUseDb;
|
public bool CanUseDb => dataBusiness.CanUseDb;
|
||||||
@@ -119,6 +149,7 @@ internal sealed class MainViewModel : ViewModelBase
|
|||||||
ZoomInCommand = new Command((sender, e) => CurrentDesigner?.ZoomIn());
|
ZoomInCommand = new Command((sender, e) => CurrentDesigner?.ZoomIn());
|
||||||
ZoomOutCommand = new Command((sender, e) => CurrentDesigner?.ZoomOut());
|
ZoomOutCommand = new Command((sender, e) => CurrentDesigner?.ZoomOut());
|
||||||
ZoomResetCommand = new Command((sender, e) => CurrentDesigner?.ZoomReset());
|
ZoomResetCommand = new Command((sender, e) => CurrentDesigner?.ZoomReset());
|
||||||
|
ZoomFitCommand = new Command((sender, e) => CurrentDesigner?.ZoomFit());
|
||||||
// Application.Shutdown() 은 Window.Closing 의 e.Cancel 을 무시하므로 VM 에서 먼저 확인한다
|
// Application.Shutdown() 은 Window.Closing 의 e.Cancel 을 무시하므로 VM 에서 먼저 확인한다
|
||||||
ExitCommand = new Command((sender, e) =>
|
ExitCommand = new Command((sender, e) =>
|
||||||
{
|
{
|
||||||
@@ -132,6 +163,9 @@ internal sealed class MainViewModel : ViewModelBase
|
|||||||
SearchSheetsCommand = new Command(async (sender, e) => await LoadSheetListAsync());
|
SearchSheetsCommand = new Command(async (sender, e) => await LoadSheetListAsync());
|
||||||
OpenSheetCommand = new Command((object param) => OnOpenSheetFromList(param as SheetSummary));
|
OpenSheetCommand = new Command((object param) => OnOpenSheetFromList(param as SheetSummary));
|
||||||
CloseDocumentCommand = new Command((object param) => OnCloseDocument(param as DesignerViewModel));
|
CloseDocumentCommand = new Command((object param) => OnCloseDocument(param as DesignerViewModel));
|
||||||
|
|
||||||
|
SheetList.CollectionChanged += (s, e) => OnPropertyChanged(nameof(SheetListStatusText));
|
||||||
|
OpenDesigners.CollectionChanged += (s, e) => OnPropertyChanged(nameof(HasOpenDocuments));
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -187,6 +221,9 @@ internal sealed class MainViewModel : ViewModelBase
|
|||||||
/// <summary>줌 100%</summary>
|
/// <summary>줌 100%</summary>
|
||||||
public ICustomCommand? ZoomResetCommand { get; set; }
|
public ICustomCommand? ZoomResetCommand { get; set; }
|
||||||
|
|
||||||
|
/// <summary>화면 맞춤 — 활성 페이지 한 장이 뷰포트에 들어오는 배율</summary>
|
||||||
|
public ICustomCommand? ZoomFitCommand { get; set; }
|
||||||
|
|
||||||
/// <summary>종료</summary>
|
/// <summary>종료</summary>
|
||||||
public ICustomCommand? ExitCommand { get; set; }
|
public ICustomCommand? ExitCommand { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
VerticalScrollBarVisibility="Auto"
|
VerticalScrollBarVisibility="Auto"
|
||||||
Background="{DynamicResource B.CanvasBg}"
|
Background="{DynamicResource B.CanvasBg}"
|
||||||
Focusable="False"
|
Focusable="False"
|
||||||
|
SizeChanged="OnScrollSizeChanged"
|
||||||
PreviewMouseWheel="OnPreviewMouseWheel"
|
PreviewMouseWheel="OnPreviewMouseWheel"
|
||||||
PreviewMouseLeftButtonDown="OnPanMouseDown"
|
PreviewMouseLeftButtonDown="OnPanMouseDown"
|
||||||
PreviewMouseMove="OnPanMouseMove"
|
PreviewMouseMove="OnPanMouseMove"
|
||||||
|
|||||||
@@ -46,9 +46,14 @@ public partial class DesignerCanvasView : UserControl
|
|||||||
if (subscribedDesigner is not null)
|
if (subscribedDesigner is not null)
|
||||||
{
|
{
|
||||||
subscribedDesigner.InlineEditRequested += ShowInlineEditor;
|
subscribedDesigner.InlineEditRequested += ShowInlineEditor;
|
||||||
|
subscribedDesigner.ReportViewport(Scroll.ViewportWidth, Scroll.ViewportHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>뷰포트 크기를 VM 에 전달 — 화면 맞춤(ZoomFit)이 시각 트리를 몰라도 되게 한다</summary>
|
||||||
|
private void OnScrollSizeChanged(object sender, SizeChangedEventArgs e)
|
||||||
|
=> subscribedDesigner?.ReportViewport(Scroll.ViewportWidth, Scroll.ViewportHeight);
|
||||||
|
|
||||||
/// <summary>대상 컨트롤 위에 편집기 표시 — 폰트/정렬 일치, 줌은 World LayoutTransform 이 처리</summary>
|
/// <summary>대상 컨트롤 위에 편집기 표시 — 폰트/정렬 일치, 줌은 World LayoutTransform 이 처리</summary>
|
||||||
private void ShowInlineEditor(ControlViewModel target)
|
private void ShowInlineEditor(ControlViewModel target)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,60 +4,141 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:ins="clr-namespace:SheetMe.Designer.ViewModels.Inspector"
|
xmlns:ins="clr-namespace:SheetMe.Designer.ViewModels.Inspector"
|
||||||
|
xmlns:ctl="clr-namespace:SheetMe.Designer.Controls"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DataContext="{d:DesignInstance ins:InspectorViewModel}">
|
d:DataContext="{d:DesignInstance ins:InspectorViewModel}">
|
||||||
|
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
|
<ctl:IconNameToVisualConverter x:Key="IconName"/>
|
||||||
|
|
||||||
<!-- 행 VM 타입별 암시적 템플릿 -->
|
<!-- 행 VM 타입별 암시적 템플릿 -->
|
||||||
<Style x:Key="RowLabel" TargetType="TextBlock">
|
<Style x:Key="RowLabel" TargetType="TextBlock">
|
||||||
<Setter Property="Width" Value="86"/>
|
<Setter Property="Width" Value="86"/>
|
||||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
|
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
|
||||||
<Setter Property="FontSize" Value="12"/>
|
<Setter Property="FontSize" Value="12"/>
|
||||||
|
<Setter Property="TextTrimming" Value="CharacterEllipsis"/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style x:Key="RowEditor" TargetType="Control">
|
<Style x:Key="RowEditor" TargetType="Control">
|
||||||
<Setter Property="FontSize" Value="12"/>
|
<Setter Property="FontSize" Value="12"/>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
"여러 값" 표시 — 다중선택에서 값이 갈리면 편집기가 빈 칸으로 보인다.
|
||||||
|
그 빈 칸은 "값 없음"과 구별되지 않아, 사용자가 여기에 무심코 입력하면 서로 다르던 값이
|
||||||
|
전부 한 값으로 덮인다. 비파괴 오버레이로만 알린다(입력을 가로막지 않는다).
|
||||||
|
-->
|
||||||
|
<Style x:Key="MixedHint" TargetType="TextBlock">
|
||||||
|
<Setter Property="Text" Value="여러 값"/>
|
||||||
|
<Setter Property="FontSize" Value="11"/>
|
||||||
|
<Setter Property="FontStyle" Value="Italic"/>
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource B.Muted}"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
<Setter Property="Margin" Value="7,0,0,0"/>
|
||||||
|
<Setter Property="IsHitTestVisible" Value="False"/>
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<MultiDataTrigger>
|
||||||
|
<MultiDataTrigger.Conditions>
|
||||||
|
<Condition Binding="{Binding IsMixed}" Value="True"/>
|
||||||
|
<Condition Binding="{Binding ValueText}" Value=""/>
|
||||||
|
</MultiDataTrigger.Conditions>
|
||||||
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
|
</MultiDataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
편집기 위에 겹치는 변형 — 입력 중에는 비운다.
|
||||||
|
값은 LostFocus 에만 소스로 올라가므로 ValueText 는 타이핑 중 계속 "" 이고, 겹친 안내가
|
||||||
|
방금 친 글자 위에 그대로 남는다.
|
||||||
|
-->
|
||||||
|
<Style x:Key="MixedHintOverlay" TargetType="TextBlock" BasedOn="{StaticResource MixedHint}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsKeyboardFocusWithin, RelativeSource={RelativeSource AncestorType=Grid}}" Value="True">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- 접이식 섹션 헤더 — 클릭으로 접기/펴기. 인스펙터는 고급 속성까지 켜면 수십 행이 된다 -->
|
||||||
<DataTemplate DataType="{x:Type ins:SectionRowViewModel}">
|
<DataTemplate DataType="{x:Type ins:SectionRowViewModel}">
|
||||||
<TextBlock Text="{Binding Label}" FontWeight="Bold" Foreground="{DynamicResource B.Muted}"
|
<Button Command="{Binding ToggleCommand}" Cursor="Hand" HorizontalContentAlignment="Stretch"
|
||||||
Margin="0,10,0,4" FontSize="12"/>
|
Margin="0,10,0,3" Padding="0,3">
|
||||||
|
<Button.Template>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border x:Name="bd" Background="Transparent" CornerRadius="5" Padding="3,4">
|
||||||
|
<DockPanel>
|
||||||
|
<ContentControl DockPanel.Dock="Left" Margin="0,0,5,0" VerticalAlignment="Center"
|
||||||
|
IsTabStop="False" Focusable="False"
|
||||||
|
Content="{Binding ChevronIcon, Converter={StaticResource IconName}, ConverterParameter=12}"/>
|
||||||
|
<TextBlock DockPanel.Dock="Right" Text="{Binding CountText}" FontSize="11"
|
||||||
|
Foreground="{DynamicResource B.Muted}" VerticalAlignment="Center" Margin="6,0,2,0"/>
|
||||||
|
<TextBlock Text="{Binding Label}" FontWeight="Bold" FontSize="12"
|
||||||
|
Foreground="{DynamicResource B.Muted}" VerticalAlignment="Center"
|
||||||
|
TextTrimming="CharacterEllipsis"/>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="bd" Property="Background" Value="{DynamicResource B.Hover}"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Button.Template>
|
||||||
|
</Button>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
<DataTemplate DataType="{x:Type ins:TextRowViewModel}">
|
<DataTemplate DataType="{x:Type ins:TextRowViewModel}">
|
||||||
<DockPanel Margin="0,2">
|
<DockPanel Margin="0,2">
|
||||||
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
|
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" ToolTip="{Binding Label}"/>
|
||||||
|
<Grid>
|
||||||
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"/>
|
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"/>
|
||||||
|
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
|
||||||
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
<DataTemplate DataType="{x:Type ins:MultilineTextRowViewModel}">
|
<DataTemplate DataType="{x:Type ins:MultilineTextRowViewModel}">
|
||||||
<DockPanel Margin="0,2">
|
<DockPanel Margin="0,2">
|
||||||
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" VerticalAlignment="Top" Margin="0,4,0,0"/>
|
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" VerticalAlignment="Top"
|
||||||
|
Margin="0,4,0,0" ToolTip="{Binding Label}"/>
|
||||||
|
<Grid>
|
||||||
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"
|
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"
|
||||||
AcceptsReturn="True" TextWrapping="Wrap" MinHeight="48" MaxHeight="120"
|
AcceptsReturn="True" TextWrapping="Wrap" MinHeight="48" MaxHeight="120"
|
||||||
VerticalScrollBarVisibility="Auto"/>
|
VerticalScrollBarVisibility="Auto"/>
|
||||||
|
<TextBlock Style="{StaticResource MixedHintOverlay}" VerticalAlignment="Top" Margin="7,7,0,0"/>
|
||||||
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
<DataTemplate DataType="{x:Type ins:NumberRowViewModel}">
|
<DataTemplate DataType="{x:Type ins:NumberRowViewModel}">
|
||||||
<DockPanel Margin="0,2">
|
<DockPanel Margin="0,2">
|
||||||
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
|
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" ToolTip="{Binding Label}"/>
|
||||||
|
<Grid>
|
||||||
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"/>
|
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"/>
|
||||||
|
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
|
||||||
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
<DataTemplate DataType="{x:Type ins:ToggleRowViewModel}">
|
<DataTemplate DataType="{x:Type ins:ToggleRowViewModel}">
|
||||||
<DockPanel Margin="0,2">
|
<DockPanel Margin="0,2">
|
||||||
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
|
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" ToolTip="{Binding Label}"/>
|
||||||
<CheckBox IsChecked="{Binding IsOn}" VerticalAlignment="Center"/>
|
<!-- 3상태: 다중선택에서 값이 갈리면 불확정(IsOn=null). 꺼짐으로 보이면 안 된다 -->
|
||||||
|
<CheckBox IsChecked="{Binding IsOn}" IsThreeState="{Binding IsMixed, Mode=OneWay}"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Style="{StaticResource MixedHint}"/>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
<DataTemplate DataType="{x:Type ins:ChoiceRowViewModel}">
|
<DataTemplate DataType="{x:Type ins:ChoiceRowViewModel}">
|
||||||
<DockPanel Margin="0,2">
|
<DockPanel Margin="0,2">
|
||||||
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
|
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}" ToolTip="{Binding Label}"/>
|
||||||
|
<Grid>
|
||||||
<ComboBox ItemsSource="{Binding Choices}" SelectedItem="{Binding ValueText}" FontSize="12"/>
|
<ComboBox ItemsSource="{Binding Choices}" SelectedItem="{Binding ValueText}" FontSize="12"/>
|
||||||
|
<TextBlock Style="{StaticResource MixedHintOverlay}" Margin="9,0,0,0"/>
|
||||||
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
@@ -66,8 +147,11 @@
|
|||||||
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
|
<TextBlock Text="{Binding Label}" Style="{StaticResource RowLabel}"/>
|
||||||
<Button DockPanel.Dock="Right" Content="…" Width="24" Margin="4,0,0,0"
|
<Button DockPanel.Dock="Right" Content="…" Width="24" Margin="4,0,0,0"
|
||||||
Command="{Binding BrowseCommand}" ToolTip="목록에서 선택"/>
|
Command="{Binding BrowseCommand}" ToolTip="목록에서 선택"/>
|
||||||
|
<Grid>
|
||||||
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"
|
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"
|
||||||
ToolTip="직접 입력 또는 … 버튼으로 선택"/>
|
ToolTip="직접 입력 또는 … 버튼으로 선택"/>
|
||||||
|
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
|
||||||
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
@@ -113,9 +197,12 @@
|
|||||||
<Button DockPanel.Dock="Right" Content="…" Padding="8,1" Margin="4,0,0,0"
|
<Button DockPanel.Dock="Right" Content="…" Padding="8,1" Margin="4,0,0,0"
|
||||||
Command="{Binding BrowseCommand}"
|
Command="{Binding BrowseCommand}"
|
||||||
ToolTip="마스크 편집기 — 자주 쓰는 마스크 목록과 미리보기"/>
|
ToolTip="마스크 편집기 — 자주 쓰는 마스크 목록과 미리보기"/>
|
||||||
|
<Grid>
|
||||||
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"
|
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"
|
||||||
FontFamily="Consolas, D2Coding, 굴림체"
|
FontFamily="Consolas, D2Coding, 굴림체"
|
||||||
ToolTip="0=필수 숫자, 9=선택 숫자, #=숫자·부호 — 오른쪽 … 버튼에서 목록·미리보기"/>
|
ToolTip="0=필수 숫자, 9=선택 숫자, #=숫자·부호 — 오른쪽 … 버튼에서 목록·미리보기"/>
|
||||||
|
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
|
||||||
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
<!-- 화면에 실제로 어떻게 보이는지 — 마스크 문법을 몰라도 결과로 확인 -->
|
<!-- 화면에 실제로 어떻게 보이는지 — 마스크 문법을 몰라도 결과로 확인 -->
|
||||||
<TextBlock Text="{Binding Preview}" FontSize="11" Margin="0,2,0,0"
|
<TextBlock Text="{Binding Preview}" FontSize="11" Margin="0,2,0,0"
|
||||||
@@ -143,17 +230,51 @@
|
|||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
</Button.Template>
|
</Button.Template>
|
||||||
</Button>
|
</Button>
|
||||||
|
<Grid>
|
||||||
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"
|
<TextBox Text="{Binding ValueText, UpdateSourceTrigger=LostFocus}" FontSize="12"
|
||||||
ToolTip="R, G, B 또는 색 이름 (예: 224, 224, 224 / White) — 오른쪽 스와치 클릭 시 피커"/>
|
ToolTip="R, G, B 또는 색 이름 (예: 224, 224, 224 / White) — 오른쪽 스와치 클릭 시 피커"/>
|
||||||
|
<TextBlock Style="{StaticResource MixedHintOverlay}"/>
|
||||||
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</UserControl.Resources>
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<TextBlock DockPanel.Dock="Top" Text="{Binding Summary}" FontWeight="Bold"
|
<TextBlock DockPanel.Dock="Top" Text="{Binding Summary}" FontWeight="Bold"
|
||||||
Margin="10,10,10,2" Foreground="{DynamicResource B.Muted}" TextTrimming="CharacterEllipsis"/>
|
Margin="10,10,10,2" Foreground="{DynamicResource B.Muted}" TextTrimming="CharacterEllipsis"/>
|
||||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
<ItemsControl ItemsSource="{Binding Rows}" Margin="10,0,10,10"/>
|
<ItemsControl ItemsSource="{Binding Rows}" Margin="10,0,10,10">
|
||||||
|
<!-- 접힌 섹션의 행은 컨테이너만 숨긴다 — 행 VM 을 재생성하지 않아 편집 상태가 유지된다 -->
|
||||||
|
<ItemsControl.ItemContainerStyle>
|
||||||
|
<Style TargetType="ContentPresenter">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsRowVisible}" Value="False">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</ItemsControl.ItemContainerStyle>
|
||||||
|
</ItemsControl>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
||||||
|
<!-- 빈 상태 — 선택이 없으면 "선택 없음" 한 줄만 떠 있어 패널이 고장난 것처럼 보였다 -->
|
||||||
|
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Margin="24,0">
|
||||||
|
<StackPanel.Style>
|
||||||
|
<Style TargetType="StackPanel">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding HasSelection, FallbackValue=False}" Value="False">
|
||||||
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</StackPanel.Style>
|
||||||
|
<ContentControl HorizontalAlignment="Center" IsTabStop="False" Focusable="False" Opacity="0.45"
|
||||||
|
Content="{Binding Source=mouse-pointer-click, Converter={StaticResource IconName}, ConverterParameter=30}"/>
|
||||||
|
<TextBlock Text="컨트롤을 선택하면 속성이 표시됩니다" FontSize="12" TextAlignment="Center"
|
||||||
|
TextWrapping="Wrap" Foreground="{DynamicResource B.Muted}" Margin="0,12,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -118,14 +118,23 @@
|
|||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- 좌: 로고 + 메뉴 -->
|
<!-- 좌: 로고 메뉴 루트 + 편집 아이콘 스트립 ([200] 타이틀바 구성) -->
|
||||||
<StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center" Margin="10,0,0,0"
|
<StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center" Margin="8,0,0,0"
|
||||||
shell:WindowChrome.IsHitTestVisibleInChrome="True">
|
shell:WindowChrome.IsHitTestVisibleInChrome="True">
|
||||||
|
<!-- 5개 텍스트 메뉴를 로고 하나 아래로 접어 넣는다 — 타이틀바가 아이콘 스트립 자리를 확보한다 -->
|
||||||
|
<Menu VerticalAlignment="Center">
|
||||||
|
<MenuItem>
|
||||||
|
<MenuItem.Header>
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
<Image Source="pack://application:,,,/SheetMe.Designer;component/Assets/sheetme-logo.png"
|
<Image Source="pack://application:,,,/SheetMe.Designer;component/Assets/sheetme-logo.png"
|
||||||
Width="20" Height="20" VerticalAlignment="Center" RenderOptions.BitmapScalingMode="HighQuality"/>
|
Width="20" Height="20" VerticalAlignment="Center" RenderOptions.BitmapScalingMode="HighQuality"/>
|
||||||
<Menu VerticalAlignment="Center" Margin="8,0,0,0">
|
<ContentControl Margin="3,1,0,0" VerticalAlignment="Center" IsTabStop="False"
|
||||||
|
Content="{Binding Source=chevron-down, Converter={StaticResource IconName}, ConverterParameter=11}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</MenuItem.Header>
|
||||||
<MenuItem Header="파일(_F)">
|
<MenuItem Header="파일(_F)">
|
||||||
<MenuItem Header="새 서식(_N)" Command="{Binding NewFileCommand}" InputGestureText="Ctrl+N"/>
|
<MenuItem Header="새 서식(_N)" Command="{Binding NewFileCommand}" InputGestureText="Ctrl+N"/>
|
||||||
<MenuItem Header="열기(_O)..." Command="{Binding OpenFileCommand}" InputGestureText="Ctrl+O"/>
|
<MenuItem Header="열기(_O)..." Command="{Binding OpenFileCommand}" InputGestureText="Ctrl+O"/>
|
||||||
@@ -209,15 +218,52 @@
|
|||||||
<MenuItem Header="상용구 관리(_W)..." Command="{Binding RecordWordCommand}"/>
|
<MenuItem Header="상용구 관리(_W)..." Command="{Binding RecordWordCommand}"/>
|
||||||
<MenuItem Header="폰트 일괄 변경(_F)..." Command="{Binding FontManagerCommand}"/>
|
<MenuItem Header="폰트 일괄 변경(_F)..." Command="{Binding FontManagerCommand}"/>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
|
<Border Width="1" Height="18" Background="{DynamicResource B.Line2}" Margin="6,0" VerticalAlignment="Center"/>
|
||||||
|
|
||||||
|
<!-- 편집 아이콘 스트립 — 메뉴 하위에만 있던 명령을 한 번에 닿게 -->
|
||||||
|
<Button Style="{StaticResource Subtle}" Width="30" Height="28" Padding="0" ToolTip="실행 취소 (Ctrl+Z)"
|
||||||
|
Command="{Binding CurrentDesigner.UndoCommand}"
|
||||||
|
Content="{Binding Source=undo-2, Converter={StaticResource IconName}, ConverterParameter=16}"/>
|
||||||
|
<Button Style="{StaticResource Subtle}" Width="30" Height="28" Padding="0" ToolTip="다시 실행 (Ctrl+Y)"
|
||||||
|
Command="{Binding CurrentDesigner.RedoCommand}"
|
||||||
|
Content="{Binding Source=redo-2, Converter={StaticResource IconName}, ConverterParameter=16}"/>
|
||||||
|
<Border Width="1" Height="18" Background="{DynamicResource B.Line2}" Margin="6,0" VerticalAlignment="Center"/>
|
||||||
|
<Button Style="{StaticResource Subtle}" Width="30" Height="28" Padding="0" ToolTip="복사 (Ctrl+C)"
|
||||||
|
Command="{Binding CurrentDesigner.CopyCommand}"
|
||||||
|
Content="{Binding Source=copy, Converter={StaticResource IconName}, ConverterParameter=16}"/>
|
||||||
|
<Button Style="{StaticResource Subtle}" Width="30" Height="28" Padding="0" ToolTip="삭제 (Delete)"
|
||||||
|
Command="{Binding CurrentDesigner.DeleteCommand}"
|
||||||
|
Content="{Binding Source=trash-2, Converter={StaticResource IconName}, ConverterParameter=16}"/>
|
||||||
|
<Button Style="{StaticResource Subtle}" Width="30" Height="28" Padding="0" ToolTip="그룹 (Ctrl+G)"
|
||||||
|
Command="{Binding CurrentDesigner.GroupCommand}"
|
||||||
|
Content="{Binding Source=group, Converter={StaticResource IconName}, ConverterParameter=16}"/>
|
||||||
|
<Button Style="{StaticResource Subtle}" Width="30" Height="28" Padding="0" ToolTip="그룹 해제 (Ctrl+Shift+G)"
|
||||||
|
Command="{Binding CurrentDesigner.UngroupCommand}"
|
||||||
|
Content="{Binding Source=ungroup, Converter={StaticResource IconName}, ConverterParameter=16}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- 중앙: 창 제목 -->
|
<!-- 중앙: 비움 — 서식명은 문서 탭이 이미 보여준다(Window.Title 은 작업표시줄용으로 유지) -->
|
||||||
<TextBlock Grid.Column="1" Text="{Binding Title}" Foreground="{DynamicResource B.Muted}"
|
<Border Grid.Column="1"/>
|
||||||
HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="12"/>
|
|
||||||
|
<!-- 우: 액션 아이콘 -->
|
||||||
|
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,8,0"
|
||||||
|
shell:WindowChrome.IsHitTestVisibleInChrome="True">
|
||||||
|
<Button Style="{StaticResource Subtle}" Width="34" Height="28" Padding="0" ToolTip="다크/라이트 전환"
|
||||||
|
Click="OnThemeToggleClick"
|
||||||
|
Content="{Binding Source=sun, Converter={StaticResource IconName}, ConverterParameter=16}"/>
|
||||||
|
<Button Style="{StaticResource Subtle}" Width="34" Height="28" Padding="0" ToolTip="DB에 저장"
|
||||||
|
Command="{Binding SaveToDbCommand}"
|
||||||
|
Content="{Binding Source=save, Converter={StaticResource IconName}, ConverterParameter=16}"/>
|
||||||
|
<Button Style="{StaticResource Subtle}" Width="34" Height="28" Padding="0" ToolTip="미리보기"
|
||||||
|
Command="{Binding PreviewCommand}"
|
||||||
|
Content="{Binding Source=play, Converter={StaticResource IconName}, ConverterParameter=16|B.Accent}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
<!-- 우: 창 제어 -->
|
<!-- 우: 창 제어 -->
|
||||||
<StackPanel Grid.Column="2" Orientation="Horizontal" shell:WindowChrome.IsHitTestVisibleInChrome="True">
|
<StackPanel Grid.Column="3" Orientation="Horizontal" shell:WindowChrome.IsHitTestVisibleInChrome="True">
|
||||||
<Button Style="{StaticResource CaptionBtn}" Click="OnMinimizeClick" ToolTip="최소화">
|
<Button Style="{StaticResource CaptionBtn}" Click="OnMinimizeClick" ToolTip="최소화">
|
||||||
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="10"/>
|
<TextBlock Text="" FontFamily="Segoe MDL2 Assets" FontSize="10"/>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -233,19 +279,21 @@
|
|||||||
|
|
||||||
<!-- ===== 하단 상태바 (줌은 플로팅 바로 이동) ===== -->
|
<!-- ===== 하단 상태바 (줌은 플로팅 바로 이동) ===== -->
|
||||||
<Border DockPanel.Dock="Bottom" Background="{DynamicResource B.Titlebar}"
|
<Border DockPanel.Dock="Bottom" Background="{DynamicResource B.Titlebar}"
|
||||||
BorderBrush="{DynamicResource B.Line}" BorderThickness="0,1,0,0" Padding="12,6">
|
BorderBrush="{DynamicResource B.Line}" BorderThickness="0,1,0,0" Padding="14,5">
|
||||||
<TextBlock Text="{Binding StatusText}" Foreground="{DynamicResource B.Muted}" FontSize="12"
|
<TextBlock Text="{Binding StatusText}" Foreground="{DynamicResource B.Muted}" FontSize="11.5"
|
||||||
TextTrimming="CharacterEllipsis"/>
|
TextTrimming="CharacterEllipsis"/>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- ===== 본문 3열: 좌 탭 패널 | 문서 탭+캔버스+플로팅 바 | 속성 ([200] 레이아웃) ===== -->
|
<!-- ===== 본문 3열: 좌 탭 패널 | 문서 탭+캔버스+플로팅 바 | 속성 ([200] 레이아웃) ===== -->
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="232" MinWidth="180" MaxWidth="480"/>
|
<!-- [200] 기본 폭 — 좌 248(레이어 행에 타입 아이콘 + 토글 2개가 들어가 232 로는 이름이 잘린다),
|
||||||
|
우 300(인스펙터 X|Y 쌍이 한 줄에 들어가는 최소치) -->
|
||||||
|
<ColumnDefinition Width="248" MinWidth="180" MaxWidth="480"/>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
<ColumnDefinition Width="Auto"/>
|
<ColumnDefinition Width="Auto"/>
|
||||||
<ColumnDefinition Width="280" MinWidth="220" MaxWidth="560"/>
|
<ColumnDefinition Width="300" MinWidth="220" MaxWidth="560"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- 좌: 알약 탭 — 서식 목록 / 레이어 / 도구 상자 -->
|
<!-- 좌: 알약 탭 — 서식 목록 / 레이어 / 도구 상자 -->
|
||||||
@@ -257,15 +305,37 @@
|
|||||||
<TabItem.Header><TextBlock Text="서식 목록" Style="{StaticResource TabHeaderText}"/></TabItem.Header>
|
<TabItem.Header><TextBlock Text="서식 목록" Style="{StaticResource TabHeaderText}"/></TabItem.Header>
|
||||||
<DockPanel>
|
<DockPanel>
|
||||||
<DockPanel DockPanel.Dock="Top" Margin="8,8,8,4">
|
<DockPanel DockPanel.Dock="Top" Margin="8,8,8,4">
|
||||||
<Button DockPanel.Dock="Right" Content="검색" Margin="6,0,0,0"
|
<Button DockPanel.Dock="Right" Style="{StaticResource Subtle}" Width="30" Padding="0"
|
||||||
Command="{Binding SearchSheetsCommand}"/>
|
Margin="6,0,0,0" ToolTip="검색 (Enter)"
|
||||||
<TextBox Text="{Binding SheetSearchKeyword, UpdateSourceTrigger=PropertyChanged}"
|
Command="{Binding SearchSheetsCommand}"
|
||||||
ToolTip="서식명/코드 검색 (Enter)">
|
Content="{Binding Source=search, Converter={StaticResource IconName}, ConverterParameter=15}"/>
|
||||||
|
<!-- 자리표시자는 TextBox 위에 겹친 TextBlock — Text 가 비었을 때만 보인다 -->
|
||||||
|
<Grid>
|
||||||
|
<TextBox x:Name="SheetSearchBox"
|
||||||
|
Text="{Binding SheetSearchKeyword, UpdateSourceTrigger=PropertyChanged}">
|
||||||
<TextBox.InputBindings>
|
<TextBox.InputBindings>
|
||||||
<KeyBinding Key="Enter" Command="{Binding SearchSheetsCommand}"/>
|
<KeyBinding Key="Enter" Command="{Binding SearchSheetsCommand}"/>
|
||||||
</TextBox.InputBindings>
|
</TextBox.InputBindings>
|
||||||
</TextBox>
|
</TextBox>
|
||||||
|
<TextBlock Text="서식명 또는 코드 검색" FontSize="12" Foreground="{DynamicResource B.Muted}"
|
||||||
|
Margin="9,0,0,0" VerticalAlignment="Center" IsHitTestVisible="False">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Text, ElementName=SheetSearchBox}" Value="">
|
||||||
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
||||||
|
<!-- 목록 상태 한 줄 — 로딩/결과 없음/조회 실패/건수를 같은 자리에서 보여준다 -->
|
||||||
|
<TextBlock DockPanel.Dock="Bottom" Text="{Binding SheetListStatusText}" FontSize="11"
|
||||||
|
Foreground="{DynamicResource B.Muted}" Margin="9,4,9,8" TextWrapping="Wrap"/>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ListBox x:Name="SheetListBox" ItemsSource="{Binding SheetList}"
|
<ListBox x:Name="SheetListBox" ItemsSource="{Binding SheetList}"
|
||||||
Margin="4,0,4,4"
|
Margin="4,0,4,4"
|
||||||
@@ -314,9 +384,6 @@
|
|||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
</ListBox>
|
</ListBox>
|
||||||
<TextBlock Text="불러오는 중..." HorizontalAlignment="Center" VerticalAlignment="Center"
|
|
||||||
Foreground="{DynamicResource B.Muted}"
|
|
||||||
Visibility="{Binding IsSheetListLoading, Converter={StaticResource BoolToVisibility}}"/>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
@@ -347,19 +414,64 @@
|
|||||||
</ListBox>
|
</ListBox>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
<Border DockPanel.Dock="Top" Height="1" Background="{DynamicResource B.Line}" Margin="8,2"/>
|
<Border DockPanel.Dock="Top" Height="1" Background="{DynamicResource B.Line}" Margin="8,2"/>
|
||||||
|
|
||||||
|
<!-- 레이어 검색칩 — 컨트롤이 수백 개인 서식에서 이름으로 좁혀 찾는다.
|
||||||
|
내부 TextBox 에 MinHeight=0 필수: 전역 TextBox MinHeight 30 때문에 32px 칩에서 글자가 잘린다. -->
|
||||||
|
<Border DockPanel.Dock="Top" Height="32" CornerRadius="7" Margin="8,2,8,4"
|
||||||
|
Background="{DynamicResource B.Input}">
|
||||||
|
<DockPanel>
|
||||||
|
<ContentControl DockPanel.Dock="Left" Margin="9,0,7,0" VerticalAlignment="Center" IsTabStop="False"
|
||||||
|
Content="{Binding Source=search, Converter={StaticResource IconName}, ConverterParameter=13}"/>
|
||||||
|
<Grid>
|
||||||
|
<TextBox x:Name="LayerSearchBox" MinHeight="0" Padding="0" BorderThickness="0"
|
||||||
|
Background="Transparent" VerticalContentAlignment="Center"
|
||||||
|
Text="{Binding CurrentDesigner.LayerFilter, UpdateSourceTrigger=PropertyChanged}"/>
|
||||||
|
<TextBlock Text="레이어 검색…" FontSize="12" Foreground="{DynamicResource B.Muted}"
|
||||||
|
VerticalAlignment="Center" IsHitTestVisible="False" Margin="1,0,0,0">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Text, ElementName=LayerSearchBox}" Value="">
|
||||||
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
<!-- 레이어 목록 (활성 페이지, 그리기 순서) -->
|
<!-- 레이어 목록 (활성 페이지, 그리기 순서) -->
|
||||||
<ListBox ItemsSource="{Binding CurrentDesigner.SelectedPage.Controls}"
|
<ListBox ItemsSource="{Binding CurrentDesigner.VisibleLayers}"
|
||||||
SelectedItem="{Binding CurrentDesigner.SelectedLayerItem}"
|
SelectedItem="{Binding CurrentDesigner.SelectedLayerItem}"
|
||||||
Margin="4,2,4,6"
|
Margin="4,0,4,6"
|
||||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
|
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<DockPanel Margin="0,1">
|
<DockPanel Margin="0,1">
|
||||||
<CheckBox DockPanel.Dock="Right" IsChecked="{Binding IsLockedFlag}"
|
<!-- 눈·자물쇠는 아이콘이 상태에 따라 바뀐다 — 사각 체크로는 잠김/숨김이 안 읽힌다 -->
|
||||||
ToolTip="잠금" Margin="4,0,0,0"/>
|
<ToggleButton DockPanel.Dock="Right" Style="{StaticResource MiniToggle}"
|
||||||
<CheckBox DockPanel.Dock="Right" IsChecked="{Binding IsHiddenFlag}"
|
IsChecked="{Binding IsLockedFlag}" ToolTip="잠금"
|
||||||
ToolTip="숨김" Margin="4,0,0,0"/>
|
Content="{Binding LockIcon, Converter={StaticResource IconName}, ConverterParameter=13}"/>
|
||||||
<TextBlock Text="{Binding Id}" FontSize="12" TextTrimming="CharacterEllipsis"/>
|
<ToggleButton DockPanel.Dock="Right" Style="{StaticResource MiniToggle}"
|
||||||
|
IsChecked="{Binding IsHiddenFlag}" ToolTip="숨김"
|
||||||
|
Content="{Binding HiddenIcon, Converter={StaticResource IconName}, ConverterParameter=13}"/>
|
||||||
|
<ContentControl DockPanel.Dock="Left" Margin="2,0,6,0" VerticalAlignment="Center" IsTabStop="False"
|
||||||
|
Content="{Binding Type, Converter={StaticResource TypeToIcon}, ConverterParameter=14|B.Accent}"/>
|
||||||
|
<TextBlock Text="{Binding LayerName}" FontSize="12" VerticalAlignment="Center"
|
||||||
|
TextTrimming="CharacterEllipsis" ToolTip="{Binding Id}">
|
||||||
|
<TextBlock.Style>
|
||||||
|
<Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsHiddenFlag}" Value="True">
|
||||||
|
<Setter Property="Opacity" Value="0.55"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Style>
|
||||||
|
</TextBlock>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
@@ -419,11 +531,24 @@
|
|||||||
<TabControl.ItemTemplate>
|
<TabControl.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<ContentControl Content="{Binding Source=file-text, Converter={StaticResource IconName}, ConverterParameter=12}"
|
<!-- 활성 탭만 아이콘을 강조색으로 — 탭이 여러 개일 때 어디가 활성인지가 배경색 차이만으로는 약하다 -->
|
||||||
Margin="0,0,6,0" VerticalAlignment="Center" IsTabStop="False" Focusable="False"/>
|
<ContentControl Margin="0,0,6,0" VerticalAlignment="Center" IsTabStop="False" Focusable="False">
|
||||||
|
<ContentControl.Style>
|
||||||
|
<Style TargetType="ContentControl">
|
||||||
|
<Setter Property="Content"
|
||||||
|
Value="{Binding Source=file-text, Converter={StaticResource IconName}, ConverterParameter=12}"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=TabItem}}" Value="True">
|
||||||
|
<Setter Property="Content"
|
||||||
|
Value="{Binding Source=file-text, Converter={StaticResource IconName}, ConverterParameter=12|B.Accent}"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</ContentControl.Style>
|
||||||
|
</ContentControl>
|
||||||
<TextBlock Text="{Binding DisplayName}" MaxWidth="170" Style="{StaticResource TabHeaderText}"
|
<TextBlock Text="{Binding DisplayName}" MaxWidth="170" Style="{StaticResource TabHeaderText}"
|
||||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center"/>
|
TextTrimming="CharacterEllipsis" VerticalAlignment="Center"/>
|
||||||
<Button Content="✕" FontSize="9" Margin="7,0,0,0"
|
<Button Content="✕" FontSize="11" Margin="7,0,0,0"
|
||||||
Style="{StaticResource Subtle}" Padding="3,0" Cursor="Hand" ToolTip="문서 닫기"
|
Style="{StaticResource Subtle}" Padding="3,0" Cursor="Hand" ToolTip="문서 닫기"
|
||||||
Command="{Binding DataContext.CloseDocumentCommand,
|
Command="{Binding DataContext.CloseDocumentCommand,
|
||||||
RelativeSource={RelativeSource AncestorType=Window}}"
|
RelativeSource={RelativeSource AncestorType=Window}}"
|
||||||
@@ -462,9 +587,7 @@
|
|||||||
ToolTip="컨트롤 추가 — 활성 페이지 중앙에 배치" Click="OnAddControlFlyoutClick"
|
ToolTip="컨트롤 추가 — 활성 페이지 중앙에 배치" Click="OnAddControlFlyoutClick"
|
||||||
Content="{Binding Source=layout-grid, Converter={StaticResource IconName}, ConverterParameter=18}"/>
|
Content="{Binding Source=layout-grid, Converter={StaticResource IconName}, ConverterParameter=18}"/>
|
||||||
<Border Width="1" Height="22" Background="{DynamicResource B.Line}" Margin="5,0" VerticalAlignment="Center"/>
|
<Border Width="1" Height="22" Background="{DynamicResource B.Line}" Margin="5,0" VerticalAlignment="Center"/>
|
||||||
<Button Style="{StaticResource Subtle}" Width="30" Height="34" Padding="0" ToolTip="축소 (Ctrl+휠↓)"
|
<!-- ±버튼은 두지 않는다([200] 와 동일) — Ctrl+휠이 주 조작이고, 배율 버튼이 플라이아웃을 겸한다 -->
|
||||||
Command="{Binding ZoomOutCommand}"
|
|
||||||
Content="{Binding Source=zoom-out, Converter={StaticResource IconName}, ConverterParameter=16}"/>
|
|
||||||
<Button x:Name="ZoomBtn" Style="{StaticResource Subtle}" Height="34" Padding="8,0,6,0" ToolTip="줌"
|
<Button x:Name="ZoomBtn" Style="{StaticResource Subtle}" Height="34" Padding="8,0,6,0" ToolTip="줌"
|
||||||
Click="OnZoomFlyoutClick">
|
Click="OnZoomFlyoutClick">
|
||||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
@@ -474,9 +597,6 @@
|
|||||||
Margin="3,1,0,0" VerticalAlignment="Center" IsTabStop="False" Focusable="False"/>
|
Margin="3,1,0,0" VerticalAlignment="Center" IsTabStop="False" Focusable="False"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Style="{StaticResource Subtle}" Width="30" Height="34" Padding="0" ToolTip="확대 (Ctrl+휠↑)"
|
|
||||||
Command="{Binding ZoomInCommand}"
|
|
||||||
Content="{Binding Source=zoom-in, Converter={StaticResource IconName}, ConverterParameter=16}"/>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
@@ -552,9 +672,42 @@
|
|||||||
<Button Style="{StaticResource FlyoutRow}" Command="{Binding ZoomResetCommand}" Click="OnZoomRowClick">
|
<Button Style="{StaticResource FlyoutRow}" Command="{Binding ZoomResetCommand}" Click="OnZoomRowClick">
|
||||||
<TextBlock Text="100%" FontSize="12.5" VerticalAlignment="Center"/>
|
<TextBlock Text="100%" FontSize="12.5" VerticalAlignment="Center"/>
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button Style="{StaticResource FlyoutRow}" Command="{Binding ZoomFitCommand}" Click="OnZoomRowClick">
|
||||||
|
<TextBlock Text="화면 맞춤" FontSize="12.5" VerticalAlignment="Center"/>
|
||||||
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
</Popup>
|
</Popup>
|
||||||
|
|
||||||
|
<!-- 빈 상태 — 열린 문서가 없으면 회색 허공 대신 다음 행동을 제시한다.
|
||||||
|
플로팅 바보다 뒤에 두어 위에 덮인다(문서 없이 줌·컨트롤 추가는 의미가 없다). -->
|
||||||
|
<Border Background="{DynamicResource B.CanvasBg}">
|
||||||
|
<Border.Style>
|
||||||
|
<Style TargetType="Border">
|
||||||
|
<Setter Property="Visibility" Value="Collapsed"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding HasOpenDocuments}" Value="False">
|
||||||
|
<Setter Property="Visibility" Value="Visible"/>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Border.Style>
|
||||||
|
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="360">
|
||||||
|
<ContentControl HorizontalAlignment="Center" IsTabStop="False" Focusable="False" Opacity="0.5"
|
||||||
|
Content="{Binding Source=file-text, Converter={StaticResource IconName}, ConverterParameter=44}"/>
|
||||||
|
<TextBlock Text="열린 서식이 없습니다" FontSize="15" FontWeight="Bold" TextAlignment="Center"
|
||||||
|
HorizontalAlignment="Center" Margin="0,16,0,6"/>
|
||||||
|
<TextBlock Foreground="{DynamicResource B.Muted}" FontSize="12.5" TextAlignment="Center"
|
||||||
|
TextWrapping="Wrap" HorizontalAlignment="Center" Margin="0,0,0,20"
|
||||||
|
Text="새 서식을 만들거나, 왼쪽 서식 목록에서 기존 서식을 두 번 눌러 엽니다."/>
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||||
|
<Button Content="새 서식" Command="{Binding NewFileCommand}" Padding="16,7" Margin="0,0,8,0"/>
|
||||||
|
<Button Content="DB에서 열기" Command="{Binding OpenFromDbCommand}" Padding="16,7"
|
||||||
|
IsEnabled="{Binding CanUseDb}"/>
|
||||||
|
<Button Content="파일 열기" Command="{Binding OpenFileCommand}" Padding="16,7" Margin="8,0,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<GridSplitter Grid.Column="3" Style="{StaticResource ColSplitter}" ToolTip="속성 패널 너비 조절"/>
|
<GridSplitter Grid.Column="3" Style="{StaticResource ColSplitter}" ToolTip="속성 패널 너비 조절"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user