입력 개요

WPF(Windows Presentation Foundation) 하위 시스템은 마우스, 키보드, 터치, 스타일러스를 비롯한 다양한 디바이스에서 입력을 가져오는 데 사용하는 강력한 API를 제공합니다. 이 항목에서는 WPF가 제공하는 서비스를 설명하고 입력 시스템의 아키텍처를 살펴봅니다.

입력 API

기본 입력 API 노출은 기본 요소 클래스인 UIElement, ContentElement, FrameworkElementFrameworkContentElement에서 찾을 수 있습니다. 기본 요소에 대한 자세한 내용은 기본 요소 개요를 참조하세요. 이러한 클래스는 키 누르기, 마우스 단추, 마우스 휠, 마우스 이동, 포커스 관리, 마우스 캡처 등과 관련된 입력 이벤트를 위한 기능을 제공합니다. 입력 아키텍처는 모든 입력 이벤트를 서비스로 처리하는 대신 기본 요소에 입력 API를 배치함으로써 UI의 특정 개체가 입력 이벤트를 발생시키도록 하고, 둘 이상의 요소가 입력 이벤트를 처리할 수 있는 이벤트 라우팅 체계를 지원할 수 있습니다. 대부분의 입력 이벤트에는 연결된 이벤트 쌍이 있습니다. 예를 들어 키 누름 이벤트는 KeyDownPreviewKeyDown 이벤트와 연결됩니다. 이벤트마다 이벤트가 대상 요소로 라우트되는 방법이 다릅니다. 미리 보기 이벤트는 요소 트리의 루트 요소에서 대상 요소로 터널링됩니다. 버블링 이벤트는 대상 요소에서 루트 요소로 버블링됩니다. WPF의 이벤트 라우팅에 대한 자세한 내용은 이 개요 항목의 이후 단원 및 라우트된 이벤트 개요에서 자세히 설명합니다.

키보드 및 마우스 클래스

기본 요소 클래스의 입력 API 외에, Keyboard 클래스 및 Mouse 클래스는 키보드 및 마우스 입력을 사용하기 위한 추가 API를 제공합니다.

Keyboard 클래스의 입력 API의 예로는 현재 누른 ModifierKeys를 반환하는 Modifiers 속성과 지정된 키를 누를지 여부를 결정하는 IsKeyDown 메서드가 있습니다.

다음 예제는 GetKeyStates 메서드를 사용하여 Key가 누름 상태인지 확인합니다.

// Uses the Keyboard.GetKeyStates to determine if a key is down.
// A bitwise AND operation is used in the comparison.
// e is an instance of KeyEventArgs.
if ((Keyboard.GetKeyStates(Key.Return) & KeyStates.Down) > 0)
{
    btnNone.Background = Brushes.Red;
}
' Uses the Keyboard.GetKeyStates to determine if a key is down.
' A bitwise AND operation is used in the comparison. 
' e is an instance of KeyEventArgs.
If (Keyboard.GetKeyStates(Key.Return) And KeyStates.Down) > 0 Then
    btnNone.Background = Brushes.Red

Mouse 클래스의 입력 API 예제는 가운데 마우스 단추의 상태를 가져오는 MiddleButton와 현재 마우스 포인터가 있는 요소를 가져오는 DirectlyOver입니다.

다음 예제는 마우스의 LeftButtonPressed 상태인지 여부를 확인합니다.

if (Mouse.LeftButton == MouseButtonState.Pressed)
{
    UpdateSampleResults("Left Button Pressed");
}
If Mouse.LeftButton = MouseButtonState.Pressed Then
    UpdateSampleResults("Left Button Pressed")
End If

MouseKeyboard 클래스는 이 개요 전체에서 좀 더 자세히 설명됩니다.

스타일러스 입력

WPF는 Stylus에 대한 통합 지원을 제공합니다. Stylus는 테블릿 PC에서 자주 사용되는 펜 입력입니다. WPF 애플리케이션은 마우스 API를 사용하여 스타일러스를 마우스로 처리할 수 있지만 WPF는 키보드 및 마우스와 비슷한 모델을 사용하는 스타일러스 디바이스 추상화도 노출합니다. 스타일러스와 관련된 모든 API에는 “Stylus”라는 단어가 포함됩니다.

스타일러스는 마우스처럼 동작할 수 있으므로 마우스 입력만 지원하는 애플리케이션도 약간의 스타일러스 지원을 자동으로 받을 수 있습니다. 이러한 방식으로 스타일러스를 사용하는 경우 애플리케이션은 알맞은 스타일러스 이벤트를 처리한 다음 해당 마우스 이벤트를 처리할 수 있게 됩니다. 뿐만 아니라 스타일러스 디바이스 추상화를 통해 잉크 입력과 같은 높은 수준의 서비스도 사용할 수 있습니다. 잉크 입력에 대한 자세한 내용은 잉크 시작을 참조하세요.

이벤트 라우팅

FrameworkElement는 다른 요소를 해당 콘텐츠 모델에 자식 요소로 포함하여 요소 트리를 형성할 수 있습니다. WPF에서 부모 요소는 이벤트를 처리하여 해당 자식 요소 또는 다른 하위 요소를 대상으로 하는 입력에 참가할 수 있습니다. 이는 더 작은 컨트롤에서 컨트롤을 빌드하는 "컨트롤 컴퍼지션" 또는 "합성" 프로세스에 특히 유용합니다. 요소 트리 및 요소 트리가 이벤트 경로와 어떻게 관련되는지에 대한 자세한 내용은 WPF의 트리를 참조하세요.

