From wpf-dev-pack
Develops WPF CustomControls using Parts and States Model best practices. Use for templatable controls with TemplatePart, TemplateVisualState, OnApplyTemplate, or VisualStateManager.
npx claudepluginhub christian289/dotnet-with-claudecode --plugin wpf-dev-packThis skill uses the workspace's default tool permissions.
Workflow for developing WPF CustomControls with appearance customization capability.
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.
Workflow for developing WPF CustomControls with appearance customization capability.
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
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";
}
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;
}
}
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:
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)OnGotFocus/OnLostFocus - Focus state (useTransitions: true)private static void OnValueChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var control = (NumericUpDown)d;
control.UpdateStates(useTransitions: true);
control.OnValueChanged(new ValueChangedEventArgs((int)e.NewValue));
}
<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>
ThemeInfo attribute to AssemblyInfo.cs → See /configuring-wpf-themeinfoTemplatePart attributeTemplateVisualState attributeGetTemplateChild + allow null in OnApplyTemplateUpdateStates helperVisualStateManager.VisualStateGroups on ControlTemplate rootDefaultStyleKeyProperty.OverrideMetadata in static constructor