사용성 2순위 — 있는 명령에 닿는 길을 낸다

1순위가 '틀린 것 고치기'였다면 여기는 <b>이미 있는 명령에 닿는 길</b>이다.
새 패널·새 화면 없이 커맨드·단축키·메뉴 층에서 끝난다.

## 선택 확장 4종 — 정렬 8종의 실효 사용률을 통째로 끌어올린다

정렬 6종·간격 4종·같은 크기가 다 있는데, 그 명령들이 전제하는 '올바른 선택'을
만들 수단이 없었다. 표 서식에서 가장 흔한 편집이 "이 행의 입력칸 8개를 조금 높이고
아래로 붙이자"인데, 그 8개를 고르는 길이 <b>Ctrl+클릭 8번뿐</b>이다.
마퀴는 교차 판정이라 행 띠를 스치는 것이 전부 딸려 온다 —
실측 787건에서 행 무리의 94.2%에 침입자가 있고 그 수가 중앙값 7개·p90 41개다.
즉 지금까지는 "드래그 → 딸려온 것 빼기"가 매번 앞에 붙었다.

주 선택 기준으로 같은 행/열/종류/크기인 <b>형제</b>를 선택에 더한다(Alt+1~4, 우클릭 메뉴 맨 위).
범위를 형제로 한 것은 정렬 명령이 쓰는 범위와 같아야 결과를 예측할 수 있어서다.

2px 오차를 허용한다. 정확히 같은 값만 묶으면 눈으로 맞춘 서식에서 <b>일부만</b> 잡히고,
8개 중 5개를 정렬해 놓고 나머지가 남은 것을 못 알아채는 것이 가장 나쁜 결과다.
격자 크기에 묶지 않았다 — 격자를 바꿨다고 선택 규칙이 달라지면 예측할 수 없다.
잠긴 것은 뺀다. 편집하려고 넓히는 선택인데 안 움직이는 것이 섞이면
정렬을 눌렀을 때 "왜 이건 그대로지"가 된다.

## 줌 단축키 — 커맨드는 처음부터 있었고 키만 없었다

8pt 글자를 확인하려 200% 로 올렸다가 전체 배치를 보려면, 캔버스에서 손을 떼고
<b>화면 맨 아래 가운데</b> 플로팅 바의 배율 버튼을 눌러 팝업을 열고 '화면 맞춤'을 골라야 했다.
확대·축소조차 Ctrl+휠뿐이라 트랙패드에서는 배율을 바꿀 키보드 수단이 아예 없었다.

Ctrl+0(100%) · Ctrl+1(화면 맞춤) · Ctrl+± 를 <b>창</b> InputBindings 에 건다.
캔버스 키 처리기에 두면 안 된다 — 그쪽은 포커스가 TextBox/ComboBox 면 통째로 비켜서므로
인스펙터 값을 고친 직후에 줌 키가 죽는다.

## Ctrl+방향키로 크기 — 위치에만 있던 미세 조정의 짝

입력칸 폭을 3px 늘리려면 오른쪽 인스펙터 W 칸으로 가서 숫자를 읽고 3 을 더해 입력했다 —
캔버스와 300px 떨어진 패널 사이를 마우스로 왕복한다. 마우스 핸들은 판정 반경이 화면 6px 이라
작은 컨트롤에서 3px 를 정확히 집기 어렵고, 스냅이 붙으면 원하는 값에 못 선다.

넛지와 같은 400ms 코얼레스를 쓴다. 선은 LineGeometry 를 태워 두께 축이 늘어나지 않게 막는다 —
허용하면 디자이너에만 두꺼운 선이 보이고 EMR 은 원래 두께로 그린다.

## 잠금·숨김을 캔버스에서, 그리고 잠긴 것을 보이게

배경 괘선 40개를 잠가 두고 그 위에서 라벨만 만지고 싶을 때, 지금은 좌측 패널을 레이어 탭으로
바꾸고 트리에서 40행을 찾아 13px 자물쇠를 40번 눌러야 했다. 마퀴로 40개를 잡아 놔도
한 번에 잠글 수단이 없었다. Ctrl+L / Ctrl+Shift+H 와 우클릭 메뉴를 붙인다.

