一键导入
wpf-rule-converter-patterns
WPF IValueConverter rules: MarkupExtension singleton, pure functions, null/UnsetValue handling, TemplateBinding.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
WPF IValueConverter rules: MarkupExtension singleton, pure functions, null/UnsetValue handling, TemplateBinding.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
WPF Freezable performance rules: Freeze() all Brush/Pen/Geometry; create-and-freeze in constructor, reuse in OnRender.
WPF MVVM layer-separation rules: no System.Windows in ViewModels, BCL-only types, CommunityToolkit.Mvvm base classes. Preloaded into MVVM-related wpf-dev-pack agents.
Banned wpf-dev-pack patterns (P-001..P-004): ViewModelLocator, code-behind DataContext, Stateless ViewModel, mixing composition paths.
WPF rendering anti-patterns: no InvalidateVisual in loops, no resource allocation in OnRender.
WPF ResourceDictionary rules: Generic.xaml as MergedDictionaries hub only, per-control style files, resource order.
View-ViewModel wiring for CommunityToolkit.Mvvm: Mappings.xaml + implicit DataTemplate (ViewModel First).
| name | wpf-rule-converter-patterns |
| description | WPF IValueConverter rules: MarkupExtension singleton, pure functions, null/UnsetValue handling, TemplateBinding. |
| user-invocable | false |
Standards for implementing IValueConverter in wpf-dev-pack projects.
Every converter is a MarkupExtension so it can be used directly in XAML without
declaring a resource, and ProvideValue returns a single shared instance
(converters are stateless, so a shared instance is safe and avoids
ResourceDictionary clutter). This is the canonical converter pattern,
matching the /wpf-dev-pack:make-wpf-converter scaffolder and the
using-converter-markup-extension knowledge topic. Pick this one pattern — do
NOT also expose a separate static Instance property.
Derive from a small base class (the scaffolder generates it once per project):
namespace MyApp.Converters;
public abstract class ConverterMarkupExtension<T> : MarkupExtension, IValueConverter
where T : class, new()
{
private static readonly Lazy<T> _converter = new(() => new T());
public override object ProvideValue(IServiceProvider serviceProvider) => _converter.Value;
public abstract object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture);
public virtual object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
=> throw new NotSupportedException("ConvertBack is not supported.");
}
public sealed class BoolToVisibilityConverter : ConverterMarkupExtension<BoolToVisibilityConverter>
{
public override object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is null || value == DependencyProperty.UnsetValue)
return Visibility.Collapsed;
var invert = parameter is "Invert" or "invert";
return (value is bool b && (b ^ invert)) ? Visibility.Visible : Visibility.Collapsed;
}
}
Usage in XAML (no ResourceDictionary entry required):
<TextBlock Visibility="{Binding IsActive,
Converter={converters:BoolToVisibilityConverter}}" />
Converters must be pure functions:
DateTime.Now inside Convert)If conversion logic requires external context, pass it via the ConverterParameter or via a MultiBinding with IMultiValueConverter.
Always handle both null and DependencyProperty.UnsetValue as the first check in Convert:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is null || value == DependencyProperty.UnsetValue)
return DependencyProperty.UnsetValue; // or a safe default
// normal conversion logic
}
Returning DependencyProperty.UnsetValue from Convert signals WPF to use the property's default value rather than throwing a binding error.
Inside a ControlTemplate, use TemplateBinding (not Binding) to reference the templated parent's properties.
TemplateBinding is a lightweight one-way binding that avoids the overhead of a full Binding object.
<!-- Correct: TemplateBinding for ControlTemplate property references -->
<ControlTemplate TargetType="{x:Type local:MyButton}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="4">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
</ControlTemplate>
<!-- Incorrect: Binding with RelativeSource is verbose and slower -->
<Border Background="{Binding Background,
RelativeSource={RelativeSource TemplatedParent}}">
Use Binding with RelativeSource=TemplatedParent only when you need two-way binding or a converter applied to a templated-parent property — TemplateBinding is one-way only and does not support converters directly.