XAML or HTML

024. 커스텀 트리거 & 액션(Custom Trigger & Action) #1

XAML 뽀개기

이전 포스트에서는 OnlyOneExpanderBehavior 비헤이비어 단독으로 사용되었지만 이전 포스트에서 살펴본 CallMethodAction 비헤이비어인 경우 Trigger(트리거) Action(액션)으로 구성되어 있던 것을 있었습니다. 이번 포스트에서는 Custom Trigger Custom Action 대해 알아 봅시다.

 

 

이번에 사용될 예제는 간단한 알람 시계입니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<Grid>
    <Grid.Resources>
        <Style TargetType="{x:Type TextBlock}">
            <Setter Property="FontSize" Value="100"/>
            <Setter Property="HorizontalAlignment" Value="Center"/>
        </Style>
    </Grid.Resources>
 
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
 
    <!-- 시 -->
    <TextBlock x:Name="txtHour" Text="0"/>
 
    <!-- 분 -->
    <TextBlock x:Name="txtMinute" Text="0" Grid.Column="1"/>
 
    <!-- 초 -->
    <TextBlock x:Name="txtSecond" Text="0" Grid.Column="2"/>
 
    <!-- Separation points -->
    <TextBlock Text=":" HorizontalAlignment="Right" Margin="0,0,-10,0"/>
    <TextBlock Text=":" HorizontalAlignment="Right" Margin="0,0,-10,0" Grid.Column="1"/>
</Grid>
cs

 

기본 XAML 코드 : MainWindow.xaml

 

예제를 위한 XAML 코드입니다. , , 초를 TextBlock으로 나누어 표현합니다. CS 코드에서 접근하기 윈해 x:Name 각각 선언했습니다. 다른 특이점은 없습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
 
        var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
        timer.Tick += Timer_Tick;
        timer.Start();
    }
 
    private void Timer_Tick(object sender, EventArgs e)
    {
        if (DateTime.Now.Hour > 12)
        {
         txtHour.Text = (DateTime.Now.Hour - 12).ToString();
        }
        else
        {
            txtHour.Text = DateTime.Now.Hour.ToString();
        }
 
        txtMinute.Text = DateTime.Now.Minute.ToString();            
        txtSecond.Text = DateTime.Now.Second.ToString();
    }
}
cs

 

기본 CS 코드 : MainWindow.xaml.cs

 

예제를 위한 CS 코드입니다. XAML 코드에 선언된 각 TextBlock 현재 시간을 1 단위로 업데이트합니다. 12단위 시간을 표현하기 위한 코드를 제외하고 다른 특이점은 없습니다.

 

, , 모두 동일한 방법이 사용될 것이므로 이제부터는 단위만 분리해서 살펴보겠습니다. 예제 시나리오는 시간이 변경될 지정한 Storyboard 실행하고, 알람이 울릴 시간(트리거) 설정할 있으며 해당 시간이 되었을 색상을 변경(액션)하는 시나리오입니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<Storyboard x:Key="StoryboardSecond">
    <DoubleAnimationUsingKeyFrames 
        Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)" 
        Storyboard.TargetName="txtSecond">
        <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="1.1">
            <EasingDoubleKeyFrame.EasingFunction>
                <SineEase EasingMode="EaseIn"/>
                    </EasingDoubleKeyFrame.EasingFunction>
                </EasingDoubleKeyFrame>
            <EasingDoubleKeyFrame KeyTime="0:0:1" Value="1">
            <EasingDoubleKeyFrame.EasingFunction>
                <SineEase EasingMode="EaseOut"/>
            </EasingDoubleKeyFrame.EasingFunction>
        </EasingDoubleKeyFrame>
    </DoubleAnimationUsingKeyFrames>
</Storyboard>
cs

 

1
2
3
4
5
6
7
8
9
10
11
<!-- 초 -->
<TextBlock x:Name="txtSecond" Text="0" Grid.Column="2" RenderTransformOrigin="0.5,1">
    <TextBlock.RenderTransform>
        <TransformGroup>
            <ScaleTransform/>
            <SkewTransform/>
            <RotateTransform/>
            <TranslateTransform/>
        </TransformGroup>
    </TextBlock.RenderTransform>
