체휘
운동하는 개발자
체휘
전체 방문자
오늘
어제
  • 분류 전체보기
    • WPF
    • C#
    • C++
    • Python
    • 경제적 자유

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 레지스트리
  • singleton
  • border
  • async
  • 리소스
  • 경제 #인생 #목표
  • ExpressionDark
  • ControlTemplate
  • ResourceDictionary
  • ComboBox
  • style
  • WPF
  • INotifyPropertyChanged
  • binding
  • c#

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
체휘

운동하는 개발자

[WPF] - Style, ControlTemplate
WPF

[WPF] - Style, ControlTemplate

2022. 12. 31. 21:44

WPF에서 사용되는 ComboBox는 Background 속성이 설정되지 않습니다.

 

그래서 ComboBox를 디자인하는 방법을 알기 위해서는 Style, ControlTemplate의 적용이 필수라는 것을 알게 되었습니다.

 

Style, ControlTempate에 대한 사용법과 이 2가지를 조합하여 ComboBox를 만드는 법을 설명하겠습니다.

기본 WPF 콤보박스

 

Style의 사용

 

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="400">
    
    <!-- Window 내부에서 리소스를 사용하기 위해 --!>
    <Window.Resources>
    	<!-- Key는 외부에서 사용하기 위한 이름, TargetType은 컨트롤 유형 --!>
        <Style x:Key="StdCmbBox" TargetType="{x:Type ComboBox}">
        	<!-- Setter를 이용하여 Property, Value를 지정할 수 있다 --!>
            <Setter Property="MinHeight" Value="20"></Setter>
            <Setter Property="MinWidth" Value="80"></Setter>
            <Setter Property="Width" Value="100"></Setter>
            <Setter Property="Height" Value="50"></Setter>
            <Setter Property="Foreground" Value="DarkOrange"></Setter>
        </Style>
    </Window.Resources>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        
        <!-- normalCmbBox에 Style을 적용하기 위해서는--!>
        <!-- "{StaticResource StdCmbBox}"로 등록하면 된다--!>
        <ComboBox x:Name="normalCmbBox" Style="{StaticResource StdCmbBox}">
            <ComboBoxItem Content="test1"></ComboBoxItem>
            <ComboBoxItem Content="test2"></ComboBoxItem>
        </ComboBox>
    </Grid>  
</Window>

Style을 이용하여 적용한 컨트롤

 

ControlTemplate의 사용

 

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="400">

    <Window.Resources>
    	<!-- ToggleButton 타입을 ComboBoxToggleButton 키로 접근하면 Template을 쓸수있다. --!>
        <ControlTemplate x:Key="ComboBoxToggleButton" TargetType="{x:Type ToggleButton}">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition Width="20" />
                </Grid.ColumnDefinitions>
                <Border
                  x:Name="Border" Grid.ColumnSpan="2" CornerRadius="0"
                  Background="#FF323232" BorderBrush="#FF97A0A5" BorderThickness="1" />
                <Border 
                  Grid.Column="0" CornerRadius="0" Margin="1" 
                  Background="#FF323232" BorderBrush="#FF4b4b4b" BorderThickness="0,0,2,0" />
                 
                <!-- 아래 방향의 삼각형을 만드는 방법 -->
                <!-- M (0,0) 절대경로에서 (4,4)로 Line, (8,0)로 Line로 간 다음 Z(종료)
                <Path 
                  x:Name="Arrow" Grid.Column="1" Fill="DarkOrange"
                  HorizontalAlignment="Center" VerticalAlignment="Center"                 
                  Data="M 0 0 L 4 4 L 8 0 Z"
                />
            </Grid>
        </ControlTemplate>
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>

        <ToggleButton Grid.Column="0" Margin="10" Template="{StaticResource ComboBoxToggleButton}"></ToggleButton>
        
    </Grid>
</Window>

ControlTemplate이 적용된 ToggleButton

 

✔️ Path 정보를 더 참고하고 싶을 때!

https://learn.microsoft.com/ko-kr/dotnet/desktop/wpf/graphics-multimedia/path-markup-syntax?view=netframeworkdesktop-4.8

 

 

Style, ControlTemplate을 조합한 ComboBox 만들기

 

우선 <Window.Resource> 내부에 Style, ControlTemplate을 다 적용하게 되면 Xaml의 길이가 매우 길어지게 된다.

 

그래서 ResourceDictionary를 App.xaml 등록하여 사용합니다.

 

App.xaml

<Application x:Class="WpfApp1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApp1"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="CmbBoxStyle.xaml"></ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

CmbBoxStyle.xaml

 
 