이벤트 라우팅은 이벤트를 여러 요소로 전달함으로써 경로 상의 특정 개체나 요소가 다른 요소에서 발생시킨 이벤트에 대해 이벤트 처리를 통해 의미 있는 응답을 제공할 수 있도록 하는 프로세스입니다. 라우트된 이벤트는 직접, 버블링, 터널링이라는 세 가지 라우팅 메커니즘 중 하나를 사용합니다. 직접 라우팅에서는 소스 요소만 이벤트에 대한 알림을 받으며 이벤트가 다른 요소로 라우트되지 않습니다. 하지만 직접 라우트된 이벤트도 표준 CLR 이벤트와는 달리 라우트된 이벤트에만 있는 몇몇 추가 기능을 제공합니다. 버블링은 먼저 이벤트에 대해 이벤트를 발생시킨 요소에 알린 다음 부모 요소 등에 알리는 순서로 요소 트리의 위쪽으로 작동합니다. 터널링은 요소 트리의 루트에서 시작하여 아래로 이동한 다음 원래 소스 요소에서 끝납니다. 라우트된 이벤트에 대한 자세한 내용은 라우트된 이벤트 개요를 참조하세요.

WPF 입력 이벤트는 일반적으로 터널링 이벤트와 버블링 이벤트의 쌍으로 구성되어 제공됩니다. 터널링 이벤트는 “Preview” 접두사가 있다는 점에서 버블링 이벤트와 다릅니다. 예를 들어, PreviewMouseMove는 마우스 이동 이벤트의 터널링 버전이고, MouseMove는 이 이벤트의 버블링 버전입니다. 이 이벤트 쌍은 요소 수준에서 구현되는 규칙이며 WPF 이벤트 시스템의 본질적인 기능은 아닙니다. 자세한 내용은 라우트된 이벤트 개요의 WPF 입력 이벤트 섹션을 참조하세요.

입력 이벤트 처리

요소에서 입력을 받으려면 특정 이벤트에 이벤트 처리기를 연결해야 합니다. XAML에서는 이 작업이 매우 간단합니다. 이 이벤트를 수신할 요소의 특성으로 이벤트 이름을 참조합니다. 그런 다음 대리자에 따라 정의하는 이벤트 처리기의 이름으로 특성 값을 설정하기만 하면 됩니다. 이벤트 처리기는 C#과 같은 코드로 작성해야 하며 코드 숨김 파일에 포함할 수 있습니다.

키보드 이벤트는 키보드 포커스가 요소에 있는 동안 발생하는 키 동작을 운영 체제에서 보고할 때 발생합니다. 마우스 및 스타일러스 이벤트는 각각 요소를 기준으로 포인터 위치의 변경을 보고하는 이벤트와 디바이스 단추의 상태 변경을 보고하는 이벤트의 두 범주로 나뉩니다.

키보드 입력 이벤트 예제

다음 예제에서는 왼쪽 화살표 키 누르기를 수신합니다. Button이 있는 StackPanel가 만들어집니다. 왼쪽 화살표 키 누르기를 수신 대기하는 이벤트 처리기가 Button 인스턴스에 연결됩니다.

이 예제의 첫 번째 섹션은 StackPanelButton을 만들고 KeyDown에 대한 이벤트 처리기를 연결합니다.

<StackPanel>
  <Button Background="AliceBlue"
          KeyDown="OnButtonKeyDown"
          Content="Button1"/>
</StackPanel>
// Create the UI elements.
StackPanel keyboardStackPanel = new StackPanel();
Button keyboardButton1 = new Button();

// Set properties on Buttons.
keyboardButton1.Background = Brushes.AliceBlue;
keyboardButton1.Content = "Button 1";

// Attach Buttons to StackPanel.
keyboardStackPanel.Children.Add(keyboardButton1);

// Attach event handler.
keyboardButton1.KeyDown += new KeyEventHandler(OnButtonKeyDown);
' Create the UI elements.
Dim keyboardStackPanel As New StackPanel()
Dim keyboardButton1 As New Button()

' Set properties on Buttons.
keyboardButton1.Background = Brushes.AliceBlue
keyboardButton1.Content = "Button 1"

' Attach Buttons to StackPanel.
keyboardStackPanel.Children.Add(keyboardButton1)

' Attach event handler.
AddHandler keyboardButton1.KeyDown, AddressOf OnButtonKeyDown

코드로 작성된 두 번째 섹션은 이벤트 처리기를 정의합니다. 왼쪽 화살표 키를 눌렀으며 Button에 키보드 포커스가 있는 경우 핸들러가 실행되고 ButtonBackground 색상이 변경됩니다. 키를 눌렀지만 왼쪽 화살표 키가 아닌 경우 ButtonBackground 색상이 시작 색상으로 다시 변경됩니다.

private void OnButtonKeyDown(object sender, KeyEventArgs e)
{
    Button source = e.Source as Button;
    if (source != null)
    {
        if (e.Key == Key.Left)
        {
            source.Background = Brushes.LemonChiffon;
        }
        else
        {
            source.Background = Brushes.AliceBlue;
        }
    }
}
Private Sub OnButtonKeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
    Dim source As Button = TryCast(e.Source, Button)
    If source IsNot Nothing Then
        If e.Key = Key.Left Then
            source.Background = Brushes.LemonChiffon
        Else
            source.Background = Brushes.AliceBlue
        End If
    End If
End Sub

마우스 입력 이벤트 예제

다음 예제에서 ButtonBackground 색상은 마우스 포인터를 Button에 놓으면 변경됩니다. Background 색상은 마우스를 Button에서 떼면 복원됩니다.

