2021년 10월 17일 일요일

WPF, 입력 이벤트의 라우팅(RoutedEvent), Bubbling, Tunneling

 WPF, 입력 이벤트의 라우팅(RoutedEvent), Bubbling, Tunneling

 

n  이벤트 라우팅은 어떤 이벤트가 엘리먼트의 하위 또는 상위로 전달되는 것을 이야기 하며 WPF에서 광범위하게 이용되는 방법이다.

n  이벤트 라우팅은 세가지로 분류할 수 있는데 이벤트가 발생했을 때 현재 엘리먼트에서 상위로 올라가면서 이벤트가 전달되는 경우를 버블링 이벤트(Bubbleing Event)라고 하고 반대로 자식 엘리먼트로 전달되는 경우를 터널링(Tunneling Event) 이벤트라고 한다하나의 엘리먼트에서만 발생하게 된다면 다이렉트 이벤트(Direct Event)라고 한다터널링 이벤트의 경우 접두사로 preview를 붙인다. PreviewMouseDown, PreviewDragDown 등으로 쓰여지는 것들을 Tunneling 이벤트라고 이해하면 된다.

n  터널링 이벤트의 경우 이벤트가 자식 요소에게 전달되기 전에 부모의 이벤트가 먼저 호출되므로 부모 이벤트를 먼저 호출할 수 있는 기회를 제공한다자식의 이벤트 발생을 막거나 이벤트의 처리 전에 부모 요소가 무엇인가 수행할 필요가 있는 경우에 사용한다.

 

n  이벤트 핸들러는 RoutedEventArgs 매개변수를 가지는데 Source 속성을 통해 실제 이벤트를 발생시킨 요소에 대한 참조를 제공한다이 속성은 여러 요소에서 발생한 이벤트를 동일한 방법으로 처리하고자 할 때 특히 유용합니다.

<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>

 

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;

}

 

 

n  WPF의 라우팅된 이벤트에서 sender는 이벤트를 등록한 객체(이벤트 발생객체 아님), RoutedEventArgs의 Source 속성은 실제 이벤트를 발생시킨 객체를 의미한다.

n  WPF의 이벤트 라우팅 모델은 자동으로 이벤트를 상위 객체로 라우팅 시켜준다.

 

 

n  비주얼 스튜디오 -> WPF 응용프로그램 프로젝트명 : EventRoutingTest

n  MainWindow.xaml

 

<Window x:Class = "WPFRoutedEvents.MainWindow"

   xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"

   xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"

   Title = "MainWindow" Height = "450" Width = "604"

   ButtonBase.Click  = "Window_Click" >

    <Grid>

        <StackPanel Margin = "20" ButtonBase.Click = "StackPanel_Click">

            <StackPanel Margin = "10">

                <TextBlock Name = "txt1" FontSize = "18" Margin = "5" Text = "This is a TextBlock 1" />

                <TextBlock Name = "txt2" FontSize = "18" Margin = "5" Text = "This is a TextBlock 2" />

                <TextBlock Name = "txt3" FontSize = "18" Margin = "5" Text = "This is a TextBlock 3" />

            </StackPanel>

            <Button Margin = "10" Content = "Click me" Click = "Button_Click" Width = "80"/>

        </StackPanel>

    </Grid>

</Window>

 

n  MainWindow.xaml.cs

 

using System.Windows;

 

namespace WPFRoutedEvents

{

    public partial class MainWindow : Window

    {

        public MainWindow()

        {

            InitializeComponent();

        }

 

        private void Button_Click(object sender, RoutedEventArgs e)

        {

            txt1.Text = "Button is Clicked";

        }

 

        private void StackPanel_Click(object sender, RoutedEventArgs e)

        {

            txt2.Text = "Click event is bubbled to Stack Panel";

        }

 

        private void Window_Click(object sender, RoutedEventArgs e)

        {

            txt3.Text = "Click event is bubbled to Window";

        }

    }

}

 

Button, StackPanel, Window의 Click 이벤트는 모두 첫 번째 인자는 이벤트 퍼블리셔를 가리키는 object 타입을두 번째 인자로 System.Windows.RoutedEventArgs 타입의 인자로 전달하게 된다. 

 

예제에서 버튼을 클릭시 클릭 이벤트가 버블링 되어 상위 객체로 라우팅 된다즉 버튼 클릭 이벤트 실행 -> 스택 패널 클릭 이벤트 실행 -> 윈도우 클릭 이벤트 순서로 된다.

 

n  실행화면

 3c7bb76c5f18329703c7ba22af466970_1595838

 

3c7bb76c5f18329703c7ba22af466970_1595838
 

 

 

 

n  상위 객체로 라우팅 되는 것을 막으려면 RoutedEventArgs의 Handled 속성을 true로 설정하면 된다버튼 클릭 이벤트에 “e.Handled = true” 라고 하면 버튼까지만스택 패널 클릭 이벤트에 “e.Handled = true” 라고 한다면 스택 패널 까지만 이벤트가 전달된다.

