미리 보기 이벤트(WPF .NET)

터널링 이벤트라고도 하는 미리 보기 이벤트는 애플리케이션 루트 요소에서 이벤트를 발생시킨 요소까지 요소 트리를 통해 아래로 트래버스하는 라우트된 이벤트입니다. 이벤트를 발생시키는 요소는 이벤트 데이터의 Source로 보고됩니다. 모든 이벤트 시나리오에서 미리 보기 이벤트를 지원하거나 필요로 하는 것은 아닙니다. 이 문서에서는 미리 보기 이벤트가 존재하는 위치와 애플리케이션 또는 구성 요소가 미리 보기 이벤트와 상호 작용하는 방법을 설명합니다. 미리 보기 이벤트를 만드는 방법에 대한 자세한 내용은 사용자 지정 라우트된 이벤트를 만드는 방법을 참조하세요.

중요

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

필수 구성 요소

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

처리됨으로 표시된 미리 보기 이벤트

미리 보기 이벤트를 이벤트 데이터에서 처리됨으로 표시할 때는 주의해야 합니다. 발생시킨 요소가 아닌 요소에서 미리 보기 이벤트를 처리됨으로 표시하면 이벤트를 발생시킨 요소에서 이벤트를 처리하지 못할 수 있습니다. 경우에 따라 의도적으로 미리 보기 이벤트를 처리됨으로 표시할 수 있습니다. 예를 들어 복합 컨트롤은 개별 구성 요소에서 발생한 이벤트를 억제하고 전체 컨트롤에서 발생한 이벤트로 대체할 수 있습니다. 컨트롤의 사용자 지정 이벤트는 구성 요소 상태 관계에 따라 사용자 지정된 이벤트 데이터와 트리거를 제공할 수 있습니다.

입력 이벤트의 경우 이벤트 데이터는 각 이벤트의 미리 보기 및 비 미리 보기(버블링) 이벤트에서 공유됩니다. 미리 보기 이벤트 클래스 처리기를 사용하여 입력 이벤트를 처리됨으로 표시하면 일반적으로 버블링 입력 이벤트의 클래스 처리기가 호출되지 않습니다. 또는 미리 보기 이벤트 인스턴스 처리기를 사용하여 이벤트를 처리됨으로 표시하면 일반적으로 버블링 입력 이벤트의 인스턴스 처리기가 호출되지 않습니다. 이벤트가 처리됨으로 표시되더라도 클래스 처리기와 인스턴스 처리기가 호출되도록 구성할 수 있지만 해당 처리기 구성은 일반적이지 않습니다. 클래스 처리에 대한 자세한 내용과 미리 보기 이벤트와의 관계는 라우트된 이벤트를 처리된 것으로 표시 및 클래스 처리를 참조하세요.

참고

모든 미리 보기 이벤트가 터널링 이벤트인 것은 아닙니다. 예를 들어 PreviewMouseLeftButtonDown 입력 이벤트는 요소 트리를 통해 하향 경로를 따르지만 경로의 각 UIElement에서 발생하고 다시 발생하는 직접 라우트된 이벤트입니다.

컨트롤에서 억제하는 이벤트 문제 해결

일부 복합 컨트롤은 사용자 지정된 상위 수준 이벤트로 바꾸기 위해 구성 요소 수준에서 입력 이벤트를 표시하지 않습니다. 예를 들어 WPF ButtonBaseMouseLeftButtonDown 버블링 입력 이벤트를 해당 OnMouseLeftButtonDown 메서드에서 처리됨으로 표시하고 Click 이벤트를 발생시킵니다. MouseLeftButtonDown 이벤트와 해당 이벤트 데이터는 요소 트리 경로를 따라 계속되지만 이벤트가 이벤트 데이터에서 Handled로 표시되므로 처리된 이벤트에 응답하도록 구성된 처리기만 호출됩니다.

애플리케이션의 루트를 향하는 다른 요소가 처리됨으로 표시된 라우트된 이벤트에 호출되도록 하려면 다음 방법 중 하나를 사용하면 됩니다.

  • handledEventsToo 매개 변수가 true로 설정된 UIElement.AddHandler(RoutedEvent, Delegate, Boolean) 메서드를 호출하여 처리기를 연결합니다. 이 방법을 사용하려면 연결할 요소에 대한 개체 참조를 가져온 후 코드 숨김에서 이벤트 처리기를 연결해야 합니다.

  • 처리됨으로 표시된 이벤트가 버블링 이벤트이면 해당하는 미리 보기 이벤트의 처리기를 연결합니다(사용 가능한 경우). 예를 들어 컨트롤에서 MouseLeftButtonDown 이벤트를 표시하지 않으면 대신 PreviewMouseLeftButtonDown 이벤트의 처리기를 연결할 수 있습니다. 이 방법은 터널링버블링 라우팅 전략을 모두 구현하고 이벤트 데이터를 공유하는 기본 요소 입력 이벤트에만 작동합니다.

