라우트된 이벤트 개요

이 토픽에서는 WPF(Windows Presentation Foundation)의 라우트된 이벤트 개념을 설명합니다. 이 항목에서는 라우트된 이벤트 용어를 정의하고, 라우트된 이벤트가 요소 트리를 통해 라우트되는 방식을 설명하고, 라우트된 이벤트를 처리하는 방법을 요약하고, 자체 사용자 지정 라우트된 이벤트를 만드는 방법을 소개합니다.

필수 구성 요소

이 토픽에서는 독자들이 CLR(공용 언어 런타임) 및 개체 지향 프로그래밍뿐 아니라 WPF 요소 간의 관계를 트리로 개념화하는 방법에 대한 기본 지식이 있다고 가정합니다. 이 항목의 예제를 따라 하려면 XAML(Extensible Application Markup Language)도 이해하고 매우 기본적인 WPF 애플리케이션 또는 페이지를 작성하는 방법도 알아야 합니다. 자세한 내용은 연습: 내 첫 WPF 데스크톱 애플리케이션WPF의 XAML을 참조하세요.

라우트된 이벤트란?

기능 또는 구현 측면에서 라우트된 이벤트를 살펴볼 수 있습니다. 사용자마다 더 유용하다고 생각하는 정의가 다르므로 여기에서는 두 가지 정의를 모두 제공합니다.

기능 정의: 라우트된 이벤트는 이벤트를 일으킨 개체에서만이 아니라 요소 트리의 여러 수신기에서 처리기를 호출할 수 있는 이벤트 형식입니다.

구현 정의: 라우트된 이벤트는 RoutedEvent 클래스 인스턴스에서 지원되고 WPF(Windows Presentation Foundation) 이벤트 시스템에서 처리되는 CLR 이벤트입니다.

일반적인 WPF 애플리케이션에는 많은 요소가 포함됩니다. 코드에서 만들든 XAML에서 선언하든 관계없이, 이러한 요소는 서로에 대한 요소 트리 관계로 존재합니다. 이벤트 경로는 이벤트 정의에 따라 두 방향 중 하나로 이동할 수 있지만, 일반적으로 경로는 소스 요소부터 이동하여 요소 트리 루트(일반적으로 페이지 또는 창)에 도달할 때까지 요소 트리를 통해 위쪽으로 "버블링"됩니다. 이전에 DHTML 개체 모델을 사용했다면 이 버블링 개념이 익숙할 것입니다.

다음 간단한 요소 트리를 살펴보세요.

<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
  <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
    <Button Name="YesButton" Width="Auto" >Yes</Button>
    <Button Name="NoButton" Width="Auto" >No</Button>
    <Button Name="CancelButton" Width="Auto" >Cancel</Button>
  </StackPanel>
</Border>

이 요소 트리는 다음과 같은 항목을 생성합니다.

예, 아니요 및 취소 단추

이 간단한 요소 트리에서 Click 이벤트의 소스는 Button 요소 중 하나이며, 클릭된 Button은 이벤트를 처리할 수 있는 첫 번째 요소가 됩니다. 하지만 Button에 연결된 처리기가 이벤트에서 작동하지 않으면 이 이벤트는 요소 트리의 Button 부모(StackPanel)까지 위쪽으로 버블링됩니다. 이벤트는 Border를 지나 요소 트리의 페이지 루트까지 버블링될 수 있습니다.

즉, 이 Click 이벤트에 대한 이벤트 경로는 다음과 같습니다.

Button-->StackPanel-->Border-->...

라우트된 이벤트에 대한 최상위 시나리오

라우트된 이벤트 개념을 유도한 시나리오에 대한 간단한 요약과 일반적인 CLR 이벤트가 이러한 시나리오에 적절하지 않은 이유는 다음과 같습니다.

컨트롤 컴퍼지션 및 캡슐화: WPF의 다양한 컨트롤에는 서식 있는 콘텐츠 모델이 있습니다. 예를 들어 단추의 시각적 트리를 효과적으로 확장하는 Button 내부에 이미지를 배치할 수 있습니다. 하지만 사용자가 기술적으로 이미지에 포함된 픽셀을 클릭한 경우에도 추가된 이미지는 단추가 콘텐츠의 Click에 응답하게 하는 적중 테스트 동작을 중단하면 안 됩니다.

단일 처리기 연결 지점: Windows Forms에서는 여러 요소에서 발생할 수 있는 이벤트를 처리하기 위해 같은 처리기를 여러 번 연결해야 합니다. 라우트된 이벤트를 사용하면 이전 예제에서 살펴본 것처럼 해당 처리기를 한 번만 연결하고 처리기 논리를 사용하여 필요한 경우 이벤트가 발생한 위치를 확인할 수 있습니다. 예를 들어 이것은 이전에 표시된 XAML에 대한 처리기일 수 있습니다.

