중첩된 UI는 컨테이너 내부에 묶인 중첩된 실행 가능한 컨트롤을 노출하는 UI(사용자 인터페이스)로, 독립적인 포커스를 취할 수도 있습니다.
중첩된 UI를 사용하여 사용자에게 중요한 작업 수행을 가속화하는 데 도움이 되는 추가 옵션을 표시할 수 있습니다. 그러나 노출하는 작업이 많을수록 UI가 더 복잡해집니다. 이 UI 패턴을 사용하도록 선택할 때는 주의해야 합니다. 이 문서에서는 특정 UI에 가장 적합한 작업 과정을 결정하는 데 도움이 되는 지침을 제공합니다.
중요 API: ListView 클래스, GridView 클래스
이 문서에서는 ListView 및 GridView 항목에서 중첩된 UI 만들기에 대해 설명합니다. 이 섹션에서는 다른 중첩된 UI 사례에 대해 이야기하지 않지만 이러한 개념은 전송할 수 있습니다. 시작하기 전에 목록 및 목록 보기 및 그리드 보기 문서에 있는 UI에서 ListView 또는 GridView 컨트롤을 사용하기 위한 일반적인 지침을 숙지해야 합니다.
이 문서에서는 여기에 정의된 대로 용어 목록, 목록 항목 및 중첩된 UI 를 사용합니다.
- 목록은 목록 보기 또는 그리드 보기에 포함된 항목의 컬렉션을 나타냅니다.
- 목록 항목 은 사용자가 목록에서 작업을 수행할 수 있는 개별 항목을 나타냅니다.
- 중첩된 UI 는 사용자가 목록 항목 자체에 대한 작업을 수행하는 것과 별도로 작업을 수행할 수 있는 목록 항목 내의 UI 요소를 나타냅니다.
참고 ListView와 GridView는 모두 ListViewBase 클래스에서 파생되므로 동일한 기능을 사용하지만 데이터를 다르게 표시합니다. 이 문서에서는 목록에 대해 이야기할 때 ListView 및 GridView 컨트롤 모두에 정보가 적용됩니다.
기본 및 보조 작업
목록을 사용하여 UI를 만들 때 사용자가 해당 목록 항목에서 수행할 수 있는 작업을 고려합니다.
- 사용자가 항목을 클릭하여 작업을 수행할 수 있나요?
- 일반적으로 목록 항목을 클릭하면 작업이 시작되지만, 반드시 작업을 시작해야 하는 것은 아닙니다.
- 사용자가 수행할 수 있는 작업이 두 개 이상 있나요?
- 예를 들어 목록에서 전자 메일을 탭하면 해당 전자 메일이 열립니다. 그러나 사용자가 전자 메일을 먼저 열지 않고 수행하려는 다른 작업(예: 전자 메일 삭제)이 있을 수 있습니다. 사용자가 목록에서 직접 이 작업에 액세스하는 것이 도움이 됩니다.
- 사용자에게 작업을 노출하려면 어떻게 해야 하나요?
- 모든 입력 형식을 고려합니다. 일부 형태의 중첩된 UI는 한 가지 입력 메서드에서 잘 작동하지만 다른 메서드에서는 작동하지 않을 수 있습니다.
기본 작업은 사용자가 목록 항목을 누를 때 발생할 것으로 예상하는 작업입니다.
보조 작업은 일반적으로 목록 항목과 연결된 가속기입니다. 이러한 가속기는 목록 관리 또는 목록 항목과 관련된 작업에 사용할 수 있습니다.
보조 작업에 대한 옵션
목록 UI를 만들 때 먼저 Windows에서 지원하는 모든 입력 메서드를 고려해야 합니다. 다양한 종류의 입력에 대한 자세한 내용은 입력 입문자를 참조하세요.
앱이 Windows에서 지원하는 모든 입력을 지원하는지 확인한 후에는 앱의 보조 작업이 기본 목록에 액셀러레이터로 노출할 만큼 중요한지 결정해야 합니다. 노출하는 작업이 많을수록 UI가 더 복잡해집니다. 주 목록 UI에서 보조 작업을 노출해야 합니까, 아니면 다른 곳에 배치할 수 있나요?
항상 입력으로 해당 작업에 액세스할 수 있어야 하는 경우 기본 목록 UI에 추가 작업을 노출하는 것이 좋습니다.
주 목록 UI에 보조 작업을 배치할 필요가 없다고 결정한 경우 사용자에게 노출할 수 있는 몇 가지 다른 방법이 있습니다. 다음은 보조 작업을 배치할 위치에 대해 고려할 수 있는 몇 가지 옵션입니다.
세부 정보 페이지에 보조 작업 배치
목록 항목이 눌렀을 때 탐색하는 페이지에 보조 작업을 배치합니다. 목록/세부 정보 패턴을 사용하는 경우 세부 정보 페이지는 종종 보조 작업을 배치하기에 좋은 위치입니다.
자세한 내용은 목록/세부 정보 패턴을 참조하세요.
상황에 맞는 메뉴에 보조 작업 배치
사용자가 마우스 오른쪽 단추를 클릭하거나 길게 눌러 액세스할 수 있는 상황에 맞는 메뉴에 보조 작업을 배치합니다. 이렇게 하면 사용자가 세부 정보 페이지를 로드하지 않고도 전자 메일 삭제와 같은 작업을 수행할 수 있습니다. 상황에 맞는 메뉴는 기본 UI가 아닌 액셀러레이터가 되므로 세부 정보 페이지에서 이러한 옵션을 사용할 수 있도록 하는 것이 좋습니다.
입력이 게임 패드 또는 리모컨에서 제공되는 경우 보조 작업을 노출하려면 상황에 맞는 메뉴를 사용하는 것이 좋습니다.
자세한 내용은 상황에 맞는 메뉴 및 플라이아웃을 참조하세요.
포인터 입력을 최적화하기 위해 가리키기 UI에 보조 작업 배치
앱이 마우스 및 펜과 같은 포인터 입력과 함께 자주 사용되며 해당 입력에서만 보조 작업을 쉽게 사용할 수 있도록 하려는 경우 마우스로 가리키면 보조 작업만 표시할 수 있습니다. 이 가속기는 포인터 입력을 사용하는 경우에만 표시되므로 다른 입력 형식도 지원하기 위해 다른 옵션을 사용해야 합니다.
자세한 내용은 마우스 조작을 참조하세요.
기본 및 보조 작업에 대한 UI 배치
보조 작업이 주 목록 UI에 노출되도록 결정하는 경우 다음 지침을 사용하는 것이 좋습니다.
기본 및 보조 작업을 사용하여 목록 항목을 만들 때 기본 작업을 왼쪽에, 보조 작업을 오른쪽에 배치합니다. 왼쪽에서 오른쪽 읽기 문화권에서 사용자는 목록 항목의 왼쪽에 있는 작업을 기본 작업으로 연결합니다.
이 예제에서는 항목이 더 가로로 흐르는 목록 UI에 대해 설명합니다(높이보다 넓음). 그러나 모양이 정사각형에 가깝거나 높이가 너비보다 더 큰 목록 항목이 있을 수 있습니다. 일반적으로 그리드에서 사용되는 항목입니다. 이러한 항목의 경우 목록이 세로로 스크롤되지 않는 경우 보조 작업을 오른쪽이 아닌 목록 항목의 맨 아래에 배치할 수 있습니다.
모든 입력을 고려하십시오.
중첩된 UI를 사용하도록 결정할 때 모든 입력 형식의 사용자 환경도 평가합니다. 앞에서 설명한 것처럼 중첩된 UI는 일부 입력 형식에 적합합니다. 그러나 그것은 다른 경우에는 항상 잘 작동하지 않습니다. 특히 키보드, 컨트롤러 및 원격 입력은 중첩된 UI 요소에 액세스하는 데 어려움을 겪을 수 있습니다. Windows가 모든 입력 형식에서 작동하는지 확인하려면 아래 지침을 따라야 합니다.
중첩된 UI 처리
목록 항목에 둘 이상의 작업이 중첩되어 있는 경우 키보드, 게임 패드, 원격 제어 또는 기타 비 포인터 입력을 사용하여 탐색을 처리하는 것이 좋습니다.
목록 항목이 작업을 수행하는 중첩된 UI
중첩 요소가 있는 목록 UI가 호출, 선택(단일 또는 다중) 또는 끌어서 놓기 작업과 같은 작업을 지원하는 경우 이러한 화살표 기술을 사용하여 중첩된 UI 요소를 탐색하는 것이 좋습니다.
게임 패드
게임 패드에서 입력하는 경우 다음 사용자 환경을 제공합니다.
- A에서 오른쪽 방향 키는 B에 포커스를 놓습니다.
- B에서 오른쪽 방향 키는 C에 포커스를 배치합니다.
- C에서 오른쪽 방향 키는 op이 아니거나 목록 오른쪽에 포커스가 있는 UI 요소가 있는 경우 포커스를 배치합니다.
- C에서 왼쪽 방향 키는 B에 포커스를 놓습니다.
- B에서 왼쪽 방향 키는 A에 포커스를 놓습니다.
- A에서 왼쪽 방향 키는 op이 아니거나 목록 오른쪽에 포커스가 있는 UI 요소가 있는 경우 포커스를 배치합니다.
- A, B 또는 C에서 아래쪽 방향 키는 D에 포커스를 켭니다.
- UI 요소에서 목록 항목의 왼쪽에 있는 오른쪽 방향 키는 포커스를 A에 배치합니다.
- 목록 항목의 오른쪽에 있는 UI 요소에서 왼쪽 방향 키는 포커스를 A에 배치합니다.
Keyboard
키보드에서 입력하는 경우 사용자가 가져오는 환경은 다음과 같습니다.
- A에서 탭 키는 B에 포커스를 놓습니다.
- B에서 탭 키는 C에 포커스를 놓습니다.
- C에서 탭 키는 포커스가 있는 다음 UI 요소에 포커스를 탭 순서로 배치합니다.
- C에서 shift+tab 키를 누르면 포커스가 B에 배치됩니다.
- B에서 shift+tab 또는 왼쪽 화살표 키는 포커스를 A에 놓습니다.
- A에서 shift+tab 키는 포커스가 있는 다음 UI 요소에 포커스를 역방향 탭 순서로 배치합니다.
- A, B 또는 C에서 아래쪽 화살표 키는 D에 포커스를 켭니다.
- UI 요소에서 목록 항목의 왼쪽에 있는 탭 키는 포커스를 A에 배치합니다.
- 목록 항목의 오른쪽에 있는 UI 요소에서 Shift Tab 키를 누르면 포커스가 C에 배치됩니다.
이 UI를 달성하려면 목록에서 IsItemClickEnabled 를 true 로 설정합니다. SelectionMode 는 모든 값일 수 있습니다.
이를 구현하는 코드는 이 문서의 예제 섹션을 참조하세요.
목록 항목이 작업을 수행하지 않는 중첩된 UI
목록 보기는 가상화 및 최적화된 스크롤 동작을 제공하지만 목록 항목과 연결된 작업이 없기 때문에 사용할 수 있습니다. 이러한 UI는 일반적으로 목록 항목을 사용하여 요소를 그룹화하고 집합으로 스크롤하는지 확인합니다.
이러한 종류의 UI는 사용자가 작업을 수행할 수 있는 중첩된 요소가 많기 때문에 이전 예제보다 훨씬 더 복잡한 경향이 있습니다.
이 UI를 수행하려면 목록에서 다음 속성을 설정합니다.
- SelectionMode 에서 None으로
- IsItemClickEnabled 를 false로 설정합니다.
- IsFocusEngagementEnabled을 true로 설정.
<ListView SelectionMode="None" IsItemClickEnabled="False" >
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="IsFocusEngagementEnabled" Value="True"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
목록 항목이 작업을 수행하지 않는 경우 게임 패드 또는 키보드로 탐색을 처리하는 데 이 지침을 사용하는 것이 좋습니다.
게임 패드
게임 패드에서 입력하는 경우 다음 사용자 환경을 제공합니다.
- 목록 항목에서 아래쪽 방향 키는 다음 목록 항목에 포커스를 놓습니다.
- 목록 항목에서 왼쪽/오른쪽 키는 작동하지 않거나, 목록 오른쪽에 포커스할 수 있는 UI 요소가 있는 경우 포커스를 그곳으로 이동합니다.
- 목록 항목에서 'A' 단추는 중첩된 UI에 포커스를 상하/좌우 우선 순위로 맞춥니다.
- 중첩된 UI 내에 있는 동안 XY 포커스 탐색 모델을 따릅니다. 포커스는 사용자가 'B' 단추를 눌러 포커스를 목록 항목으로 되돌릴 때까지 현재 목록 항목 내에 포함된 중첩된 UI를 탐색할 수 있습니다.
Keyboard
키보드에서 입력하는 경우 사용자가 가져오는 환경은 다음과 같습니다.
- 목록 항목에서 아래쪽 화살표 키를 누르면 포커스가 다음 목록 항목에 배치됩니다.
- 목록 항목에서 왼쪽/오른쪽 키를 누르는 것은 작동하지 않습니다.
- 목록 항목에서 탭 키를 누르면 중첩된 UI 항목 사이에 있는 다음 탭 정지에 포커스가 놓입니다.
- 중첩된 UI 항목 중 하나에서 탭을 누르면 중첩된 UI 항목이 탭 순서대로 트래버스됩니다. 모든 중첩된 UI 항목이 이동되면 ListView 다음에 포커스를 탭 순서대로 다음 컨트롤에 배치합니다.
- Shift+Tab은 탭 동작에서 역방향으로 동작합니다.
예시
이 예제에서는 목록 항목이 작업을 수행하는 중첩된 UI를 구현하는 방법을 보여 줍니다.
<ListView SelectionMode="None" IsItemClickEnabled="True"
ChoosingItemContainer="listview1_ChoosingItemContainer"/>
private void OnListViewItemKeyDown(object sender, KeyRoutedEventArgs e)
{
// Code to handle going in/out of nested UI with gamepad and remote only.
if (e.Handled == true)
{
return;
}
var focusedElementAsListViewItem = FocusManager.GetFocusedElement() as ListViewItem;
if (focusedElementAsListViewItem != null)
{
// Focus is on the ListViewItem.
// Go in with Right arrow.
Control candidate = null;
switch (e.OriginalKey)
{
case Windows.System.VirtualKey.GamepadDPadRight:
case Windows.System.VirtualKey.GamepadLeftThumbstickRight:
var rawPixelsPerViewPixel = DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel;
GeneralTransform generalTransform = focusedElementAsListViewItem.TransformToVisual(null);
Point startPoint = generalTransform.TransformPoint(new Point(0, 0));
Rect hintRect = new Rect(startPoint.X * rawPixelsPerViewPixel, startPoint.Y * rawPixelsPerViewPixel, 1, focusedElementAsListViewItem.ActualHeight * rawPixelsPerViewPixel);
candidate = FocusManager.FindNextFocusableElement(FocusNavigationDirection.Right, hintRect) as Control;
break;
}
if (candidate != null)
{
candidate.Focus(FocusState.Keyboard);
e.Handled = true;
}
}
else
{
// Focus is inside the ListViewItem.
FocusNavigationDirection direction = FocusNavigationDirection.None;
switch (e.OriginalKey)
{
case Windows.System.VirtualKey.GamepadDPadUp:
case Windows.System.VirtualKey.GamepadLeftThumbstickUp:
direction = FocusNavigationDirection.Up;
break;
case Windows.System.VirtualKey.GamepadDPadDown:
case Windows.System.VirtualKey.GamepadLeftThumbstickDown:
direction = FocusNavigationDirection.Down;
break;
case Windows.System.VirtualKey.GamepadDPadLeft:
case Windows.System.VirtualKey.GamepadLeftThumbstickLeft:
direction = FocusNavigationDirection.Left;
break;
case Windows.System.VirtualKey.GamepadDPadRight:
case Windows.System.VirtualKey.GamepadLeftThumbstickRight:
direction = FocusNavigationDirection.Right;
break;
default:
break;
}
if (direction != FocusNavigationDirection.None)
{
Control candidate = FocusManager.FindNextFocusableElement(direction) as Control;
if (candidate != null)
{
ListViewItem listViewItem = sender as ListViewItem;
// If the next focusable candidate to the left is outside of ListViewItem,
// put the focus on ListViewItem.
if (direction == FocusNavigationDirection.Left &&
!listViewItem.IsAncestorOf(candidate))
{
listViewItem.Focus(FocusState.Keyboard);
}
else
{
candidate.Focus(FocusState.Keyboard);
}
}
e.Handled = true;
}
}
}
private void listview1_ChoosingItemContainer(ListViewBase sender, ChoosingItemContainerEventArgs args)
{
if (args.ItemContainer == null)
{
args.ItemContainer = new ListViewItem();
args.ItemContainer.KeyDown += OnListViewItemKeyDown;
}
}
// DependencyObjectExtensions.cs definition.
public static class DependencyObjectExtensions
{
public static bool IsAncestorOf(this DependencyObject parent, DependencyObject child)
{
DependencyObject current = child;
bool isAncestor = false;
while (current != null && !isAncestor)
{
if (current == parent)
{
isAncestor = true;
}
current = VisualTreeHelper.GetParent(current);
}
return isAncestor;
}
}
Windows developer