[C#][WPF] Event 버블링과 터널링

LimJaeJun·2025년 1월 25일

WPF

목록 보기
11/12

버블링(Bubbling)과 터널링(Tunneling)

WPF의 이벤트 시스템은 라우트 이벤트(Routed Events)라는 개념을 사용하여 UI 요소 간의 이벤트 흐름을 관리한다.

라우트 이벤트란?

라우트 이벤트는 이벤트가 특정 요소에서 발생한 후, WPF의 논리 트리(Logical Tree) 또는 비주얼 트리(Visual Tree)를 따라 전달되는 이벤트이다. 일반적인 이벤트와 달리, 라우트 이벤트는 트리를 따라 이동하며 여러 UI 요소에 의해 처리될 수 있다.

라우트 이벤트의 3가지 유형

  1. 터널링(Tunneling): 부모 요소에서 자식 요소로 이벤트가 전달
  2. 버블링(Bubbling): 자식 요소에서 부모 요소로 이벤트가 전달
  3. 직접(Direct): 이벤트가 발생한 요소에서만 처리

버블링 이벤트 (Bubbling)

버블링 이벤트는 이벤트가 가장 안쪽의 자식 요소에서 발생한 뒤, 부모 요소로 전달되는 방식이다. 이 방식은 이벤트가 트리의 아래에서 위로 "거품이 올라가듯" 전달된다고 해서 버블링이라고 명명한다.

예: 버튼 클릭 이벤트

버블링 이벤트의 대표적인 예는 Click 이벤트이다다. 사용자가 버튼을 클릭하면 버튼 요소에서 이벤트가 발생하고, 이 이벤트는 버튼의 부모 요소(예: 패널, 윈도우)로 전달된다.

버블링 이벤트의 처리 순서

  1. 이벤트가 자식 요소에서 발생
  2. 이벤트가 부모 요소로 전달
  3. 루트 요소에 도달할 때까지 이벤트가 계속 전달
    • e.Handled = true라면 멈춤

터널링 이벤트 (Tunneling)

터널링 이벤트는 부모 요소에서 시작해 자식 요소로 전달되는 방식이다. 이는 이벤트가 트리의 위에서 아래로 전달되므로 터널링이라고 명명한다.

터널링 이벤트는 "Preview" 접두사가 붙은 이벤트로 사용된다다. 예를 들어, PreviewMouseDown 이벤트는 터널링 이벤트이고, MouseDown 이벤트는 버블링 이벤트이다.

예: 키보드 입력 이벤트

PreviewKeyDown 이벤트는 터널링 이벤트의 대표적인 예이다다. 키보드 입력이 발생하면 윈도우(루트 요소)에서 시작하여 해당 입력이 전달될 자식 요소로 이벤트가 전파된다.

터널링 이벤트의 처리 순서

  1. 이벤트가 부모 요소에서 시작
  2. 이벤트가 자식 요소로 전달
  3. 최하위 요소에 도달할 때까지 이벤트가 계속 전달
    • e.Handled = true라면 멈춤

버블링과 터널링의 차이점

특징터널링(Tunneling)버블링(Bubbling)
이벤트 전달 방향부모 요소 → 자식 요소자식 요소 → 부모 요소
이벤트 이름 접두사Preview(없음)
처리 우선순위부모가 자식보다 먼저 이벤트를 처리자식이 부모보다 먼저 이벤트를 처리
주요 사용 사례입력 이벤트 사전 처리사용자 상호작용 처리

라우트 이벤트 사용 시 유의점

  1. Handled 속성 활용
    라우트 이벤트가 처리되었음을 명시하기 위해 e.Handled = true를 설정할 수 있다. 이렇게 하면 이벤트가 더 이상 트리를 따라 전파되지 않는다.

  2. 터널링과 버블링의 조합
    터널링 이벤트는 사전 검사를 위해 사용하고, 버블링 이벤트는 최종 처리를 위해 사용하는 방식으로 조합할 수 있다.

연습 코드

WPF01 참고

좌측은 버블링, 우측은 터널링이다.

Code 참고

Code

MainWindow.xaml

<Window x:Class="WPF01.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF01"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
		<Grid.RowDefinitions>
			<RowDefinition Height="1*"/>
			<RowDefinition Height="50"/>
		</Grid.RowDefinitions>

		<Grid.ColumnDefinitions>
			<ColumnDefinition Width="1*"/>
			<ColumnDefinition Width="1*"/>
		</Grid.ColumnDefinitions>

		<Button Grid.Row="0" Grid.Column="0"
				Background="Red" Margin="35,15,35,15"
				Click ="Button_Click">
			<Border Background="Blue" Width="250" Height="250"
					MouseDown="Border_MouseDown">
				<Rectangle Fill="Green" Width="150" Height="150"
						   MouseDown="Rectangle_MouseDown"/>
			</Border>
		</Button>

		<Button Grid.Row="0" Grid.Column="1"
				Background="Red" Margin="35,15,35,15"
				PreviewMouseDown="Button_PreviewMouseDown">
			<Border Background="Blue" Width="250" Height="250"
					PreviewMouseDown="Border_PreviewMouseDown">
				<Rectangle Fill="Green" Width="150" Height="150"
						   PreviewMouseDown="Rectangle_PreviewMouseDown"/>
			</Border>
		</Button>

		<TextBlock x:Name="tbBubbling" Grid.Row="1" Grid.Column="0"
				   FontSize="20" Text="버블링 순서도" 
				   VerticalAlignment="Center"
				   HorizontalAlignment="Center"/>

		<TextBlock x:Name="tbTunneling" Grid.Row="1" Grid.Column="1"
				   FontSize="20" Text="터널링 순서도" 
				   VerticalAlignment="Center"
				   HorizontalAlignment="Center"/>
				   
	</Grid>
</Window>

MainWindow.xaml.cs

using System.Collections.Generic;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

namespace WPF01
{
    public partial class MainWindow : Window
    {
        List<string> list = new List<string>();

        public MainWindow()
        {
            InitializeComponent();
        }

        private void SetBubblingTextBox()
        {
            tbBubbling.Text = string.Join(">", list);
            AsyncClearList();
        }

        private void SetTunnelingTextBox()
        {
            tbTunneling.Text = string.Join(">", list);
            AsyncClearList();
        }

        private async void AsyncClearList()
        {
            await Task.Delay(100);
            list.Clear();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            list.Add("Red Button");
            SetBubblingTextBox();
        }

        private void Border_MouseDown(object sender, MouseButtonEventArgs e)
        {
            // 더 이상 이벤트를 전달하지 않음
            // e.Handled = true;
            list.Add("Blue Border");
            SetBubblingTextBox();
        }

        private void Rectangle_MouseDown(object sender, MouseButtonEventArgs e)
        {
            list.Add("Green Rectangle");
            SetBubblingTextBox();
        }

        private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            list.Add("Red Button");
            SetTunnelingTextBox();
        }

        private void Border_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            // 더 이상 이벤트를 전달하지 않음
            // e.Handled = true;
            list.Add("Blue Border");
            SetTunnelingTextBox();
        }

        private void Rectangle_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            list.Add("Green Rectangle");
            SetTunnelingTextBox();
        }
    }
}

참고

profile
Dreams Come True

0개의 댓글