From wpf-dev-pack
Provides C# base classes for IValueConverter and IMultiValueConverter as MarkupExtensions to enable direct XAML usage without StaticResource or resource dictionaries. Use when creating converters for WPF/MAUI data bindings.
npx claudepluginhub christian289/dotnet-with-claudecode --plugin wpf-dev-packThis skill uses the workspace's default tool permissions.
Combine `MarkupExtension` with `IValueConverter` for direct XAML usage without resource declarations.
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.
Combine MarkupExtension with IValueConverter for direct XAML usage without resource declarations.
| Aspect | StaticResource Converter | MarkupExtension Converter |
|---|---|---|
| Declaration | Required in Resources | Not required |
| XAML Usage | {StaticResource MyConverter} | {local:MyConverter} |
| Singleton | Manual implementation | Built-in lazy singleton |
| Boilerplate | More | Less |
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)
{
return _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();
}
}
namespace MyApp.Converters;
public abstract class MultiConverterMarkupExtension<T> : MarkupExtension, IMultiValueConverter
where T : class, new()
{
private static readonly Lazy<T> _converter = new(() => new T());
public override object ProvideValue(IServiceProvider serviceProvider)
{
return _converter.Value;
}
public abstract object? Convert(
object?[] values,
Type targetType,
object? parameter,
CultureInfo culture);
public virtual object?[] ConvertBack(
object? value,
Type[] targetTypes,
object? parameter,
CultureInfo culture)
{
throw new NotSupportedException();
}
}
public sealed class BoolToVisibilityConverter : ConverterMarkupExtension<BoolToVisibilityConverter>
{
public override object? Convert(
object? value,
Type targetType,
object? parameter,
CultureInfo culture)
{
if (value is not bool boolValue)
return Visibility.Collapsed;
var invert = parameter is "Invert" or "invert";
return (boolValue ^ invert) ? Visibility.Visible : Visibility.Collapsed;
}
}
XAML Usage:
<Button Visibility="{Binding IsEnabled, Converter={local:BoolToVisibilityConverter}}"/>
<!-- With parameter -->
<Button Visibility="{Binding IsDisabled, Converter={local:BoolToVisibilityConverter}, ConverterParameter=Invert}"/>
public sealed class NullToVisibilityConverter : ConverterMarkupExtension<NullToVisibilityConverter>
{
public override object? Convert(
object? value,
Type targetType,
object? parameter,
CultureInfo culture)
{
var isNull = value is null;
var invert = parameter is "Invert";
return (isNull ^ invert) ? Visibility.Collapsed : Visibility.Visible;
}
}
public sealed class StringFormatConverter : ConverterMarkupExtension<StringFormatConverter>
{
public override object? Convert(
object? value,
Type targetType,
object? parameter,
CultureInfo culture)
{
if (parameter is not string format)
return value?.ToString();
return string.Format(culture, format, value);
}
}
XAML Usage:
<TextBlock Text="{Binding Price, Converter={local:StringFormatConverter}, ConverterParameter='{}{0:C}'}"/>
public sealed class FullNameConverter : MultiConverterMarkupExtension<FullNameConverter>
{
public override object? Convert(
object?[] values,
Type targetType,
object? parameter,
CultureInfo culture)
{
if (values.Length < 2)
return string.Empty;
var firstName = values[0]?.ToString() ?? string.Empty;
var lastName = values[1]?.ToString() ?? string.Empty;
return $"{firstName} {lastName}".Trim();
}
}
XAML Usage:
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{local:FullNameConverter}">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
global using System;
global using System.Globalization;
global using System.Windows;
global using System.Windows.Data;
global using System.Windows.Markup;
<Window.Resources>
<local:BoolToVisibilityConverter x:Key="BoolToVisibility"/>
</Window.Resources>
<Button Visibility="{Binding IsVisible, Converter={StaticResource BoolToVisibility}}"/>
<!-- No resource declaration needed -->
<Button Visibility="{Binding IsVisible, Converter={local:BoolToVisibilityConverter}}"/>
MyApp/
├── Converters/
│ ├── ConverterMarkupExtension.cs # Base class
│ ├── MultiConverterMarkupExtension.cs # Multi base class
│ ├── BoolToVisibilityConverter.cs
│ ├── NullToVisibilityConverter.cs
│ └── StringFormatConverter.cs
ConverterMarkupExtension<T> or MultiConverterMarkupExtension<T>sealed (no inheritance needed)DependencyProperty.UnsetValue for invalid input if neededConverterParameter for variations instead of multiple converters