XAML or HTML

017. 두개의 콘텐츠 버튼(Two Contents Button) #3

XAML 뽀개기

AttachedProperty.cs : 결합 속성


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class AP_TwoContentsButton
{
    #region SecondContent
 
    public static readonly DependencyProperty SecondContentProperty =
        DependencyProperty.RegisterAttached(
            "SecondContent",
            typeof(string),
            typeof(AP_TwoContentsButton),
            new PropertyMetadata(string.Empty));
 
    public static string GetSecondContent(DependencyObject dp)
    {
        return (string)dp.GetValue(SecondContentProperty);
    }
 
    public static void SetSecondContent(DependencyObject dp, string value)
    {
        dp.SetValue(SecondContentProperty, value);
    }
 
    #endregion
}
cs


이번엔 Attached Property 이용해 봅시다. 먼저 Dependency Property와는 달리 Button 컨트롤을 상속 받을 필요 없이 새로운 클래스를 만들었습니다. 이전 Class 명과 구분하기 위해 보기는 싫지만 AP_ 덧붙여 작성했습니다.


1
xmlns:atcp="clr-namespace:Sample.AttachedProperty"
cs


XAML 코드에서 결합 속성을 사용하기 위해 네임스페이스를 추가합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<Button Content="Menu03" atcp:AP_TwoContentsButton.SecondContent="Lorem Ipsum is simply" Grid.Column="2">
    <Button.Template>
        <ControlTemplate TargetType="Button">
            <Border BorderBrush="{TemplateBinding BorderBrush}" 
                    BorderThickness="{TemplateBinding BorderThickness}" 
                    Background="{TemplateBinding Background}">
                <StackPanel HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                            Margin="5">
                    <ContentPresenter Content="{TemplateBinding Content}"/>
                    <TextBlock Text="{Binding (atcp:AP_TwoContentsButton.SecondContent), RelativeSource={RelativeSource TemplatedParent}}" TextWrapping="Wrap"/>
                </StackPanel>
            </Border>
        </ControlTemplate>
    </Button.Template>
</Button>
cs


기존 Button 컨트롤에 atcp:AP_TwoContentsButton.SecondContent 결 속성을 정의합니다. 결합 속성은 [정의타입].[속성명] 형태의 문법을 사용합니다.


1
<TextBlock Text="{Binding (atcp:AP_TwoContentsButton.SecondContent), RelativeSource={RelativeSource TemplatedParent}}" TextWrapping="Wrap"/>
cs


이전 의존 속성에서 사용한 TextBlock 코드를 재사용합니다. Binding 문법의 Path 속성만 추가한 결합속성에 알맞게 수정합니다. 간혹 Visual Studio에서 에러가 발생하는데 VS 끄고 다시 켜면 에러가 나지 않습니다. 생각에는 버그인 합니다.



동일한 표현이 가능합니다.



왼쪽이 결합 속성, 오른쪽이 의존 속성입니다. 결합 속성과 의존 속성을 속성 창에서 찾아 비교해보면 의존 속성만 노출되는 것을 알 수 있습니다. 당연한 결과라 생각할 수 있습니다. 결합 속성은 자신이 정의된 클래스가 아닌 다른 클래스에서 적용될 수 있다는 점이 다릅니다. 이전 결합 속성 포스트에서 레이아웃을 정의하는데 사용된다고 이야기했었습니다. 


결합 속성은 레이아웃 정의 뿐만 아니라 무한 확장 기능 구현에 큰 기능을 합니다. 좀 더 속 깊은 설명은 박문찬 MVP님의 블로그 포스트를 참고하시면 좋을 듯 합니다.


Dependency Property와 Attached Property의 차이점이 뭐지? #1

Dependency Property와 Attached Property의 차이점이 뭐지? #2


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


관련 목차


015. 두개의 컨텐츠 버튼(Two contents button) #1

016. 두개의 컨텐츠 버튼(Two contents button) #2

017. 두개의 컨텐츠 버튼(Two contents button) #3

