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-packThis skill uses the workspace's default tool permissions.
A guide for decision-making when authoring WPF controls.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
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)));
}