msdn 라우팅된 이벤트 링크

링크 정리

  • 라우트된 이벤트란 이벤트를 발생시킨 특정 개체뿐 아니라 요소 트리의 여러 수신기에서 처리기를 호출할수 있는 이벤트 형식
  • RoutedEvent클래스의 지원을 받고 WPF 이벤트 시스템에 의해 처리 되는 CLR이벤트이다.
  • 라우팅 전략

    -버블링 - 이벤트 소스의 이벤트처리기가 호출됩니다. 그런 다음 라우트된 이벤트가 요소 트리 루트에 도달할때까지 이후 부모 요소에 라우팅합니다. 대부분의 라우트된 에븐트는 버블링 라우팅 전략을 사용합니다. 버블링 라우트된 이벤트는 일반적으로 특정 콘트롤 또는 기타 UI 요소의 입력 또는 상태 변경을 보고 하는데 사용됩니다.

    -직접 - 이벤트에 응답하여 처리기를 호출할수 있는 기회가 소스 요소 자체에만 부여됩니다.

    -터널링 - 처음에는 요소 트리 루트의 이벤트 처리기가 호출됩니다. 그런 다음 라우트된 이븐테가 경로의 이후 자식 요소를 따라 라우트된 이엡느 소스인 노드 요소를 향해 라우팅합니다. 터널링 라우팅된 이벤트는 일반적으로 콘트롤 합성에 사용하거나 그 일부로 처리되어 합성 파트에 온 이벤트는 의도적으로 표시하지 않거나 전체 콘트롤에 고유한 이벤트로 대체됩니다.터널링 라우트된 이벤트 이름에는 "Preview"라는 접두사가 붙는다.

     

  • 어떤 엘리먼트들이 트리 관계를 이룰때 발생된 이벤트는 트리를 구성하는 부모 또는 자식에게 전달될 필요가 있고 트리를 따라서 이벤트가 전달되는 과정을 이벤트 라우팅이라고 하고 이렇게 트리를 따라 마치 chain과 같이 전달된 이벤트를 RoutedEvent라 한다 - 공도소프트  엘리먼트 트리에서 이벤트를 전달하는 방법에는 발생된 이벤트를 이벤트가 발생된 엘리먼트부터 최상위 엘리먼트까지 위쪽 방향으로 올려주는 버블링(Bubbling) 과 발생된 이벤트를 이벤트가 발생된 엘리먼트를 포함하는 최상위 엘리먼트에서 이벤트가 발생된 엘리먼트까지 아래 방향으로 내려주는 것을 터널링(Tunneling)이다.(Flex에선 Capturing)

 

 

클래스들은 여러개의 이벤트를 노출하며 각각의 이벤트는 여러개의 이벤트 구독자를 가질수 있다.

ex

<Button PreviewMouseDown="PreviewMouseDonwButton" MouseDown="MouseDonwButton"/>

직접 전달 이벤트는 보통의 .Net 이벤트처럼 동작한다. 이는 이벤트를 발생시킨 요소에 등록된 이벤트 핸들러만 이벤트가 통지되며 어떤 라우팅도 발생하지 않음을 의미한다.

 

WPF는 직접 전달 이벤트를 제외한 대부분의 라우팅된 이벤트를 버블링과 터널링의 쌍으로 구성한다. 터널링 이벤트 이름은 항상 Preview 로 시작하며 우선적으로 발생한다.

 

 

이벤트 라우팅 중단하기

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

 ex.

void ButtonDownCanvas(object sender, RoutedEventArgs e)

{

e.handled = true;

}

 

이벤트 핸들러 등록하기

ex 4-4 myEllipse.MouseDown += MouseDownEllipse;

     myEllipse.PreviewMouseDown += PreviewMouseDownEllipse;

라우팅된 이벤트 시스템의 이벤트 핸들러 등록

ex 4-5 myEllipse.AddHandler(Ellipse.MouseDonwEvent, new MouseButtonEventHandler(MouseDownEllipse));

     myEllipse.AddHandler(Ellipse.PreviewMouseDownEvent, new MouseButtonEventHandler(PreviewMouseDownEllipse));

두개의 코드(4-4, 4-5)는 동일한 의미이다.

코드 비하인드 파일은 통상적으로 이벤트 핸들러를 등록하기 위한 최적의 위치다.

디자이너가 XAML파일의 요소들에 이름을 할당하고 개발자는 코드 비하인드 파일에서 이벤트 핸들러를 등록하는 것이다.

 

결합이벤트(attached event)

결합이벤트는 결합속성과 동일한 방법으로 라우팅된 이벤트를 사용하는 것이다. 결합이벤트를 처리하는 코드는 약간 다르다. 일반적인 CLR 이벤트들은 결합이벤트 개념을