n  Preview 이벤트에 Handled 속성을 지정하면 Preview 이벤트의 터널링이 중단될 뿐만아니라 버블링 이벤트의 발생도 중단 됩니다.

 

private void StackPanel_Click(object sender, RoutedEventArgs e) {

   txt2.Text = "Click event is bubbled to Stack Panel";

   e.Handled = true;

}

 

 

n  이번에는 Tunneling Event에 대해 실습을 해보자.

n  MainWindow.xaml

<Window x:Class="EventRoutingTest.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="Events Demo" Height="215" Width="343"

        PreviewMouseDown="Window_PreviewMouseDown">

    <Grid>

        <Border Margin="20" Padding="5" Background="LightYellow"

          BorderBrush="SteelBlue" BorderThickness="3,5,3,5" CornerRadius="3"

          VerticalAlignment="Top">

            <StackPanel Height="120" HorizontalAlignment="Center"

                    VerticalAlignment="Center" Width="200"

                    Background="Transparent"                    

                    PreviewMouseDown="StackPanel_PreviewMouseDown">

                <Button  Margin ="20" Height="50" Width="50"

                     PreviewMouseDown="Button_PreviewMouseDown"/>

            </StackPanel>

        </Border>

    </Grid>

</Window>

 

n  MainWindow.xaml.cs

using System.Windows;

 

namespace EventRoutingTest

{

 

    public partial class MainWindow : Window

    {

        string mouseActivity = string.Empty;

 

        public MainWindow()

        {

            InitializeComponent();

        }

 

        private void Button_PreviewMouseDown(object sender, RoutedEventArgs e)

        {

            mouseActivity = "PreviewMouseDown Button \n";

            MessageBox.Show(mouseActivity);

        }

 

         private void StackPanel_PreviewMouseDown(object sender, RoutedEventArgs e)

        {

            mouseActivity = "PreviewMouseDown StackPanel \n";

            MessageBox.Show(mouseActivity);

        }

 

        private void Window_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)

        {

            mouseActivity = "PreviewMouseDown Window \n";

            MessageBox.Show(mouseActivity);

        }

 

    }

}

 

n  실행화면

3c7bb76c5f18329703c7ba22af466970_1595838
 

 

n  Window -> Grid -> StackPanel -> Button의 구조를 가지며 터널링 이벤트는 이벤트명 앞에 Peview가 붙는다.

n  만약 버튼에서 클릭하면 Window -> StackPanel -> Button의 PreviewMouseDown 이벤트가 호출되고 StackPanel에서 클릭하면 Window -> StackPanel의 PreviewMouseDown 이벤트가 호출된다즉 최상위 객체에서 클릭한 객체까지 이벤트가 내려가면서 라우팅 된다.


WPF, 의존프로퍼티(DependencyProperty), 의존속성


원본 : http://ojc.asia/bbs/board.php?bo_table=WPF&wr_id=140&page=2


WPF, 의존프로퍼티(DependencyProperty), 의존속성

 

n  기존의 닷넷 Property에 WPF 요소를 가미하여 새롭게 탄생.

n  XAML, C# 코드 비하인드에서 사용 가능하며 의존속성 값이 변경되면 자동으로 어떤 것을 로드되게 하거나 랜더링 되도록 할 수 있는데 애니메이션스타일링데이터바인딩 등에 자주 사용된다.

n  어떤 속성을 애니메이션 시켜야 하거나 데이터 바인딩을 하려면 그 속성은 반드시 의존 속성이어야 한다.

n  기본으로 제공되는 UI 컨트롤은 대부분의 속성이 의존 속성으로 되어 있다.

n  FrameworkElement , Control 등과 같이 DependencyObject 에서 파생 된 클래스에서만 정의 할 수 있습니다.

 

n  우선 간단히 예제를 하나 만들어 보자.

n  비주얼 스튜디오 -> WPF 응용프로그램 프로젝트명 : DependencyPropertyTest

n  MainWindow.xaml

 

<Window x:Class="DependencyPropertyTest.MainWindow"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    Title="Window1" Height="295" Width="300">

    <Window.ContextMenu>

        <ContextMenu MenuItem.Click="ContextMenu_Click">

            <MenuItem Header="BLUE"/>

            <MenuItem Header="YELLOW"/>

            <MenuItem Header="GREEN"/>

            <MenuItem Header="BLACK"/>        </ContextMenu>

    </Window.ContextMenu>

    <TextBox x:Name="textBox1" Height="23" TextWrapping="Wrap" Text="TextBox" Width="120"/> </Window>

 

n  MainWindow.xaml.cs

 

using System;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Media;

 

namespace DependencyPropertyTest

{

    public partial class MainWindow : Window

    {

        public MainWindow()

        {

            InitializeComponent();

        }

 

          //DependencyProperty(MyProperty)를 위한 래퍼속성 MyColor

        // 이 래퍼속성에서는 System.Windows.DependencyObject 클래스의 //GetValue() SetValue() 메서드를 이용해서 get, set을 정의해야 한다.

        public String MyColor

        {

            get { return (String)GetValue(MyProperty); }

            set { SetValue(MyProperty, value);  }

        }

 

