developing-wpf-customcontrols

WPF CustomControl Development - Parts and States Model

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "developing-wpf-customcontrols" with this command: npx skills add christian289/dotnet-with-claudecode/christian289-dotnet-with-claudecode-developing-wpf-customcontrols

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
  1. 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"; }

  1. 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;
}

}

  1. 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

  1. 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)

  1. 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)); }

  1. 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}">

      &#x3C;!-- Place VisualStateGroups on root element -->
      &#x3C;VisualStateManager.VisualStateGroups>
        &#x3C;VisualStateGroup x:Name="ValueStates">
          &#x3C;VisualState x:Name="Positive"/>
          &#x3C;VisualState x:Name="Negative">
            &#x3C;Storyboard>
              &#x3C;ColorAnimation To="Red"
                Storyboard.TargetName="ValueText"
                Storyboard.TargetProperty="(Foreground).(SolidColorBrush.Color)"/>
            &#x3C;/Storyboard>
          &#x3C;/VisualState>
        &#x3C;/VisualStateGroup>

        &#x3C;VisualStateGroup x:Name="FocusStates">
          &#x3C;VisualState x:Name="Focused">
            &#x3C;Storyboard>
              &#x3C;ObjectAnimationUsingKeyFrames
                Storyboard.TargetName="FocusVisual"
                Storyboard.TargetProperty="Visibility">
                &#x3C;DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
              &#x3C;/ObjectAnimationUsingKeyFrames>
            &#x3C;/Storyboard>
          &#x3C;/VisualState>
          &#x3C;VisualState x:Name="Unfocused"/>
        &#x3C;/VisualStateGroup>
      &#x3C;/VisualStateManager.VisualStateGroups>

      &#x3C;!-- Define Part elements with x:Name -->
      &#x3C;RepeatButton x:Name="PART_UpButton" Content="▲"/>
      &#x3C;TextBlock x:Name="ValueText" Text="{TemplateBinding Value}"/>
      &#x3C;RepeatButton x:Name="PART_DownButton" Content="▼"/>

    &#x3C;/Grid>
  &#x3C;/ControlTemplate>
&#x3C;/Setter.Value>

</Setter> </Style>

Checklist

  • Inherit from Control class (not UserControl)

  • Add ThemeInfo attribute to AssemblyInfo.cs → See /configuring-wpf-themeinfo

  • Declare required Parts with TemplatePart attribute

  • Declare states with TemplateVisualState attribute

  • Define Part/State names as const strings

  • Subscribe/unsubscribe events in Part property setter

  • Use GetTemplateChild

  • allow null in OnApplyTemplate
  • Centralize state transitions with UpdateStates helper

  • Place VisualStateManager.VisualStateGroups on ControlTemplate root

  • Place default style in Themes/Generic.xaml

  • Call DefaultStyleKeyProperty.OverrideMetadata in static constructor

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

converting-html-css-to-wpf-xaml

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

publishing-wpf-apps

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

managing-styles-resourcedictionary

No summary provided by upstream source.

Repository SourceNeeds Review