이 예제의 첫 번째 섹션은 StackPanelButton을 만들고 MouseEnterMouseLeave에 대한 이벤트 처리기를 Button에 연결합니다.

<StackPanel>
  <Button Background="AliceBlue"
          MouseEnter="OnMouseExampleMouseEnter"
          MouseLeave="OnMosueExampleMouseLeave">Button
          
  </Button>
</StackPanel>
// Create the UI elements.
StackPanel mouseMoveStackPanel = new StackPanel();
Button mouseMoveButton = new Button();

// Set properties on Button.
mouseMoveButton.Background = Brushes.AliceBlue;
mouseMoveButton.Content = "Button";

// Attach Buttons to StackPanel.
mouseMoveStackPanel.Children.Add(mouseMoveButton);

// Attach event handler.
mouseMoveButton.MouseEnter += new MouseEventHandler(OnMouseExampleMouseEnter);
mouseMoveButton.MouseLeave += new MouseEventHandler(OnMosueExampleMouseLeave);
' Create the UI elements.
Dim mouseMoveStackPanel As New StackPanel()
Dim mouseMoveButton As New Button()

' Set properties on Button.
mouseMoveButton.Background = Brushes.AliceBlue
mouseMoveButton.Content = "Button"

' Attach Buttons to StackPanel.
mouseMoveStackPanel.Children.Add(mouseMoveButton)

' Attach event handler.
AddHandler mouseMoveButton.MouseEnter, AddressOf OnMouseExampleMouseEnter
AddHandler mouseMoveButton.MouseLeave, AddressOf OnMosueExampleMouseLeave

예제에서 코드로 작성된 두 번째 섹션은 이벤트 처리기를 정의합니다. 마우스를 Button에 놓으면 ButtonBackground 색상이 SlateGray로 변경됩니다. Button에서 마우스를 떼면 ButtonBackground 색상이 AliceBlue로 다시 변경됩니다.

private void OnMouseExampleMouseEnter(object sender, MouseEventArgs e)
{
    // Cast the source of the event to a Button.
    Button source = e.Source as Button;

    // If source is a Button.
    if (source != null)
    {
        source.Background = Brushes.SlateGray;
    }
}
Private Sub OnMouseExampleMouseEnter(ByVal sender As Object, ByVal e As MouseEventArgs)
    ' Cast the source of the event to a Button.
    Dim source As Button = TryCast(e.Source, Button)

    ' If source is a Button.
    If source IsNot Nothing Then
        source.Background = Brushes.SlateGray
    End If
End Sub
private void OnMosueExampleMouseLeave(object sender, MouseEventArgs e)
{
    // Cast the source of the event to a Button.
    Button source = e.Source as Button;

    // If source is a Button.
    if (source != null)
    {
        source.Background = Brushes.AliceBlue;
    }
}
Private Sub OnMosueExampleMouseLeave(ByVal sender As Object, ByVal e As MouseEventArgs)
    ' Cast the source of the event to a Button.
    Dim source As Button = TryCast(e.Source, Button)

    ' If source is a Button.
    If source IsNot Nothing Then
        source.Background = Brushes.AliceBlue
    End If
End Sub

텍스트 입력

TextInput 이벤트를 사용하여 디바이스 독립적 방식으로 텍스트 입력을 수신 대기할 수 있습니다. 텍스트 입력에는 주로 키보드를 사용하지만 음성, 필기 및 기타 입력 디바이스를 통해서도 텍스트 입력을 생성할 수 있습니다.

키보드 입력의 경우 WPF는 먼저 적절한 KeyDown/KeyUp 이벤트를 보냅니다. 해당 이벤트가 처리되지 않았으며 키가 방향 화살표 키 또는 기능 키와 같은 제어 키가 아니라 텍스트 키이면 TextInput 이벤트가 발생합니다. 여러 키스트로크로 단일 텍스트 입력 문자를 생성할 수 있고 단일 키스트로크로 다중 문자 문자열을 생성할 수 있으므로 KeyDown/KeyUpTextInput 이벤트 간에 항상 단순히 일 대 일 매핑이 구성되는 것은 아닙니다. 이는 특히 각 언어의 알파벳으로 수천 개의 가능한 문자를 생성하는 데 IME(입력기)를 사용하는 한국어, 중국어 및 일본어 등의 언어에서 더욱 그렇습니다.

WPF가 KeyUp/KeyDown 이벤트를 보내면 키스트로크가 TextInput 이벤트의 일부가 될 수 있는 경우(예를 들어 Alt+S를 누르는 경우)KeyKey.System으로 설정됩니다. 이를 통해 KeyDown 이벤트 처리기의 코드는 Key.System을 확인하고, 찾은 경우 이후에 발생한 TextInput 이벤트에 대한 처리기가 처리되도록 둘 수 있습니다. 이러한 경우 TextCompositionEventArgs 인수의 다양한 속성을 사용하여 원래 키스트로크를 확인할 수 있습니다. 마찬가지로 IME가 활성 상태인 경우 KeyKey.ImeProcessed 값을 가지며 ImeProcessedKey가 원래 키스트로크를 제공합니다.

다음 예제에서는 Click 이벤트에 대한 처리기와 KeyDown 이벤트에 대한 처리기를 정의합니다.

코드 또는 태그의 첫 번째 세그먼트에서는 사용자 인터페이스를 만듭니다.

<StackPanel KeyDown="OnTextInputKeyDown">
  <Button Click="OnTextInputButtonClick"
          Content="Open" />
  <TextBox> . . . </TextBox>
</StackPanel>
// Create the UI elements.
StackPanel textInputStackPanel = new StackPanel();
Button textInputeButton = new Button();
TextBox textInputTextBox = new TextBox();
textInputeButton.Content = "Open";