        //의존속성(Dependency Property) MyProperty

        // DependencyProperty 클래스에는 public 생성자가 없기 때문에 static //메소드인 DependencyProperty.Register()를 사용해서 등록한다.

         // 수정이 불가능 하도록 의존속성은 읽기전용(readonly) 필드로 //선언되는데 일반 UI컨트롤의 Height, Width 

//대부분의 의존속성은 FrameworkElement에 DependencyProprty

// 정의되어있다.

public static readonly DependencyProperty MyProperty = DependencyProperty.Register(

            "MyColor",            //의존속성으로 등록될 속성

            typeof(String),         //등록할 의존속성 타입

            typeof(MainWindow),  // 의존속성을 소유하게될 OWNER

            new FrameworkPropertyMetadata(new PropertyChangedCallback(OnMyPropertyChanged)));

//속성값 변경시 호출될 메소드

// 프로퍼티 값의 변경에 따른 Callback 메서드 등 새로운 속성을 추가하기 위해 FrameworkPropertyMetadata를 인자 값으로 전달 할수 있다.

 

        private static void OnMyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

        {

            MainWindow win = d as MainWindow;

            SolidColorBrush brush = (SolidColorBrush)new BrushConverter().ConvertFromString(e.NewValue.ToString());

            win.Background = brush;

            win.Title = (e.OldValue == null) ? "이전배경색 없음" : "배경색 : " + e.OldValue.ToString();           

win.textBox1.Text = e.NewValue.ToString();

        }

 

        private void ContextMenu_Click(object sender, RoutedEventArgs e)

        {

            string str = (e.Source as MenuItem).Header as string;

            MyColor = str;

        }

    }

}

 

n  실행화면

1dfca4e343be71c9c0653621e5b036dd_1596983
 

 

n  의존 속성은 의존속성을 선언등록 그리고 프로퍼티를 생성하는 3단계로 작성된다.

n  의존 속성 선언 및 등록

//의존 속성 선언 및 등록

public static readonly DependencyProperty MyProperty = DependencyProperty.Register(

 " MyColor",            //등록할 의존 속성 이름

  typeof(String),

  typeof(MainWindow),

 new FrameworkPropertyMetadata(new PropertyChangedCallback(OnMyPropertyChanged)));  //속성변경시 호출될 메소드

 

대부분의 UI 컨트롤의 의존속성은 읽기전용(readonly) 필드로 선언되는데 이것은 오직 FrameworkElement 클래스의 static 생성자에서만 설정될 수 있다는 것을 의미한다.

 

DependencyProperty 클래스에는 public 생성자가 없기 때문에 static 메소드인 DependencyProperty.Register()를 사용해서 등록한다.

 

Register 메서드의 입력 파라미터의 첫 번째 파라미터는 프로퍼티 이름이다여기서는 “MyColor”, 두 번째 인자는 프로퍼티(MyColor)가 사용할 데이터 타입으로 여기에서는 String 이다세 번째 인자는 프로퍼티를 소유하게 될 타입이 예제에서는 MainWindow 클래스가 된다네 번째 인자는 실제로 어떻게 동작할 것인지에 대한 옵션을 설정을 할당해 준다. FrameworkPropertyMetadata 객체를 통하여 만약 값이 수정되었을 때의 알림을 어떻게 받을 것인가를 정의했으며 본 예제에서는 OnMyPropertyChanged 메소드가 알림을 받을 콜백 함수로 정의되었다선택적으로 new ValidateValueCallback을 사용하여 값의 유효성 검사를 어떻게 할 것인지 등을 설정하면 된다네 번째다섯 번째 파라미터는 옵션 파라미터이다.

 

n  DependencyProperty(MyProperty)를 위한 래퍼 프로퍼티 MyColor 선언

public String MyColor

{

   get { return (String)GetValue(MyProperty); }

   set { SetValue(MyProperty, value);  }

}

 

이 래퍼 프로퍼티에서는 System.Windows.DependencyObject 클래스의 GetValue() SetValue() 메서드를 이용해서 get, set을 정의해야 한다.

 

n  Context Menu Click 이벤트에서는 MyColor 프로퍼티에 값을 설정하면 자동으로 위에서 선언한 콜백 함수(OnMyPropertyChanged)가 호출된다.

private void ContextMenu_Click(object sender, RoutedEventArgs e)

        {

            string str = (e.Source as MenuItem).Header as string;

            MyColor = str;

        }

 

우리가 흔히 알고 있는 Height Width와 같은 멤버들은 FrameworkElement를 상속받았고 Content 속성은 ControlContent로부터 상속받은 속성으로 모두 의존속성이다  

(C#교육동영상)C# ADO.NET 실습 ODP.NET/ODAC 설치 오라클 함수 호출 실습, C#학원, WPF학원, 닷넷학원, 자바학원

  (C#교육동영상)C# ADO.NET 실습  ODP.NET/ODAC 설치  오라클 함수 호출 실습, C#학원, WPF학원, 닷넷학원, 자바학원 https://www.youtube.com/watch?v=qIPU85yAlzc&list=PLxU-i...