사용자 지정 라우트된 이벤트를 만드는 방법(WPF .NET)

WPF(Windows Presentation Foundation) 애플리케이션 개발자 및 구성 요소 작성자는 사용자 지정 라우트된 이벤트를 만들어 CLR(공용 언어 런타임) 이벤트의 기능을 확장할 수 있습니다. 라우트된 이벤트 기능에 대한 자세한 내용은 라우트된 이벤트를 사용하는 이유를 참조하세요. 이 문서에서는 사용자 지정 라우트된 이벤트 만들기의 기본 사항을 설명합니다.

중요

.NET 7 및 .NET 6에 관한 데스크톱 가이드 설명서는 제작 중입니다.

필수 구성 요소

이 문서에서는 독자들이 라우트된 이벤트에 대한 기본 지식을 갖고 있으며 라우트된 이벤트 개요를 읽었다고 가정합니다. XAML(Extensible Application Markup Language)에 익숙하고 WPF(Windows Presentation Foundation) 애플리케이션을 작성하는 방법을 알고 있으면 이 문서의 예제를 따라 하는 데 도움이 됩니다.

라우트된 이벤트 단계

라우트된 이벤트를 만드는 기본 단계는 다음과 같습니다.

  1. RegisterRoutedEvent 메서드를 사용하여 RoutedEvent를 등록합니다.

  2. 등록 호출은 등록된 이벤트 이름, 라우팅 전략 및 기타 이벤트 세부 정보가 있는, 라우트된 이벤트 식별자라는 RoutedEvent 인스턴스를 반환합니다. 정적 읽기 전용 필드에 식별자를 할당합니다. 규칙은 다음과 같습니다.

    • 버블링 전략을 사용하여 라우트된 이벤트의 식별자는 <event name>Event로 이름이 지정됩니다. 예를 들어 이벤트 이름이 Tap이면 식별자의 이름을 TapEvent로 지정해야 합니다.
    • 터널링 전략을 사용하여 라우트된 이벤트의 식별자는 Preview<event name>Event로 이름이 지정됩니다. 예를 들어 이벤트 이름이 Tap이면 식별자의 이름을 PreviewTapEvent로 지정해야 합니다.
  3. CLR 추가제거 이벤트 접근자를 정의합니다. CLR 이벤트 접근자가 없으면 UIElement.AddHandlerUIElement.RemoveHandler 메서드에 대한 직접 호출을 통해서만 이벤트 처리기를 추가하거나 제거할 수 있습니다. CLR 이벤트 접근자가 있으면 다음과 같은 이벤트 처리기 할당 메커니즘을 얻을 수 있습니다.

    • XAML(Extensible Application Markup Language)의 경우 특성 구문을 사용하여 이벤트 처리기를 추가할 수 있습니다.
    • C#의 경우 +=-= 연산자를 사용하여 이벤트 처리기를 추가하거나 제거할 수 있습니다.
    • VB의 경우 AddHandlerRemoveHandler 문을 사용하여 이벤트 처리기를 추가하거나 제거할 수 있습니다.
  4. 라우트된 이벤트를 트리거하기 위한 사용자 지정 논리를 추가합니다. 예를 들어 논리는 사용자 입력과 애플리케이션 상태에 따라 이벤트를 트리거할 수 있습니다.

예제

다음 예제는 사용자 지정 컨트롤 라이브러리에서 CustomButton 클래스를 구현합니다. Button에서 파생되는 CustomButton 클래스는 다음을 수행합니다.

  1. RegisterRoutedEvent 메서드를 사용하여 ConditionalClick이라는 RoutedEvent를 등록하고 등록하는 동안 버블링 전략을 지정합니다.
  2. 등록 호출에서 반환되는 RoutedEvent 인스턴스를 ConditionalClickEvent라는 정적 읽기 전용 필드에 할당합니다.
  3. CLR 추가제거 이벤트 접근자를 정의합니다.
  4. CustomButton이 클릭되고 외부 조건이 적용될 때 사용자 지정 라우트된 이벤트를 발생시키는 사용자 지정 논리를 추가합니다. 예제 코드는 재정의된 OnClick 가상 메서드 내에서 ConditionalClick 라우트된 이벤트를 발생시키지만 원하는 방식으로 이벤트를 발생시킬 수 있습니다.
