diff --git a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs
index 4eae119..dd8b223 100644
--- a/src/SheetMe.Designer/Diagnostics/EditSmoke.cs
+++ b/src/SheetMe.Designer/Diagnostics/EditSmoke.cs
@@ -1112,6 +1112,64 @@ public static class EditSmoke
$"ValueText='{choiceRow.ValueText}' IsMixed={choiceRow.IsMixed}");
}
+ // 15-a0000) 레이어 목록이 계층과 범위를 말하는가.
+ //
+ // 사용자가 콕 집어 "레이어 구분이 잘 안 된다"고 한 곳이다. 원인은 색이 아니라 산수였다 —
+ // 셰브론이 자식 없는 행에서 Collapsed 로 사라지며 폭 16 이 통째로 없어지는데
+ // 컨테이너 자식 들여쓰기도 정확히 16 이라, 부모와 자식의 아이콘이 같은 x 에 놓였다.
+ {
+ var tree = new DesignerViewModel(new FormDesignDataBusiness().CreateNew());
+ tree.AddControlAt("RadioButton", new Point(100, 100));
+ tree.AddControlAt("RadioButton", new Point(100, 140));
+ tree.Selection.Set(tree.Pages[0].Controls.ToList(), tree.Pages[0].Controls[0]);
+ tree.GroupSelection();
+ var wrapped = tree.Pages[0].Controls.OfType().First();
+
+ Check("레이어: 컨테이너가 자식 수를 말한다", wrapped.LayerChildCountText == "(2)",
+ wrapped.LayerChildCountText);
+
+ // 중첩까지 센다 — "이걸 지우면 몇 개가 같이 지워지나"가 그 수다.
+ // 중첩 구조는 모델로 직접 짠다(Ctrl+G 를 두 번 겹치는 경로는 이 검사의 대상이 아니다).
+ var nestDoc = new FormDocument();
+ var nestPage = new FormPage { Width = 720, Height = 856 };
+ nestDoc.Pages.Add(nestPage);
+ var outerModel = new ControlElement
+ {
+ Type = "Panel", Id = "바깥",
+ Bounds = new LayoutRect { X = 20, Y = 20, W = 300, H = 200 },
+ };
+ var innerModel = new ControlElement
+ {
+ Type = "Panel", Id = "안쪽",
+ Bounds = new LayoutRect { X = 10, Y = 10, W = 200, H = 120 },
+ };
+ for (var i = 0; i < 2; i++)
+ {
+ innerModel.Children.Add(new ControlElement
+ {
+ Type = "RadioButton", Id = $"보기{i}",
+ Bounds = new LayoutRect { X = 10, Y = 10 + (i * 30), W = 100, H = 20 },
+ });
+ }
+ outerModel.Children.Add(innerModel);
+ nestPage.Controls.Add(outerModel);
+ var nested = new DesignerViewModel(nestDoc);
+
+ var outer = nested.Pages[0].Controls.OfType().First();
+ var inner = outer.Children.OfType().First();
+ Check("레이어: 자식 수는 중첩까지 센다", outer.LayerChildCountText == "(3)",
+ $"{outer.LayerChildCountText} (바깥 = 안쪽 패널 1 + 라디오 2)");
+
+ // 컨테이너를 고르면 그 안이 밴드로 묶여야 한다
+ nested.Selection.SetSingle(outer);
+ Check("레이어: 고른 컨테이너의 안쪽이 범위로 표시된다",
+ inner.Highlight == LayerHighlight.Navy && outer.Highlight == LayerHighlight.Blue,
+ $"바깥={outer.Highlight} 안쪽={inner.Highlight}");
+ Check("레이어: 손자까지 범위에 들어간다",
+ inner.Children.All(c => c.Highlight == LayerHighlight.Navy),
+ string.Join(",", inner.Children.Select(c => c.Highlight.ToString())));
+ }
+
// 15-a000) 데이터소스를 실사용과 같은 경로로 만들었을 때도 러너가 찾는가.
//
// 이 검사가 없어서 배선 전체가 실사용에서 죽어 있는 것을 오래 못 봤다.
diff --git a/src/SheetMe.Designer/ViewModels/Controls/TypedControlViewModels.cs b/src/SheetMe.Designer/ViewModels/Controls/TypedControlViewModels.cs
index e59bc3a..f5b58c5 100644
--- a/src/SheetMe.Designer/ViewModels/Controls/TypedControlViewModels.cs
+++ b/src/SheetMe.Designer/ViewModels/Controls/TypedControlViewModels.cs
@@ -136,6 +136,30 @@ public abstract class ContainerViewModel : ControlViewModel
/// 레이어 목록에서 펼침 셰브론을 그릴지 — 자식이 있을 때만
public override bool HasLayerChildren => Children.Count > 0;
+ ///
+ /// 레이어 목록에 붙는 자식 수 — 접혀 있어도 무엇이 딸려 있는지 알 수 있어야 한다.
+ ///
+ /// 페이지 행에는 (7), 그룹 폴더에는 (3)이 이미 붙는데 컨테이너만 없었다.
+ /// 그래서 접힌 패널이 3개를 품었는지 300개를 품었는지 펴 보기 전에는 알 수 없었고,
+ /// 그 패널을 지우거나 옮기기 전에 무엇이 함께 가는지도 알 수 없었다.
+ /// 중첩까지 세어 실제로 딸려 가는 수를 말한다.
+ ///
+ public string LayerChildCountText => $"({CountAll(Children)})";
+
+ private static int CountAll(IEnumerable items)
+ {
+ var total = 0;
+ foreach (var item in items)
+ {
+ total++;
+ if (item is ContainerViewModel container)
+ {
+ total += CountAll(container.Children);
+ }
+ }
+ return total;
+ }
+
protected ContainerViewModel(ControlElement model) : base(model) { }
/// 시각 재해석 — 자식에게 상속 컨텍스트 전파(WinForms Font/ForeColor 상속)
diff --git a/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs b/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs
index 1f2116b..3a273f1 100644
--- a/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs
+++ b/src/SheetMe.Designer/ViewModels/DesignerViewModel.cs
@@ -2611,6 +2611,19 @@ public sealed class DesignerViewModel : ViewModelBase
=> total.TryGetValue(id, out var t) && t > 0
&& selected.TryGetValue(id, out var s) && s == t;
+ // 조상 중에 '선택된 컨테이너'가 있는가 — 선택한 패널의 범위를 목록에 그리기 위한 판정
+ static bool HasSelectedContainerAncestor(ControlViewModel viewModel)
+ {
+ for (var parent = viewModel.Parent; parent is not null; parent = parent.Parent)
+ {
+ if (parent.IsSelected)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
var states = new LayerHighlight[LayerRows.Count];
for (var i = 0; i < LayerRows.Count; i++)
{
@@ -2620,6 +2633,12 @@ public sealed class DesignerViewModel : ViewModelBase
ControlViewModel c when c.Model.GroupId is { } gid && GroupFullySelected(gid) => LayerHighlight.Navy,
ControlViewModel c when c.IsSelected => LayerHighlight.Blue,
LayerGroupViewModel g when GroupFullySelected(g.GroupId) => LayerHighlight.Blue,
+ // 선택한 컨테이너 안에 있는 것 — 그룹 판정보다 뒤에 둔다(그룹 색을 이기면 안 된다).
+ //
+ // 캔버스에서 패널을 끌면 자식이 전부 따라 움직이는데 목록은 그 사실을 말하지 않았다.
+ // 밴드가 붙으면 "이걸 지우면 무엇이 같이 지워지나"가 선택 즉시 보인다 —
+ // 접었다 펴 보지 않아도 된다.
+ ControlViewModel c when HasSelectedContainerAncestor(c) => LayerHighlight.Navy,
_ => LayerHighlight.None,
};
}
diff --git a/src/SheetMe.Designer/Views/LayerPanelView.xaml b/src/SheetMe.Designer/Views/LayerPanelView.xaml
index bd508b7..ca96be7 100644
--- a/src/SheetMe.Designer/Views/LayerPanelView.xaml
+++ b/src/SheetMe.Designer/Views/LayerPanelView.xaml
@@ -283,13 +283,41 @@
+
+
+
+
+
+
-
-
+
+
+
+