<b>표시가 없던 것이 더 문제였다.</b> 숨김은 0.25 투명이라 티가 나는데 잠금은 아무 표시가 없어,
클릭도 드래그도 안 되는 이유가 화면에 없었다 — "왜 이것만 안 잡히지"만 남는다.
점선 테두리를 <b>오버레이 층</b>에 그린다. 종이 층(PageView)에 넣으면
db-render 게이트가 그것을 찍어 md5 가 깨진다 — 레거시와의 픽셀 동일성을 잠그는 게이트다.

메뉴 이름은 '선택 잠그기'로 한정했다. <b>해제는 캔버스에서 못 한다</b> —
히트 판정이 잠긴 것을 후보에서 빼므로 우클릭조차 닿지 않는다. 해제 경로는 레이어 패널이다.

## Ctrl+\ — 유일한 레이아웃 변경

좌 248 + 스플리터 10 + 우 300 = 558px 가 <b>항상</b> 점유된다. MinWidth 180/250 때문에
스플리터를 끝까지 밀어도 사라지지 않고, 보기 메뉴에도 항목이 없었다.
가로 용지를 120% 로만 올려도 캔버스에 가로 스크롤이 생기는데 '종이만 보기' 상태가 없다.

좌우를 <b>함께</b> 접는다. 개별 토글 두 개를 두지 않았다 — 목적이 하나라 키도 하나여야 손이 기억한다.
MinWidth 도 함께 0 으로 내려야 실제로 사라진다(Width=0 만으로는 최소폭이 이긴다).
펼친 폭은 따로 기억한다 — 접힌 0 을 사용자가 정한 폭으로 착각하면 다음에 못 되돌린다.
F4(인스펙터 진입)를 누르면 접힌 인스펙터를 먼저 편다.

레이아웃은 이것 말고 바꾸지 않았다. 좌 248 / 우 300 은 이미 측정된 값이고
(레이어 행 최소치, 인스펙터 X|Y 한 줄 최소치) 라벨 1개 선택 시 자주 쓰는 4종이
0 스크롤·0 클릭이다. 컨트롤 1,000개짜리 서식에서 트리와 속성을 동시에 봐야 하는 작업이라
떠 있는 패널로 바꾸면 겹침·재배치 비용만 생긴다.

## 판정

- 단위 시험 388
- edit-smoke 376건 전건 통과 (선택 확장 7건 · 크기 조절 2건 · 잠금 5건 추가)
- --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 불변 (잠금 표시가 종이에 안 나온다는 증거)
- <b>--cleartype 은 이번에 판정 불가다</b> — 게이트가 스스로 "캡처 실패, 색 픽셀 0" 이라고 보고한다.
  이전 커밋에서 stash 로 되돌려 돌려도 같은 결과라 이 변경 탓이 아니다(환경 문제).
  화면 캡처가 되는 상태에서 다시 확인할 것.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Msystech