다음 예제에서는 TextBox가 포함된 componentWrapper라는 기본 사용자 지정 컨트롤을 구현합니다. 컨트롤이 outerStackPanel이라고 하는 StackPanel에 추가됩니다.

<StackPanel Name="outerStackPanel"
    VerticalAlignment="Center"
    custom:ComponentWrapper.CustomKey="Handler_PrintEventInfo"
    TextBox.KeyDown="Handler_PrintEventInfo"
    TextBox.PreviewKeyDown="Handler_PrintEventInfo" >
    <custom:ComponentWrapper
        x:Name="componentWrapper"
        TextBox.KeyDown="ComponentWrapper_KeyDown"
        custom:ComponentWrapper.CustomKey="Handler_PrintEventInfo"
        HorizontalAlignment="Center">
        <TextBox Name="componentTextBox" Width="200" KeyDown="Handler_PrintEventInfo" />
    </custom:ComponentWrapper>
</StackPanel>

componentWrapper 컨트롤은 키 입력이 발생할 때마다 TextBox 구성 요소에서 발생하는 KeyDown 버블링 이벤트를 수신 대기합니다. 이러한 경우 componentWrapper 컨트롤은 다음을 수행합니다.

  1. KeyDown 버블링 라우트된 이벤트가 표시되지 않도록 처리됨으로 표시합니다. 따라서 코드 숨김에서 처리됨KeyDown 이벤트에 응답하도록 구성된 outerStackPanel 처리기만 트리거됩니다. KeyDown 이벤트의 XAML에 연결된 outerStackPanel 처리기는 호출되지 않습니다.

  2. CustomKey 이벤트의 outerStackPanel 처리기를 트리거하는 CustomKey라고 하는 사용자 지정 버블링 라우트된 이벤트를 발생시킵니다.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        // Attach a handler on outerStackPanel that will be invoked by handled KeyDown events.
        outerStackPanel.AddHandler(KeyDownEvent, new RoutedEventHandler(Handler_PrintEventInfo), 
            handledEventsToo: true);
    }

    private void ComponentWrapper_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
    {
        Handler_PrintEventInfo(sender, e);

        Debug.WriteLine("KeyDown event marked as handled on componentWrapper.\r\n" +
            "CustomKey event raised on componentWrapper.");

        // Mark the event as handled.
        e.Handled = true;

        // Raise the custom click event.
        componentWrapper.RaiseCustomRoutedEvent();
    }

    private void Handler_PrintEventInfo(object sender, System.Windows.Input.KeyEventArgs e)
    {
        string senderName = ((FrameworkElement)sender).Name;
        string sourceName = ((FrameworkElement)e.Source).Name;
        string eventName = e.RoutedEvent.Name;
        string handledEventsToo = e.Handled ? " Parameter handledEventsToo set to true." : "";

        Debug.WriteLine($"Handler attached to {senderName} " +
            $"triggered by {eventName} event raised on {sourceName}.{handledEventsToo}");
    }

    private void Handler_PrintEventInfo(object sender, RoutedEventArgs e)
    {
        string senderName = ((FrameworkElement)sender).Name;
        string sourceName = ((FrameworkElement)e.Source).Name;
        string eventName = e.RoutedEvent.Name;
        string handledEventsToo = e.Handled ? " Parameter handledEventsToo set to true." : "";

        Debug.WriteLine($"Handler attached to {senderName} " +
            $"triggered by {eventName} event raised on {sourceName}.{handledEventsToo}");
    }

    // Debug output:
    //
    // Handler attached to outerStackPanel triggered by PreviewKeyDown event raised on componentTextBox.
    // Handler attached to componentTextBox triggered by KeyDown event raised on componentTextBox.
    // Handler attached to componentWrapper triggered by KeyDown event raised on componentTextBox.
    // KeyDown event marked as handled on componentWrapper.
    // CustomKey event raised on componentWrapper.
    // Handler attached to componentWrapper triggered by CustomKey event raised on componentWrapper.
    // Handler attached to outerStackPanel triggered by CustomKey event raised on componentWrapper.
    // Handler attached to outerStackPanel triggered by KeyDown event raised on componentTextBox. Parameter handledEventsToo set to true.
}

public class ComponentWrapper : StackPanel
{
    // Register a custom routed event using the Bubble routing strategy.
    public static readonly RoutedEvent CustomKeyEvent = 
        EventManager.RegisterRoutedEvent(
            name: "CustomKey",
            routingStrategy: RoutingStrategy.Bubble,
            handlerType: typeof(RoutedEventHandler),
            ownerType: typeof(ComponentWrapper));

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

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

