좌측 패널 — 페이지와 레이어를 두 구획으로 가른다

레이어 탭 하나에 페이지 마디와 컨트롤이 한 시퀀스로 섞여 있었다. 페이지를 추가·복제·삭제하는
조작이 컨트롤을 눈으로 찾는 일과 같은 목록에서 경쟁했다. 위는 페이지, 아래는 레이어로 나눈다.

페이지 행을 누르면 그 페이지를 활성화한다 — 캔버스가 거기로 스크롤하고 인스펙터가 용지 속성을 연다.
경로는 이미 있던 ActivatePage 그대로다. 강조는 ListBox 의 IsSelected 가 아니라 IsCurrent 가 그린다
(레이어 행이 Highlight 3상태로 그리는 것과 같은 규율 — 두 강조가 겹치면 어느 쪽이 진짜인지 모른다).

트리는 그 페이지로 좁히지 않는다. 세 가지가 거기 걸려 있다: 레이어 검색이 문서 전체를 훑는 성질,
그룹 강조가 페이지를 넘나드는 멤버까지 집계해야 폴더가 파래지는 것, 그리고 캔버스가 전 페이지를
세로로 이어 그리므로 트리만 한 장으로 좁으면 두 뷰의 1:1 대응이 깨진다는 것.
좁히고 싶으면 페이지 접기가 이미 있고 그건 명시적이다.

트리의 페이지 마디는 장수로 갈린다.
· 한 장이면 아예 없앤다 — 모든 컨트롤이 그 페이지 소속이라 정보가 0 이고,
  최상위가 들여쓰기 없이 바로 보인다. 실사용 문서 대부분이 여기다.
· 두 장 이상이면 남기되 경계 구분자로 줄인다(높이 20, 회색, 개수·복제·삭제 없음).
  통째로 빼면 안 되는 이유가 있다 — 페이지 행이 항상 무강조라서 연속 선택 블록이 페이지 경계에서
  저절로 끊기는데, 빼면 앞 페이지 마지막 컨트롤과 뒷 페이지 첫 컨트롤이 하나의 파란 띠로 붙는다.

들여쓰기 상수를 계산값으로 바꿨다. 페이지 마디가 없으면 들여쓸 이유도 없다 —
페이지 직속 12→0, 그룹 멤버 32→20.

트리 맨 아래 '페이지 추가' 행은 없앴다. 페이지 구획 머리의 + 가 그 자리다.
목록 안에 조작 행이 섞여 있으면 스크롤 끝까지 가야 눌 수 있다.

페이지 목록은 5행까지 보이고 그 뒤로는 목록 안에서만 스크롤한다 — 7장짜리 문서에서도
레이어 트리가 밀려나지 않는다. 트리는 남은 영역을 그대로 차지해 가상화를 유지한다.

이름 변경과 순서 변경은 넣지 않았다. FormPage 에 이름 필드가 없고 유일한 식별자인 Root.Id 는
E_SctMst 사전 매핑에 물려 있어 개명이 위험하다. 페이지 이동 API 도 없다(Pages 조작은 통째 재생성뿐).
표시 전용 이름을 새로 만들면 레거시 XML 에 담을 곳이 없어 왕복 diff 0 불변식과 충돌한다.