</TextBlock>
cs

 

StoryboardHour, StoryboardMinute, StoryboardSecond 스토리보드 추가

 

스토리보드는 리소스에 정의해야 합니다시간이 가고 있다는 느낌을 주기 위해 ScaleY축에 약간의 움직임을 반복하도록 했습니다 모두 각각 분리해서 작성합니다.


Blend for VS 이용하면 쉽게 작성할 있습니다. RenderTransform 속성을 이용한 스토리보드를 작성하기 위해서는 각 TextBlock에 RenderTransformOrigin과 RenderTransform 그룹이 미리 정의되어 있어야 하는 것 주의가 필요 합니다. Blend for VS 이용해 스토리보드를 작성한다면 자동으로 추가되므로 걱정할 필요가 없습니다

 

다음 포스트에서는 Custom Trigger Custom Action 추가해봅시다.

 

참고 : MSDN > Creating custom triggers and actions

 

샘플 코드 : https://github.com/CharlesKwon/XamlSimplified

 

관련 목차

 

023. 사용자 추가 비헤이비어(Custom Behavior)

024. 사용자 추가 비헤이비어(Custom Trigger & Action) #1

025사용자 추가 비헤이비어(Custom Trigger & Action) #2 


023. 커스텀 비헤이비어(Custom Behavior)

XAML 뽀개기

이전 포스트에서는 기본으로 제공되는 비헤이비어 사용법을 살펴보았습니다. 이번 포스트에서는 사용자가 직접 비헤이비어를 만들어보는 방법을 계속해서 살펴보겠습니다.

 

 

먼저 예제를 위한 상황 설정에 대한 설명입니다. 위 이미지처럼 Expander 컨트롤을 여러 동시에 사용하는 작은 인터페이스에서는 컨텐츠 노출을 효율적으로 하기 위해 한번에 하나씩만 열리도록 제약을 줄 때가 종종 있습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<StackPanel Margin="5">
    <TextBlock Text="Only one can expand at a time" FontWeight="Bold" Margin="0,0,0,5"/>
            
    <Expander Header="Header 01" IsExpanded="True">
        <TextBlock Text="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book." 
                   TextWrapping="Wrap"/>
    </Expander>
    <Expander Header="Header 02">
        <TextBlock Text="It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged." 
                   TextWrapping="Wrap"/>
    </Expander>
    <Expander Header="Header 03">
        <TextBlock Text="It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." 
                   TextWrapping="Wrap"/>
    </Expander>
</StackPanel>
cs

기본 XAML 코드 : MainWindow.xaml


XAML 코드는 최대한 간단히 StackPanel 이용해 여러 개의 Expander 컨트롤을 하나의 그룹으로 만들었습니다.

 