지원하지 않으므로 ex4-4에서 사용했던 원래의 C# 이벤트 문접은 사용할수 없다. 대신 결합 이벤트를 표현하는 RoutedEvent 객체를 전달하는 AddHandler 메소드를 호출해야 한다.

4-5 처럼 써야 한다. 명시적 결합이벤트 처리.

도우미 함수를 이용한 결합 이벤트처리

ex

Mouse.AddPreviewMouseDownHandler(myEllipse, PreviewMouseDOwnEllipse);

Mouse.AddMouseDownHandler(myEllipse, MouseEllipse);

AddHandler 메소드는 모든 종류의 이벤트에 대한 핸들러를 등록할수 있기 때문에 두번째 매개 변수는 Delegate타입이어야 한다. 결합 이벤트를 정의하는 클래스들은 일반적으로

그에 대응하는 도우미 메소드도 함께 제공하여 개발자의 편의를 제공해야 하는 것으로 약속되어 있다.

 


마우스 입력

기본적인 마우스 이벤트에는 Click 이벤트가 포함되지 않는다. 이는 클릭 이벤트가 기본적인 마우스 입력보다 더 높은 수준의 개념이기때문이다. 게다가 클릭이벤트는 하나의 마우스 이벤트에 직접 대응할 필요가 없다. 일반적으로 사용자는 클릭 이벤트가 등록된 컨트롤 위에 마우스가 위치해 있는 동안 마우스 버튼을 눌렀다 뗀다. 따라서 이 고수준의 이벤트는 좀 더 특별한 요소 타입에 의해 제공된다. Control 클래스는 PreviewMouseDoubleClick과 MouseDoubleClick 이벤트 쌍을 정의하고 있다.

 

마우스 입력과 히트 테스트

WPF는 마우스 입력을 처리할때 항상 요소의 모양을 고려한다. 많은 그래픽 시스템은 히트 테스트(hit test)를 수행할때 사각형을 기준으로 처리한다. WPF는 히트테스트의 대상이 되는 사용자

인터페이스 요소의 모양이 단순하든 아니든 복잡하던 이처럼 단순하게 히트테스트를 처리 하지 않는다.

도넛 모양의 버튼이 있을때 빈공간을 클릭했을때 다른 콘트롤의 이벤트를 발생시키고 싶지 않을때 투명브러쉬로 가운데를 칠한다. 그러면 가운데 공간에 클릭하더라도 도넛모냥의 버튼이 이벤트 대상이 된다.

반면에 사람눈에는 보이지만 마우스 이벤트를 받지 않으려면(마우스에게 보이지 않는 상태) IsHotTestVisible속성을 false 로 지정한다. 지정하면 도넛 버튼 아래 있는 요소가 이벤트를 발생하게 된다.

 

마우스 상태

Mouse 클래스는 마우스의 상태를 확인하거나 변경하기 위한  static 속성과 메소드를 제공한다.

ex. Mouse.GetPosition(요소) - 현재의 마우스 위치를 알아낼수 있다.

마우스 캡쳐 - 마우스 위치에 관계없이 모든 마우스 입력 이벤트가 마우스를 캡쳐하는 요소에 전달되는 것

 

키보드 입력

마우스 입력의 대상은 항상 마우스 아래에 위치한 요소이거나 현재 마우스를 캡처하는 요소다.  키보드 입력은 다르다.

WINDOWS는 직접적인 키보드 입력을 위한 다른 매커니즘이 있다. 어떤 한 순간에는 툭정 교소만이 포커스(focus)를 갖도록 돼있다. 포커스를 갖는다는 것은 해당 요소가 키보드 입력에 대한 대상으로 동작하고 있음을 의미한다. UIElement 기반 클래스는 IsFocused 속성을 가지고 있어 원칙적으로 모든 요소는 포커스를 가질수 있다. 이 속성을 false 하면 포커스를 가질수 없다.

WPF는 논리적 포커스와 키보드 포커스를 구분한다. 키보드 포커스는 오직 하나의 요소에만 주어진다.  그리고 키보드 입력을 받은 요소를 가리키고 논리적 포커스는 포커스 범위내에서 포커스가 있는 요소를 가르킨다.

 

키보드 상태

Keyboard클래스는 Modifiers라는 이름의 static 속성을 제공한다. 이 속성으로 언제든지 alt, shift, ctrl 눌려있는지 알수 있다.

ex ctrol 키 사용여부

if(Kyeboard.Modifiers & ModifierKeys.Control) != 0)

{

isCopy = true;

}

ex 개별적인 키(Home) 상태알아내기

bool homeKeyPressed = Keyboard.IsKeyDown(Key.Home);

 

커맨드처리

커맨드(command)란 응용프로그램이 사용자의 요청에 의해 수행하는 동작이다. 커맨드는 사용자 입력을 추상화 한 것으로 키보드로 ctrl+C를 눌렀든 메뉴에서 [편집][복사]를 선택했건

