Help us improve
Share bugs, ideas, or general feedback.
From wpf-dev-pack
Guides WPF control authoring decisions: UserControl vs Control vs FrameworkElement selection, and Style/Template/Trigger alternatives. Use when creating new controls.
npx claudepluginhub christian289/dotnet-with-claudecode --plugin wpf-dev-packHow this skill is triggered — by the user, by Claude, or both
Slash command
/wpf-dev-pack:authoring-wpf-controlssonnetThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
A guide for decision-making when authoring WPF controls.
Generates WPF CustomControl C# class and XAML ControlTemplate style from a control name. Use when creating a new custom control, scaffolding a templated control, or generating CustomControl boilerplate code.
Designs WinUI 3 UIs and reviews XAML for correctness, covering layout planning, control selection, Fluent Design, theming (Light/Dark/HighContrast), typography, spacing, brushes, accessibility, and data binding.
Building WPF on .NET 8+. Host builder, MVVM Toolkit, Fluent theme, performance, modern C# patterns.
Share bugs, ideas, or general feedback.
A guide for decision-making when authoring WPF controls.
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:
┌─────────────────────────────────────────────────────────────┐
│ Control Type Decision │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ UserControl │ │ Control │ │ FrameworkElement│ │
│ └──────┬──────┘ └──────┬──────┘ └────────┬────────┘ │
│ │ │ │ │
│ Combine existing ControlTemplate Direct rendering │
│ Quick development Customization Full control │
│ No template Theme support Performance │
│ optimization │
└─────────────────────────────────────────────────────────────┘
// ❌ 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:
| 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" />
Prefer higher items:
ComboBox.IsDropDownOpen ↔ ToggleButton.IsCheckedScrollBar.LineUpCommandTabPanel in TabControlContentPresenter in ButtonTextBox in ComboBoxButtonChrome in ButtonDependencyProperty 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);
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);
┌────────────────────────────────────────────────────────────┐
│ 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) │
│ │
└────────────────────────────────────────────────────────────┘
📁 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)));
}