Christian Waluga Christian Waluga - 4 days ago 3
C# Question

Data binding/conversion for simple math-styling in a WPF DataGridColumnHeader template

My goal is to pass a format string like "A__B" to the header of a DataGrid column and display it as
A with a subscript B (i.e., A_B). To do this on a per-column basis I intend to use the template for this as follows.

<DataGridTextColumn Binding="{Binding AB}", Header="A__B" HeaderStyle="{StaticResource ColumnHeaderTemplate}" />


To accomplish the implementation of a suitable template, I thought I might want work with a converter. Thus, I wrote
a simple converter that splits the string into an object of class Symbol, having the properties Text and Subscript.

using System;
using System.Windows.Data;
using System.Globalization;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace Test
{
public class Symbol : INotifyPropertyChanged
{
string text_, subscript_;

public event PropertyChangedEventHandler PropertyChanged;

private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}

public Symbol(string text, string subscript)
{
Text = text;
Subscript = subscript;

NotifyPropertyChanged();
}

public String Text
{
get { return text_; }
set { text_ = value; NotifyPropertyChanged(); }
}

public String Subscript
{
get { return subscript_; }
set { subscript_ = value; NotifyPropertyChanged(); }
}
}

[ValueConversion(typeof(string), typeof(Symbol))]
public class StringToSymbolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter == null) return null;

var format = parameter as string;

int idx = format.IndexOf("__");
if (idx < 0) return new Symbol(format, "");

return new Symbol(format.Substring(0, idx), format.Substring(idx + 2);
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}
}


I added this converter to the window resources

<Window.Resources>
<l:StringToSymbolConverter x:Key="stringToSymbolConverter" />
</Window.Resources>


And in my data grid I did the following

<DataGrid x:Name="dataGrid" ItemsSource="{Binding Results}" AutoGenerateColumns="False">

<DataGrid.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}" x:Key="ColumnHeaderTemplate">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock TextWrapping="Wrap" DataContext="{Binding Converter={StaticResource stringToSymbolConverter}}">
<Run Text="{Binding Path=Text}"/>
<Run Text="{Binding Path=Subscript}" BaselineAlignment="Subscript" FontSize="8"/>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>

<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding AB, Mode=OneWay}" ClipboardContentBinding="{x:Null}" Header="A__B" HeaderStyle="{StaticResource ColumnHeaderTemplate}" />
</DataGrid.Columns>
</DataGrid>


My idea here is to do the conversion once
and then plug the components which reside in the Symbol object into the right places. For this I attempted to (mis?)use the DataContext of the text block.

Now this does not work as expected and I get no visible output. I see that the template is being applied to the respective column, so I seem to have that one right. Also, if I replace the bindings by plain text or add fallback values, the text is correctly rendered. I suspect that the bindings fail and deliver an empty string at runtime. Being new to WPF and given the rather limited debugging toolchain in XAML/WPF it is quite tough for me to find out what's wrong and where to go from here.

I may have gotten the involved dependencies in the binding game entirely wrong and this may be due to my lack of understanding the underlying mechanisms, especially when it comes to templates. Thus I am thankful for any helpful hints!

Any ideas?

Answer

The error lies in your converter - namely, instead of the value, you're trying to convert the parameter, which is null, because you didn't specify ConverterParameter for the binding where your converter is used. Thus you're getting an empty column header. You should convert the value instead:

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    //here you should use 'value' instead of 'parameter'
    if (value == null) return null;
    var format = value as string;

    int idx = format.IndexOf("__");
    if (idx < 0) return new Symbol(format, "");

    return new Symbol(format.Substring(0, idx), format.Substring(idx + 2);
}

Note that this kind of bug is fairly easy to debug - whenever you're facing binding issues where a converter is involved it is beneficial to set a breakpoint in the Convert method and step through it.

Comments