private void CommonClickHandler(object sender, RoutedEventArgs e)
{
  FrameworkElement feSource = e.Source as FrameworkElement;
  switch (feSource.Name)
  {
    case "YesButton":
      // do something here ...
      break;
    case "NoButton":
      // do something ...
      break;
    case "CancelButton":
      // do something ...
      break;
  }
  e.Handled=true;
}
Private Sub CommonClickHandler(ByVal sender As Object, ByVal e As RoutedEventArgs)
  Dim feSource As FrameworkElement = TryCast(e.Source, FrameworkElement)
  Select Case feSource.Name
    Case "YesButton"
      ' do something here ...
    Case "NoButton"
      ' do something ...
    Case "CancelButton"
      ' do something ...
  End Select
  e.Handled=True
End Sub

클래스 처리: 라우트된 이벤트는 클래스를 통해 정의된 정적 처리기를 허용합니다. 이 클래스 처리기는 연결된 인스턴스 처리기보다 먼저 이벤트를 처리할 수 있습니다.

리플렉션 없이 이벤트 참조: 특정 코드 및 태그 기술에는 특정 이벤트를 식별하는 방법이 필요합니다. 라우트된 이벤트는 RoutedEvent 필드를 식별자로 만듭니다. 이 식별자는 정적 또는 런타임 리플렉션이 필요하지 않은 강력한 이벤트 식별 기술을 제공합니다.

라우트된 이벤트를 구현하는 방법

라우트된 이벤트는 RoutedEvent 클래스의 인스턴스에서 지원되고 WPF 이벤트 시스템에 등록된 CLR 이벤트입니다. 일반적으로 등록에서 얻은 RoutedEvent 인스턴스는 라우트된 이벤트를 등록하고 이에 따라 "소유"하는 클래스의 publicstaticreadonly 필드 멤버로 유지됩니다. 동일하게 명명된 CLR 이벤트(경우에 따라 "래퍼" 이벤트라고 함)에 연결하려면 CLR 이벤트에 대한 addremove 구현을 재정의합니다. 일반적으로 addremove는 해당 이벤트의 처리기를 추가 및 제거하기 위한 적절한 언어별 이벤트 구문을 사용하는 암시적 기본값으로 남아 있습니다. 라우트된 이벤트 지원 및 연결 메커니즘은 종속성 속성이 DependencyProperty 클래스에서 지원되고 WPF 속성 시스템에 등록된 CLR 속성이 되는 방식과 개념적으로 비슷합니다.

다음 예제에서는 RoutedEvent 식별자 필드의 등록 및 표시와 Tap CLR 이벤트에 대한 addremove 구현을 비롯하여 사용자 지정 Tap 라우트된 이벤트에 대한 선언을 보여줍니다.

public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
    "Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyButtonSimple));

// Provide CLR accessors for the event
public event RoutedEventHandler Tap
{
        add { AddHandler(TapEvent, value); }
        remove { RemoveHandler(TapEvent, value); }
}
Public Shared ReadOnly TapEvent As RoutedEvent = EventManager.RegisterRoutedEvent("Tap", RoutingStrategy.Bubble, GetType(RoutedEventHandler), GetType(MyButtonSimple))

' Provide CLR accessors for the event
Public Custom Event Tap As RoutedEventHandler
    AddHandler(ByVal value As RoutedEventHandler)
        Me.AddHandler(TapEvent, value)
    End AddHandler

    RemoveHandler(ByVal value As RoutedEventHandler)
        Me.RemoveHandler(TapEvent, value)
    End RemoveHandler

    RaiseEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)
        Me.RaiseEvent(e)
    End RaiseEvent
End Event

라우트된 이벤트 처리기 및 XAML

XAML을 사용하여 이벤트 처리기를 추가하려면 이벤트 이름을 이벤트 수신기인 요소의 특성으로 선언합니다. 특성 값은 구현된 처리기 메서드의 이름으로, 코드 숨김 파일의 partial 클래스에 있어야 합니다.

<Button Click="b1SetColor">button</Button>

표준 CLR 이벤트 처리기를 추가하는 XAML 구문은 라우트된 이벤트 처리기를 추가하는 것과 같습니다. 실제로는 라우트된 이벤트 구현을 기반으로 하는 CLR 이벤트 래퍼에 처리기를 추가하기 때문입니다. XAML에서 이벤트 처리기 추가에 대한 자세한 내용은 WPF의 XAML을 참조하세요.

라우트 전략

