이 도구의 일은 "컨트롤 하나 골라 값 하나 바꾸고 다음으로"의 반복인데, <b>그 루프가 마우스 없이는 한 바퀴도 돌지 않았다.</b> 선택을 옮길 키가 하나도 없었다 — 캔버스 키맵 18개 중 선택 이동 0, 편집 시작 0. 화살표는 선택이 아니라 컨트롤을 민다. Label 59,997 + TextBox 28,574 의 문구를 고치는 일이 전부 더블클릭에 묶여 있었다. <b>읽는 순서로 돈다 — z-order 가 아니다.</b> 계획서는 z-order 순회를 적었지만, Tab 을 누르는 이유는 "서식을 위에서 아래로 훑으며 문구를 고치는 것"이다. 기록지는 위→아래·왼쪽→오른쪽으로 읽으므로 그 순서가 손에 맞는다. z-order 는 겹침을 정하는 축이라 화면상 순서와 무관하게 튄다. 같은 줄로 볼 만큼 가까우면(8px = 그리드 두 칸) X 로 가른다. EMR 런타임 입력 순서인 ApplyTabOrder 와는 다른 개념이다 — 그건 저장되는 값이다. <b>범위는 형제다.</b> 컨테이너 안으로는 Enter, 밖으로는 Esc. 전체를 한 줄로 이으면 패널 안 라디오 15,026개를 지나야 다음 항목에 닿는다. Esc 가 이제 한 겹만 나간다. 전에는 항상 선택을 통째로 풀어서, 패널 자식을 고른 뒤 Esc 를 누르면 부모로 돌아가는 대신 선택이 사라졌다. 드래그 중 Esc 는 그대로 드래그 취소다. 선택이 옮겨지면 RevealControl 로 화면에 데려온다 — 레이어 목록 화살표 이동에 이것이 없어서 화면 밖 컨트롤을 눈 없이 편집할 수 있었다. PreviewKeyDown 에 붙인다. KeyDown 이면 WPF 포커스 관리자가 Tab 을 먼저 먹어 포커스가 캔버스 밖(좌측 패널·문서 탭·플로팅 바)으로 나간다. 검사는 배선이 아니라 <b>한 바퀴가 도는지</b>를 본다 — 9건: 읽는 순서 · 끝에서 처음으로 되돌아옴 · 역방향 · 컨테이너 안 · 밖 · 나갈 곳 없음 · 편집 시작. 컨테이너는 GroupSelection 이 아니라 문서에 직접 만든다 — 그 경로에는 거절 조건(라디오·z 불연속)이 있어 막히면 순회 대신 그룹 규칙을 재게 된다 (실제로 처음에 그렇게 막혔다). 게이트: 테스트 285/285, --edit-smoke 0실패(신규 9), --dialog-shots 넘침 0(대조군 4/4), --scale-budget 5/5, --maxrect 0실패, --cleartype 11/11, --modal-check 0실패, 빌드 경고 0, --db-smoke 1271건 diff 0, --db-render P062 md5 8d683835f5d81e7bb41c79071d6bf954 동일. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
77 lines
3.2 KiB
C#
77 lines
3.2 KiB
C#
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Controls.Primitives;
|
|
using System.Windows.Input;
|
|
using Microsoft.Xaml.Behaviors;
|
|
using SheetMe.Designer.ViewModels;
|
|
|
|
namespace SheetMe.Designer.Behaviors;
|
|
|
|
/// <summary>
|
|
/// 캔버스 키보드 단축키 — 텍스트 입력 요소에 포커스가 있으면 통과(입력 보호).
|
|
/// 화살표 넛지(Shift=그리드), Del, Ctrl+A/C/V/D/Z/Y, Esc.
|
|
/// </summary>
|
|
public sealed class CanvasKeyboardBehavior : Behavior<UIElement>
|
|
{
|
|
#region Methods
|
|
protected override void OnAttached()
|
|
{
|
|
base.OnAttached();
|
|
AssociatedObject.PreviewKeyDown += OnPreviewKeyDown;
|
|
}
|
|
|
|
protected override void OnDetaching()
|
|
{
|
|
AssociatedObject.PreviewKeyDown -= OnPreviewKeyDown;
|
|
base.OnDetaching();
|
|
}
|
|
|
|
private void OnPreviewKeyDown(object sender, KeyEventArgs e)
|
|
{
|
|
if ((AssociatedObject as FrameworkElement)?.DataContext is not DesignerViewModel designer)
|
|
{
|
|
return;
|
|
}
|
|
// 텍스트 입력 보호 — 포커스가 편집 컨트롤이면 단축키 라우팅 안 함
|
|
if (Keyboard.FocusedElement is TextBoxBase or ComboBox or PasswordBox)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var ctrl = Keyboard.Modifiers.HasFlag(ModifierKeys.Control);
|
|
var shift = Keyboard.Modifiers.HasFlag(ModifierKeys.Shift);
|
|
var step = shift ? designer.Snap.GridSize * 2 : 1;
|
|
var handled = true;
|
|
|
|
switch (e.Key)
|
|
{
|
|
case Key.Left: designer.NudgeSelection(-step, 0); break;
|
|
case Key.Right: designer.NudgeSelection(step, 0); break;
|
|
case Key.Up: designer.NudgeSelection(0, -step); break;
|
|
case Key.Down: designer.NudgeSelection(0, step); break;
|
|
case Key.Delete: designer.DeleteSelection(); break;
|
|
case Key.Escape: designer.Interaction.Cancel(); break;
|
|
|
|
// 루프를 키보드로 닫는 세 키. 여기까지는 "컨트롤 하나 골라 값 하나 바꾸고 다음으로"가
|
|
// 마우스 없이 한 바퀴도 돌지 않았다 — 선택을 옮길 키가 하나도 없었다.
|
|
// PreviewKeyDown 이라 WPF 포커스 관리자보다 먼저 Tab 을 본다(안 그러면 캔버스 밖으로 나간다).
|
|
case Key.Tab: designer.SelectSibling(backward: shift); break;
|
|
case Key.Enter: handled = designer.EnterSelection(); break;
|
|
case Key.F2: handled = designer.EnterSelection(); break;
|
|
case Key.A when ctrl: designer.SelectAllOnActivePage(); break;
|
|
case Key.C when ctrl: designer.CopySelection(); break;
|
|
case Key.X when ctrl: designer.CutSelection(); break;
|
|
case Key.V when ctrl: designer.Paste(); break;
|
|
case Key.D when ctrl: designer.CopySelection(); designer.Paste(); break;
|
|
case Key.Z when ctrl: designer.Undo.Undo(); break;
|
|
case Key.Y when ctrl: designer.Undo.Redo(); break;
|
|
case Key.G when ctrl && shift: designer.UngroupSelection(); break;
|
|
case Key.G when ctrl: designer.GroupSelection(); break;
|
|
default: handled = false; break;
|
|
}
|
|
|
|
e.Handled = handled;
|
|
}
|
|
#endregion
|
|
}
|