홈으로 돌아가기

사용자 지정 WPF 컨트롤: 스타일 및 Attached Properties

이 기사는 상속 및 UserControl 없이 사용자 지정 WPF 컨트롤을 만드는 방법을 설명합니다. 스타일을 매개변수화하기 위한 attached properties, VisualStateManager를 통한 애니메이션 토글 구현, 테마 시스템 아키텍처를 다룹니다.

WPF 컨트롤 스타일링: Code-Behind 없이 버튼과 토글
Advertisement 728x90

상속 없이 사용자 지정 WPF 컨트롤 만들기: 버튼과 스위치 스타일링

WPF 애플리케이션을 개발할 때 상용 라이브러리를 구매하지 않고 표준 컨트롤을 기업 디자인에 맞게 조정해야 하는 경우가 많습니다. 사용자 지정 UserControl을 만들거나 기본 클래스에서 상속하는 대신 스타일과 첨부 속성을 사용하는 것이 더 효율적입니다. — 이는 프레임워크와의 호환성을 유지하고 유지 관리를 단순화합니다.

WPF의 테마 시스템 아키텍처

애플리케이션의 외관을 관리하기 위해 모든 리소스를 Style 폴더에 중앙 집중화하는 것이 권장됩니다. 각 테마(예: 라이트와 다크)는 색상 팔레트 파일과 주요 리소스 사전이 포함된 별도의 디렉터리로 구성합니다. 이렇게 하면 Application.Resources에서 단일 ResourceDictionary를 교체하여 런타임에 테마를 쉽게 전환할 수 있습니다.

테마 전환 구현은 다음과 같습니다:

Google AdInline article slot
public void ChangeTheme(Theme theme)
{
    if (CurrentTheme == theme) return;

    CurrentTheme = theme;
    Uri themeSource = theme switch
    {
        Theme.Dark => new Uri("pack://application:,,,/CustomWpfControls;component/Style/DarkTheme/DarkTheme.xaml", UriKind.Absolute),
        Theme.Light => new Uri("pack://application:,,,/CustomWpfControls;component/Style/LightTheme/LightTheme.xaml", UriKind.Absolute),
        _ => throw new ArgumentOutOfRangeException(nameof(theme))
    };

    var dictionary = Resources.MergedDictionaries[0];
    dictionary.Clear();
    dictionary.Source = themeSource;
}

이 접근 방식은 모든 UI 요소가 창을 다시 로드하지 않고 즉시 외관을 업데이트하도록 보장합니다.

첨부 속성을 사용한 버튼 스타일링

사용자 지정 RoundedButton 클래스를 만드는 대신 정적 클래스에 CornerRadius라는 첨부 속성을 정의하는 것이 더 좋습니다. 이렇게 하면 XAML에서 직접 코너 반경을 설정할 수 있으며 합성 원칙을 위반하지 않습니다:

public static class RoundedButton
{
    public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.RegisterAttached(
        "CornerRadius",
        typeof(CornerRadius),
        typeof(RoundedButton),
        new FrameworkPropertyMetadata(new CornerRadius(0d), 
            FrameworkPropertyMetadataOptions.AffectsMeasure | 
            FrameworkPropertyMetadataOptions.AffectsRender));

    public static void SetCornerRadius(UIElement element, CornerRadius value) =>
        element.SetValue(CornerRadiusProperty, value);

    public static CornerRadius GetCornerRadius(UIElement element) =>
        (CornerRadius)element.GetValue(CornerRadiusProperty);
}

버튼 스타일 템플릿에서 이 속성은 TemplatedParent에 바인딩하여 사용됩니다:

Google AdInline article slot
<Border CornerRadius="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(customWpfControls:RoundedButton.CornerRadius)}" ... />

이 접근 방식의 이점:

  • 추가 코드 비하인드 작성 불필요
  • Button과의 완전한 호환성 유지
  • 스타일과 인스턴스 수준에서 모두 반경 재정의 가능
  • 애니메이션 및 상태 트리거 지원

애니메이션 스위치가 있는 ToggleButton 구현

iOS 스타일을 모방한 사용자 지정 스위치는 단 한 줄의 C# 코드 없이 ControlTemplate으로 완전히 구현할 수 있습니다. 주요 템플릿 구성 요소:

  • 정확한 요소 위치를 위한 Canvas
  • 아이콘(체크마크와 원)을 위한 DrawingImage
  • 애니메이션 이동을 위한 RenderTransform이 있는 Border
  • Checked/Unchecked에 응답하기 위한 VisualStateManager

이동 애니메이션은 Storyboard 내부의 DoubleAnimation으로 구현됩니다:

Google AdInline article slot
<DoubleAnimation Duration="0:0:0.1"
                 To="20"
                 AccelerationRatio="0.2"
                 DecelerationRatio="0.7"
                 Storyboard.TargetName="Switcher"
                 Storyboard.TargetProperty="(RenderTransform).(TranslateTransform.X)"/>

중요한 점은 초기 값(From)을 지정하지 않는 것입니다. 이렇게 하면 애니메이션이 항상 현재 위치에서 시작하여 빈번한 토글 시 점프를 방지합니다.

스타일 구성 권장 사항

프로젝트 확장성을 위해 다음 규칙을 따르세요:

  • 기본 스타일(without x:Key)은 Template과 공통 Setter만 포함
  • 특정 변형(PrimaryButton, IconButton 등)은 BasedOn으로 상속
  • 모든 색상은 별도 파일로 이동(예: DarkColors.xaml)
  • 첨부 속성은 기능별로 정적 클래스에 그룹화

이렇게 하면 리팩토링이 단순화되고 기존 템플릿을 기반으로 새로운 컨트롤 유형을 빠르게 만들 수 있습니다.

주요 포인트

  • 동작 로직이 변경되지 않으면 Button/ToggleButton에서 상속 피함
  • 첨부 속성이 스타일 기능 확장의 선호 방법
  • VisualStateManager가 코드 없이 선언적 상태 관리 제공
  • 중앙 집중 테마 시스템이 여러 색상 스키마 지원 단순화
  • 애니메이션은 시각적 아티팩트 방지를 위해 상대 값(From 없음) 사용

— Editorial Team

Advertisement 728x90

다음 읽기