diff --git a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs index ee0ad2b..a1f9433 100644 --- a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs +++ b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs @@ -1229,6 +1229,37 @@ public static class EditSmoke Check("레이어: 컨테이너가 자식 수를 말한다", wrapped.LayerChildCountText == "(2)", wrapped.LayerChildCountText); + // 둘째 줄은 항상 있어야 한다 — 없으면 1줄/2줄이 섞여 훑는 리듬이 깨진다. + // 세 갈래를 다 본다: 문구 없음 / 문구가 타입명 그대로(갓 놓은 것) / 사람이 쓴 문구. + var fresh = wrapped.Children[0]; // 갓 놓은 라디오 — 문구가 "라디오버튼"이다 + Check("레이어: 갓 놓은 것은 둘째 줄이 이름뿐이다(타입명 중복 없음)", + fresh.LayerSubText == fresh.Id, + $"'{fresh.LayerName}' / '{fresh.LayerSubText}'"); + + var bare = new ControlElement + { + Type = "TextBox", Id = "TextBox9", + Bounds = new LayoutRect { X = 10, Y = 10, W = 100, H = 20 }, + }; + var bareDoc = new FormDocument(); + var barePage = new FormPage { Width = 720, Height = 856 }; + barePage.Controls.Add(bare); + bareDoc.Pages.Add(barePage); + var bareVm = new DesignerViewModel(bareDoc).Pages[0].Controls[0]; + Check("레이어: 문구가 없는 행도 둘째 줄이 있다", bareVm.LayerSubText.Length > 0, + $"'{bareVm.LayerName}' / '{bareVm.LayerSubText}'"); + Check("레이어: 문구가 없으면 둘째 줄은 타입명이다", bareVm.LayerSubText == "텍스트박스", + bareVm.LayerSubText); + Check("레이어: 같은 글자를 두 줄에 두 번 쓰지 않는다", + !bareVm.LayerSubText.Contains(bareVm.Id, StringComparison.Ordinal), + $"Id={bareVm.Id} 둘째줄='{bareVm.LayerSubText}'"); + + bare.Props.SetText("Text", "환자명"); + var named = new DesignerViewModel(bareDoc).Pages[0].Controls[0]; + Check("레이어: 사람이 쓴 문구면 타입·이름을 함께 보여 준다", + named.LayerSubText == "텍스트박스 · TextBox9", + $"'{named.LayerName}' / '{named.LayerSubText}'"); + // 중첩까지 센다 — "이걸 지우면 몇 개가 같이 지워지나"가 그 수다. // 중첩 구조는 모델로 직접 짠다(Ctrl+G 를 두 번 겹치는 경로는 이 검사의 대상이 아니다). var nestDoc = new FormDocument(); diff --git a/src/SheetMe.Designer/ViewModels/Controls/ControlViewModel.cs b/src/SheetMe.Designer/ViewModels/Controls/ControlViewModel.cs index 7968473..ff6efc2 100644 --- a/src/SheetMe.Designer/ViewModels/Controls/ControlViewModel.cs +++ b/src/SheetMe.Designer/ViewModels/Controls/ControlViewModel.cs @@ -199,6 +199,33 @@ public abstract class ControlViewModel : ViewModelBase, ILayerRow /// 이름 칸을 그릴지 — 문구와 이름이 다를 때만 public bool HasLayerId => LayerId.Length > 0; + /// + /// 레이어 행 둘째 줄 — 항상 있다. 그래야 행 높이가 일정하다. + /// + /// 전에는 표시명이 곧 Id 인 행(문구가 없는 컨트롤)에서 둘째 줄이 통째로 빠져 + /// 목록에 1줄과 2줄이 섞였다. 훑을 때 리듬이 깨지고, 어느 행이 무엇인지 + /// 눈이 매번 다시 자리를 잡아야 했다. + /// + /// 타입명을 앞에 둔다. 아이콘만으로는 타입이 안 갈리는 짝이 실제로 있다 — + /// 라벨과 마스크입력이 같은 글리프를 쓰고, 리스트박스와 체크리스트도 같다. + /// 이름(Id)은 표시명과 다를 때만 뒤에 붙인다 — 같으면 윗줄과 같은 글자를 두 번 쓰는 셈이다. + /// 이름을 계속 보여 주는 이유는 그것이 외부 참조 키이기 때문이다(수식·액션 대상·이벤트 규약). + /// + public string LayerSubText + { + get + { + var typeName = SheetMe.Core.Catalog.ControlRegistry.Find(Type)?.DisplayName ?? Type; + // 이름이 곧 표시명이면(문구 없는 컨트롤) 타입만 — 같은 글자를 두 번 쓰지 않는다 + if (!HasLayerId) + { + return typeName; + } + // 갓 놓은 컨트롤은 문구가 타입명 그대로다("라디오버튼") — 윗줄이 이미 타입을 말했으므로 이름만 + return LayerName == typeName ? LayerId : $"{typeName} · {LayerId}"; + } + } + /// 레이어 잠금 토글 아이콘명 public string LockIcon => Model.Locked ? "lock" : "unlock"; diff --git a/src/SheetMe.Designer/Views/LayerPanelView.xaml b/src/SheetMe.Designer/Views/LayerPanelView.xaml index ca96be7..213e442 100644 --- a/src/SheetMe.Designer/Views/LayerPanelView.xaml +++ b/src/SheetMe.Designer/Views/LayerPanelView.xaml @@ -349,10 +349,16 @@ - 항상 있다 — 없으면 목록에 1줄 행과 2줄 행이 섞여 + 훑을 때 눈이 매번 자리를 다시 잡아야 한다. + 타입명을 앞에 두는 이유: 아이콘만으로 안 갈리는 짝이 실제로 있다 + (라벨·마스크입력이 같은 글리프, 리스트박스·체크리스트도 같다). + 이름(Id)은 표시명과 다를 때만 뒤에 붙는다 — 외부 참조 키라 계속 보여야 한다. + --> + + ToolTip="{Binding LayerSubText}">