XAML or HTML

026. 툴팁 말풍선(ToolTip Balloon)

XAML 뽀개기

이번 포스트는 잠시 쉬어가는 포스트입니다. 다음 포스트에 사용될 디자인 요소도 제작할 겸해서 말입니다. 구글에서 말풍선 이미지 검색해보면 매우 다양한 말풍선 디자인을 찾을 있습니다. 참고하세요. 저는 최대한 간소하게 표현해볼까 합니다.

 

 

다음 포스트에서 사용될 예제를 먼저 보도록 합시다. 기본 슬라이더의 Thumb() 드래그하면 현재 Value() 말풍선 안에서 표출되는 간단한 기능입니다. 실제 구현은 그리 간단하지 않습니다만 다음 포스트에서 만들어 보려고 합니다.

 

1
2
3
4
5
6
7
8
<!-- ToolTip balloon #1 -->
<StackPanel Margin="5">
    <Border CornerRadius="5" Background="SkyBlue">
        <TextBlock Text="Lorem Ipsum is simply dummy text." HorizontalAlignment="Center" Margin="10,5"/>
    </Border>
    <ed:RegularPolygon Fill="SkyBlue" Margin="0,-8,0,0" Panel.ZIndex="-1" 
                       PointCount="4" Width="10" Height="15"/>
</StackPanel>
cs

 

첫번째 방법입니다. 말풍선 몸체와 꼭지는 세로로 정렬되는 형태이므로 전체를 StackPanel 그룹화했습니다. 값이 들어갈 TextBlock Border 한번 감싸주었습니다. 이렇게 하면 TextBlock 문자열이 들어와도 자동으로 Border 밀어내 늘어나므로 나름 효율적입니다.

 

말풍선 꼭지는 Microsoft.Expression.Drawing.dll 어셈블리를 참조하면 사용할 있는 도형 RegularPolygon을 이용해 만들었습니다. 삼각형 꼭지를 사용할 수도 있지만 사각형(마름모) 꼭지를 만들어서 사용할 수도 있습니다. StackPanel로 감싸주었기 때문에 레이어 순서가 문제가 되지만 Panel.Zindex 속성에 "-"값을 주어 레이어 순서를 추가로 변경하면 문제가 되지 않습니다. 약간의 위치조정은 Margin 속성을 이용해 조정했습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
<!-- ToolTip balloon #2 -->
<Grid Margin="5">
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
 
    <Path Fill="PaleVioletRed" Margin="0,-8,0,0" Grid.Row="1" 
          Width="10" Height="15" Data="M5,0 L10,7.5 L5,15 L0,7.5 z"/>
    <Rectangle RadiusX="5" RadiusY="5" Fill="PaleVioletRed"/>
    <TextBlock Text="Lorem Ipsum is simply dummy text." HorizontalAlignment="Center" Margin="10,5"/>
</Grid>
cs

 

두번째 방법입니다. Grid 이용해 말풍선 몸체와 꼭지를 그룹화했습니다. 말풍선 몸체와 꼭지는 Grid RowDefinition 속성을 이용해 2개로 나누어진 Row 각각 위치하도록 했습니다. 말풍선 몸체는 Rectangle 이용했습니다. 첫번째 방법과 조금 다른 점이라면 Border CornerRadius 속성이고 Rectangle RadiusX, RadiusY 속성을 이용해 코너의 라운드를 표현하는 점입니다. 그리고 Rectangle TextBlock 자식으로 가질 없습니다.

 