// Attach elements to StackPanel.
textInputStackPanel.Children.Add(textInputeButton);
textInputStackPanel.Children.Add(textInputTextBox);

// Attach event handlers.
textInputStackPanel.KeyDown += new KeyEventHandler(OnTextInputKeyDown);
textInputeButton.Click += new RoutedEventHandler(OnTextInputButtonClick);
' Create the UI elements.
Dim textInputStackPanel As New StackPanel()
Dim textInputeButton As New Button()
Dim textInputTextBox As New TextBox()
textInputeButton.Content = "Open"

' Attach elements to StackPanel.
textInputStackPanel.Children.Add(textInputeButton)
textInputStackPanel.Children.Add(textInputTextBox)

' Attach event handlers.
AddHandler textInputStackPanel.KeyDown, AddressOf OnTextInputKeyDown
AddHandler textInputeButton.Click, AddressOf OnTextInputButtonClick

코드의 두 번째 세그먼트에는 이벤트 처리기가 포함되어 있습니다.

private void OnTextInputKeyDown(object sender, KeyEventArgs e)
{
    if (e.Key == Key.O && Keyboard.Modifiers == ModifierKeys.Control)
    {
        handle();
        e.Handled = true;
    }
}

private void OnTextInputButtonClick(object sender, RoutedEventArgs e)
{
    handle();
    e.Handled = true;
}

public void handle()
{
    MessageBox.Show("Pretend this opens a file");
}
Private Sub OnTextInputKeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
    If e.Key = Key.O AndAlso Keyboard.Modifiers = ModifierKeys.Control Then
        handle()
        e.Handled = True
    End If
End Sub

Private Sub OnTextInputButtonClick(ByVal sender As Object, ByVal e As RoutedEventArgs)
    handle()
    e.Handled = True
End Sub

Public Sub handle()
    MessageBox.Show("Pretend this opens a file")
End Sub

입력 이벤트는 이벤트 경로의 위쪽으로 버블링하므로 StackPanel은 어떤 요소에 키보드 포커스가 있는지에 관계없이 입력을 받습니다. TextBox 컨트롤이 먼저 알림을 받으며 OnTextInputKeyDown 처리기는 TextBox가 입력을 처리하지 않은 경우에만 호출됩니다. PreviewKeyDown 이벤트가 KeyDown 이벤트 대신 사용되면 OnTextInputKeyDown 처리기가 먼저 호출됩니다.

이 예제에서 처리 논리는 Ctrl+O에 대해 한 번, 그리고 단추의 클릭 이벤트에 대해 한 번으로 총 두 번 작성되었습니다. 입력 이벤트를 직접 처리하는 대신 명령을 사용하면 이를 간편하게 처리할 수 있습니다. 명령에 대해서는 이 개요 항목과 명령 개요에서 설명합니다.

터치 및 조작

Windows 7 운영 체제의 새로운 하드웨어 및 API를 사용하면 애플리케이션이 여러 터치에서 동시에 입력을 수신할 수 있습니다. WPF를 사용하면 애플리케이션에서 터치가 발생할 때 이벤트를 발생시킴으로써 마우스나 키보드와 같은 다른 입력에 응답하는 것과 유사한 방식으로 터치를 감지하고 이에 응답할 수 있습니다.

WPF는 터치가 발생할 때 두 가지 형식의 이벤트, 즉 터치 이벤트와 조작 이벤트를 노출합니다. 터치 이벤트는 터치 스크린의 각 손가락과 그 이동에 대한 원시 데이터를 제공합니다. 조작 이벤트는 특정 작업으로 입력을 해석합니다. 이 섹션에서는 두 가지 형식의 이벤트에 대해 모두 설명합니다.

사전 요구 사항

터치에 응답하는 애플리케이션을 개발하려면 다음 구성 요소가 필요합니다.

  • Visual Studio 2010

  • Windows 7

  • Windows Touch를 지원하는 터치 스크린과 같은 디바이스

용어

터치에 대해 설명할 때 다음 용어가 사용됩니다.

  • 터치는 Windows 7에서 인식되는 사용자 입력 형식입니다. 일반적으로 터치 스크린에 손가락을 대면 터치가 시작됩니다. 랩톱 컴퓨터에서 일반적으로 사용되는 터치 패드와 같은 디바이스는 디바이스가 손가락의 위치와 움직임을 마우스 입력으로 단순히 변환하는 경우 터치를 지원하지 않습니다.

  • 멀티 터치는 둘 이상의 지점에서 동시에 발생하는 터치입니다. Windows 7 및 WPF에서 멀티 터치를 지원합니다. WPF에 대한 설명서에서 터치를 설명할 때마다 이 개념이 멀티 터치에 적용됩니다.

  • 터치가 개체에 적용되는 물리적 액션으로 해석되면 조작이 발생합니다. WPF에서 조작 이벤트는 입력을 변환, 확장 또는 회전 조작으로 해석합니다.

  • touch device는 터치 스크린에서 한 손가락과 같은 터치식 입력을 생성하는 디바이스를 나타냅니다.

터치에 반응하는 컨트롤

보기에서 스크롤된 콘텐츠가 있는 경우 컨트롤에서 손가락을 드래그하여 다음 컨트롤을 스크롤할 수 있습니다.

