Claude-skill-registry authoring-wpf-controls
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.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/authoring-wpf-controls" ~/.claude/skills/majiayu000-claude-skill-registry-authoring-wpf-controls && rm -rf "$T"
manifest:
skills/data/authoring-wpf-controls/SKILL.mdsource content
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.
| Requirement | Alternative | Example |
|---|---|---|
| Change appearance only | Style | Unify TextBlock to red Arial 14pt |
| Change control structure | ControlTemplate | Make RadioButton look like traffic light |
| Change data display method | DataTemplate | Add checkbox to ListBox items |
| Change state-based behavior | Trigger | Make selected item bold red |
| Display composite content | Rich Content | Show 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
Control Selection Criteria (Recommended)
- ✅ 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
| Type | Description | Example |
|---|---|---|
| Standalone | Independent, reusable | Popup, ScrollViewer, TabPanel |
| Type-based | Recognizes TemplatedParent, auto-binding | ContentPresenter, ItemsPresenter |
| Named | Referenced in code via x:Name | PART_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:
- Property Binding -
↔ComboBox.IsDropDownOpenToggleButton.IsChecked - Trigger/Animation - Background color change on Hover state
- Command -
ScrollBar.LineUpCommand - Standalone Helper -
inTabPanelTabControl - Type-based Helper -
inContentPresenterButton - Named Helper -
inTextBoxComboBox - Bubbled Event - Event bubbling from Named element
- Custom OnRender -
inButtonChromeButton
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?