I’ve been a WPF developer for about 10 years now, and I’ve seen a lot of idiocy from it in all shapes and sizes, but today I just stumbled upon a fresh batch of male cow manure that just absolutely takes the cake. Every cake in existence, actually.
Putting a converter at the top of a Window’s resources list somehow breaks resources of UI objects that come after it.
Here is the simplest example of an application that reproduces this issue, with the least amount of code I can muster:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:WpfApp1.Converters"
xmlns:system="clr-namespace:System;assembly=mscorlib"
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<converters:BaseConverter x:Key="test"/>
<Grid x:Key="TestItemTemplateGrid" x:Shared="false">
<Button Content="{Binding StringFormat='Button {0}'}" Width="300"/>
</Grid>
</Window.Resources>
<ItemsControl>
<system:Int32>0</system:Int32>
<system:Int32>1</system:Int32>
<system:Int32>2</system:Int32>
<system:Int32>3</system:Int32>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ContentControl Content="{StaticResource TestItemTemplateGrid}" Margin="0,5,0,0"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
The converter:
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;
namespace WpfApp1.Converters
{
public class BaseConverter : MarkupExtension, IValueConverter
{
public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
}
This is just a simple attempt to load a variable amount of items inside an ItemsControl, where each item is a ContentControl that populates itself to be a Grid defined in a resource.
This is what the app is supposed to look like based on this xaml:
But here is what it actually looks like:
It’s like the x:Shared is breaking and becoming true or something, just because a converter resource came before it.
Things I’ve tested:
- The converter’s key name doesn’t matter
- The converter’s type and/or method implementations don’t matter
- The ItemsControl having a bound ItemsSource vs hard-coded entries doesn’t matter
- The actual content inside the Grid doesn’t matter
Of course, removing the <converters:BaseConverter x:Key="test"/>
fixes it, but here’s the real kicker: Putting it last in the resources list instead ALSO fixes it.
…Wow. Just, wow. What is this? Am I dead? Am I dreaming? Is April fools day programmed into WPF, but it’s bugged (like everything else) and came a month late? Can someone explain this? I’m losing my mind here.
The project file, in case it matters:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net5.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
</PropertyGroup>
</Project>