Grid StackPanel 달리 레이어 정렬에 강하게 영향을 주지 않으므로 말풍선 꼭지를 먼저 선언하고 몸체를 선언해 가려지지 않도록 했습니다. 꼭지는 Path 이용했습니다. Path Width, Height Data 속성을 이용해 Path Point들의 위치에 따라 그려지므로 다루기가 까다롭습니다. 깔끔한 픽셀 단위 작업이 쉽지 않습니다. 약간의 위치조정은 첫번째 방법과 동일하게 Margin 속성을 이용했습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- ToolTip balloon #3 -->
<Grid Margin="5">
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
            
    <Border CornerRadius="5" Background="DarkSeaGreen">
        <TextBlock Text="Lorem Ipsum is simply dummy text." HorizontalAlignment="Center" Margin="10,5"/>
    </Border>
    <ed:RegularPolygon Fill="DarkSeaGreen" Margin="0,0,0,-4" Grid.Row="1"
                       PointCount="3" Width="10" Height="8" RenderTransformOrigin="0.5,0.5">
        <ed:RegularPolygon.RenderTransform>
            <TransformGroup>
                <ScaleTransform ScaleY="-1" ScaleX="1"/>
            </TransformGroup>
        </ed:RegularPolygon.RenderTransform>
    </ed:RegularPolygon>
</Grid>
cs

 

세번째 방법입니다. 두번째 방법과 동일하게 Grid 이용해 말풍선 몸체와 꼭지를 그룹화했고 Row 위치하도록 했습니다. 값이 들어갈 TextBlock Border 한번 감싸주는 첫번째 방법을 이용했습니다.

 

말풍선 꼭지는 첫번째 방법과 동일하게 도형 RegularPolygon을 이용했습니다. 첫번째 방법과 다른 점은 이번엔 삼각형이라는 점입니다. 하지만 상하 방향을 뒤집어야 하기 때문에 ScaleTransform의 ScaleY 속성에 "-"값을 주어 반전 효과를 주었습니다. RotateTransform 이용해 회전 시켜도 동일한 효과를 얻을 있습니다. 약간의 위치조정은 첫번째, 두번째 방법과 동일하게 Margin 속성을 이용했습니다.

 

1
2
3
4
5
6
7
8
<!-- ToolTip balloon #4 -->
<DockPanel Margin="5">
    <Border CornerRadius="5" Background="Gainsboro" DockPanel.Dock="Top">
        <TextBlock Text="Lorem Ipsum is simply dummy text." HorizontalAlignment="Center" Margin="10,5"/>
    </Border>
    <ed:RegularPolygon Fill="Gainsboro" Margin="0,-8,0,0" Panel.ZIndex="-1" 
                       PointCount="4" Width="10" Height="15" DockPanel.Dock="Top"/>
</DockPanel>
cs

 

4번째 방법입니다. 이번에는 DockPanel 이용해 말풍선 몸체와 꼭지를 그룹화했습니다. DockPanel 사용했기 때문에 하위 자식들은 DockPanel.Dock 의존 속성을 정의해 위치를 각각 지정해 주어야 합니다. 외에는 StackPanel 이용했던 첫번째 방법과 다른 점은 없습니다. 동일한 표현이 가능은 하지만 DockPanel 사용했다는 점이 께름칙합니다.

 

 

위에서 알아본 4가지 방법 모두는 현재로서는 문제가 보이지 않습니다. 결과적으로는 동일한 결과를 보이고 있습니다. 인터페이스를 마주하는 사용자 입장에서는 더더욱 상관없습니다. 하지만 코드 간결성, 효율성 여러면에서 문제가 발생할 수도 있습니다. 그리고 만약에 요구사항이 변경되거나 기능이 추가되었을 수정의 용이성, 한계점 어떤 문제가 발생할지 예측하기 힘듭니다.

 

 