Style 설계

  • 콤보박스를 [텍스트박스 + 토글버튼] 으로 구성
  • 토글버튼을 누르면 아래 방향으로 Slide 하여 Popup 오픈
  • Popup이 열리면 콤보박스 항목이 보이며 항목을 선택하면 텍스트 박스에 표시됨
  • 콤보박스 항목도 ControlTemplate 적용
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

	<!-- ToggleButton의 Template 생성 --!>
    <ControlTemplate x:Key="ComboBoxToggleButton" TargetType="{x:Type ToggleButton}">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition Width="20" />
            </Grid.ColumnDefinitions>
            <Border
                  x:Name="Border" 
                  Grid.ColumnSpan="2"
                  CornerRadius="0"
                  Background="#FF323232"
                  BorderBrush="#FF97A0A5"
                  BorderThickness="1" />
            <Border 
                  Grid.Column="0"
                  CornerRadius="0" 
                  Margin="1" 
                  Background="#FF323232" 
                  BorderBrush="#FF4b4b4b"
                  BorderThickness="0,0,2,0" />
            <Path 
                  x:Name="Arrow"
                  Grid.Column="1"     
                  Fill="DarkOrange"
                  HorizontalAlignment="Center"
                  VerticalAlignment="Center"                 
                  Data="M 0 0 L 4 4 L 8 0 Z"
                />
        </Grid>
    </ControlTemplate>

	<!-- TextBox의 Template 생성 --!>
    <!-- 부모 컨트롤의 속성을 받아들이기 위해 TemplateBinding 사용 --!>
    <ControlTemplate x:Key="ComboBoxTextBox" TargetType="{x:Type TextBox}">
        <Border x:Name="PART_ContentHost" Focusable="False" 
        Background="{TemplateBinding Background}" />
    </ControlTemplate>

	<!-- ComboBox의 Style을 Key로 접근할 수 있게 만듬 --!>
    <Style x:Key="StdCmbBox" TargetType="{x:Type ComboBox}">
        <Setter Property="SnapsToDevicePixels" Value="true"/>
        <Setter Property="OverridesDefaultStyle" Value="true"/>
        <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
        <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
        <Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
        <Setter Property="MinWidth" Value="80"/>
        <Setter Property="MinHeight" Value="20"/>
        <Setter Property="Foreground" Value="DarkOrange"/>
        
        <!-- Style 내부에서 외부의 Template을 가져올 수 있음 --!>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ComboBox}">
                    <Grid>
                    	<!-- IsChecked 속성에 사용된 "{Binding}" --!>
                        <!-- IsDropDownOpen 속성이 true면 ToggleButton IsChecked도 true --!>
                        <ToggleButton 
                            Name="ToggleButton" 
                            Template="{StaticResource ComboBoxToggleButton}" 
                            Grid.Column="2" 
                            Focusable="false"
                            IsChecked="{Binding Path=IsDropDownOpen,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}"
                            ClickMode="Press">
                        </ToggleButton>
                        <ContentPresenter Name="ContentSite" IsHitTestVisible="False" 
                        	Content="{TemplateBinding SelectionBoxItem}"
                            ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
                            ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
                            Margin="3,3,23,3"
                            VerticalAlignment="Center"
                            HorizontalAlignment="Left" />
                        <TextBox x:Name="PART_EditableTextBox"
                            Style="{x:Null}" 
                            Template="{StaticResource ComboBoxTextBox}" 
                            HorizontalAlignment="Left" 
                            VerticalAlignment="Center" 
                            Margin="3,3,23,3"
                            Focusable="True" 
                            Background="#FF323232"
                            Foreground="DarkOrange"
                            Visibility="Hidden"
                            IsReadOnly="{TemplateBinding IsReadOnly}"/>
                        <Popup 
                            Name="Popup"
                            Placement="Bottom"
                            IsOpen="{TemplateBinding IsDropDownOpen}"
                            AllowsTransparency="True" 
                            Focusable="False"
                            PopupAnimation="Slide">

                            <Grid Name="DropDown"
                              SnapsToDevicePixels="True"                
                              MinWidth="{TemplateBinding ActualWidth}"
                              MaxHeight="{TemplateBinding MaxDropDownHeight}">
                                <Border 
                                x:Name="DropDownBorder"
                                Background="#FF323232"
                                BorderThickness="1"
                                BorderBrush="#888888"/>
                                <ScrollViewer Margin="2,4,2,4" SnapsToDevicePixels="True">
                                    <StackPanel IsItemsHost="True" 
                                    KeyboardNavigation.DirectionalNavigation="Contained" />
                                </ScrollViewer>
                            </Grid>
                        </Popup>
                    </Grid>
                    <!-- ControlTemplate 동작에 관련 내용이다. --!>
                    <ControlTemplate.Triggers>
                        <Trigger Property="HasItems" Value="false">
                            <Setter TargetName="DropDownBorder" Property="MinHeight" Value="95"/>
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Foreground" Value="#888888"/>
                        </Trigger>
                        <Trigger Property="IsGrouping" Value="true">
                            <Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
                        </Trigger>
                        <Trigger SourceName="Popup" Property="Popup.AllowsTransparency" Value="true">
                            <Setter TargetName="DropDownBorder" Property="CornerRadius" Value="0"/>
                            <Setter TargetName="DropDownBorder" Property="Margin" Value="0,2,0,0"/>
                        </Trigger>
                        <Trigger Property="IsEditable"  Value="true">
                            <Setter Property="IsTabStop" Value="false"/>
                            <Setter TargetName="PART_EditableTextBox" Property="Visibility" Value="Visible"/>
                            <Setter TargetName="ContentSite" Property="Visibility" Value="Hidden"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
        </Style.Triggers>
    </Style>

    <!-- ComboBoxItem의 스타일 생성 --!>
    <Style x:Key="StdCmbBoxItem" TargetType="{x:Type ComboBoxItem}">
        <Setter Property="BorderBrush" Value="Black"></Setter>
        <Setter Property="BorderThickness" Value="1"></Setter>
        <Setter Property="SnapsToDevicePixels" Value="true"/>
        <Setter Property="Foreground" Value="DarkOrange"/>
        <Setter Property="OverridesDefaultStyle" Value="true"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ComboBoxItem}">
                    <Border Name="Border"
                              Padding="1" BorderBrush="Black" BorderThickness="1"
                              SnapsToDevicePixels="true">
                        <ContentPresenter />
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsHighlighted" Value="true">
                            <Setter TargetName="Border" Property="Background" Value="#FF4b4b4b"/>
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Foreground" Value="LightGray"/>
                            <Setter Property="Background" Value="DarkGray"></Setter>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

 

