XAML or HTML

'비헤이비어'에 해당되는 글 1건

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

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