예를 들어 표출될 컨텐츠의 양이 늘어나 여러 줄로 표현이 되어야 한다면 그에 따라 말풍선의 크기도 늘어나야 것입니다. 예제에 사용된 4가지 방법 모두는 가능하도록 코드를 작성했습니다. 하지만 Blend for VS는 아시다시피 WYSIWYG(위지위그방식의 툴입니다. 심심치 않게 디자인 윈도우에서 마우스를 조작해 디자인을 수정할 수 있습니다.

 

4가지 방법에서 각각 그룹화에 쓰인 StackPanel, Grid, DockPanel 모두를 다시 한번 살펴보면 Width, Height 속성이 어느 하나도 쓰이지 않은 것을 있습니다. 이유는 하위 자식의 컨텐츠 양이 얼마나 될지 확신하지 못하기 때문에 의도적으로 제한하지 않은 것입니다. 다른 의미로는 컨텐츠 양에 따라 유연하게 늘어나도록 일부러 정의하지 않은 것입니다


물론 컨텐츠양이 변경될 때마다 크기를 변경하는 관련 코드를 추가할 수도 있겠습니다만 그리 좋지 않은 접근 방식이라 생각합니다. 이런 요구사항이 있을 때 위지위그 방식으로 작업을 하다보면 Width, Height, Margin 등의 속성이 원치않게 자동으로 생성되는 것을 쉽게 경험할 수 있습니다.

 


결론은 위지위그 방식의 툴을 사용할 주의해야 한다는 것입니다. C# 또는 XAML 코드를 다루는 우리는 Blend for VS Visual Studio 사용이 필수고 당연한 일이기도 합니다. 그리고 디자인 윈도우에서 마우스 조작을 이용해 디자인 관련 작업을 하는 것은 매우 쉽고 직관적입니다. 하지만 위지위그 방식의 툴들이 대부분 그러하듯 때론 사용자가 의도하지 않은 코드를 생성해내기도 합니다. 그렇기 때문에 위지위그 방식의 작업 중에도 XAML 코드 확인  수정해줘야 하는 일이 때에 따라 필요할 있습니다.


상황에 따라 어떤 코드를 선택할지는 여러분의 몫이지만 어떤 차이가 있는지 어떤 방법이 더 효과적인지를 판단하 점 중요할 것 있습니다.


013. 속성 상속(Resolved Dynamically)

XAML 뽀개기

프로젝트에서 XAML 코드 작업을 하다보면 점점 늘어나는 코드 양에 스트레스를 받을 때가 간혹 있습니다. Style 묶어 관리하기도 하고 Resource Dictionary 파일을 여러 나누어 관리도 하면서 나름대로의 효율을 찾아서 자신만의 스타일을 갖게 됩니다.

       

1
2
3
4
5
6
7
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
    <Label Content="Local property" FontSize="12" FontFamily="Gulim" FontWeight="Bold" FontStyle="Italic"/>
    <TextBlock Text="Lorem Ipsum is simply " FontSize="12" FontFamily="Gulim" FontWeight="Bold" FontStyle="Italic"/>
    <TextBlock Text="dummy text of the" FontSize="12" FontFamily="Gulim" FontWeight="Bold" FontStyle="Italic"/>
    <TextBlock Text="printing and typesetting " FontSize="12" FontFamily="Gulim" FontWeight="Bold" FontStyle="Italic"/>
    <TextBlock Text="industry." FontSize="12" FontFamily="Gulim" FontWeight="Bold" FontStyle="Italic"/>
</StackPanel>
cs

 

 

이해를 돕기위해 극단적으로 예제를 들고자합니다. 텍스트의 Font 관련된 속성들을 보면 FontSize, FontFamily, FontWeight, FontStyle, FontStretch, Foreground 많은 속성들이 사용됩니다. 앞에서 이야기했 이런 복잡함은 스타일, 리소스딕셔너리 다양한 방법들을 이용해 효율적으로 관리될 있습니다.

 

그런데 일반적으로 Label, TextBlock 문자열 표시에 사용되는 컨트롤들은 단독으로 흔히 사용되지 않습니다. 다른 컨트롤도 마찬가지입니다. Grid, StackPanel, ScrollViewer 다양한 패널(Panel) 자식으로 사용됩니다.

 

1
2
3
4
5
6
7
<StackPanel ChildrenFontSize="15">
    <Label Content="Local property"/>
    <TextBlock Text="Lorem Ipsum is simply "/>
    <TextBlock Text="dummy text of the"/>
    <TextBlock Text="printing and typesetting "/>
    <TextBlock Text="industry."/>
</StackPanel>
cs

 

이런 환경 구조 안에서 패널이 자식의 속성을 일괄적으로 적용해주면 어떨까요? 그러면 어떤면에서 효율적이면서도 XAML 코드를 조금이나마 작성할 있지않을까요? 다시 한번 말하지만 예제를 위한 설정입니다. 어떤 방법이든지 자신만의 스타일과 그러한 결정에 따른 장점과 단점은 항상 존재하는 법입니다.

 

1
2
3
4
5
6
7
<StackPanel FontSize="15">
    <Label Content="Attached property"/>
    <TextBlock Text="Lorem Ipsum is simply "/>
    <TextBlock Text="dummy text of the"/>
    <TextBlock Text="printing and typesetting "/>
    <TextBlock Text="industry."/>
</StackPanel>
cs

 

StackPanel은 FontSize 속성이 없기 때문에 에러가 발생합니다. StackPanel을 상속 받아서 새로운 패널 컨트롤(Custom Control) 만들 수도 있습니다. 그러면 일이 쉽게 풀릴지도 모르겠습니다.

 

1
2
3
4
5
6
7
8
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="1"
            TextBlock.FontSize="15" TextBlock.Foreground="Red">        
    <Label Content="Attached property"/>
    <TextBlock Text="Lorem Ipsum is simply "/>
    <TextBlock Text="dummy text of the"/>
    <TextBlock Text="printing and typesetting "/>
    <TextBlock Text="industry."/>
</StackPanel>
cs

 

이런 방법은 어떨까요? Class명과 함께 속성을 정의하는 것이 가능합니다. VisualTree 요소들은 부모의 속성을 상속 받으려는 특성이 있기 때문에 이러한 동작이 가능합니다. 자세한 설명은 생략합니다. 더 자세한 내용은 박문찬 MVP님의 블로그를 참고하세요.

 

 

하지만 Foreground 속성은 영향을 미치지 못하는 한계가 있습니다. 문자열을 표현하는데 있어서 Foreground 속성을 무시할 수는 없습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="1">
    <StackPanel.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="FontSize" Value="15"/>
        </Style>
        <Style TargetType="Label">
            <Setter Property="FontSize" Value="15"/>
        </Style>
    </StackPanel.Resources>
 
    <Label Content="Attached property"/>
    <TextBlock Text="Lorem Ipsum is simply "/>
    <TextBlock Text="dummy text of the"/>
    <TextBlock Text="printing and typesetting "/>
    <TextBlock Text="industry."/>
</StackPanel>
cs

 

이런 식의 코드도 작성 가능합니다. 저도 많이 사용하는 방법 중에 하나입니다. 저런 식의 리소스 선언은 하위 컨트롤에만 영향을 준다는 점이 매우 매력적입니다. Style의 BasedOn 속성을 이용해 기존의 스타일도 재사용할 수 있는 점도 아주 매력적입니다. 방법의 단점은 다양한 컨트롤이 복합적으로 존재할 경우 코드가 많이 늘어난다는 점입니다


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

009. 4가지 트리거(Triggers)

XAML 뽀개기

Style Trigger 비슷 점이 많습니다. 다른 점이라면 스타일은 무조건 적용되는 반면 트리거는 조건을 수반한다는 점입니다.

 

트리거는 4가지 종류가 있습니다.

 

  1. Property : Dependency Property 변경될 호출됩니다.
  2. Event : 이벤트가 발생할 호출됩니다.
  3. Data : Binding 문법으로 연결된 .NET property 특정 데이타일 호출됩니다.
  4. Multi(& MultiData) : 조건을 다수 사용해 논리곱(AND) 관계를 정의합니다.

 

1
2
3
<TextBox Style="{StaticResource Style_BlueFocusTextBox}"/>
<TextBox Style="{StaticResource Style_BlueFocusTextBox}"
         Grid.Column="1"/>
cs

 

2개의 TextBox에 동일한 스타일을 적용했습니다. 2개를 예제로 이유는 포커스 상태를 쉽게 잃기 위함일뿐 다른 이유는 없습니다.

 

1. 프로퍼티 트리거


1
2
3
4
5
6
7
8
9
10
<Style x:Key="Style_BlueFocusTextBox">
    <Style.Triggers>
        <!-- IsFocused 프로퍼티 트리거 -->
        <Trigger Property="TextBox.IsFocused" Value="True">
            <Setter Property="TextBox.BorderThickness" Value="3"/>
        </Trigger>
    </Style.Triggers>
    <Setter Property="Control.Width" Value="60"/>
    <Setter Property="Control.Height" Value="30"/>
</Style>
cs

 

TextBox Focused 상태(State) 되면 IsFocused 속성이 True 변경됩니다. 조건일 BorderThickness 속성이 3으로 변경되는 트리거를 정의했습니다.

 

 

해당 조건을 벗어나면 추가 코드 없이도 트리거가 자동 취소됩니다.

 

2. 이벤트 트리거


1
2
3
4
5
6
7
8
9
10
<Storyboard x:Key="MouseEnterAnimation">
    <ColorAnimation 
        Storyboard.TargetProperty="(TextBox.Background).(SolidColorBrush.Color)" 
        To="LightGray" Duration="0:0:0.1"/>
</Storyboard>
<Storyboard x:Key="MouseLeaveAnimation">
    <ColorAnimation 
        Storyboard.TargetProperty="(TextBox.Background).(SolidColorBrush.Color)" 
        To="White" Duration="0:0:0.1"/>
</Storyboard>
cs


1
2
3
4
5
6
7
8
<!-- MouseEnter 이벤트 트리거 -->
<EventTrigger RoutedEvent="TextBox.MouseEnter">
    <BeginStoryboard Storyboard="{StaticResource MouseEnterAnimation}"/>
</EventTrigger>
<!-- MouseLeave 이벤트 트리거 -->
<EventTrigger RoutedEvent="TextBox.MouseLeave">
    <BeginStoryboard Storyboard="{StaticResource MouseLeaveAnimation}"/>
</EventTrigger>
cs

  

MouseEnter와 MouseLeave 이벤트가 발생할 MouseEnterAnimation와 MouseLeaveAnimation Storyboard 각각 실행합니다. 스토리보드는 리소스에 정의합니다. 이벤트 트리거는 프로퍼티 트리거와 약간 다릅니다. 스토리보드를 사용하기 때문에 트리거를 취소하려면 이전 트리거에 반하는 또는 그에 준하는 트리거를 추가해야 합니다.

 

 

이렇게 추가 코드가 없으면 트리거가 자동 취소되지 않습니다. 스토리보드의 동작을 이해한다면 알맞은 트리거를 추가할 있습니다.

 

3. 데이 트리거


1
2
3
4
<!-- Text 데이타 트리거 -->
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Self}}" Value="Bad">
    <Setter Property="TextBox.Foreground" Value="Red" />