ScrollViewer는 터치 이동이 가로 또는 세로, 두 방향 모두로 지원되는지 또는 어떤 방향으로도 지원되지 않는지를 지정할 수 있는 ScrollViewer.PanningMode 연결된 속성을 정의합니다. ScrollViewer.PanningDeceleration 속성은 사용자가 터치스크린에서 손가락을 뗄 때 스크롤 속도가 얼마나 빠르게 느려지는지 지정합니다. ScrollViewer.PanningRatio 연결된 속성은 조작 오프셋을 변환하기 위한 스크롤 오프셋 비율을 지정합니다.

터치 이벤트

기본 클래스인 UIElement, UIElement3DContentElement는 애플리케이션이 터치에 반응하도록 구독할 수 있는 이벤트를 정의합니다. 터치 이벤트는 애플리케이션이 터치를 개체 조작이 아닌 다른 것으로 해석할 때 유용합니다. 예를 들어 사용자가 하나 이상의 손가락으로 그릴 수 있는 애플리케이션은 터치 이벤트를 구독합니다.

세 클래스 모두 다음과 같은 이벤트를 정의합니다. 이 이벤트는 정의 클래스에 관계없이 유사하게 동작합니다.

키보드 및 마우스 이벤트와 마찬가지로 터치 이벤트는 라우트된 이벤트입니다. Preview로 시작하는 이벤트는 터널링 이벤트이고 Touch로 시작하는 이벤트는 버블링 이벤트입니다. 라우트된 이벤트에 대한 자세한 내용은 라우트된 이벤트 개요를 참조하세요. 이러한 이벤트를 처리할 때는 GetTouchPoint 또는 GetIntermediateTouchPoints 메서드를 호출하여 요소와 상대적인 입력 위치를 가져올 수 있습니다.

터치 이벤트 간의 상호 작용을 이해하려면 사용자가 한 손가락을 요소 위에 놓고 손가락을 요소에서 움직인 다음 요소에서 손가락을 들어 올리는 시나리오를 고려해 보세요. 다음 그림에서는 버블링 이벤트를 실행하는 것을 보여 줍니다(단순하게 하기 위해 터널링 이벤트는 생략됨).

터치 이벤트 시퀀스. 터치 이벤트

다음 목록은 앞의 그림에서 이벤트 시퀀스를 설명합니다.

  1. TouchEnter 이벤트는 사용자가 요소에 손가락을 놓으면 1번 발생합니다.

  2. TouchDown 이벤트는 1번 발생합니다.

  3. TouchMove 이벤트는 요소 내에서 손가락을 이동할 때 여러 번 발생합니다.

  4. TouchUp 이벤트는 요소에서 손가락을 뗄 때 1번 발생합니다.

  5. TouchLeave 이벤트는 1번 발생합니다.

3개 이상의 손가락이 사용되면 각 손가락마다 이벤트가 발생합니다.

조작 이벤트

애플리케이션에서 사용자가 개체를 조작할 수 있게 하는 경우 UIElement 클래스에서 조작 이벤트를 정의합니다. 터치 위치를 단순히 보고하는 터치 이벤트와 달리 조작 이벤트는 입력을 해석할 수 있는 방법을 보고합니다. 변환, 확장 및 회전이라는 세 가지 형식의 조작이 있습니다. 다음 목록은 세 가지 형식의 조작을 호출하는 방법을 설명합니다.

  • 개체에 손가락을 대고 터치 스크린에서 손가락을 움직이면 변환 조작을 호출합니다. 그러면 일반적으로 개체를 이동합니다.

  • 개체 위에 두 개의 손가락을 놓고 손가락을 서로 더 가깝게 또는 멀리 움직여 확장 조작을 호출합니다. 그러면 일반적으로 개체의 크기를 조정합니다.

  • 개체에 두 손가락을 놓고 손가락을 서로 회전하면 회전 조작을 호출합니다. 그러면 일반적으로 개체를 회전합니다.

둘 이상의 조작 형식이 동시에 발생할 수 있습니다.

개체가 조작에 응답하게 하면 개체에 관성이 있는 것처럼 보일 수 있습니다. 그러면 개체가 실제 세계를 시뮬레이트하게 만들 수 있습니다. 예를 들어 테이블에서 책을 밀 때 충분히 세게 밀면 책을 놓은 후에도 책이 계속 움직입니다. WPF를 사용하면 사용자가 손가락을 개체에서 뗀 후 조작 이벤트를 발생시켜 이 동작을 시뮬레이트할 수 있습니다.

사용자가 개체를 이동, 크기 조정 및 회전할 수 있게 하는 애플리케이션을 만드는 방법에 대한 자세한 내용은 연습: 첫 번째 터치 애플리케이션 만들기를 참조하세요.

UIElement는 다음 조작 이벤트를 정의합니다.

기본적으로 UIElement는 이러한 조작 이벤트를 수신하지 않습니다. UIElement에 대한 조작 이벤트를 수신하려면 UIElement.IsManipulationEnabledtrue로 설정합니다.

조작 이벤트 실행 경로

사용자가 개체를 “throw”하는 시나리오를 고려해 보겠습니다. 사용자가 개체 위에 손가락을 놓고 터치 스크린에서 짧은 거리만큼 손가락을 이동한 다음 개체가 움직이는 동안 손가락을 뗍니다. 그 결과 사용자가 손가락을 뗀 후에도 개체가 사용자의 손가락 아래에서 계속 움직입니다.

다음 그림에서는 조작 이벤트의 실행 경로 및 각 이벤트에 대한 중요한 정보를 보여 줍니다.

조작 이벤트 시퀀스. 조작 이벤트

