실험을 위해 예시프로그램을 하나 만들어보겠습니다.
WPF 애플리케이션을 만들고 간단히 버튼과 Label을 만든 후 각 element에 MouseDown 이벤트를 추가해봅니다.
<Window x:Class="WpfEvent.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:WpfEvent"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="300">
<Grid MouseDown="Grid_MouseDown">
<Button Content="Button" HorizontalAlignment="Center" VerticalAlignment="Center" Height="80" Width="80" MouseDown="Button_MouseDown"/>
<Label x:Name="lblTest" Content="Label" HorizontalAlignment="Center" Margin="0,198,0,0" VerticalAlignment="Top"/>
</Grid>
</Window>
namespace WpfEvent
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_MouseDown(object sender, MouseButtonEventArgs e)
{
lblTest.Content += "버튼";
}
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
lblTest.Content += "그리드";
}
}
}
그러면 버튼을 눌렀을때 동작이 되지 않고 오른클릭을 했을때 이벤트가 동작됩니다.
이는 Winform과 동작이 다릅니다. WPF에서의 이벤트는 Winform과 비슷하면서 다르게 동작합니다.
WPF는 Routed Event이기 때문입니다.
위 코드에서 button의 parent요소, 즉 상위 요소는 grid입니다. WPF는 기본적으로 Event가 상위요소로 전파(Routing)이 됩니다. button의 이벤트 함수도 이벤트를 수신하고 button의 상위 요소인 grid도 이벤트를 수신하기에 화면에 "버튼","그리드"가 둘 다 표시가 된겁니다.
이벤트를 발생시킨 button에서 먼저 수신하고 이를 상위 요소 grid로 전달하는 형태가 되겠습니다.
( 그렇기에 "버튼"이 먼저 출력 )
디버깅 모드를 통해 요소 선택을 하고 라이브 시각적 트리를 볼 수 있습니다.
WPF의 Routed Event는 3가지 전략( Strategies ) 중 하나에 속합니다.
위에서 이야기한 예시는 Bubbling routing strategy에 속합니다. 이는 이벤트를 발생시킨 지점부터 root까지 event가 전달이 되는 형태입니다.
Tunneling Event는 Bubble Event와 반대로 root에서 시작하여 이벤트를 발생한 지점으로 역순으로 event가 전달이 되는 형태입니다.
버튼의 이벤트들을 보면 preview가 추가된 event들이 있습니다. 이 이벤트들이 Tunneling Event입니다.
앞서 해보았던 것과 동일하게 PreviewMouseDown 이벤트를 추가해보고 실험해보면,
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfEvent
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_MouseDown(object sender, MouseButtonEventArgs e)
{
lblTest.Content += "버튼";
}
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
lblTest.Content += "그리드";
}
private void Button_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
lblTest.Content += "preview버튼";
}
private void Grid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
lblTest.Content += "preview그리드";
}
}
}
버블 이벤트와 반대로 부모 요소(그리드)가 이벤트를 먼저 수신하고 이벤트를 발생시킨 원점까지 전달(Routing) 되는 것을 볼 수 있습니다.
이처럼 Tunneling Event는 root에서 시작하여 이벤트를 발생시킨 요소로 routing되는 형태입니다.
그리고 터널이벤트가 버블 이벤트보다 먼저 실행된다는 특징이 있습니다.
여기서 마우스 좌클릭은 버블이벤트는 수신하지않지만 터널 이벤트는 수신하는데, 이는 추후 다시 이야기하겠습니다.
WPF에서는 기본적으로 Event 명에 Preview가 안달린 이벤트는 버블링이벤트이고 Event 명에 Preview가 달린 이벤트는 터널링 이벤트로 구분합니다.
그렇다면 Direct Event란 무엇일까요? 이론상으로는 버튼을 클릭하면 버튼의 Event만 발생한다고 MSDN설명에 나와있습니다. 그러나 대부분 실제로는 Bubbling Event, Tunneling Event 처럼 동작하고 예외가 많습니다. ( 사실상 두 형태로 존재한다고 생각해도 무방함 )
Focus를 강제로 잡는 요소가 생기게 되면 Direct로 동작하게 되고 그렇지 않고 Focus를 강제로 잡는 요소가 없으면 Direct Event인데 마치 Bubbling Event인것 처럼 동작하게 됩니다.
Focus를 강제하는 함수 : MessageBox.Show 함수, ShowDialog 함수
즉, MouseEnter, MouseLeave이벤트 함수 내에서 ShowDialog,MessageBox의 Show 처럼 강제로 Focus를 잡는 요소가 있느냐에 따라 이벤트 동작 방식이 Direct냐 Bubble이냐 결정되는 특이사항이있습니다.
MouseEnter,MouseLeave 처리 함수에서는 Focus에 따라 실행이 달라지고 PreviewMouseDoubleclick 처리 함수에서는 focus에 상관없이 Tunneling Event처럼 동작합니다. Direct Event의 경우 터널링 버블링 이벤트라 생각하는 것이 마음이 편하고, 단지 MouseEnter,PreviewMouseDoubleClick 이벤트 함수가 자식요소들 또는 부모요소들과 겹치는 상황이 생겼을 때 여러분 뜻대로 동작안한다면 이 점을 알아두시면 좋습니다.
즉, 결론은 root 에서 이벤트를 발생시킨 지점까지 겹치는 이벤트 함수가 있다면 Direct Event만 상식과 어긋나게 Focus강제 유무, 어느 이벤트 함수에서 처리하느냐에 따라 버블, 터널링 처럼 동작하는 경우도 있기에 복잡하게 이런게 있구나 정도만 기억해두고 터널링과 버블 이벤트만 있다고 생각하는게 편합니다.
만약 Bubbling Event에서 상위 요소로 event를 routing 시키는 것을 원치 않으실 경우 e.Handled를 true로 해주시면 됩니다.
'공부 정리 > WPF' 카테고리의 다른 글
WPF의 Event를 어렵게하는 요인 (0) | 2025.01.02 |
---|---|
WPF vs Winform (2) | 2025.01.02 |
코드 비하인드에서 접근 (0) | 2024.12.27 |
WPF, C# 개발 Visual Studio 간단 기능 (0) | 2024.12.25 |