</DataTrigger>
cs

 

 

데이타 트리거는 프로퍼티 트리거와 비슷합니다. 예제는 Bad라는 문자열이 Text 속성에 들어왔을 호출됩니다.

 

4. 멀티 트리거


1
2
3
4
5
6
7
8
9
10
<!-- Text 멀티 트리거 -->
<MultiTrigger>
    <MultiTrigger.Conditions>
        <Condition Property="TextBox.Text" Value="Good"/>
        <!-- AND -->
        <Condition Property="TextBox.IsMouseOver" Value="True"/>
    </MultiTrigger.Conditions>
    <Setter Property="TextBox.Foreground" Value="LimeGreen"/>
    <Setter Property="TextBox.FontWeight" Value="Bold"/>
</MultiTrigger>
cs


1
2
3
4
<TextBox Style="{StaticResource Style_BlueFocusTextBox}" 
         Tag="Good"/>
<TextBox Style="{StaticResource Style_BlueFocusTextBox}"
         Grid.Column="1"/>
cs



이전에 정의한 각각의 트리거가 논리합(OR)이라면, 멀티트리거는 논리곱(AND)이라 있습니다. 정의된 모든 Condition이 참일 트리거가 호출됩니다. 예제에서는  Text 속성 Good으로 참이되고 IsMouseOver 속성이 True로 참이될 트리거가 호출됩니다. 

 

