Help us improve
Share bugs, ideas, or general feedback.
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-packHow this skill is triggered — by the user, by Claude, or both
Slash command
/wpf-dev-pack:using-converter-markup-extensionhaikuThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Combine `MarkupExtension` with `IValueConverter` for direct XAML usage without resource declarations.
Generates WPF IValueConverter or IMultiValueConverter classes with MarkupExtension pattern for direct XAML usage. Invoke via /wpf-dev-pack:make-wpf-converter for new converters or MultiValueConverter scaffolding.
Guides .NET MAUI XAML data bindings including compiled bindings, value converters, binding modes, multi-binding, relative bindings, BindingContext, and MVVM with INotifyPropertyChanged.
Migrate WPF apps to WinUI 3: namespace replacement, control mapping, threading, imaging, MVVM conversion. Use for converting WPF code or fixing migration build errors.
Share bugs, ideas, or general feedback.
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