Agent skill
developing-wpf-customcontrols
Develops WPF CustomControls using Parts and States Model best practices. Use when creating templatable controls with TemplatePart, TemplateVisualState, OnApplyTemplate, or VisualStateManager.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/developing-wpf-customcontrols
SKILL.md
WPF CustomControl Development - Parts and States Model
Workflow for developing WPF CustomControls with appearance customization capability.
Development Flow
1. Control Inheritance Decision
UserControl Selection Criteria:
- Need rapid development
- ControlTemplate customization not required
- Complex theme support not required
Control Inheritance Selection Criteria:
- Need appearance customization via ControlTemplate
- Need various theme support
- Need same extensibility as WPF built-in controls
2. Define Control Contract
Declare TemplatePart and TemplateVisualState attributes on the class:
[TemplatePart(Name = PartUpButton, Type = typeof(RepeatButton))]
[TemplatePart(Name = PartDownButton, Type = typeof(RepeatButton))]
[TemplateVisualState(Name = StatePositive, GroupName = GroupValueStates)]
[TemplateVisualState(Name = StateNegative, GroupName = GroupValueStates)]
[TemplateVisualState(Name = StateFocused, GroupName = GroupFocusStates)]
[TemplateVisualState(Name = StateUnfocused, GroupName = GroupFocusStates)]
public class NumericUpDown : Control
{
// Define Part/State names as const
private const string PartUpButton = "PART_UpButton";
private const string PartDownButton = "PART_DownButton";
private const string GroupValueStates = "ValueStates";
private const string GroupFocusStates = "FocusStates";
private const string StatePositive = "Positive";
private const string StateNegative = "Negative";
private const string StateFocused = "Focused";
private const string StateUnfocused = "Unfocused";
}
3. Template Part Property Pattern
Wrap Part elements as private properties, subscribe/unsubscribe events in setter:
private RepeatButton? _upButton;
private RepeatButton? UpButtonElement
{
get => _upButton;
set
{
// Unsubscribe from existing element's events
if (_upButton is not null)
_upButton.Click -= OnUpButtonClick;
_upButton = value;
// Subscribe to new element's events
if (_upButton is not null)
_upButton.Click += OnUpButtonClick;
}
}
4. OnApplyTemplate Implementation
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// GetTemplateChild + as cast (null on type mismatch)
UpButtonElement = GetTemplateChild(PartUpButton) as RepeatButton;
DownButtonElement = GetTemplateChild(PartDownButton) as RepeatButton;
// Set initial state (without transition)
UpdateStates(useTransitions: false);
}
Core Principles:
- If Part is missing or type differs, it's null → don't cause errors
- Control must work even with incomplete ControlTemplate
5. UpdateStates Helper Method
Centralize state transition logic in a single method:
private void UpdateStates(bool useTransitions)
{
// ValueStates group
VisualStateManager.GoToState(this,
Value >= 0 ? StatePositive : StateNegative,
useTransitions);
// FocusStates group
VisualStateManager.GoToState(this,
IsFocused ? StateFocused : StateUnfocused,
useTransitions);
}
When to call UpdateStates:
OnApplyTemplate- Initial state (useTransitions: false)- Property changed callback - Reflect value change (useTransitions: true)
OnGotFocus/OnLostFocus- Focus state (useTransitions: true)
6. Property Changed Callback
private static void OnValueChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = (NumericUpDown)d;
control.UpdateStates(useTransitions: true);
control.OnValueChanged(new ValueChangedEventArgs((int)e.NewValue));
}
7. ControlTemplate Structure (Generic.xaml)
<Style TargetType="{x:Type local:NumericUpDown}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:NumericUpDown}">
<Grid Background="{TemplateBinding Background}">
<!-- Place VisualStateGroups on root element -->
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ValueStates">
<VisualState x:Name="Positive"/>
<VisualState x:Name="Negative">
<Storyboard>
<ColorAnimation To="Red"
Storyboard.TargetName="ValueText"
Storyboard.TargetProperty="(Foreground).(SolidColorBrush.Color)"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="FocusVisual"
Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Unfocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<!-- Define Part elements with x:Name -->
<RepeatButton x:Name="PART_UpButton" Content="▲"/>
<TextBlock x:Name="ValueText" Text="{TemplateBinding Value}"/>
<RepeatButton x:Name="PART_DownButton" Content="▼"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Checklist
- Inherit from Control class (not UserControl)
- Declare required Parts with
TemplatePartattribute - Declare states with
TemplateVisualStateattribute - Define Part/State names as const strings
- Subscribe/unsubscribe events in Part property setter
- Use
GetTemplateChild+ allow null inOnApplyTemplate - Centralize state transitions with
UpdateStateshelper - Place
VisualStateManager.VisualStateGroupson ControlTemplate root - Place default style in Themes/Generic.xaml
- Call
DefaultStyleKeyProperty.OverrideMetadatain static constructor
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?