💥Xaml에서 사용한 속성 정리

SnapToDevicePixels Offset을 적용하여 객체의 크기를 장치 픽셀에 맞추거나 렌더링 시점에서 제거
OverrideDefaultStyle 테마 스타일 속성을 포함할 지 여부, 기본 컨트롤 템플릿 사용X
ScrollViewer.CanContentScroll 실제 스크롤(실제 픽셀만큼)을 해야하는 경우 false, 아니면 true
Focusable 요소가 Focus를 받을 수 있는지 여부
IsHitTestVisible False로 설정하면 보이지 않는 상태로 만들 수 있다 (기본값은 True)
ContentPresenter 외부의 Content 속성이 ContentPresenter가 만들어진 곳에 사용된다.
AllowTransparency 창에서 투명도를 지원하면 true, 아니면 false, 배경을 투명으로 하면 설정 true

MainWindow.xaml

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="400">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>

        <ComboBox x:Name="cmbBox1" Margin="20" Style="{StaticResource StdCmbBox}">
            <ComboBoxItem Content="test1" Style="{StaticResource StdCmbBoxItem}"></ComboBoxItem>
            <ComboBoxItem Content="test2" Style="{StaticResource StdCmbBoxItem}"></ComboBoxItem>
            <ComboBoxItem Content="test3" Style="{StaticResource StdCmbBoxItem}"></ComboBoxItem>
        </ComboBox>
    </Grid>
</Window>

CmbBoxStyle을 적용한 ComboBox


각종 커스텀 컨트롤을 만들고 싶으면 Style, ControlTemplate을 많이 사용해 보는 것이 좋습니다.

 

저도 프로젝트를 진행할 때마다 여러개 만들어보는 편입니다!.

 

감사합니다.

'WPF' 카테고리의 다른 글

[WPF] - INotifyPropertyChanged를 이용한 Binding 방법  (0) 2023.01.01
[WPF] - Border  (0) 2022.12.31
[WPF] - ResourceDictionary 사용  (0) 2022.12.30
[WPF] - 프로젝트 생성 및 시작  (0) 2022.12.28
[WPF] - 시작  (0) 2022.12.28
    'WPF' 카테고리의 다른 글
    • [WPF] - INotifyPropertyChanged를 이용한 Binding 방법
    • [WPF] - Border
    • [WPF] - ResourceDictionary 사용
    • [WPF] - 프로젝트 생성 및 시작
    체휘
    체휘
    항상 발전하는 개발자가 되고 싶습니다~

    티스토리툴바