도구 막대에서 [복사]를 눌럿던 동일한 커맨드의 다른 표현으로 취급할수 있께 해준다. 커맨드 시스템을 통해 UI요소가 커맨드에 대한 하나의 이벤트 핸들러만을 제공함으로써 코드의 복잡성을 감소시키고 깔끔하게 유지할수 있다. 게다가 UI 요소들에 대한 선언적인 스타일의 이벤트 처리도 가능하다.

ex.

<DockPanel>

<Menu DOckPanel.Dock="Top">

<MenuItem Header="_Edit">

<MenuItem Header="Cu_t" Command="ApplicationCommands.Cut"/>

<MenuItem Header=" _Copy" Command="ApplicationCommands.Copy" />

<MenuItem Header="_Paste" Commands="ApplicationCommands.Paste" />

</MenuItem>

</Menu>

<ToolBatTray DockPanel.Dock="Top">

<ToolBar>

<Button Command="Cut" Content="Cut" />

<Button Command="Copy" Content="Copy" />

<Button Command="Paste" Content="Paste" />

</ToolBar>

</ToolBarTray>

</DockPanel>

각각 메뉴 아이템은 커맨드와 연결되어 있다. 이것이 텍스트 상자에서 클립보드 작업을 수행하기 위해 필요한 전부다. 내장된 잘라내기, 복사 및 붙여넣기 커맨드들은 표준 키보드 단축키와 자동적으로 연동된다.

 

커맨들 시스템의 5인방

커맨드 객체(Command object) - 복사나 붙여넣기 등 특정 커맨드를 표현하는 객체

입력 바인딩(Input binding) - 특정 입력(ex. ctrl+C)과 커맨드(ex 복사)사이의 연동을 의미한다.

커맨드 원본(Command source) - Button과 같이 커맨드를 수행하는 객체 또는 입력 바인딩을 의미한다

커맨드 대상(Command target) - 커맨드를 수행하도록 요청을 받은 UI요소-대체로 커맨드가 실행될때 키보드 포커스를 가진 콘트롤을 의미한다

커맨드 바인딩(Command binding) - 특정 커맨드를 처리하는 방법을 알고 있는 특정 UI요소를 선언하는 것

 

커맨드객체

커맨드 정의하기

커맨드 객체는 커맨드를 어떻게 처리해야 하는지는 알지 못하며 이는 커맨드 바인딩이 수행해야 할 작업이다. 커맨드 객체는 대체로 ApplicationCommands.Properties와 같이 static 속성을 통해 접근할수 있도록 만들어진다.

ex. 사용자 정의 커맨드를 사용하는 예제

//...

using System.Windows.Input;

//...

 public static RoutedUICOmmand AddToBasketCommand;

 

static MyAppCommands()

{

InputGestureCollection addToBasketImputs = new InputGestureCollection();

addToBasketInputs.Add(new KeyGesture(Key.B, ModifierKeys.Control | ModifierKeys.Shift));

AddToBasketCommand = new RoutedUICommand("Add to basket", "AddToBasket", typeof(MyAppCommands), addToBasketInputs);

}

RoutedUICommand의 첫번째 인자는 사용자 인터페이스에 나타나야 할 이름이다. 두번째 인자는 내부적으로 코드에서 사용될 커맨드 이름이다. 이 이름은 커맨드가 저장될 필드의 이름에서

command 접미사를 제외한 이름과 정확히 일치해야 한다. 내장 커맨드를 사용하는 경우 응용프로그램 커맨드는 자신에게 정의된 어떤 것도 실행하지 않는다. 단지 식별자일뿐이다.

XAML코드에서 커맨드 활용하기

ex

<Button Command="Copy">COPY</Button>

커맨드 Copy는 ApplicationCommands.Copy의 약칭이다.

 

커맨드가 MyNameSpace.MyAppCommands 클래스에 정의되어 있고 MyLib 컴포넌트내에 구현되어 있고 AddToBaskteCommand 이름의 필드에 보관되어있다면

ex

<Window xmlns:m="clr-namespace:MyNamespace; assembly=MyuLib" ...>

//...

<Button Command="m:MyAddCommands.AddToBasketCommand>Add to basket(/Button>

//...

 

입력바인딩

키보드 단칙크와 같은 특정 형태의 입력 행동양식과 커맨드를 연동시키는 것이다. 대부분의 내장 커맨드들은 표준 행동양식과 연동되어 있다. 예를 들어 Application.Commands.Copy

커맨드는 복사를 수행하는(ctrl+c) 표준키보드와 연동되어 있다.

 

커맨드 원본 - 커맨드를 실행하기 위해 사용되는 객체며, 버튼이나 하이퍼 링크 또는 메뉴 아이템과 같은 사용자 인터페이스요소가 사용될 것이다.