다음 목록은 앞의 그림에서 이벤트 시퀀스를 설명합니다.

  1. ManipulationStarting 이벤트는 사용자가 개체 위에 손가락을 놓을 때 발생합니다. 다른 이벤트 중에서 이 이벤트를 사용하여 ManipulationContainer 속성을 설정할 수 있습니다. 후속 이벤트에서 조작 위치는 ManipulationContainer에 상대적입니다. ManipulationStarting 이외의 이벤트에서 이 속성은 읽기 전용이므로 ManipulationStarting 이벤트가 이 속성을 설정할 수 있는 유일한 경우입니다.

  2. 다음 번에는 ManipulationStarted 이벤트가 발생합니다. 이 이벤트는 조작의 출처를 보고합니다.

  3. ManipulationDelta 이벤트는 사용자가 터치스크린에서 손가락을 이동할 때 여러 번 발생합니다. ManipulationDeltaEventArgs 클래스의 DeltaManipulation 속성은 조작이 이동, 확장 또는 변환 중 어떤 것으로 해석되는지를 보고합니다. 바로 여기서 개체 조작 작업의 대부분이 수행됩니다.

  4. ManipulationInertiaStarting 이벤트는 사용자의 손가락이 개체에 닿을 때 발생합니다. 이 이벤트를 사용하면 관성이 발생하는 동안 수행되는 조작의 감속을 지정할 수 있습니다. 따라서 사용자가 선택한 여러 가지 실제 공간 또는 특성을 개체가 에뮬레이트할 수 있습니다. 예를 들어 애플리케이션에 실제 세계의 항목을 나타내는 개체가 두 개 있고 한 개체가 다른 개체보다 무거운 경우를 가정해 봅니다. 이 경우 무거운 개체가 가벼운 개체보다 더 빠르게 감속되도록 할 수 있습니다.

  5. ManipulationDelta 이벤트는 관성이 발생할 때 여러 번 발생합니다. 이 이벤트는 사용자가 터치 스크린에서 손가락을 이동하고 WPF에서 관성을 시뮬레이트하는 경우에 발생합니다. 즉, ManipulationDeltaManipulationInertiaStarting 이벤트 전/후에 발생합니다. ManipulationDeltaEventArgs.IsInertial 속성은 ManipulationDelta 이벤트가 관성 중에 발생하는지 여부를 보고하므로 해당 속성을 확인한 후 해당 값에 따라 다른 동작을 수행할 수 있습니다.

  6. ManipulationCompleted 이벤트는 조작 및 관성이 끝날 때 발생합니다. 즉, 모든 ManipulationDelta 이벤트가 발생한 후, 조작이 완료되었음을 알리기 위해 ManipulationCompleted 이벤트가 발생합니다.

또한 UIElementManipulationBoundaryFeedback 이벤트를 정의합니다. 이 이벤트는 ManipulationDelta 이벤트에서 ReportBoundaryFeedback 메서드를 호출할 때 발생합니다. ManipulationBoundaryFeedback 이벤트는 개체가 경계에 닿을 때 애플리케이션 또는 구성 요소가 시각적 피드백을 제공할 수 있도록 합니다. 예를 들어, Window 클래스는 ManipulationBoundaryFeedback 이벤트를 처리하여 가장자리에 도달할 때 창이 약간 이동되도록 합니다.

ManipulationBoundaryFeedback 이벤트를 제외한 모든 조작 이벤트의 이벤트 인수에 대해 Cancel 메서드를 호출하여 조작을 취소할 수 있습니다. Cancel을 호출하면 조작 이벤트가 더 이상 발생하지 않으며 터치에 대해 마우스 이벤트가 발생합니다. 다음 표에서는 조작이 취소되는 시점과 발생하는 마우스 이벤트 간의 관계에 대해 설명합니다.

Cancel이 호출되는 이벤트 이미 발생한 입력에 대해 발생하는 마우스 이벤트
ManipulationStartingManipulationStarted 마우스 누름 이벤트
ManipulationDelta 마우스 누름 및 마우스 이동 이벤트
ManipulationInertiaStartingManipulationCompleted 마우스 누름, 마우스 이동 및 마우스 놓기 이벤트

조작에 관성이 작용할 때 Cancel을 호출하면 이 메서드는 false를 반환하고 입력은 마우스 이벤트를 발생시키지 않습니다.

터치 이벤트와 조작 이벤트의 관계

UIElement는 항상 터치 이벤트를 수신할 수 있습니다. IsManipulationEnabled 속성이 true로 설정되면 UIElement는 터치 및 조작 이벤트를 둘 다 수신할 수 있습니다. TouchDown 이벤트가 처리되지 않는 경우(즉, Handled 속성이 false인 경우) 조작 논리에서 요소에 터치를 캡처하고 조작 이벤트를 생성합니다. TouchDown 이벤트에서 Handled 속성이 true로 설정된 경우 조작 논리에서 조작 이벤트를 생성하지 않습니다. 다음 그림에서는 터치 이벤트와 조작 이벤트의 관계를 보여 줍니다.

터치 이벤트와 조작 이벤트의 관계 터치 및 조작 이벤트

다음 목록에서는 앞의 그림에 나와 있는 터치 이벤트와 조작 이벤트의 관계에 대해 설명합니다.

포커스

WPF에는 포커스와 관련된 두 가지 주요 개념인 키보드 포커스와 논리 포커스가 있습니다.

키보드 포커스

키보드 포커스는 키보드 입력을 받고 있는 요소를 말합니다. 키보드 포커스가 있는 전체 데스크탑에는 요소가 하나뿐이어야 합니다. WPF에서 키보드 포커스가 있는 요소에는 true로 설정된 IsKeyboardFocused가 있습니다. 정적 Keyboard 메서드 FocusedElement는 현재 키보드 포커스가 있는 요소를 반환합니다.