라우트된 이벤트는 다음 세 가지 라우트 전략 중 하나를 사용합니다.

  • 버블링: 이벤트 소스에서 이벤트 처리기가 호출됩니다. 라우트된 이벤트는 요소 트리 루트에 도달할 때까지 다음 부모 요소로 라우트됩니다. 대부분의 라우트된 이벤트는 버블링 라우트 전략을 사용합니다. 버블링 라우트된 이벤트는 일반적으로 개별 컨트롤 또는 기타 UI 요소에서 입력 또는 상태 변경을 보고하는 데 사용됩니다.

  • 직접: 소스 요소 자체만 응답으로 처리기를 호출할 수 있습니다. 이 전략은 Windows Forms에서 이벤트에 사용하는 "라우팅"과 비슷합니다. 하지만 표준 CLR 이벤트와 달리 직접 라우트된 이벤트는 클래스 처리(클래스 처리는 이후 섹션에서 설명됨)를 지원하고 EventSetterEventTrigger에서 사용할 수 있습니다.

  • 터널링: 처음에 요소 트리 루트에 있는 이벤트 처리기가 호출됩니다. 그 다음에 라우트된 이벤트는 경로를 따라 있는 다음 자식 요소를 통해 경로를 이동하여 라우트된 이벤트 소스인 노드 요소(라우트된 이벤트를 발생시킨 요소)를 향합니다. 터널링 라우트된 이벤트는 보통 컨트롤 합치기의 일부로 사용 또는 처리되므로 복합 부분의 이벤트가 의도적으로 전체 컨트롤에 관련된 이벤트에 의해 억제되거나 대체될 수 있습니다. WPF에서 제공된 입력 이벤트는 터널링/버블링 쌍으로 구현됩니다. 쌍에 사용되는 명명 규칙 때문에 터널링 이벤트를 미리 보기 이벤트라고도 합니다.

라우트된 이벤트를 사용하는 이유는 무엇인가요?

애플리케이션 개발자가 항상 처리 중인 이벤트가 라우트된 이벤트로 구현되는지 알거나 주의할 필요는 없습니다. 라우트된 이벤트에는 특별한 동작이 있지만 이벤트가 발생한 요소에서 해당 이벤트를 처리할 경우 이 동작은 대체로 표시되지 않습니다.

공통 루트에서 공통 처리기 정의, 자체 컨트롤 합치기 또는 자체 사용자 지정 컨트롤 클래스 정의와 같은 제안된 시나리오를 사용할 경우 라우트된 이벤트가 강력해집니다.

라우트된 이벤트 수신기와 라우트된 이벤트 소스는 계층 구조에서 공통 이벤트를 공유할 필요가 없습니다. UIElement 또는 ContentElement는 라우트된 이벤트의 이벤트 수신기가 될 수 있습니다. 따라서 애플리케이션의 서로 다른 요소가 이벤트 정보를 교환하는 데 사용되는 개념적 “인터페이스”로 설정된 전체 작업 API에서 사용 가능한 전체 라우트된 이벤트 집합을 사용할 수 있습니다. 라우트된 이벤트에 대한 이 “인터페이스” 개념은 특히 입력 이벤트에 적합합니다.

이벤트에 대한 이벤트 데이터는 경로의 각 요소에 대해 지속되기 때문에 라우트된 이벤트는 요소 트리를 통해 통신하는 데도 사용될 수 있습니다. 하나의 요소가 이벤트 데이터의 무엇인가를 변경할 수 있고 해당 변경은 경로의 다음 요소에 사용할 수 있습니다.

라우트 측면 외에 특정 WPF 이벤트가 표준 CLR 이벤트 대신에 라우트된 이벤트로 구현될 수 있는 두 가지 다른 이유가 있습니다. 자체 이벤트를 구현할 경우 다음 원칙을 고려할 수도 있습니다.

  • EventSetterEventTrigger와 같은 특정 WPF 스타일 지정 및 템플릿 지정 기능을 사용하려면 참조된 이벤트가 라우트된 이벤트여야 합니다. 이는 앞에서 언급한 이벤트 식별자 시나리오입니다.

  • 라우트된 이벤트는 등록된 인스턴스 처리기가 라우트된 이벤트에 액세스하기 전에 클래스가 라우트된 이벤트를 처리할 기회를 가지는 정적 메서드를 지정하는 데 사용될 수 있는 클래스 처리 메커니즘을 지원합니다. 이 메커니즘은 컨트롤 디자인에서 매우 유용합니다. 클래스는 이벤트가 인스턴스에서 처리되어 실수로 억제될 수 있는 이벤트 기반 클래스 동작을 강제 실행할 수 있기 때문입니다.

위 고려 사항은 각각 이 항목의 개별 섹션에서 설명합니다.

라우트된 이벤트에 대한 이벤트 처리기 추가 및 구현

XAML에서 이벤트 처리기를 추가하려면 이벤트 이름을 요소에 특성으로 추가하고 특성 값을 다음 예제와 같이 적절한 대리자를 구현하는 이벤트 처리기의 이름으로 설정하면 됩니다.

<Button Click="b1SetColor">button</Button>