5. 멀티 데이 트리거


1
2
3
4
5
6
7
8
9
10
<!-- Text  & Tag 멀티 데이타 트리거 -->
<MultiDataTrigger>
    <MultiDataTrigger.Conditions>
        <Condition Binding="{Binding Text, RelativeSource={RelativeSource Self}}" Value="Bad"/>
        <!-- AND -->
        <Condition Binding="{Binding Tag, RelativeSource={RelativeSource Self}}" Value="Bad"/>
    </MultiDataTrigger.Conditions>
    <Setter Property="TextBox.Foreground" Value="Red"/>
    <Setter Property="TextBox.FontWeight" Value="Bold"/>
</MultiDataTrigger>
cs

  

1
2
3
4
<TextBox Style="{StaticResource Style_BlueFocusTextBox}" 
         Tag="Good"/>
<TextBox Style="{StaticResource Style_BlueFocusTextBox}"
         Grid.Column="1" Tag="Bad"/>
cs

 


이번엔 설명이 필요없을 같습니다. 기존 두번째 TextBox  Tag="Bad"를 추가하 트리거의 모든 조건이 참이 되도록 했습니다. DataTrigger 대해서는 다음 포스트에서 다시 살펴보겠습니다.


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

008. 스타일 파생(Derive Style)

XAML 뽀개기

1
2
3
4
5
6
7
8
9
10
11
<Style x:Key="Style_ControlBase">
    <Setter Property="Control.Width" Value="60"/>
    <Setter Property="Control.Height" Value="30"/>
</Style>
            
<SolidColorBrush x:Key="Color_Background_ReadOnlyTextBox" Color="LightGray"/>
<Style x:Key="Style_ReadOnlyTextBox" TargetType="TextBox" 
       BasedOn="{StaticResource Style_ControlBase}">
    <Setter Property="TextBox.IsReadOnly" Value="True"/>
    <Setter Property="TextBox.Background" Value="{StaticResource Color_Background_ReadOnlyTextBox}"/>
</Style>
cs


Style BasedOn 속성을 이용해 이전 Style_ControlBase 스타일을 상속 받아 새로운 스타일을 정의합니다. TargetType을 TextBox 제한하고 Setter 정의시 Class명과 함께 정의한다면 더욱 견고히 스타일을 정의할 있습니다.


1
2
3
4
5
<TextBox Grid.Column="0" Style="{StaticResource Style_ControlBase}"
         Text="TextBox"/>
 
<TextBox Grid.Column="1" Style="{StaticResource Style_ReadOnlyTextBox}"
         Text="ReadOnlyTextBox" Width="100"/>
cs


 

Style_ControlBase 스타일은 Control Class에만 국한되고 Style_ReadOnlyTextBox 스타일은 TextBox Class에만 국한되어 깔끔히 분리되어 재사용될 있습니다.

 

1
2
3
4
5
6
<Style x:Key="Style_ReadOnlyTextBox" TargetType="TextBox" 
       BasedOn="{StaticResource Style_ControlBase}">
    <Setter Property="IsReadOnly" Value="True"/>
    <Setter Property="Background" Value="{StaticResource Color_Background_ReadOnlyTextBox}"/>
    <Setter Property="Width" Value="100"/>
