상속 없이 사용자 지정 WPF 컨트롤 만들기: 버튼과 스위치 스타일링
WPF 애플리케이션을 개발할 때 상용 라이브러리를 구매하지 않고 표준 컨트롤을 기업 디자인에 맞게 조정해야 하는 경우가 많습니다. 사용자 지정 UserControl을 만들거나 기본 클래스에서 상속하는 대신 스타일과 첨부 속성을 사용하는 것이 더 효율적입니다. — 이는 프레임워크와의 호환성을 유지하고 유지 관리를 단순화합니다.
WPF의 테마 시스템 아키텍처
애플리케이션의 외관을 관리하기 위해 모든 리소스를 Style 폴더에 중앙 집중화하는 것이 권장됩니다. 각 테마(예: 라이트와 다크)는 색상 팔레트 파일과 주요 리소스 사전이 포함된 별도의 디렉터리로 구성합니다. 이렇게 하면 Application.Resources에서 단일 ResourceDictionary를 교체하여 런타임에 테마를 쉽게 전환할 수 있습니다.
테마 전환 구현은 다음과 같습니다:
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에 바인딩하여 사용됩니다:
<Border CornerRadius="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(customWpfControls:RoundedButton.CornerRadius)}" ... />
이 접근 방식의 이점:
- 추가 코드 비하인드 작성 불필요
- Button과의 완전한 호환성 유지
- 스타일과 인스턴스 수준에서 모두 반경 재정의 가능
- 애니메이션 및 상태 트리거 지원
애니메이션 스위치가 있는 ToggleButton 구현
iOS 스타일을 모방한 사용자 지정 스위치는 단 한 줄의 C# 코드 없이 ControlTemplate으로 완전히 구현할 수 있습니다. 주요 템플릿 구성 요소:
- 정확한 요소 위치를 위한 Canvas
- 아이콘(체크마크와 원)을 위한 DrawingImage
- 애니메이션 이동을 위한 RenderTransform이 있는 Border
- Checked/Unchecked에 응답하기 위한 VisualStateManager
이동 애니메이션은 Storyboard 내부의 DoubleAnimation으로 구현됩니다:
<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
아직 댓글이 없습니다.