b1SetColorClick 구현된 처리기의 이름이며 이벤트를 처리하는 코드를 포함하고 있습니다. b1SetColor에는 Click 이벤트의 이벤트 처리기 대리자인 RoutedEventHandler 대리자와 같은 시그니처가 있어야 합니다. 모든 라우트된 이벤트 처리기 대리자의 첫 번째 매개 변수는 이벤트 처리기가 추가되는 요소를 지정하고 두 번째 매개 변수는 이벤트에 대한 데이터를 지정합니다.

void b1SetColor(object sender, RoutedEventArgs args)
{
  //logic to handle the Click event
}
Private Sub b1SetColor(ByVal sender As Object, ByVal args As RoutedEventArgs)
  'logic to handle the Click event
End Sub

RoutedEventHandler는 기본 라우트된 이벤트 처리기 대리자입니다. 특정 컨트롤 또는 시나리오에 특수화된 라우트된 이벤트의 경우 라우트된 이벤트 처리기에 사용할 대리자는 더 특수화되어 특수화된 이벤트 데이터를 전송할 수 있습니다. 예를 들어 일반 입력 시나리오에서는 DragEnter 라우트된 이벤트를 처리할 수 있습니다. 처리기는 DragEventHandler 대리자를 구현해야 합니다. 가장 구체적인 대리자를 사용하면 처리기에서 DragEventArgs를 처리하고 끌기 작업의 클립보드 페이로드가 포함된 Data 속성을 읽을 수 있습니다.

XAML을 사용하여 이벤트 처리기를 요소에 추가하는 방법의 전체 예제를 보려면 라우트된 이벤트 처리를 참조하세요.

코드로 만들어진 애플리케이션에 라우트된 이벤트에 대한 처리기를 추가하는 방법은 간단합니다. 라우트된 이벤트 처리기는 항상 도우미 메서드 AddHandler(기존 지원에서 add에 대해 호출하는 것과 같은 메서드)를 통해 추가할 수 있습니다. 하지만 기존 WPF 라우트된 이벤트에는 일반적으로 언어별 이벤트 구문(도우미 메서드보다 더 직관적인 구문)을 통해 라우트된 이벤트에 대한 처리기를 추가할 수 있는 addremove 논리의 지원 구현이 포함됩니다. 도우미 메서드 사용 예제는 다음과 같습니다.

void MakeButton()
 {
     Button b2 = new Button();
     b2.AddHandler(Button.ClickEvent, new RoutedEventHandler(Onb2Click));
 }
 void Onb2Click(object sender, RoutedEventArgs e)
 {
     //logic to handle the Click event
 }
Private Sub MakeButton()
     Dim b2 As New Button()
     b2.AddHandler(Button.ClickEvent, New RoutedEventHandler(AddressOf Onb2Click))
End Sub
 Private Sub Onb2Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
     'logic to handle the Click event     
 End Sub

다음 예제에서는 C# 연산자 구문을 보여 줍니다(역참조 처리 때문에 Visual Basic의 연산자 구문은 약간 다름).

void MakeButton2()
{
  Button b2 = new Button();
  b2.Click += new RoutedEventHandler(Onb2Click2);
}
void Onb2Click2(object sender, RoutedEventArgs e)
{
  //logic to handle the Click event
}
Private Sub MakeButton2()
  Dim b2 As New Button()
  AddHandler b2.Click, AddressOf Onb2Click2
End Sub
Private Sub Onb2Click2(ByVal sender As Object, ByVal e As RoutedEventArgs)
  'logic to handle the Click event     
End Sub

이벤트 처리기를 코드에 추가하는 방법의 예제를 보려면 코드를 사용하여 이벤트 처리기 추가를 참조하세요.

Visual Basic을 사용 중인 경우 Handles 키워드를 사용하여 처리기를 처리기 선언의 일부로 추가할 수도 있습니다. 자세한 내용은 Visual Basic 및 WPF 이벤트 처리를 참조하세요.

Handled 개념

모든 라우트된 이벤트는 공통 이벤트 데이터 기본 클래스인 RoutedEventArgs를 공유합니다. RoutedEventArgsHandled 부울 값을 사용하는 속성을 정의합니다. Handled 속성의 목적은 Handled 값을 true로 설정하여 경로를 따라 있는 이벤트 처리기가 라우트된 이벤트를 처리됨으로 표시할 수 있게 하는 것입니다. 경로를 따라 있는 한 요소에 있는 처리기에서 처리된 후 공유된 이벤트 데이터는 다시 경로를 따라 있는 각 수신기에 보고됩니다.

Handled의 값은 라우트된 이벤트가 경로를 따라 추가로 이동할 때 보고되거나 처리되는 방법에 영향을 미칩니다. 라우트된 이벤트에 대한 이벤트 데이터에서 Handledtrue이면 일반적으로 다른 요소에서 해당 라우트된 이벤트를 수신하는 처리기는 더 이상 해당 특정 이벤트 인스턴스에 대해 호출되지 않습니다. XAML에서 연결된 처리기 및 += 또는 Handles와 같은 언어별 이벤트 처리기 연결 구문을 통해 추가된 처리기에도 이 내용이 적용됩니다. 대부분의 공통 처리기 시나리오에서 Handledtrue로 설정하여 이벤트를 handled로 표시하면 터널링 경로 또는 버블링 경로에 대한 라우트와 클래스 처리기를 통해 경로의 특정 지점에서 처리되는 이벤트에 대한 라우트가 "중지"됩니다.

하지만 이벤트 데이터에서 Handledtrue인 경우 수신기가 라우트된 이벤트에 대한 응답으로 처리기를 실행하는 데 사용될 수 있는 "handledEventsToo" 메커니즘이 있습니다. 즉, 이벤트 데이터를 처리됨으로 표시해도 실제로 이벤트 경로는 중지되지 않습니다. handledEventsToo 메커니즘은 코드 또는 EventSetter에서만 사용할 수 있습니다.

Handled 상태가 라우트된 이벤트에서 생성하는 동작 외에도 Handled 개념은 애플리케이션을 디자인하고 이벤트 처리기 코드를 작성해야 하는 방법에 영향을 미칩니다. Handled를 라우트된 이벤트가 표시하는 간단한 프로토콜로 개념화할 수 있습니다. 정확히 말하자면 이 프로토콜을 사용하는 방법은 사용자에 따라 다르지만 Handled 값을 사용하는 방법에 대한 개념적 디자인은 다음과 같습니다.

  • 라우트된 이벤트가 처리됨으로 표시되면 해당 경로를 따라 있는 다른 요소가 해당 라우트된 이벤트를 다시 처리할 필요가 없습니다.

  • 라우트된 이벤트가 처리됨으로 표시되지 않으면 이전에 경로를 따라 있었던 다른 수신기가 처리기를 등록하지 않도록 선택했거나 등록된 처리기가 이벤트 데이터를 조작하고 Handledtrue로 설정하지 않도록 선택한 것입니다. (또는 현재 수신기가 경로의 첫 번째 지점일 수도 있습니다.) 이제 현재 수신기의 처리기에는 세 가지 가능한 작업 과정이 있습니다.

    • 아무 작업도 수행하지 않습니다. 이벤트는 처리되지 않고 다음 수신기로 라우트됩니다.

    • 이벤트에 대한 응답으로 코드를 실행하지만 수행된 작업이 이벤트를 처리됨으로 표시하도록 보장할 만큼 충분히 효과적인지에 대한 결정을 내립니다. 이벤트는 다음 수신기로 라우트됩니다.

    • 이벤트에 대한 응답으로 코드를 실행합니다. 처리기에 전달된 이벤트 데이터에서 이벤트를 처리됨으로 표시합니다. 이는 수행된 작업이 처리됨으로 표시하도록 보장할 만큼 충분히 효과적이라고 생각되기 때문입니다. 이벤트는 계속해서 다음 수신기로 라우트되지만 이벤트 데이터에서 Handled=true이므로 handledEventsToo 수신기만 추가 처리기를 호출할 수 있습니다.

이 개념적 디자인은 앞에서 언급한 라우트 동작을 통해 강화됩니다. 경로를 따라 있는 이전 처리기가 이미 Handledtrue로 설정한 경우에도 호출되는 라우트된 이벤트에 대한 처리기를 연결하기가 더 어렵습니다(하지만 코드 또는 스타일에서는 가능함).

, 라우트된 이벤트의 클래스 처리, 라우트된 이벤트를 로 표시하는 것이 적절한 시기에 대한 권장 사항은 Handled라우트된 이벤트를 처리된 것으로 표시 및 클래스 처리Handled를 참조하세요.

애플리케이션에서는 일반적으로 버블링 라우트된 이벤트를 발생시킨 개체에서 이 이벤트를 처리하며 이벤트의 라우트 특징은 전혀 관련이 없습니다. 하지만 요소 트리에서 더 위쪽에 있는 요소에 같은 라우트된 이벤트에 대한 연결된 처리기가 있는 경우 예기치 않은 부작용을 방지하기 위해 이벤트 데이터에서는 라우트된 이벤트를 처리됨으로 표시하는 것이 좋습니다.

클래스 처리기

DependencyObject에서 파생된 클래스를 정의할 경우 클래스의 선언되거나 상속된 이벤트 멤버인 라우트된 이벤트에 대한 클래스 처리기를 정의하고 연결할 수도 있습니다. 라우트된 이벤트가 경로의 요소 인스턴스에 도달할 때마다 클래스 처리기는 해당 클래스의 인스턴스에 연결된 인스턴스 수신기 처리기보다 먼저 호출됩니다.