016. 두개의 콘텐츠 버튼(Two Contents Button) #2

XAML 뽀개기

TwoContentsButton.cs : 의존 속성


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TwoContentsButton : Button
{
    #region SecondContent : 두번째 Content
 
    public string SecondContent
    {
        get { return (string)GetValue(SecondContentProperty); }
        set { SetValue(SecondContentProperty, value); }
    }
 
    public static readonly DependencyProperty SecondContentProperty =
        DependencyProperty.Register(
            "SecondContent"
            typeof(string), 
            typeof(TwoContentsButton), 
            new PropertyMetadata(string.Empty));
 
    #endregion
}
cs


Button 컨트롤을 상속 받아서 TwoContentsButton이라는 새로운 컨트롤을 만드는 방법입니다. 이전 포스트에서 알아본 Dependency Property 이용해 두번째 Content 속성을 추가할 있습니다.


1
xmlns:ctr="clr-namespace:Sample.Control"
cs


 컨트롤을 만든 것이므로 XAML에서 사용하려면 네임스페이스 추가가 필요합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<ctr:TwoContentsButton Content="Menu03" Margin="20" Grid.Column="2">
    <Button.Template>
        <ControlTemplate TargetType="Button">
            <Border BorderBrush="{TemplateBinding BorderBrush}" 
                    BorderThickness="{TemplateBinding BorderThickness}" 
                    Background="{TemplateBinding Background}">
                <StackPanel HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                            Margin="5">
                    <ContentPresenter Content="{TemplateBinding Content}"/>
                    <TextBlock Text=" " TextWrapping="Wrap"/>
                </StackPanel>
            </Border>
        </ControlTemplate>
    </Button.Template>
</ctr:TwoContentsButton>
cs


기존 버튼도  버튼에 맞춰 변경해야 합니다. 점점 수정해야 것들이 늘어납니다.



빌드 과정을 거친 후에는 IntelliSense 도움을 받아 SecondContent 속성을 정의할 있습니다


1
<TextBlock Text="{TemplateBinding SecondContent}" TextWrapping="Wrap"/>
cs


의존 속성을 정의한 후에는 두번째 Content 역할을 TextBlock 연결해줘야 겠습니다. 기존 ContentPresenter처럼 TemplateBinding 문법을 사용할 수는 없습니다. 빌드 자체가 되지 않습니다.


1
<TextBlock Text="{Binding SecondContent, RelativeSource={RelativeSource TemplatedParent}}" TextWrapping="Wrap"/>
cs


Binding 문법에서 템플릿 부모를 Source 지정하는 방법으로 SecondContent 속성에 접근할 있습니다.



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


관련 목차


015. 두개의 컨텐츠 버튼(Two contents button) #1

016. 두개의 컨텐츠 버튼(Two contents button) #2

017. 두개의 컨텐츠 버튼(Two contents button) #3

015. 두개의 콘텐츠 버튼(Two Contents Button) #1

XAML 뽀개기


위와 같이 2개의 Content가 사용되는 버튼 컨트롤은 실제 프로젝트에서 흔히 사용됩니다. 기본으로 제공되는 버튼 컨트롤은 Content 속성이 하나 밖에 제공되지 않지만 Object 타입이기 때문에 아래와 같은 방법으로 흔히 표현할 수 있습니다.


1
2
3
4
5
6
7
8
<Button>
    <Button.Content>
        <StackPanel Margin="5">
            <TextBlock Text="Menu01" FontWeight="Bold"/>
            <TextBlock Text="Lorem Ipsum is simply" TextWrapping="Wrap"/>
        </StackPanel>
    </Button.Content>
</Button>
cs


1
2
3
4
5
6
<Button>
    <StackPanel Margin="5">
        <TextBlock Text="Menu01" FontWeight="Bold"/>
        <TextBlock Text="Lorem Ipsum is simply" TextWrapping="Wrap"/>
    </StackPanel>
</Button>
cs