OnlyOneExpanderBehavior.cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class OnlyOneExpanderBehavior : Behavior<Panel>
{
    protected override void OnAttached()
    {
        base.OnAttached();
 
        AssociatedObject.Loaded += AssociatedObject_Loaded;
    }
 
    protected override void OnDetaching()
    {            
        AssociatedObject.Loaded -= AssociatedObject_Loaded;
        
        base.OnDetaching();
    }
        
    private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
    {
        foreach (var child in AssociatedObject.Children)
        {
            if (child == nullreturn;
            var allExpander = child as Expander;
            if (allExpander != null)
            {
                allExpander.Expanded += Expander_Expanded;
            }
        }
    }
 
    private void Expander_Expanded(object sender, RoutedEventArgs e)
    {
        foreach (var child in AssociatedObject.Children)
        {
            if (child == nullreturn;
            var allExpander = child as Expander;
            if (allExpander != null)
            {
                allExpander.IsExpanded = false;
            }
        }
 
        var expander = sender as Expander;
        if (expander == nullreturn;
        expander.Expanded -= Expander_Expanded;
        expander.IsExpanded = true;
        expander.Expanded += Expander_Expanded;
    }
}
 
cs

 

코드는 상황을 해결하는데 사용하기 위해 추가한 OnlyOneExpanderBehavior 비헤이비어 전체 코드입니다. 부분으로 나누어 살펴보겠습니다.

 

1
2
3
4
5
6
public class OnlyOneExpanderBehavior : Behavior<Panel>
{
    
// 생략
 
}
cs

 

OnlyOneExpanderBehavior는 Behavior를 상속받습니다. StackPanel 뿐만 아니라 Grid, WrapPanel Panel 상속받아 자식을 가질 있는 모든 패널 요소에 적용 가능하게 하기 위해 Panel 사용할 요소로 지정했습니다. 예제에서는 StackPanel 사용할 요소로 제한해도 상관없습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
protected override void OnAttached()
{
    base.OnAttached();
 
    AssociatedObject.Loaded += AssociatedObject_Loaded;
}
 
protected override void OnDetaching()
{
    AssociatedObject.Loaded -= AssociatedObject_Loaded;
 
    base.OnDetaching();
}
cs

 

OnAttached와 OnDetaching 메소드를 재정의합니다. Behavior를 작성할 때  메소드를 재정의하는 일은 필수라 있습니다. AssociatedObject를 통해 XAML 코드에 적용한 UI 요소를 가져옵니다. OnAttached 메소드에서는 가져온 요소에 Loaded 이벤트를 연결하고 OnDetaching 메소드에서는 Loaded 이벤트를 연결 해제합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
    foreach (var child in AssociatedObject.Children)
    {
        if (child == nullreturn;
        var allExpander = child as Expander;
        if (allExpander != null)
        {
            allExpander.Expanded += Expander_Expanded;
        }
    }
}
cs

 

AssociatedObject_Loaded 이벤트 핸들러에서는 패널의 하위 자식들 모든 Expander 컨트롤을 찾아서 Expanded 이벤트를 연결합니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void Expander_Expanded(object sender, RoutedEventArgs e)
{
    foreach (var child in AssociatedObject.Children)
    {
        if (child == nullreturn;
        var allExpander = child as Expander;
        if (allExpander != null)
        {
            allExpander.IsExpanded = false;
        }
    }
 
    var expander = sender as Expander;            
    if (expander == nullreturn;
    expander.Expanded -= Expander_Expanded;
    expander.IsExpanded = true;
    expander.Expanded += Expander_Expanded;
}
cs

 

Expander_Expanded 이벤트 핸들러에서는 모든 Expander 컨트롤을 IsExpanded 속성을 이용해 닫은 sender 통해 들어온 Expander 컨트롤만 다시 열어줍니다. 그냥 열어주게되면 이벤트 핸들러를 반복해서 타게 되므로 열어주기 전과 후에 이벤트 핸들러 해제 연결을 다시 한번 해줍니다.

 

1
2
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
xmlns:bhv="clr-namespace:Sample.Behavior"
cs

 

1
2
3
4
5
6
7
8
<StackPanel Margin="5">
    <i:Interaction.Behaviors>
        <bhv:OnlyOneExpanderBehavior/>
    </i:Interaction.Behaviors>
 
// 생략
 
</StackPanel>
cs

 

XAML 코드에 완성된 비헤이비어를 적용합니다. 추가한 비헤이비어를 사용하기 위해서는 두개의 네임스페이스가 우선 정의되어 있어야 합니다.

 

 

적용하기 이전 OnlyOneExpanderBehavior 비헤이비어 코드가 막 준비된 빌드를 했다면 Blend for VS Assets(자산) 윈도우에서 보이게 됩니다. 이전 비헤이비어 포스트에서 설명했습니다만 드래그  드롭 동작을 이용해 디자인 뷰에서 패널에 적용하는 것이 훨씬 간편합니다. 네임스페이스 정의도 함께 이루어지므로 매우 편리합니다.

 

 

실행해서 올바르게 동작하는지 확인해 봅니다.

 

샘플 코드 : https://github.com/CharlesKwon/XamlSimplified

 

관련 목차

 

023. 사용자 추가 비헤이비어(Custom Behavior)

024. 사용자 추가 비헤이비어(Custom Trigger & Action) #1

025. 사용자 추가 비헤이비어(Custom Trigger & Action) #2