        // Raise the event, which will bubble up through the element tree.
        RaiseEvent(routedEventArgs);
    }
}
Partial Public Class MainWindow
        Inherits Window

        Public Sub New()
        InitializeComponent()

        ' Attach a handler on outerStackPanel that will be invoked by handled KeyDown events.
        outerStackPanel.[AddHandler](KeyDownEvent, New RoutedEventHandler(AddressOf Handler_PrintEventInfo),
                                     handledEventsToo:=True)
    End Sub

    Private Sub ComponentWrapper_KeyDown(sender As Object, e As KeyEventArgs)
        Handler_PrintEventInfo(sender, e)
        Debug.WriteLine("KeyDown event marked as handled on componentWrapper." &
                        vbCrLf & "CustomKey event raised on componentWrapper.")

        ' Mark the event as handled.
        e.Handled = True

        ' Raise the custom click event.
        componentWrapper.RaiseCustomRoutedEvent()
    End Sub

    Private Sub Handler_PrintEventInfo(sender As Object, e As KeyEventArgs)
        Dim senderName As String = CType(sender, FrameworkElement).Name
        Dim sourceName As String = CType(e.Source, FrameworkElement).Name
        Dim eventName As String = e.RoutedEvent.Name
        Dim handledEventsToo As String = If(e.Handled, " Parameter handledEventsToo set to true.", "")
        Debug.WriteLine($"Handler attached to {senderName} " &
                        $"triggered by {eventName} event raised on {sourceName}.{handledEventsToo}")
    End Sub

    Private Sub Handler_PrintEventInfo(sender As Object, e As RoutedEventArgs)
        Dim senderName As String = CType(sender, FrameworkElement).Name
        Dim sourceName As String = CType(e.Source, FrameworkElement).Name
        Dim eventName As String = e.RoutedEvent.Name
        Dim handledEventsToo As String = If(e.Handled, " Parameter handledEventsToo set to true.", "")
        Debug.WriteLine($"Handler attached to {senderName} " &
                        $"triggered by {eventName} event raised on {sourceName}.{handledEventsToo}")
    End Sub

    ' Debug output
    '
    ' Handler attached to outerStackPanel triggered by PreviewKeyDown event raised on componentTextBox.
    ' Handler attached to componentTextBox triggered by KeyDown event raised on componentTextBox.
    ' Handler attached to componentWrapper triggered by KeyDown event raised on componentTextBox.
    ' KeyDown event marked as handled on componentWrapper.
    ' CustomKey event raised on componentWrapper.
    ' Handler attached to componentWrapper triggered by CustomKey event raised on componentWrapper.
    ' Handler attached to outerStackPanel triggered by CustomKey event raised on componentWrapper.
    ' Handler attached to outerStackPanel triggered by KeyDown event raised on componentTextBox. Parameter handledEventsToo set to true.
End Class

    Public Class ComponentWrapper
        Inherits StackPanel

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

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

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

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

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

        End Event

    Public Sub RaiseCustomRoutedEvent()
        ' Create a RoutedEventArgs instance & raise the event,
        ' which will bubble up through the element tree.
        Dim routedEventArgs As New RoutedEventArgs(routedEvent:=CustomKeyEvent)
            [RaiseEvent](routedEventArgs)
        End Sub
    End Class

이 예에서는 outerStackPanel에 연결된 이벤트 처리기를 호출하기 위해 억제된 KeyDown 라우트된 이벤트를 가져올 수 있는 두 가지 해결 방법을 보여줍니다.

  • PreviewKeyDown 이벤트 처리기를 outerStackPanel에 연결합니다. 미리 보기 입력 라우트된 이벤트는 동일한 버블링 라우트된 이벤트보다 먼저 실행되므로 예의 PreviewKeyDown 처리기는 공유 이벤트 데이터를 통해 미리 보기 이벤트와 버블링 이벤트를 모두 표시하지 않는 KeyDown 처리기보다 먼저 실행됩니다.

  • handledEventsToo 매개 변수가 true로 설정된 코드 숨김에서 UIElement.AddHandler(RoutedEvent, Delegate, Boolean) 메서드를 사용하여 KeyDown 이벤트 처리기를 outerStackPanel에 연결합니다.

참고

입력 이벤트의 미리 보기 또는 비 미리 보기 이벤트를 처리됨으로 표시하는 것 모두 컨트롤의 구성 요소에서 발생하는 이벤트를 표시하지 않는 전략입니다. 사용하는 방법은 애플리케이션 요구 사항에 따라 다릅니다.

참고 항목