가장 간단한 방법은 위와 같지 않을까 생각됩니다. 하지만 버튼 컨트롤 자체의 Style과 Template에 영향을 받을 수 없는 한계가 있습니다. 물론 TextBlock 자체에 Style를 입힐 수는 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<Button Content="Menu01">
    <Button.Template>
        <ControlTemplate TargetType="Button">
            <Border BorderBrush="{TemplateBinding BorderBrush}" 
                    BorderThickness="{TemplateBinding BorderThickness}" 
                    Background="{TemplateBinding Background}" 
                    SnapsToDevicePixels="True">
                <ContentPresenter x:Name="contentPresenter" 
                    ContentTemplate="{TemplateBinding ContentTemplate}" 
                    Content="{TemplateBinding Content}" 
                    ContentStringFormat="{TemplateBinding ContentStringFormat}" 
                    Focusable="False" 
                    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                    VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                    Margin="{TemplateBinding Padding}" 
                    RecognizesAccessKey="True" 
                    SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
            </Border>
        </ControlTemplate>
    </Button.Template>
</Button>
cs


기본으로 제공되는 버튼의 템플릿은 위와 같습니다. 약간 복잡해 보입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<Button Content="Menu02" Grid.Column="1">
    <Button.Template>
        <ControlTemplate TargetType="Button">
            <Border BorderBrush="{TemplateBinding BorderBrush}" 
                    BorderThickness="{TemplateBinding BorderThickness}" 
                    Background="{TemplateBinding Background}">
                <StackPanel HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                            Margin="5">
                    <ContentPresenter Content="{TemplateBinding Content}"/>
                </StackPanel>
            </Border>
        </ControlTemplate>
    </Button.Template>
</Button>
cs


이번 포스트 주제에만 집중할 수 있도록 예제 코드를 간략히 수정했습니다. 2개의 콘텐츠를 세로로 정렬하기 위해 StackPanel로 ContentPresenter를 감싸주었습니다.


Content="{TemplateBinding Content}" 코드도 생략할 수있습니다. ContentPresenter의 Content는 암시적으로 바인딩되기 때문에 가능합니다. 이 예제에서는 일부러 생락하지 않습니다. ContentTemplate과 ContentPresenter는 다른 포스트에서 다룰 예정입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<Button Content="Menu02" Tag="Lorem Ipsum is simply" Grid.Column="1">
    <Button.Template>
        <ControlTemplate TargetType="Button">
            <Border BorderBrush="{TemplateBinding BorderBrush}" 
                    BorderThickness="{TemplateBinding BorderThickness}" 
                    Background="{TemplateBinding Background}">
                <StackPanel HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" 
                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                            Margin="5">
                    <ContentPresenter Content="{TemplateBinding Content}"/>
                    <TextBlock Text="{TemplateBinding Tag}" TextWrapping="Wrap"/>
                </StackPanel>
            </Border>
        </ControlTemplate>
    </Button.Template>
</Button>
cs


위 코드는 FrameworkElement를 상속 받은 모든 컨트롤들이 공통적으로 가지고 있는 Tag 속성을 이용하는 예제 코드입니다. 두번째 Content를 추가하기 위해 ContentPresenter를 하나 더 추가해도 되지만 TextWrapping 속성을 이용해 줄내림 효과를 바로 얻기 위해 TextBlock 컨트롤을 추가했습니다. 그런 후 Tag 속성과 TextBlock 컨트롤의 Text 속성을 TemplateBinding 문법을 이용해 결합했습니다.



이런 간단한 방법으로도 2개의 컨텐츠를 표현할 수 있습니다. 하지만 Tag 속성은 의미적(Semantic)으로 적합하지 않고 일부 개발하는 과정에서 다른 용도로 사용될 수 있는 가능성이 있으므로 이렇게 사용하는 것은 프로토타입니나 샘플 코드에서나 적합할 것입니다.


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


관련 목차


015. 두개의 컨텐츠 버튼(Two contents button) #1

016. 두개의 컨텐츠 버튼(Two contents button) #2

017. 두개의 컨텐츠 버튼(Two contents button) #3