일부 WPF 컨트롤에는 특정 라우트된 이벤트에 대한 고유 클래스 처리 기능이 있습니다. 이 기능을 사용하면 표면적으로는 라우트된 이벤트가 발생하지 않은 것처럼 보이지만 실제로는 클래스가 처리되는 것이고 특정 기술을 사용하면 라우트된 이벤트가 인스턴스 처리기에서 처리될 수 있습니다. 또한 많은 기본 클래스 및 컨트롤이 클래스 처리 동작을 재정의하는 데 사용될 수 있는 가상 메서드를 표시합니다. 원하지 않는 클래스 처리를 해결하는 방법 및 사용자 지정 클래스에서 자체 클래스 처리를 정의하는 방법에 대한 자세한 내용은 라우트된 이벤트를 처리된 것으로 표시 및 클래스 처리를 참조하세요.

WPF의 연결된 이벤트

XAML 언어는 연결된 이벤트라는 특수 이벤트 형식을 정의합니다. 연결된 이벤트를 사용하여 특정 이벤트에 대한 처리기를 임의 요소에 추가할 수 있습니다. 이벤트를 처리하는 요소는 연결된 이벤트를 정의하거나 상속할 필요가 없고 이벤트를 발생시키는 개체 및 인스턴스를 처리하는 대상은 모두 해당 이벤트를 클래스 멤버로 정의하거나 소유하면 안 됩니다.

WPF 입력 시스템은 연결된 이벤트를 광범위하게 사용합니다. 하지만 이러한 연결된 이벤트는 거의 모두 기본 요소를 통해 전달됩니다. 그 다음에 입력 이벤트는 기본 요소 클래스의 멤버인 해당하는 연결되지 않은 라우트된 이벤트로 표시됩니다. 예를 들어 기본 연결된 이벤트 Mouse.MouseDown은 XAML이나 코드에서 연결된 이벤트 구문을 처리하는 대신 해당 UIElement에서 MouseDown을 사용하여 지정된 UIElement에서 더 쉽게 처리할 수 있습니다.

WPF의 연결된 이벤트에 대한 자세한 내용은 연결된 이벤트 개요를 참조하세요.

XAML의 정규화된 이벤트 이름

typename.eventname 연결된 이벤트 구문과 비슷하지만 엄격히 말하면 연결된 이벤트 사용이 아닌 또 다른 구문 사용은 자식 요소에서 발생한 라우트된 이벤트에 대한 처리기를 연결하는 경우입니다. 공통 부모에 관련 라우트된 이벤트가 멤버로 포함되지 않더라도 처리기를 공통 부모에 연결하면 이벤트 라우트를 이용할 수 있습니다. 이 예제를 다시 살펴보세요.

<Border Height="50" Width="300" BorderBrush="Gray" BorderThickness="1">
  <StackPanel Background="LightGray" Orientation="Horizontal" Button.Click="CommonClickHandler">
    <Button Name="YesButton" Width="Auto" >Yes</Button>
    <Button Name="NoButton" Width="Auto" >No</Button>
    <Button Name="CancelButton" Width="Auto" >Cancel</Button>
  </StackPanel>
</Border>

여기서 처리기가 추가되는 부모 요소 수신기는 StackPanel입니다. 하지만 이 수신기는 선언된 라우트된 이벤트에 대한 처리기를 추가하며 Button 클래스에 의해 발생합니다(실제로는 ButtonBase이나 상속을 통해 Button에 사용 가능). Button은 이벤트를 "소유"하지만 라우트된 이벤트 시스템에서는 라우트된 이벤트에 대한 처리기를 UIElement 또는 ContentElement 인스턴스 수신기에 연결할 수 있습니다. 그렇지 않으면 CLR(공용 언어 런타임) 이벤트에 대한 수신기를 연결할 수 있습니다. 일반적으로 이러한 정규화된 이벤트 특성 이름에 대한 기본 xmlns 네임스페이스는 기본 WPF xmlns 네임스페이스이지만 사용자 지정 라우트된 이벤트에 대한 접두사가 추가된 네임스페이스를 지정할 수도 있습니다. xmlns에 대한 자세한 내용은 WPF XAML을 위한 XAML 네임스페이스 및 네임스페이스 매핑을 참조하세요.

WPF 입력 이벤트

