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

011. 의존 속성(Dependency Property)

XAML 뽀개기

간단한 예제를 통해 Dependency property 대해 알아봅니다. 기본으로 제공되는 UI 컨트롤은 대부분의 속성이 의존 속성으로 되어 있습니다. 기본 TextBox 이용해 입력을 유도하는 가이드 문구를 추가해 사용하는 예제를 만들어 봅니다.


GuideTextBox.cs : 의존 속성


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#region GuideText : 가이드 문구
 
public object GuideText
{
    get { return (object)GetValue(GuideTextProperty); }
    set { SetValue(GuideTextProperty, value); }
}
 
public static readonly DependencyProperty GuideTextProperty =
    DependencyProperty.Register(
        "GuideText"
        typeof(object), 
        typeof(GuideTextBox), 
        new PropertyMetadata("Guide Text"));
 
#endregion
 
cs


TextBox 상속 받은 GuideTextBox 클래스를 추가합니다. GuideText를 Dependency property로 추가 정의합니다.


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
<Style x:Key="Style_GuideTextBox" TargetType="{x:Type ctr:GuideTextBox}">
    <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
    <Setter Property="BorderBrush" Value="#FFABADB3"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
    <Setter Property="HorizontalContentAlignment" Value="Left"/>
    <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
    <Setter Property="AllowDrop" Value="True"/>
    <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
    <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
    <Setter Property="Template">                
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ctr:GuideTextBox}">
                <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
                    <ScrollViewer x:Name="PART_ContentHost" Focusable="False" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsEnabled" Value="False">
                        <Setter Property="Opacity" TargetName="border" Value="0.56"/>
                    </Trigger>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="BorderBrush" TargetName="border" Value="#FF7EB4EA"/>
                    </Trigger>
                    <Trigger Property="IsKeyboardFocused" Value="True">
                        <Setter Property="BorderBrush" TargetName="border" Value="#FF569DE5"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Style.Triggers>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="IsInactiveSelectionHighlightEnabled" Value="True"/>
                <Condition Property="IsSelectionActive" Value="False"/>
            </MultiTrigger.Conditions>
            <Setter Property="SelectionBrush" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/>
        </MultiTrigger>
    </Style.Triggers>
</Style>
cs


Style_GuideTextBox Key명으로 스타일을 작성합니다. Blend for VS 템플릿 편집 기능을 이용하면 쉽게 기본 템플릿을 얻을 있습니다.

 

1
2
3
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
    <ScrollViewer x:Name="PART_ContentHost" Focusable="False" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
</Border>
cs

 

기본 템플릿은 최상위 요소가 Border 감싸져 있습니다.

 

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

 

GuideTextBox 사용하기 위해 네임스페이스 선언이 필요합니다.

 

1
2
3
4
5
6
7
8
9
10
11
<ctr:GuideTextBox Height="30" Margin="5" Style="{DynamicResource Style_GuideTextBox}"
                  GuideText="입력해주세요"/>
 
<ctr:GuideTextBox Height="30" Margin="5" Style="{DynamicResource Style_GuideTextBox}" Grid.Column="1">
    <ctr:GuideTextBox.GuideText>
        <StackPanel Orientation="Horizontal">
            <Ellipse Width="10" Height="10" Fill="Red" Margin="5,0"/>
            <TextBlock Text="입력해주세요"/>
        </StackPanel>
    </ctr:GuideTextBox.GuideText>
</ctr:GuideTextBox>
cs

 

태스트를 위한 GuideTextBox를 XAML 작성합니다. 첫번째는 문자열만 두번째는 복잡하게 구성했습니다.

 

 

이것만으로는 가이드 문구를 표시할 없습니다. 스타일 Template을 수정해야 합니다.

 

1
2
3
4
5
6
7
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
    <Grid>
        <ScrollViewer x:Name="PART_ContentHost" Focusable="False" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
        
        <Label x:Name="PART_GuideText" Content="{TemplateBinding GuideText}" Foreground="Red"/>
    </Grid>
</Border>
cs

 

Border는 하나의 자식만 가질 있으므로 Grid로 한번 감싸주었습니다. 그리고 가이드 문구를 TextBlock으로 간단히 정의할 있지만 복잡한 구성이 가능케 하기위해 Label 정의했습니다. TextBlock Text 속성은 문자열 타입만 자식으로 가질 있고 Label Content Object 타입을 가질 있습니다. 반드시 Label이어야하는 것은 아닙니다. 자세한 설명은 여기서 다루지 않습니다.

 

 


Dependency property 추가해 자유롭게 변경 가능한 가이드 문구를 추가했습니다. 간단한 예를 들었지만 다양한 방식으로 응용할 있을 것입니다.


 

이어서 다음 포스트에서는 Value Converter 이용해 입력한 Text 있을 때처럼 가이드 문구가 이상 필요하지 않을 자동으로 보이지 않도록 하는 방법을 알아보려 합니다.


Dependency property에 대한 더 자세한 내용은 박문찬 MVP님의 블로그를 참고하세요.


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