키보드 포커스는 탭을 사용하여 요소로 이동하거나 TextBox와 같은 특정 요소를 마우스로 클릭하여 얻을 수 있습니다. 또한 Keyboard 클래스에서 Focus 메서드를 사용하여 키보드 포커스를 프로그래밍 방식으로 가져올 수 있습니다. Focus에서 지정된 요소 키보드 포커스를 제공하려고 합니다. Focus에서 반환된 요소는 현재 키보드 포커스가 있는 요소입니다.

요소에서 키보드 포커스를 가져오게 하려면 기본 요소의 Focusable 속성 및 IsVisible 속성을 true로 설정해야 합니다. Panel과 같은 일부 클래스는 Focusable이 기본적으로 false로 설정되어 있기 때문에 이러한 요소가 포커스를 갖도록 하려면 이 속성을 true로 설정해야 할 수 있습니다.

다음 예제에서는 Focus를 사용하여 Button에 키보드 포커스를 설정합니다. 애플리케이션에서 초기 포커스를 설정할 권장 위치는 Loaded 이벤트 처리기에 있습니다.

private void OnLoaded(object sender, RoutedEventArgs e)
{
    // Sets keyboard focus on the first Button in the sample.
    Keyboard.Focus(firstButton);
}
Private Sub OnLoaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
    ' Sets keyboard focus on the first Button in the sample.
    Keyboard.Focus(firstButton)
End Sub

키보드 포커스에 대한 자세한 내용은 포커스 개요를 참조하세요.

논리 포커스

논리 포커스는 포커스 범위의 FocusManager.FocusedElement를 참조합니다. 애플리케이션에 논리 포커스가 있는 요소는 여러 개일 수 있지만, 특정 포커스 범위에 논리 포커스가 있는 요소는 하나뿐일 수 있습니다.

포커스 범위는 범위 내에서 FocusedElement를 추적하는 컨테이너 요소입니다. 포커스가 포커스 범위를 벗어나면 포커스가 있는 요소에서 키보드 포커스를 잃지만 논리 포커스는 유지합니다. 포커스가 포커스 범위로 돌아오면 포커스가 있는 요소가 키보드 포커스를 갖습니다. 이렇게 하면 여러 포커스 범위 사이에서 키보드 포커스가 변경되더라도 포커스 범위 내에서 포커스가 있는 요소는 포커스가 다시 돌아올 때 포커스가 있는 상태로 유지됩니다.

FocusManager에 연결된 속성 IsFocusScopetrue로 설정하거나 코드에서 SetIsFocusScope 메서드를 사용하여 이 연결된 속성을 지정하여 XAML(Extensible Application Markup Language)에서 요소를 포커스 범위로 전환할 수 있습니다.

다음 예제에서는 IsFocusScope에 연결된 속성을 설정하여 StackPanel을 포커스 범위로 전환합니다.

<StackPanel Name="focusScope1" 
            FocusManager.IsFocusScope="True"
            Height="200" Width="200">
  <Button Name="button1" Height="50" Width="50"/>
  <Button Name="button2" Height="50" Width="50"/>
</StackPanel>
StackPanel focuseScope2 = new StackPanel();
FocusManager.SetIsFocusScope(focuseScope2, true);
Dim focuseScope2 As New StackPanel()
FocusManager.SetIsFocusScope(focuseScope2, True)

기본적으로 포커스 범위인 WPF의 클래스는 Window, Menu, ToolBarContextMenu입니다.

키보드 포커스가 있는 요소는 자신이 속한 포커스 범위에 대한 논리 포커스도 가지므로 Keyboard 클래스 또는 기본 요소 클래스의 Focus 메서드를 사용하여 한 요소에 포커스를 설정하면 해당 요소에 키보드 포커스와 논리 포커스가 설정됩니다.

포커스 범위에서 포커스가 있는 요소를 확인하려면 GetFocusedElement를 사용합니다. 포커스 범위에서 포커스가 있는 요소를 변경하려면 SetFocusedElement를 사용합니다.

논리 포커스에 대한 자세한 내용은 포커스 개요를 참조하세요.

마우스 위치

WPF 입력 API는 좌표 공간과 관련된 유용한 정보를 제공합니다. 예를 들어 좌표 (0,0)은 좌표의 왼쪽 위를 나타냅니다. 그러나 트리에서 어떤 요소의 왼쪽 위인가요? 즉, 좌표가 나타내는 요소가 입력 대상인지 이벤트 처리기를 연결한 요소인지 또는 다른 요소인지 알 수 없습니다. 이러한 혼동을 피하기 위해 WPF 입력 API에서 마우스로 좌표를 사용할 때는 참조 프레임을 지정해야 합니다. GetPosition 메서드는 지정된 요소를 기준으로 마우스 포인터의 좌표를 반환합니다.

마우스 캡처

마우스 디바이스에는 마우스 캡처라고 하는 모달 특성이 있습니다. 마우스 캡처는 끌어서 놓기 작업이 시작될 때 전환되는 입력 상태를 유지함으로써 마우스 포인터의 명목상 화면 위치와 관련된 다른 작업이 발생할 필요가 없도록 하기 위해 사용됩니다. 마우스를 끄는 동안 끌어서 놓기 작업을 중단하지 않고는 클릭할 수 없으므로 끌기 원점에서 마우스 캡처를 보유하는 동안에는 대부분의 마우스 이동 동작이 적절하지 않게 됩니다. 입력 시스템은 마우스 캡처 상태를 확인할 수 있는 API와 특정 요소에 마우스 캡처를 적용하거나 마우스 캡처 상태를 지울 수 있는 API를 제공합니다. 끌어서 놓기 작업에 대한 자세한 내용은 끌어서 놓기 개요를 참조하세요.