public class CustomButton : Button
{
    // Register a custom routed event using the Bubble routing strategy.
    public static readonly RoutedEvent ConditionalClickEvent = EventManager.RegisterRoutedEvent(
        name: "ConditionalClick",
        routingStrategy: RoutingStrategy.Bubble,
        handlerType: typeof(RoutedEventHandler),
        ownerType: typeof(CustomButton));

    // Provide CLR accessors for assigning an event handler.
    public event RoutedEventHandler ConditionalClick
    {
        add { AddHandler(ConditionalClickEvent, value); }
        remove { RemoveHandler(ConditionalClickEvent, value); }
    }

    void RaiseCustomRoutedEvent()
    {
        // Create a RoutedEventArgs instance.
        RoutedEventArgs routedEventArgs = new(routedEvent: ConditionalClickEvent);

        // Raise the event, which will bubble up through the element tree.
        RaiseEvent(routedEventArgs);
    }

    // For demo purposes, we use the Click event as a trigger.
    protected override void OnClick()
    {
        // Some condition combined with the Click event will trigger the ConditionalClick event.
        if (DateTime.Now > new DateTime())
            RaiseCustomRoutedEvent();

        // Call the base class OnClick() method so Click event subscribers are notified.
        base.OnClick();
    }
}
Public Class CustomButton
    Inherits Button

    ' Register a custom routed event with the Bubble routing strategy.
    Public Shared ReadOnly ConditionalClickEvent As RoutedEvent = EventManager.RegisterRoutedEvent(
        name:="ConditionalClick",
        routingStrategy:=RoutingStrategy.Bubble,
        handlerType:=GetType(RoutedEventHandler),
        ownerType:=GetType(CustomButton))

    ' Provide CLR accessors to support event handler assignment.
    Public Custom Event ConditionalClick As RoutedEventHandler

        AddHandler(value As RoutedEventHandler)
            [AddHandler](ConditionalClickEvent, value)
        End AddHandler

        RemoveHandler(value As RoutedEventHandler)
            [RemoveHandler](ConditionalClickEvent, value)
        End RemoveHandler

        RaiseEvent(sender As Object, e As RoutedEventArgs)
            [RaiseEvent](e)
        End RaiseEvent

    End Event

    Private Sub RaiseCustomRoutedEvent()

        ' Create a RoutedEventArgs instance.
        Dim routedEventArgs As New RoutedEventArgs(routedEvent:=ConditionalClickEvent)

        ' Raise the event, which will bubble up through the element tree.
        [RaiseEvent](routedEventArgs)

    End Sub

    ' For demo purposes, we use the Click event as a trigger.
    Protected Overrides Sub OnClick()

        ' Some condition combined with the Click event will trigger the ConditionalClick event.
        If Date.Now > New DateTime() Then RaiseCustomRoutedEvent()

        ' Call the base class OnClick() method so Click event subscribers are notified.
        MyBase.OnClick()

    End Sub
End Class

이 예제에는 XAML 태그를 사용하여 CustomButton의 인스턴스를 StackPanel에 추가하고 Handler_ConditionalClick 메서드를 CustomButtonStackPanel1 요소의 ConditionalClick 이벤트 처리기로 할당하는 별도의 WPF 애플리케이션이 포함되어 있습니다.

