Back to list
christian289

authoring-wpf-controls

by christian289

ClaudeCode와 함께하는 .NET 개발 튜토리얼

1🍴 0📅 Jan 25, 2026

SKILL.md


name: authoring-wpf-controls description: "Guides decision-making for WPF control authoring including UserControl vs Control vs FrameworkElement selection. Use when creating new controls or evaluating Style/Template/Trigger alternatives."

WPF Control Authoring Guide

A guide for decision-making when authoring WPF controls.

1. Do You Need a New Control?

Review alternatives first. Thanks to WPF's extensibility, most requirements can be solved without creating a new control.

RequirementAlternativeExample
Change appearance onlyStyleUnify TextBlock to red Arial 14pt
Change control structureControlTemplateMake RadioButton look like traffic light
Change data display methodDataTemplateAdd checkbox to ListBox items
Change state-based behaviorTriggerMake selected item bold red
Display composite contentRich ContentShow image+text together in Button

When a new control is needed:

  • New functionality/behavior not available in existing controls
  • Reusable composite components
  • Special input/interaction patterns

2. Base Class Selection

┌─────────────────────────────────────────────────────────────┐
│                    Control Type Decision                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────────┐  │
│  │ UserControl │    │   Control   │    │ FrameworkElement│  │
│  └──────┬──────┘    └──────┬──────┘    └────────┬────────┘  │
│         │                  │                    │           │
│  Combine existing    ControlTemplate      Direct rendering  │
│  Quick development   Customization        Full control      │
│  No template         Theme support        Performance       │
│                                           optimization      │
└─────────────────────────────────────────────────────────────┘

UserControl Selection Criteria

  • ✅ Combining existing controls is sufficient
  • ✅ Prefer application-like development approach
  • ✅ ControlTemplate customization not needed
  • ❌ Theme support not needed
  • ✅ Need appearance customization via ControlTemplate
  • ✅ Need various theme support
  • ✅ Need WPF built-in control level extensibility
  • ✅ Complete separation of UI and logic

FrameworkElement Selection Criteria

  • ✅ Appearance not achievable by simple element composition
  • ✅ Need direct rendering via OnRender
  • ✅ Custom composition based on DrawingVisual
  • ✅ Extreme performance optimization needed

3. Principles for Designing Stylable Controls

3.1 Don't Strictly Enforce Template Contract

// ❌ Wrong: Throws exception if Part is missing
public override void OnApplyTemplate()
{
    var button = GetTemplateChild("PART_Button") as Button;
    if (button == null)
        throw new InvalidOperationException("PART_Button required!");
}

// ✅ Correct: Works even if Part is missing
public override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    ButtonElement = GetTemplateChild("PART_Button") as Button;
    // If null, only that feature is disabled, control continues to work
}

Core Principles:

  • ControlTemplate may be incomplete at design time
  • Panel doesn't throw exceptions for too many or too few children
  • If required elements are missing, only disable that feature

3.2 Helper Element Patterns

TypeDescriptionExample
StandaloneIndependent, reusablePopup, ScrollViewer, TabPanel
Type-basedRecognizes TemplatedParent, auto-bindingContentPresenter, ItemsPresenter
NamedReferenced in code via x:NamePART_TextBox, PART_Button
// Type-based: ContentPresenter automatically binds to TemplatedParent.Content
<ContentPresenter />

// Named: Direct reference needed in code
<TextBox x:Name="PART_EditableTextBox" />

3.3 State/Behavior Expression Priority

Prefer higher items:

  1. Property Binding - ComboBox.IsDropDownOpenToggleButton.IsChecked
  2. Trigger/Animation - Background color change on Hover state
  3. Command - ScrollBar.LineUpCommand
  4. Standalone Helper - TabPanel in TabControl
  5. Type-based Helper - ContentPresenter in Button
  6. Named Helper - TextBox in ComboBox
  7. Bubbled Event - Event bubbling from Named element
  8. Custom OnRender - ButtonChrome in Button