새 스타일 3종(SectionHead / SectionHeadText / PageRowItem). 구획 머리의 아래 1px 선은 필수다 —
라이트에서 B.PanelHeader(#FAFAFA)와 B.Panel(#FFFFFF)이 1.02:1 이라 배경만으로는 경계가 안 보인다.

게이트: 테스트 267/267, --edit-smoke 236건 0실패(레이어 단언 5건 갱신),
--query-popup 17/17, --maxrect 10/10, --db-smoke 3000 diff 0·예외 0,
--db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일, --dialog-shots 57파일.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-14 11:33:57 +09:00
co-authored by Claude Opus 5
parent e82d520de6
commit 6ee4435575
5 changed files with 177 additions and 42 deletions
+11 -7
View File
@@ -285,10 +285,14 @@ public static class EditSmoke
$"실제 {groupRow?.Members.Count} / {groupRow?.Name}"); $"실제 {groupRow?.Members.Count} / {groupRow?.Name}");
// 아웃라인은 페이지 헤더로 시작하고 '페이지 추가' 행으로 끝난다 // 아웃라인은 페이지 헤더로 시작하고 '페이지 추가' 행으로 끝난다
Check("레이어: 첫 행은 페이지 헤더", lay.LayerRows.FirstOrDefault() is LayerPageViewModel); // 페이지는 별도 구획으로 나갔다 — 한 장짜리 문서의 트리에는 페이지 마디가 없다
Check("레이어: 마지막 행은 페이지 추가", lay.LayerRows.LastOrDefault() is LayerAddPageViewModel); Check("레이어: 페이지 구획이 페이지를 담는다",
lay.PageRows.Count == lay.Pages.Count && lay.PageRows.Count == 1);
Check("레이어: 한 장이면 트리에 페이지 마디가 없다",
!lay.ShowPageNodes && !lay.LayerRows.OfType<LayerPageViewModel>().Any());
Check("레이어: 첫 행이 곧 컨트롤·그룹이다", lay.LayerRows.FirstOrDefault() is not LayerPageViewModel);
Check("레이어: 페이지 헤더는 선택 대상 아님", Check("레이어: 페이지 헤더는 선택 대상 아님",
lay.LayerRows.OfType<ILayerRow>().Where(r => r is LayerPageViewModel or LayerAddPageViewModel) lay.LayerRows.OfType<ILayerRow>().Where(r => r is LayerPageViewModel)
.All(r => !r.IsSelectableRow)); .All(r => !r.IsSelectableRow));
// 폴더 바로 뒤에 멤버가 들여쓰기된 채 이어져야 한다(평탄화 시퀀스) // 폴더 바로 뒤에 멤버가 들여쓰기된 채 이어져야 한다(평탄화 시퀀스)
@@ -296,11 +300,11 @@ public static class EditSmoke
Check("레이어: 폴더 뒤에 멤버가 들여쓰여 이어짐", Check("레이어: 폴더 뒤에 멤버가 들여쓰여 이어짐",
folderIndex >= 0 folderIndex >= 0
&& lay.LayerRows.Skip(folderIndex + 1).Take(2) && lay.LayerRows.Skip(folderIndex + 1).Take(2)
.OfType<ControlViewModel>().Count(c => c.LayerIndent == 32) == 2); .OfType<ControlViewModel>().Count(c => c.LayerIndent == 20) == 2);
Check("레이어: 페이지 직속 컨트롤 들여쓰기 12", Check("레이어: 한 장이면 최상위 들여쓰기가 0",
lay.LayerRows.OfType<ControlViewModel>() lay.LayerRows.OfType<ControlViewModel>()
.Where(c => c.Model.GroupId is null && c.Parent is null) .Where(c => c.Model.GroupId is null && c.Parent is null)
.All(c => c.LayerIndent == 12)); .All(c => c.LayerIndent == 0));
// 목록 맨 위 = z-order 최상위 (모델 리스트 index 0) // 목록 맨 위 = z-order 최상위 (모델 리스트 index 0)
var topControl = lay.LayerRows.OfType<ControlViewModel>().First(); var topControl = lay.LayerRows.OfType<ControlViewModel>().First();
@@ -493,7 +497,7 @@ public static class EditSmoke
{ {
var pg = new DesignerViewModel(business.CreateNew()); var pg = new DesignerViewModel(business.CreateNew());
pg.AddControlAt("Label", new Point(100, 100)); pg.AddControlAt("Label", new Point(100, 100));
var pageRow = pg.LayerRows.OfType<LayerPageViewModel>().First(); var pageRow = pg.PageRows[0];
pg.ActivatePage(pageRow.Page); pg.ActivatePage(pageRow.Page);
Check("페이지 속성: 페이지 모드 진입", pg.InspectedPage is not null && pg.Selection.Items.Count == 0); Check("페이지 속성: 페이지 모드 진입", pg.InspectedPage is not null && pg.Selection.Items.Count == 0);
@@ -993,6 +993,55 @@
<Setter Property="CornerRadius" Value="8" /> <Setter Property="CornerRadius" Value="8" />
<Setter Property="Padding" Value="12" /> <Setter Property="Padding" Value="12" />
</Style> </Style>
<!--
구획 머리 — 좌측 패널을 페이지/레이어로 가르는 띠.
아래 1px 선이 필수다: 라이트에서 B.PanelHeader(#FAFAFA)와 B.Panel(#FFFFFF)이 1.02:1 이라
배경색만으로는 구획 경계가 보이지 않는다.
-->
<Style x:Key="SectionHead" TargetType="Border">
<Setter Property="Background" Value="{DynamicResource B.PanelHeader}" />
<Setter Property="BorderBrush" Value="{DynamicResource B.Line}" />
<Setter Property="BorderThickness" Value="0,0,0,1" />
<Setter Property="Padding" Value="10,5,6,5" />
<Setter Property="MinHeight" Value="30" />
</Style>
<Style x:Key="SectionHeadText" TargetType="TextBlock">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="FontSize" Value="12" />
<Setter Property="Foreground" Value="{DynamicResource B.Ink}" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
</Style>
<!--
페이지 행 — 강조는 IsSelected 가 아니라 IsCurrent 가 그린다.
암시 ListBoxItem 의 선택 배경을 꺼야 두 강조가 겹쳐 보이지 않는다(레이어 행과 같은 규율).
-->
<Style x:Key="PageRowItem" TargetType="ListBoxItem">
<Setter Property="Padding" Value="4,2" />
<Setter Property="Margin" Value="2,1" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border x:Name="bd" Background="Transparent" CornerRadius="5"
Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="bd" Property="Background" Value="{DynamicResource B.Hover}" />
</Trigger>
<DataTrigger Binding="{Binding IsCurrent}" Value="True">
<Setter TargetName="bd" Property="Background" Value="{DynamicResource B.LayerSubtree}" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="PanelHeader" TargetType="Border"> <Style x:Key="PanelHeader" TargetType="Border">
<Setter Property="Background" Value="{DynamicResource B.Surface}" /> <Setter Property="Background" Value="{DynamicResource B.Surface}" />
<Setter Property="BorderBrush" Value="{DynamicResource B.Line}" /> <Setter Property="BorderBrush" Value="{DynamicResource B.Line}" />
@@ -157,7 +157,7 @@ public sealed class DesignerViewModel : ViewModelBase
Selection.ActivePage = value; Selection.ActivePage = value;
} }
// 아웃라인이 전 페이지를 담으므로 목록 자체는 그대로다 — 활성 표시만 갱신한다 // 아웃라인이 전 페이지를 담으므로 목록 자체는 그대로다 — 활성 표시만 갱신한다
foreach (var header in LayerRows.OfType<LayerPageViewModel>()) foreach (var header in PageRows)
{ {
header.NotifyStateChanged(); header.NotifyStateChanged();
} }
@@ -213,6 +213,12 @@ public sealed class DesignerViewModel : ViewModelBase
/// </summary> /// </summary>
public ObservableCollection<object> LayerRows { get; } = new(); public ObservableCollection<object> LayerRows { get; } = new();
/// <summary>페이지 구획 목록 — 트리와 별도. 페이지 조작(추가·복제·삭제·이동)은 전부 이쪽이다</summary>
public ObservableCollection<LayerPageViewModel> PageRows { get; } = new();
/// <summary>트리에 페이지 마디를 둘 것인가 — 한 장짜리 문서에서는 뜻이 없다</summary>
public bool ShowPageNodes => Pages.Count > 1;
/// <summary> /// <summary>
/// 컨트롤 행이 하나도 없는지 — 빈 목록에 안내를 띄우기 위한 플래그. /// 컨트롤 행이 하나도 없는지 — 빈 목록에 안내를 띄우기 위한 플래그.
/// 페이지 헤더와 '페이지 추가' 행은 늘 있으므로 행 수가 아니라 컨트롤 행 유무로 판단한다. /// 페이지 헤더와 '페이지 추가' 행은 늘 있으므로 행 수가 아니라 컨트롤 행 유무로 판단한다.
@@ -1843,10 +1849,17 @@ public sealed class DesignerViewModel : ViewModelBase
IsRebuildingLayers = true; IsRebuildingLayers = true;
LayerRows.Clear(); LayerRows.Clear();
PageRows.Clear();
foreach (var page in Pages) foreach (var page in Pages)
{ {
var header = new LayerPageViewModel(this, page); var header = new LayerPageViewModel(this, page);
LayerRows.Add(header); PageRows.Add(header);
// 한 장뿐이면 트리에 페이지 마디를 두지 않는다 — 모든 컨트롤이 그 페이지 소속이라 정보가 0 이고,
// 최상위가 들여쓰기 없이 바로 보인다. 두 장 이상일 때만 경계 구분자로 남긴다.
if (ShowPageNodes)
{
LayerRows.Add(header);
}
if (!header.IsExpanded) if (!header.IsExpanded)
{ {
continue; continue;
@@ -1884,7 +1897,6 @@ public sealed class DesignerViewModel : ViewModelBase
} }
} }
} }
LayerRows.Add(new LayerAddPageViewModel());
IsRebuildingLayers = false; IsRebuildingLayers = false;
UpdateLayerHighlight(); UpdateLayerHighlight();
@@ -1894,10 +1906,10 @@ public sealed class DesignerViewModel : ViewModelBase
} }
/// <summary>페이지 직속 컨트롤·그룹 폴더의 들여쓰기 — 페이지 노드 아래라 항상 한 칸 들어간다</summary> /// <summary>페이지 직속 컨트롤·그룹 폴더의 들여쓰기 — 페이지 노드 아래라 항상 한 칸 들어간다</summary>
private const double PageChildIndent = 12; private double PageChildIndent => ShowPageNodes ? 12 : 0;
/// <summary>그룹 멤버 들여쓰기 — 페이지 직속보다 한 단계 더</summary> /// <summary>그룹 멤버 들여쓰기 — 페이지 직속보다 한 단계 더</summary>
private const double GroupMemberIndent = 32; private double GroupMemberIndent => PageChildIndent + 20;
/// <summary>컨테이너(패널/그룹박스) 자식의 추가 들여쓰기</summary> /// <summary>컨테이너(패널/그룹박스) 자식의 추가 들여쓰기</summary>
private const double ContainerChildIndent = 16; private const double ContainerChildIndent = 16;
@@ -2058,7 +2070,7 @@ public sealed class DesignerViewModel : ViewModelBase
} }
Selection.Clear(); Selection.Clear();
InspectedPage = page; InspectedPage = page;
foreach (var header in LayerRows.OfType<LayerPageViewModel>()) foreach (var header in PageRows)
{ {
header.NotifyStateChanged(); header.NotifyStateChanged();
} }
+77 -26
View File
@@ -23,7 +23,68 @@
<ctl:IconSpecConverter x:Key="IconSpec"/> <ctl:IconSpecConverter x:Key="IconSpec"/>
<BooleanToVisibilityConverter x:Key="BoolToVisibility"/> <BooleanToVisibilityConverter x:Key="BoolToVisibility"/>
</UserControl.Resources> </UserControl.Resources>
<DockPanel> <!--
두 구획으로 나눈다 — 위는 페이지, 아래는 레이어.
페이지 조작(추가·복제·삭제·활성화)이 트리 안에 섞여 있으면 컨트롤을 찾는 눈과 경쟁한다.
페이지 행을 누르면 캔버스가 그 페이지로 스크롤한다(ActivatePage).
트리는 좁히지 않는다 — 검색이 문서 전체를 훑는 성질과 그룹의 페이지 횡단 집계가 거기 걸려 있다.
-->
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- ══ 페이지 ══ -->
<DockPanel Grid.Row="0">
<Border DockPanel.Dock="Top" Style="{StaticResource SectionHead}">
<DockPanel>
<Button DockPanel.Dock="Right" Style="{StaticResource Subtle}" Width="24" Height="22" Padding="0"
Click="OnPageAddClick" ToolTip="페이지 추가" AutomationProperties.Name="페이지 추가"
Content="{Binding Source=plus, Converter={StaticResource IconName}, ConverterParameter=14|B.Muted}"/>
<TextBlock Style="{StaticResource SectionHeadText}" Text="페이지"/>
</DockPanel>
</Border>
<!-- 5행까지 보이고 그 뒤로는 이 목록 안에서만 스크롤한다 — 레이어 트리를 밀어내면 안 된다 -->
<ListBox x:Name="PageListBox" ItemsSource="{Binding CurrentDesigner.PageRows}"
MaxHeight="134" Padding="2,3" BorderThickness="0" Background="Transparent"
ItemContainerStyle="{StaticResource PageRowItem}"
PreviewMouseLeftButtonDown="OnPageRowPressed"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel Height="22">
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal"
Style="{StaticResource LayerRowActions}">
<Button Style="{StaticResource Subtle}" Width="24" Height="20" Padding="0"
Margin="1,0,0,0" Click="OnPageDuplicateClick" ToolTip="페이지 복제"
Content="{Binding Source=copy, Converter={StaticResource IconName}, ConverterParameter=13|B.Muted}"/>
<Button Style="{StaticResource Subtle}" Width="24" Height="20" Padding="0"
Margin="1,0,0,0" Click="OnPageDeleteClick" ToolTip="페이지 삭제"
Visibility="{Binding CanDelete, Converter={StaticResource BoolToVisibility}}"
Content="{Binding Source=trash-2, Converter={StaticResource IconName}, ConverterParameter=13|B.Muted}"/>
</StackPanel>
<ContentControl DockPanel.Dock="Left" Margin="2,0,6,0" VerticalAlignment="Center"
IsTabStop="False" Focusable="False"
Content="{Binding PageIconSpec, Converter={StaticResource IconSpec}}"/>
<TextBlock DockPanel.Dock="Right" Text="{Binding CountText}" FontSize="11" Margin="6,0,4,0"
VerticalAlignment="Center" Foreground="{DynamicResource B.Muted}"/>
<TextBlock Text="{Binding Title}" VerticalAlignment="Center" FontSize="12"
TextTrimming="CharacterEllipsis"/>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
<Border Grid.Row="1" Height="1" Background="{DynamicResource B.Line}"/>
<!-- ══ 레이어 ══ -->
<DockPanel Grid.Row="2">
<Border DockPanel.Dock="Top" Style="{StaticResource SectionHead}">
<TextBlock Style="{StaticResource SectionHeadText}" Text="레이어"/>
</Border>
<!-- 검색줄 — 검색칩(가변폭) + 우측 타입 필터 버튼 2개 --> <!-- 검색줄 — 검색칩(가변폭) + 우측 타입 필터 버튼 2개 -->
<DockPanel DockPanel.Dock="Top"> <DockPanel DockPanel.Dock="Top">
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" <StackPanel DockPanel.Dock="Right" Orientation="Horizontal"
@@ -131,30 +192,19 @@
</Style.Triggers> </Style.Triggers>
</Style> </Style>
</DockPanel.Style> </DockPanel.Style>
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" <!--
Style="{StaticResource LayerRowActions}"> 페이지 조작은 전부 위쪽 페이지 구획으로 갔다. 여기 남은 것은
<Button Style="{StaticResource Subtle}" Width="24" Height="22" Padding="0" 경계 구분자다 — 연속 선택 블록이 페이지 경계에서 끊기게 하는 역할과
Margin="1,0,0,0" Click="OnPageDuplicateClick" ToolTip="페이지 복제" 페이지 접기(트리를 좁히는 유일한 수단)만 남긴다.
Content="{Binding Source=copy, Converter={StaticResource IconName}, ConverterParameter=13|B.Muted}"/> -->
<Button Style="{StaticResource Subtle}" Width="24" Height="22" Padding="0" <StackPanel Orientation="Horizontal" Height="20">
Margin="1,0,0,0" Click="OnPageDeleteClick" ToolTip="페이지 삭제" <ToggleButton Style="{StaticResource MiniChevron}" Width="18" Height="18"
Visibility="{Binding CanDelete, Converter={StaticResource BoolToVisibility}}" IsChecked="{Binding IsExpanded}" ToolTip="페이지 접기/펼치기"
Content="{Binding Source=trash-2, Converter={StaticResource IconName}, ConverterParameter=13|B.Muted}"/> Content="{Binding ChevronSpec, Converter={StaticResource IconSpec}}"/>
</StackPanel> <ContentControl Margin="0,0,5,0" VerticalAlignment="Center" IsTabStop="False"
<StackPanel> Content="{Binding Source=file-text, Converter={StaticResource IconName}, ConverterParameter=11|B.Muted}"/>
<StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Title}" FontSize="11" VerticalAlignment="Center"
<ToggleButton Style="{StaticResource MiniChevron}" Width="18" Height="20" Foreground="{DynamicResource B.Muted}"/>
IsChecked="{Binding IsExpanded}" ToolTip="페이지 접기/펼치기"
Content="{Binding ChevronSpec, Converter={StaticResource IconSpec}}"/>
<ContentControl Margin="0,0,6,0" VerticalAlignment="Center" IsTabStop="False"
Content="{Binding PageIconSpec, Converter={StaticResource IconSpec}}"/>
<TextBlock Text="{Binding Title}" FontWeight="SemiBold" VerticalAlignment="Center"/>
<TextBlock Text="{Binding CountText}" FontSize="11" Margin="6,0,0,0"
VerticalAlignment="Center" Foreground="{DynamicResource B.Muted}"/>
</StackPanel>
<TextBlock Text="(비어 있음)" FontSize="11" Margin="20,1,0,0"
Foreground="{DynamicResource B.Muted}"
Visibility="{Binding IsEmpty, Converter={StaticResource BoolToVisibility}}"/>
</StackPanel> </StackPanel>
</DockPanel> </DockPanel>
</DataTemplate> </DataTemplate>
@@ -310,5 +360,6 @@
IsHitTestVisible="False" IsHitTestVisible="False"
Visibility="{Binding CurrentDesigner.IsLayerListEmpty, Converter={StaticResource BoolToVisibility}}"/> Visibility="{Binding CurrentDesigner.IsLayerListEmpty, Converter={StaticResource BoolToVisibility}}"/>
</Grid> </Grid>
</DockPanel> </DockPanel>
</Grid>
</UserControl> </UserControl>
@@ -69,9 +69,6 @@ public partial class LayerPanelView : System.Windows.Controls.UserControl
case LayerPageViewModel page: case LayerPageViewModel page:
designer.ActivatePage(page.Page); designer.ActivatePage(page.Page);
break; break;
case LayerAddPageViewModel:
designer.AddPage();
break;
} }
} }
@@ -116,6 +113,28 @@ public partial class LayerPanelView : System.Windows.Controls.UserControl
e.Handled = handled; e.Handled = handled;
} }
/// <summary>페이지 구획의 — 트리 안에 있던 '페이지 추가' 행을 대신한다</summary>
private void OnPageAddClick(object sender, RoutedEventArgs e) => Designer?.AddPage();
/// <summary>
/// 페이지 행 누름 — 그 페이지를 활성화한다(캔버스가 거기로 스크롤하고 인스펙터가 용지 속성을 연다).
///
/// ListBox 의 선택으로 처리하지 않는 이유: 복제·삭제 버튼을 눌러도 행 선택이 먼저 일어나
/// 엉뚱한 페이지가 활성화된다. 눌린 지점이 버튼 위면 넘긴다.
/// </summary>
private void OnPageRowPressed(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (e.OriginalSource is DependencyObject source
&& FindAncestor<System.Windows.Controls.Primitives.ButtonBase>(source) is not null)
{
return;
}
if (RowDataOf(e.OriginalSource) is LayerPageViewModel row)
{
Designer?.ActivatePage(row.Page);
}
}
/// <summary>페이지 헤더의 복제 버튼</summary> /// <summary>페이지 헤더의 복제 버튼</summary>
private void OnPageDuplicateClick(object sender, RoutedEventArgs e) private void OnPageDuplicateClick(object sender, RoutedEventArgs e)
{ {