<Window x:Class="CodeSample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:custom="clr-namespace:WpfControl;assembly=WpfControlLibrary"
        Title="How to create a custom routed event" Height="100" Width="300">

    <StackPanel Name="StackPanel1" custom:CustomButton.ConditionalClick="Handler_ConditionalClick">
        <custom:CustomButton
            Name="customButton"
            ConditionalClick="Handler_ConditionalClick"
            Content="Click to trigger a custom routed event"
            Background="LightGray">
        </custom:CustomButton>
    </StackPanel>
</Window>

코드 숨김에서 WPF 애플리케이션은 Handler_ConditionalClick 이벤트 처리기 메서드를 정의합니다. 이벤트 처리기 메서드는 코드 숨김에서만 구현할 수 있습니다.

// The ConditionalClick event handler.
private void Handler_ConditionalClick(object sender, RoutedEventArgs e)
{
    string senderName = ((FrameworkElement)sender).Name;
    string sourceName = ((FrameworkElement)e.Source).Name;

    Debug.WriteLine($"Routed event handler attached to {senderName}, " +
        $"triggered by the ConditionalClick routed event raised on {sourceName}.");
}

// Debug output when CustomButton is clicked:
// Routed event handler attached to CustomButton,
//     triggered by the ConditionalClick routed event raised on CustomButton.
// Routed event handler attached to StackPanel1,
//     triggered by the ConditionalClick routed event raised on CustomButton.
' The ConditionalClick event handler.
Private Sub Handler_ConditionalClick(sender As Object, e As RoutedEventArgs)

    Dim sourceName As String = CType(e.Source, FrameworkElement).Name
    Dim senderName As String = CType(sender, FrameworkElement).Name

    Debug.WriteLine($"Routed event handler attached to {senderName}, " +
        $"triggered by the ConditionalClick routed event raised on {sourceName}.")

End Sub

' Debug output when CustomButton is clicked:
' Routed event handler attached to CustomButton,
'     triggered by the ConditionalClick routed event raised on CustomButton.
' Routed event handler attached to StackPanel1,
'     triggered by the ConditionalClick routed event raised on CustomButton.

CustomButton을 클릭하는 경우:

  1. ConditionalClick 라우트된 이벤트가 CustomButton에서 발생합니다.
  2. CustomButton에 연결된 Handler_ConditionalClick 이벤트 처리기가 트리거됩니다.
  3. ConditionalClick 라우트된 이벤트는 StackPanel1까지 요소 트리 위로 트래버스합니다.
  4. StackPanel1에 연결된 Handler_ConditionalClick 이벤트 처리기가 트리거됩니다.
  5. ConditionalClick 라우트된 이벤트는 요소 트리를 계속 올라가 다른 트래버스된 요소에 연결된 다른 ConditionalClick 이벤트 처리기를 트리거할 수 있습니다.

Handler_ConditionalClick 이벤트 처리기는 트리거한 이벤트에 대한 다음 정보를 가져옵니다.

  • 이벤트 처리기가 연결된 요소인 보낸 사람 개체. sender는 처리기가 처음 실행될 때 CustomButton이 되고 두 번째 실행될 때 StackPanel1이 됩니다.
  • 원래 이벤트를 발생시킨 RoutedEventArgs.Source 개체 이 예제에서 Source는 항상 CustomButton입니다.

참고

라우트된 이벤트와 CLR 이벤트의 주요 차이점은 라우트된 이벤트가 요소 트리를 트래버스하여 처리기를 찾는 반면 CLR 이벤트는 요소 트리를 트래버스하지 않으며 처리기는 이벤트를 발생시킨 원본 개체에만 연결할 수 있다는 것입니다. 따라서 라우트된 이벤트 sender는 요소 트리의 모든 트래버스된 요소일 수 있습니다.

이벤트 등록 호출에서 라우팅 전략을 Tunnel로 설정하는 것을 제외하고 버블링 이벤트와 동일한 방식으로 터널링 이벤트를 만들 수 있습니다. 터널링 이벤트에 대한 자세한 내용은 WPF 입력 이벤트를 참조하세요.

추가 정보