WPF 플랫폼 내에서 라우트된 이벤트가 자주 사용되는 한 가지 애플리케이션은 입력 이벤트와 관련됩니다. WPF에서 터널링 라우트된 이벤트에는 규칙에 따라 "Preview"라는 단어가 접두사로 추가됩니다. 입력 이벤트는 보통 쌍으로 제공되고, 쌍 중 하나는 버블링 이벤트가 되고 다른 하나는 터널링 이벤트가 됩니다. 예를 들어 KeyDown 이벤트와 PreviewKeyDown 이벤트에는 같은 시그니처가 포함되며, 전자는 버블링 입력 이벤트가 되고 후자는 터널링 입력 이벤트가 됩니다. 입력 이벤트에 버블링 버전만 포함되거나 직접 라우트된 버전만 포함되는 경우도 있습니다. 문서의 라우트된 이벤트 항목에서는 라우트된 이벤트가 있는 경우 대체 라우트 전략을 통해 비슷한 라우트된 이벤트를 상호 참조하고 관리되는 참조 페이지의 섹션에서는 각 라우트된 이벤트의 라우트 전략을 설명합니다.

쌍으로 제공되는 WPF 입력 이벤트는 마우스 단추 누르기와 같은 입력의 단일 사용자 작업이 해당 쌍의 라우트된 이벤트를 둘 다 순차적으로 발생시키도록 구현됩니다. 먼저 터널링 이벤트가 발생하고 경로를 이동합니다. 그 다음에 버블링 이벤트가 발생하고 경로를 이동합니다. 문자 그대로 두 이벤트는 같은 이벤트 데이터 인스턴스를 공유합니다. 이는 버블링 이벤트를 발생시키는 구현 클래스의 RaiseEvent 메서드 호출이 터널링 이벤트의 이벤트 데이터를 수신하고 이를 발생한 새 이벤트에서 다시 사용하기 때문입니다. 터널링 이벤트에 대한 처리기가 있는 수신기는 우선적으로 라우트된 이벤트를 처리됨으로 표시할 수 있습니다(클래스 처리기 우선, 그 다음에 인스턴스 처리기). 터널링 경로에 따라 있는 요소가 라우트된 이벤트를 처리됨으로 표시한 경우 버블링 이벤트에 대한 이미 처리된 이벤트 데이터가 전송되고 해당하는 버블링 입력 이벤트에 대한 일반 연결된 처리기는 호출되지 않습니다. 표면적으로는 처리된 버블링 이벤트가 발생하지 않은 경우처럼 보입니다. 이 처리 동작은 컨트롤 합치기에 유용합니다. 이 경우 모든 적중 테스트 기반 입력 이벤트나 포커스 기반 입력 이벤트를 복합 부분이 아닌 최종 컨트롤이 보고하도록 할 수 있습니다. 최종 컨트롤 요소는 합치기에서 루트에 더 근접하므로 터널링 이벤트를 먼저 클래스에서 처리하고 라우트된 이벤트를 컨트롤에 대한 관련성이 더 높은 이벤트(컨트롤 클래스를 지원하는 코드의 일부)로 “대체”할 수 있습니다.

입력 이벤트 처리가 적용되는 방식에 대한 그림으로 다음 입력 이벤트 예제를 살펴보겠습니다. 다음 트리 그림에서 leaf element #2PreviewMouseDownMouseDown 이벤트의 소스입니다.

이벤트 라우팅 다이어그램

이벤트 처리 순서는 다음과 같습니다.

  1. 루트 요소의 PreviewMouseDown(터널링).

  2. 중간 요소 #1의 PreviewMouseDown(터널링).

  3. 소스 요소 #2의 PreviewMouseDown(터널링).

  4. 소스 요소 #2의 MouseDown(버블링).

  5. 중간 요소 #1의 MouseDown(버블링).

  6. 루트 요소의 MouseDown(버블링).

라우트된 이벤트 처리기 대리자는 두 개체인, 이벤트를 발생시킨 개체 및 처리기가 호출된 개체에 대한 참조를 제공합니다. 처리기가 호출된 개체는 sender 매개 변수를 통해 보고된 개체입니다. 이벤트가 처음 발생한 개체는 이벤트 데이터의 Source 속성을 통해 보고됩니다. 라우트된 이벤트는 같은 개체에서 계속 발생하고 처리될 수 있습니다. 이 경우 senderSource는 동일합니다(이벤트 처리 예제 목록에서 3단계 및 4단계를 사용하는 경우).

터널링 및 버블링 때문에 부모 요소는 Source가 자식 요소 중 하나인 입력 이벤트를 수신합니다. 소스 이벤트가 무엇인지 알아야 하는 경우 Source 속성에 액세스하여 소스 요소를 식별할 수 있습니다.

일반적으로 입력 이벤트가 Handled로 표시되고 나면 추가 처리기가 호출되지 않습니다. 보통은 입력 이벤트의 의미에 대한 애플리케이션별 논리 처리를 해결하는 처리기가 호출된 후 바로 입력 이벤트를 처리됨으로 표시해야 합니다.

Handled 상태에 대한 이 일반적인 문의 예외는 의도적으로 이벤트 데이터의 Handled 상태를 무시하기 위해 등록된 입력 이벤트 처리기가 한쪽 경로를 따라 호출되는 경우입니다. 자세한 내용은 미리 보기 이벤트 또는 라우트된 이벤트를 처리된 것으로 표시 및 클래스 처리를 참조하세요.