2026-08-20 17:28:43 +09:00
co-authored by Claude Fable 5
parent fccd441193
commit 020595f8d4
8 changed files with 450 additions and 0 deletions
@@ -26,6 +26,20 @@
-->
<UserControl.ContextMenu>
<ContextMenu>
<!--
선택 확장이 <b>맨 위</b>다. 아래 정렬·간격 명령이 전제하는 '올바른 선택'을
만드는 수단이고, 그것 없이는 매번 "마퀴로 끌고 딸려온 것 빼기"가 앞에 붙는다.
실측(787건): 행 무리의 94.2%에 침입자가 있고 그 수가 중앙값 7개다.
-->
<MenuItem Header="같은 행 선택" InputGestureText="Alt+1"
Command="{Binding SelectSameCommand}" CommandParameter="Row"/>
<MenuItem Header="같은 열 선택" InputGestureText="Alt+2"
Command="{Binding SelectSameCommand}" CommandParameter="Column"/>
<MenuItem Header="같은 종류 선택" InputGestureText="Alt+3"
Command="{Binding SelectSameCommand}" CommandParameter="Type"/>
<MenuItem Header="같은 크기 선택" InputGestureText="Alt+4"
Command="{Binding SelectSameCommand}" CommandParameter="Size"/>
<Separator/>
<MenuItem Header="왼쪽 맞춤" InputGestureText="Alt+A"
Command="{Binding AlignCommand}" CommandParameter="Left"/>
<MenuItem Header="오른쪽 맞춤" InputGestureText="Alt+D"
@@ -61,6 +75,13 @@
<MenuItem Header="맨 앞으로" Command="{Binding BringToFrontCommand}"/>
<MenuItem Header="맨 뒤로" Command="{Binding SendToBackCommand}"/>
<Separator/>
<!--
'잠그기'로 이름을 한정한다 — 해제는 여기서 못 한다. 히트 판정이 잠긴 것을
후보에서 빼므로 잠긴 컨트롤에는 우클릭조차 닿지 않는다. 해제는 레이어 패널에서.
-->
<MenuItem Header="선택 잠그기" InputGestureText="Ctrl+L" Command="{Binding LockCommand}"/>
<MenuItem Header="선택 숨기기" InputGestureText="Ctrl+Shift+H" Command="{Binding HideCommand}"/>
<Separator/>
<MenuItem Header="복제" InputGestureText="Ctrl+D" Command="{Binding DuplicateCommand}"/>
<MenuItem Header="삭제" InputGestureText="Del" Command="{Binding DeleteCommand}"/>
</ContextMenu>
+19
View File
@@ -29,6 +29,25 @@
<KeyBinding Key="S" Modifiers="Control" Command="{Binding SaveFileCommand}"/>
<KeyBinding Key="P" Modifiers="Control" Command="{Binding PrintCommand}"/>
<KeyBinding Key="W" Modifiers="Control" Command="{Binding CloseDocumentCommand}"/>
<!--
줌 — 커맨드 넷은 처음부터 있었고 <b>키만 없었다</b>. 8pt 글자를 확인하려 200% 로 올렸다가
전체 배치를 보려면 캔버스에서 손을 떼고 화면 맨 아래 가운데 플로팅 바의 배율 버튼을 눌러
팝업을 열고 '화면 맞춤'을 골라야 했다(2단계 + 마우스 왕복). 확대·축소조차 Ctrl+휠뿐이라
트랙패드에서는 배율을 바꿀 키보드 수단이 아예 없었다.
반드시 창 InputBindings 여야 한다 — 캔버스 키 처리기는 포커스가 TextBox/ComboBox 면
통째로 비켜서므로, 인스펙터 값을 고친 직후에는 줌 키가 죽는다.
-->
<KeyBinding Key="D0" Modifiers="Control" Command="{Binding ZoomResetCommand}"/>
<KeyBinding Key="NumPad0" Modifiers="Control" Command="{Binding ZoomResetCommand}"/>
<KeyBinding Key="D1" Modifiers="Control" Command="{Binding ZoomFitCommand}"/>
<KeyBinding Key="NumPad1" Modifiers="Control" Command="{Binding ZoomFitCommand}"/>
<KeyBinding Key="OemPlus" Modifiers="Control" Command="{Binding ZoomInCommand}"/>
<KeyBinding Key="Add" Modifiers="Control" Command="{Binding ZoomInCommand}"/>
<KeyBinding Key="OemMinus" Modifiers="Control" Command="{Binding ZoomOutCommand}"/>
<KeyBinding Key="Subtract" Modifiers="Control" Command="{Binding ZoomOutCommand}"/>
<!-- 좌우 패널 접기(Ctrl+\)는 코드비하인드에서 다룬다 — 열 너비를 직접 만지는 일이라
뷰모델이 알 필요가 없다(F4 와 같은 규범) -->
<!-- F4 로 인스펙터 첫 칸으로 들어간다. 전에는 인스펙터로 포커스를 보내는 키가 저장소에
하나도 없어서, 캔버스에서 값을 고치려면 Tab 으로 좌측 패널·문서 탭·플로팅 바를 다 지나야 했다. -->
<!-- F4 는 코드비하인드에서 다룬다 — 커맨드로 하면 뷰모델이 뷰의 포커스를 알아야 한다 -->
@@ -232,10 +232,24 @@ public partial class MainView : Window
{
if (e.Key == System.Windows.Input.Key.F4)
{
// 인스펙터가 접혀 있으면 먼저 편다 — 안 그러면 보이지도 않는 칸으로 포커스가 들어간다
if (panelsCollapsed)
{
TogglePanels();
}
Inspector.FocusFirstRow();
e.Handled = true;
return;
}
// Ctrl+\ — 좌우 패널을 함께 접었다 편다('종이만 보기').
// 개별 토글 두 개를 두지 않는다: 목적이 하나라 키도 하나여야 손이 기억한다.
if (e.Key is System.Windows.Input.Key.OemBackslash or System.Windows.Input.Key.Oem5
&& System.Windows.Input.Keyboard.Modifiers == System.Windows.Input.ModifierKeys.Control)
{
TogglePanels();
e.Handled = true;
return;
}
// Ctrl+O — 서식 목록으로. 전에는 OS 파일 대화상자로 갔다.
// 손이 먼저 가는 키인데 디스크로 가고, 운영 서식 1,271건은 전부 DB 에 있다.
if (e.Key == System.Windows.Input.Key.O
@@ -246,6 +260,47 @@ public partial class MainView : Window
}
}
/// <summary>지금 좌우 패널이 접혀 있는가 — 접힌 폭을 '사용자가 정한 폭'으로 오해하지 않기 위한 상태</summary>
private bool panelsCollapsed;
/// <summary>펼쳤을 때의 폭 — 접는 순간 기억해 두었다가 그대로 되돌린다</summary>
private GridLength expandedLeft;
private GridLength expandedRight;
private double expandedLeftMin;
private double expandedRightMin;
/// <summary>
/// 좌우 패널을 함께 접었다 편다 — <b>종이만 보기</b>.
///
/// 좌 248 + 스플리터 5 + 스플리터 5 + 우 300 = 558px 가 <b>항상</b> 점유된다.
/// MinWidth 180/250 때문에 스플리터를 끝까지 밀어도 사라지지 않고, 보기 메뉴에도 항목이 없었다.
/// 가로 용지를 120% 로만 올려도 캔버스에 가로 스크롤이 생기는데 '종이만 보기' 상태가 없었다.
///
/// <b>MinWidth 도 함께 0 으로 내려야</b> 실제로 사라진다 — Width=0 만으로는 최소폭이 이긴다.
/// 되돌릴 때 쓰려고 펼친 폭을 따로 기억한다(접힌 0 을 사용자가 정한 폭으로 착각하면 안 된다).
/// </summary>
private void TogglePanels()
{
if (panelsCollapsed)
{
LeftColumn.MinWidth = expandedLeftMin;
RightColumn.MinWidth = expandedRightMin;
LeftColumn.Width = expandedLeft;
RightColumn.Width = expandedRight;
panelsCollapsed = false;
return;
}
expandedLeft = LeftColumn.Width;
expandedRight = RightColumn.Width;
expandedLeftMin = LeftColumn.MinWidth;
expandedRightMin = RightColumn.MinWidth;
LeftColumn.MinWidth = 0;
RightColumn.MinWidth = 0;
LeftColumn.Width = new GridLength(0);
RightColumn.Width = new GridLength(0);
panelsCollapsed = true;
}
/// <summary>파일 메뉴·빈 화면 버튼에서 서식 목록으로 보낸다</summary>
private void OnOpenFromListClick(object sender, RoutedEventArgs e) => FocusSheetList();
@@ -39,6 +39,33 @@
</ItemsControl.ItemTemplate>
</ItemsControl>
<!--
잠긴 컨트롤 표시 — 편집 크롬이므로 종이(PageView)에는 없다.
그쪽에 넣으면 db-render md5 가 깨진다(레거시와의 픽셀 동일성을 잠그는 게이트다).
숨김은 0.25 투명으로 티가 나는데 잠금은 아무 표시가 없어,
안 잡히는 이유를 화면에서 알 방법이 없었다.
-->
<ItemsControl ItemsSource="{Binding LockMarks}">
<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>
<Rectangle Width="{Binding W}" Height="{Binding H}"
Stroke="#8C8C8C" StrokeThickness="1" StrokeDashArray="2 2"
Fill="Transparent" Opacity="0.7"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- 마퀴(러버밴드) -->
<Rectangle Canvas.Left="{Binding MarX}" Canvas.Top="{Binding MarY}"
Width="{Binding MarW}" Height="{Binding MarH}"