커맨드 원본은 모두 ICommandSource 인터페이스를 구현하고 있다.

CommandParameter 속성을 이용하면 커맨들가 실행될때 추가 정보를 전달할수 있다. 이때 파라미터 값은 하드코딩하는 것보단

필요로 하는 데이터를 지정한다면 커맨드 핸들러는 아래 예제에서 보듯 커맨드 대상의 DataContect 속성을 조회할수 있다

ex

void AddToBasketHandler(object sender, ExecuteRoutedEventArgs s)

{

FrameworkElement source = (FrameworkElement) e.Source;

ProduectInfo product = (ProductInfo) source.DataContext;

//...

}

 

커맨드 바인딩

언제든지 커맨드를 사용하려면 무언가가 커맨드의 실행에 반드시 대응해야 한다.

CommandBinding 클래스 객체는 특정 상요자 인터페이스 요소의 범위 내에서 특정 커맨드 객체와 핸들러 함수를 연결시킨다.

이 CommandBinding 객체는 커맨드가 UI를 통해 터널링 또는 버블링될때 발생하는 PreviewExecute와 Execute 이벤트를 제공한다.

커맨드 바인딩은 UIElement 요소에 정의된 CommandBindings  컬렉션 속성에 보관된다.

ex. ApplicationCommands.New 커맨드를 바하인드 코드에서 처리하는 방법.

public partial class Window1 : Window

{

InitializeComponent();

CommandBinding cmdBindingNew = new CommandBinding(ApplicationCommands.New);

cmdBindingNew.Executed += NewCommandHandler;

CommandBnidings.Add(cmdBindingNew);

}

 

void NewCommandHandler( ....)

{

//...

}

 

커맨드의 활성화 및 비활성화

CommandBinding 객체는 커맨드의 실행을 지원하는 것은 물론 특정 커맨드가 현재 사용가능한지 여부를 결정할때도 사용할수 있다. 터널링에 사용되는

PreviewCanExecute와 버블링에 사용되는 CanExecute 이벤트쌍을 제공한다.

public Winodw1(){

CommandBinding redoCommand = new CommandBinding(ApplicationCommands.Redo);

redoCommandBinding.CanExecute += RedoCommandCanExecute;

CommandBindings.Add(redoCommandsBinding);

}

 

void RedoCommandCanExecute(object sender, CanExecuteRoutedEventArgs s)

{

e.CanExecute = myCustomUndoManager.CanRedo;

}

커맨드 바인딩은 커맨드 라우팅 중 버블링에 의존하는 기능이다. 이벤트 버블링 덕분에 커맨드를 단 한곳에서 손쉽게 처리할수 있다. 대부분의 커맨드 라우팅은 매우 직관적이다.

일반적으로 커맨드 라우팅의 대상은 키보드 포커스를 가진 요소이며, 다른 이벤트들과 달리 터널링과 버블링을 사용한다.

 

커맨드 라우팅

모든 내장 커맨드 객체는 RoutedUICommand 라는 클래스를 사용하며 응용 프로그램에 특화된 커맨드를 정의하려면 일반적으로 이 클래스를 이용한다. RoutedUIElement 클래스는 커맨드가 실행될때 올바른 커맨드 바인딩을 찾아내는 메커니즘을 제공한다. 커맨드 바인딩을 찾는 케머니즘은 때떄로 콘텍스트에 의해 결정된다.

RoutedUICommand 클래스는 포서크 범위(focus scope)에 의존한다. RoutedUIComponent 클래스는 커맨드 바인딩이 실패하면 최초의 커맨드 대상이 포커스 범위에 속하는지를 검사한다. 만일 커맨드 대상이 포커스 범위에 속해 있다면 WPF는 포커스 범위의 부모(대체로 윈도우)를 찾은 후 커맨드 대상을 다시 지정하고 부모 포커스 범위에서 놀리적인 포커스를 가진 요소, 즉 마지막으로 포커스를 가지고 있던 요소를 선택한다. 이렇게 되면 두번째 터널링과 버블링 과정이 발생한다. 결국 커맨드 대상은 메뉴가 열리기 전 또는 도구 막개의 버튼이 클릭되기 전에 포커스를

가지고 있던 요소가 된다.

 

 

코드 기반 입력 처리와 트리거 기반 입력처리의 비교ㅣ

만일 사용자 입력을 처리하는 이유가 단순히 사용자에게 가시적인 피드백을 제공하기 위한 것이라면 이벤트 핸들러나 사용자 정의 커맨드를 작성하는 것은 지나치다!!

단지 가시적인 피드백을 위한 것이라면 사용자 인터페이스 마크업에서 트리고(trigger)를 이용하기만 해도 가능하다.

이 글은 스프링노트에서 작성되었습니다.

by 무위자연 2008. 9. 23. 19:25