터널링 이벤트와 버블링 이벤트 간에 공유된 이벤트 데이터 모델과 터널링 이벤트 다음에 버블링 이벤트가 발생하는 순차적 발생은 일반적으로 모든 라우트된 이벤트에 적용되는 개념이 아닙니다. 이 동작은 특별히 WPF 입력 디바이스가 입력 이벤트 쌍을 발생시키고 연결하도록 선택하는 방법을 통해 구현됩니다. 자체 입력 이벤트를 구현하는 것은 고급 시나리오이지만 자체 입력 이벤트에 대해 해당 모델을 따르도록 선택할 수도 있습니다.

특정 클래스는 대개 특정 사용자 기반 입력 이벤트가 해당 컨트롤 내에서 가지는 의미를 다시 정의하고 새 이벤트를 발생시키기 위해 특정 입력 이벤트를 클래스에서 처리하도록 선택합니다. 자세한 내용은 라우트된 이벤트를 처리된 것으로 표시 및 클래스 처리를 참조하세요.

입력 및 입력과 이벤트가 일반적인 애플리케이션 시나리오에서 상호 작용하는 방식에 대한 자세한 내용은 입력 개요를 참조하세요.

EventSetter 및 EventTrigger

스타일에서는 EventSetter를 사용하여 미리 선언된 XAML 이벤트 처리 구문을 태그에 포함할 수 있습니다. 스타일이 적용되면 참조된 처리기가 스타일 지정된 인스턴스에 추가됩니다. 라우트된 이벤트의 경우 EventSetter만 선언할 수 있습니다. 다음은 예제입니다. 여기서 참조되는 b1SetColor 메서드는 코드 숨김 파일에 있습니다.

<StackPanel
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  x:Class="SDKSample.EventOvw2"
  Name="dpanel2"
  Initialized="PrimeHandledToo"
>
  <StackPanel.Resources>
    <Style TargetType="{x:Type Button}">
      <EventSetter Event="Click" Handler="b1SetColor"/>
    </Style>
  </StackPanel.Resources>
  <Button>Click me</Button>
  <Button Name="ThisButton" Click="HandleThis">
    Raise event, handle it, use handled=true handler to get it anyway.
  </Button>
</StackPanel>

여기서 얻는 장점은 애플리케이션의 단추에 적용할 수 있는 많은 기타 정보를 스타일에 포함할 수 있다는 것이고 EventSetter를 해당 스타일의 일부로 포함하면 태그 수준에서도 코드 재사용률을 높일 수 있습니다. 또한 EventSetter는 메서드 이름을 일반적인 애플리케이션 및 페이지 태그에서 한 단계 더 추상화합니다.

WPF의 라우트된 이벤트와 애니메이션 기능을 결합하는 또 다른 특수화된 구문은 EventTrigger입니다. EventSetter와 마찬가지로 EventTrigger에는 라우트된 이벤트만 사용할 수 있습니다. 일반적으로 EventTrigger는 스타일의 일부로 선언되지만, EventTrigger는 페이지 수준 요소에서 Triggers 컬렉션의 일부로 선언되거나 ControlTemplate에서 선언될 수 있습니다. EventTrigger를 사용하면 라우트된 이벤트가 해당 이벤트에 대한 EventTrigger를 선언하는 경로의 요소에 도달할 때마다 실행되는 Storyboard를 지정할 수 있습니다. 이벤트를 처리하고 이벤트가 기존 스토리보드를 시작하게 하는 방법에 비해 EventTrigger의 장점은 EventTrigger가 스토리보드를 더 세밀하게 제어할 수 있다는 것입니다. 자세한 내용은 Storyboard를 시작한 후 이벤트 트리거를 사용하여 제어를 참조하세요.

라우트된 이벤트에 대한 추가 정보

이 항목에서는 기본 개념을 설명하고 다양한 기본 요소 및 컨트롤에 이미 있는 라우트된 이벤트에 응답하는 방법 및 시점에 대한 지침을 제공하는 관점에서 주로 라우트된 이벤트를 살펴봅니다. 하지만 특수화된 이벤트 데이터 클래스 및 대리자와 같은 모든 필요한 지원과 함께 사용자 지정 클래스에서 자체 라우트된 이벤트를 만들 수 있습니다. 라우트된 이벤트 소유자는 클래스일 수 있지만 라우트된 이벤트는 UIElement 또는 ContentElement 파생 클래스에서 발생하고 처리되어야 유용하게 사용할 수 있습니다. 사용자 지정 이벤트에 대한 자세한 내용은 사용자 지정 라우트된 이벤트 만들기를 참조하세요.

참고 항목