명령

명령을 사용하면 디바이스 입력에 비해 보다 의미적 수준에서 입력을 처리할 수 있습니다. 명령은 Cut, Copy, Paste 또는 Open과 같은 단순한 지시문입니다. 명령은 명령 논리를 중앙 집중화하는 데 유용합니다. 즉, 동일한 명령을 Menu, ToolBar 또는 바로 가기 키를 통해 액세스할 수 있습니다. 또한 명령은 명령을 사용할 수 없을 때 컨트롤을 비활성화하는 메커니즘도 제공합니다.

RoutedCommandICommand의 WPF 구현입니다. RoutedCommand를 실행하면 명령 대상에서 PreviewExecutedExecuted 이벤트가 발생하여 다른 입력과 마찬가지로 요소 트리를 통해 터널링 및 버블링합니다. 명령 대상이 설정되어 있지 않으면 키보드 포커스가 있는 요소가 명령 대상이 됩니다. 명령을 수행하는 논리는 CommandBinding에 연결되어 있습니다. Executed 이벤트가 해당 특정 명령에 대한 CommandBinding에 도달하면 CommandBindingExecutedRoutedEventHandler가 호출됩니다. 이 처리기는 명령의 작업을 수행합니다.

명령에 대한 자세한 내용은 명령 개요를 참조하세요.

WPF는 ApplicationCommands, MediaCommandsComponentCommands, NavigationCommandsEditingCommands로 구성되는 공통 명령의 라이브러리를 제공합니다. 그렇지 않으면 사용자가 직접 정의할 수 있습니다.

다음 예제에서는 MenuItem을 설정하는 방법을 보여 주며, 클릭하는 경우 TextBox에 키보드 포커스가 있다는 가정 하에 TextBox에서 Paste 명령을 호출합니다.

<StackPanel>
  <Menu>
    <MenuItem Command="ApplicationCommands.Paste" />
  </Menu>
  <TextBox />
</StackPanel>
// Creating the UI objects
StackPanel mainStackPanel = new StackPanel();
TextBox pasteTextBox = new TextBox();
Menu stackPanelMenu = new Menu();
MenuItem pasteMenuItem = new MenuItem();

// Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem);
mainStackPanel.Children.Add(stackPanelMenu);
mainStackPanel.Children.Add(pasteTextBox);

// Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste;

// Setting the command target to the TextBox
pasteMenuItem.CommandTarget = pasteTextBox;
' Creating the UI objects
Dim mainStackPanel As New StackPanel()
Dim pasteTextBox As New TextBox()
Dim stackPanelMenu As New Menu()
Dim pasteMenuItem As New MenuItem()

' Adding objects to the panel and the menu
stackPanelMenu.Items.Add(pasteMenuItem)
mainStackPanel.Children.Add(stackPanelMenu)
mainStackPanel.Children.Add(pasteTextBox)

' Setting the command to the Paste command
pasteMenuItem.Command = ApplicationCommands.Paste

WPF의 명령에 대한 자세한 내용은 명령 개요를 참조하세요.

입력 시스템 및 기본 요소

Mouse, KeyboardStylus 클래스가 정의하는 연결된 이벤트와 같은 입력 이벤트는 입력 시스템에 의해 발생하며 런타임에 시각적 트리에 대한 적중 횟수 테스트를 기반으로 개체 모델의 특정 위치에 삽입됩니다.

또한 Mouse, KeyboardStylus가 연결된 이벤트로 정의하는 각 이벤트는 기본 요소 클래스인 UIElementContentElement에 의해 새로운 라우트된 이벤트로 다시 노출됩니다. 기본 요소의 라우트된 이벤트는 원래 연결된 이벤트를 처리하고 이벤트 데이터를 다시 사용하는 클래스를 통해 생성됩니다.

입력 이벤트가 해당 기본 요소 입력 이벤트 구현을 통해 특정 소스 요소와 연결되는 경우 이 입력 이벤트는 논리적 트리 개체 및 시각적 트리 개체의 조합을 기반으로 하는 이벤트 경로의 나머지 부분을 통해 전송할 수 있으며 애플리케이션 코드에서 처리할 수 있습니다. 이러한 디바이스 관련 입력 이벤트의 경우 XAML과 코드 모두에서 보다 이해하기 쉬운 이벤트 처리기 구문을 사용할 수 있으므로 일반적으로 UIElementContentElement에서 라우트된 이벤트를 사용하여 처리하는 것이 더 편리합니다. 대신 프로세스를 시작한 연결된 이벤트를 처리할 수도 있지만 이 경우 몇 가지 문제에 직면할 수 있습니다. 예를 들어 기본 요소 클래스 처리를 통해 연결된 이벤트가 처리된 것으로 표시될 수 있습니다. 또한 연결된 이벤트에 대한 처리기를 연결하기 위해 실제 이벤트 구문 대신 접근자 메서드를 사용해야 합니다.

다음 단계

이제 보다 다양한 기술로 WPF에서 입력을 처리할 수 있게 되었습니다. 따라서 WPF에서 사용하는 라우트된 이벤트 메커니즘 및 다양한 형식의 입력 이벤트에 대해 보다 잘 이해하고 있어야 합니다.

WPF 프레임워크 요소 및 이벤트 라우팅에 대해 보다 자세히 설명하는 추가 리소스가 있습니다. 자세한 내용은 명령 개요, 포커스 개요, 기본 요소 개요,WPF의 트리라우트된 이벤트 개요와 같은 개요를 참조하세요.

참고 항목