4. DependencyProperty Implementation

DependencyProperty is required to support styles, bindings, animations, and dynamic resources.

public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register(
        nameof(Value),
        typeof(int),
        typeof(NumericUpDown),
        new FrameworkPropertyMetadata(
            defaultValue: 0,
            propertyChangedCallback: OnValueChanged,
            coerceValueCallback: CoerceValue));

public int Value
{
    get => (int)GetValue(ValueProperty);
    set => SetValue(ValueProperty, value);
}

// ⚠️ Don't add logic to CLR wrapper! It's bypassed during binding
// Use callbacks instead:
private static void OnValueChanged(DependencyObject d,
    DependencyPropertyChangedEventArgs e) { }

private static object CoerceValue(DependencyObject d, object value)
    => Math.Clamp((int)value, 0, 100);

5. RoutedEvent Implementation

Use RoutedEvent to support bubbling, EventSetter, and EventTrigger.

public static readonly RoutedEvent ValueChangedEvent =
    EventManager.RegisterRoutedEvent(
        nameof(ValueChanged),
        RoutingStrategy.Bubble,
        typeof(RoutedPropertyChangedEventHandler<int>),
        typeof(NumericUpDown));

public event RoutedPropertyChangedEventHandler<int> ValueChanged
{
    add => AddHandler(ValueChangedEvent, value);
    remove => RemoveHandler(ValueChangedEvent, value);
}

protected virtual void OnValueChanged(RoutedPropertyChangedEventArgs<int> e)
    => RaiseEvent(e);

6. Customization Support Strategy

┌────────────────────────────────────────────────────────────┐
│           Exposure Strategy by Customization Frequency     │
├────────────────────────────────────────────────────────────┤
│                                                            │
│  Very Frequent  →  Expose as DependencyProperty            │
│                    (Background, Foreground, etc.)          │
│                                                            │
│  Sometimes      →  Expose as Attached Property             │
│                    (Grid.Row, Canvas.Left, etc.)           │
│                                                            │
│  Rarely         →  Guide to redefine ControlTemplate       │
│                    (Documentation required)                │
│                                                            │
└────────────────────────────────────────────────────────────┘

7. Theme Resource Organization

📁 Themes/
├── Generic.xaml          ← Default (required)
├── Aero.NormalColor.xaml ← Windows Vista/7
├── Luna.NormalColor.xaml ← Windows XP Blue
├── Luna.Homestead.xaml   ← Windows XP Olive
└── Luna.Metallic.xaml    ← Windows XP Silver

Add ThemeInfo to AssemblyInfo.cs:

[assembly: ThemeInfo(
    ResourceDictionaryLocation.SourceAssembly,  // Theme-specific resources
    ResourceDictionaryLocation.SourceAssembly)] // Generic resources

Set DefaultStyleKey in static constructor:

static NumericUpDown()
{
    DefaultStyleKeyProperty.OverrideMetadata(
        typeof(NumericUpDown),
        new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}

Decision Checklist

Before Creating a New Control

  • Can it be solved with Style?
  • Can it be solved with ControlTemplate?
  • Can it be solved with DataTemplate?
  • Can it be solved with Trigger?
  • Can it be solved with Rich Content?

Base Class Selection

  • Need ControlTemplate customization? → Control
  • Need theme support? → Control
  • Combining existing controls is sufficient? → UserControl
  • Need direct rendering? → FrameworkElement

Control Design

  • Did you minimize Template Contract?
  • Does it work even if Part is missing?
  • Handling with feature disable instead of exception?
  • Did you follow state expression priority?

Properties/Events

  • Are style/binding supporting properties DependencyProperty?
  • Is there no logic in CLR wrapper?
  • Are events implemented as RoutedEvent?

Theme/Resources

  • Is there a default style in Generic.xaml?
  • Did you set ThemeInfo attribute?
  • Did you set DefaultStyleKey?

Score

Total Score

65/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon