WPF Command-Part3 : RoutedCommand2

프로그래밍 2009.02.02 20:01 Posted by 아일레프

이번 포스팅도 RoutedCommand가 Invoked되었을 때 RotedCommand 객체가 Command Target을 찾는 과정을 설명하도록 하겠습니다.

Command Target을 찾는 다는 것은 곧 ExecuteEvent Handler를 찾는다는 것을 의미하며, 일반적으로 Command가 Invoke되었을 때 ExecuteEvent 이벤트는 VisualTree내에서 Command를 Invoke를 시킨 Object부터 Tree의 루트까지 타고 올라가게 됩니다.

위와 같은 VisualTree에서 Button1이 특정 RoutedCommand를 Invoke를 했다면 ExecuteEvent는 Button1 -> Grid -> Window로 타고 올라가게 되는 것이고 이는 Button2에서도 마찬가지 입니다. 이것이 가장 일반적인 RouteCommand의 라우팅 메커니즘입니다. 하지만 이 Routing 방법으로는 충분하지 않은 경우가 있습니다. 다음과 같은 경우를 생각해봅시다.

 
위 프로그램은 아래와 같은 VisualTree로 구성 되어있습니다.

코드는 아래와 같습니다.

<Window x:Class="CommandTest.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loacl="clr-namespace:CommandTest"
Title="Window1" Height="300" Width="300">
<Grid
    <Grid.RowDefinitions
        <RowDefinition Height="Auto"/> 
        <RowDefinition/> 
    </Grid.RowDefinitions
    <Menu
       
<MenuItem Header="Cut" Command="ApplicationCommands.Cut"/> 
    </Menu
    <TextBox Height="20" Grid.Row="1"/> 
</Grid>
</Window>

TextBox에 글자를 쓰고 선택한 후 MenuItem의 "Cut"을 누르면 선택된 글자가 잘려지는 것을 확인할 수 있습니다. 그런데 두 가지 이상한 점이 있습니다. 일단 ExecuteEventHandler를 등록하는 CommandBinding객체가 정의 되어있지 않네요. 일반적으로 사용자는 Cut, Copy, Paste Command에 CommandBinding으로 ExecuteEventHandler를 등록할 필요가 없는데요, 이는 TextBox가 기본적으로 Cut, Copy, Paste Command에 대한 CommandBinding객체를 내장하고 있기 때문입니다. 따라서 이 질문은 해결 되었네요. 두 번째로 Cut이벤트가 발생했을 때 ApplicationCommands.Cut Command객체는 어떻게 TextBox의 ExecuteEventHandler를 찾아갈 수 있었을 까요? Visual Tree를 따라가 보면 다음과 같이 Routing이 진행될 것입니다.

MenuItem->Menu->Grid->Window

Tree의 Root에 이를 때 까지 ApplicationCommands.Cut에 바인딩 되어있는 Command Target을 찾지 못했으므로 아무 일도 벌어지지 않아야 할 것 같은데 분명히 "Cut" Command가 잘 동작하고 있으니 이상한 일입니다.
이는 RoutedCommand의 Routing의 두 번째 원칙 때문입니다. RoutedCommand는 CommandInvoker 부터 Tree의 Root에 이를 때 까지 CommandBinding된 객체를 찾지 못하면 CommandInvoker와 Tree Root가 동일한 Focus Scope에 있는지 확인 합니다. 만약 CommandInvoker와 Tree의 Root가 동일한 Focus Scope에 있지 않다면 RoutedCommand는 Tree Root의 Logical Focus를 가지고 있는 컨트롤을 기점으로 해서 다시 Routing을 시작합니다. 즉 다음과 같이 Routing이 되는 것입니다.

첫 번째 Routing : MenuItem->Menu->Grid->Window ---- CommandTarget 찾기 실패 
 
MenuItem과 Window가 동일한 Focus Scope에 있지 않으므로 Window의 Local Focus를 가지는 Control을 기점으로 다시 Routing시작

두 번째 Routing: TextBox -> CommandTarget찾기 성공

위와 같은 방법으로 RoutedCommand는 CommandTarget을 찾을 수 있었던 겁니다. 위의 일이 가능하게 하기 위해서는 MenuItem과 Window가 다른 FocusScope내에 있어야 하는데 WPF에서 Menu와 ToolBar와 같은 Control은 기본적으로 FocusScope을 가지고 있기 때문입니다.

이번에는 다음과 같은 시나리오를 생각해 보겠습니다.
 

<Window x:Class="CommandHandler.Window1"

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

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

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

   

    <Grid>

        <StackPanel>

            <Button Command="ApplicationCommands.Cut">Cut</Button>

            <TextBox Name="textBox"/>

        </StackPanel>       

    </Grid>

</Window>

위 프로그램에서 TextBox에 글자를 쓰고 선택해도 Cut 버튼은 절대 활성화 되지 않습니다.
 
Button -> TextBox->Grid->Window
 
Tree의 Root에 이르기까지 아무런 ApplicationCommands.Cut에 바인딩 되어있는 객체를 찾지 못했으므로 Cut버튼이 활성화 되지 않는 것입니다. 이를 해결하기 위해서는? 예, 맞습니다. Button을 Window와 다른 Focus Scope내에 두어야 합니다. 다음과 같은 코드로 이것이 가능합니다.
<Button Command="ApplicationCommands.Cut" FocusManager.IsFocusScope="True">Cut</Button>
 
다시 프로그램을 실행하면 기대하는 결과를 볼 수 있을 겁니다.
 
Command에 대한 이슈는 이것이 끝이 아닙니다. 앞으로 두 번 정도 더 포스팅 할 계획인데, 다음 포스팅에서는 DelegateCommand라는 것을 소개할 것이고, 그 다음 포스팅에서는 Command가 event에 비해 가지는 장점에 대해 알아보도록 하겠습니다.
신고