</Style>
cs

 

TargetType을 TextBox 제한한 상태에서는 TextBox Class명을 생략해도 됩니다. 상속 받은 Setter 속성도 다시 정의할 있습니다.

 

1
2
3
4
5
6
<Style TargetType="TextBox" 
       BasedOn="{StaticResource Style_ControlBase}">
    <Setter Property="IsReadOnly" Value="True"/>
    <Setter Property="Background" Value="{StaticResource Color_Background_ReadOnlyTextBox}"/>
    <Setter Property="Width" Value="100"/>
</Style>
cs

 

스타일의 Key 이름을 정의하지 않으면 TargetType에 정의된 모든 Class 일괄로 스타일을 적용할 있습니다.

 

1
2
<TextBox Grid.Column="2" 
         Text="ReadOnlyTextBox"/>
cs

 

 

1
2
3
4
5
6
7
8
Style Style_ReadOnlyTextBox = new Style();
Style_ReadOnlyTextBox.TargetType = typeof(TextBox);
Style_ReadOnlyTextBox.BasedOn = (Style)this.grdTest.FindResource("Style_ControlBase");
Setter setterIsReadOnly = new Setter(TextBox.IsReadOnlyProperty, true);
Setter setterBackgroundS = new Setter(TextBox.BackgroundProperty, Brushes.LightGray);
Style_ReadOnlyTextBox.Setters.Add(setterIsReadOnly);
Style_ReadOnlyTextBox.Setters.Add(setterBackgroundS);
this.txbTest.Style = Style_ReadOnlyTextBox;
cs

 

2번의 스타일 관련 포스트에서 살펴본 XAML 코드는 비하인드 코드에서 동일하게 작성할 있습니다.

 

1
this.txbTest.Style = (Style)this.grdTest.FindResource("Style_ReadOnlyTextBox");
cs

 

XAML 코드에서 이미 정의된 스타일을 적용할 수도 있습니다. 하지만 특수한 경우를 제외하고 여러모로 비효율적이고 선호하지도 권장하지도 않습니다.




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

007. 스타일 정의(Override Style)

XAML 뽀개기
1
2
3
4
5
6
7
<Grid.Resources>
    <Style x:Key="Style_ControlBase">
        <!-- Property=”[ClassName].[DependencyProperty]”-->
        <Setter Property="Control.Width" Value="60"/>
        <Setter Property="Control.Height" Value="30"/>
    </Style>
</Grid.Resources>
cs

 

Style의 Setter 정의할 Property 앞에 Class명을 지정할 있습니다.

 

1
2
3
4
<TextBox Grid.Column="0" Style="{StaticResource Style_ControlBase}"
         Text="TextBox"/>
<Button Grid.Column="1" Style="{StaticResource Style_ControlBase}"
        Content="Button"/>
cs

 

 

TextBox Button 모두 Control에서 파생된 컨트롤이기 때문에 스타일에 영향을 받습니다. 추가로 TargetType 지정하지 않았기 때문에 가능한 일이기도 합니다.

 

 

1
2
3
4
5
<Style x:Key="Style_ControlBase" TargetType="{x:Type TextBox}">
    <!-- Property=”[ClassName].[DependencyProperty]”-->
    <Setter Property="Control.Width" Value="60"/>
    <Setter Property="Control.Height" Value="30"/>
</Style>
cs

 

TargetType을 지정하면 해당 타입에만 영향을 있기 때문에 많은 스타일을 다루는 프로젝트에서는 약이 되기도 하고 독이 되기도 합니다. 저는 TargetType을 지정해서 관리하는 것을 선호합니다.

 

1
2
<TextBox Grid.Column="1" Style="{StaticResource Style_ControlBase}"
         Text="TextBox" Width="80" Height="40"/>
cs

 

 

Style을 지정했지만 해당 속성을 다시 정의하면 지정된 Style을 무시(Override)하고 다시 정의할 있습니다.


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