views:

1420

answers:

1

I can't seem to find the magic combination to make the HeaderStringFormat work for a WPF Expander.

Here are all the things I've tried:

<Expander Header="{Binding Path=MyProperty, StringFormat=Stuff: ({0})}"  >
    <TextBlock Text="Some Content" />
</Expander>
<Expander HeaderStringFormat="{}Stuff ({0})" Header="{Binding Path=MyProperty}">
    <TextBlock Text="Some More Content" />
</Expander>
<Expander HeaderStringFormat="{}Stuff ({0:0})" Header="{Binding Path=MyProperty}">
    <TextBlock Text="Even More Content" />
</Expander>

The only way I can get a formatted string to work correctly in my code is to do this:

<Expander>
    <Expander.Header>
        <TextBlock Text="{Binding Path=MyProperty, StringFormat=Stuff: ({0})}" />
    </Expander.Header>
    <Expander.Content>
        A Expander with working header
    </Expander.Content>
</Expander>

What am I doing wrong?

+5  A: 

First thing to note is this:

If you set the HeaderTemplate or HeaderTemplateSelector property of a HeaderedContentControl, the HeaderStringFormat property is ignored. MSDN

There are quite a few gotchas like this in WPF to watch out for. You didn't show that in your example, but just keep it in mind. However, I don't think this is your problem.

Second thing to note is that this isn't the same as:

String.Format("My string value is: {0}", myValue");

HeaderedContentControl and HeaderStringFormat are used specifically for classes that implement IFormattable. HederStringFormat formats the header, and ContentStringFormat formats the content. The value of either property is the format that gets passed to your classes implementation if IFormattable.ToString. You can read the full example on MSDN. But here is the gist of how to make it work.

public class MyTestClass : IFormattable
{
    #region IFormattable Members
    public string ToString(string format, IFormatProvider formatProvider)
    {
        if(format == "n")
        {
            return "This is my formatted string";
        }
        else
        {
            return "this is my non-formatted string";
        }
    }
    #endregion
}

    <Style TargetType="{x:Type TabItem}">
        <Setter Property="HeaderStringFormat" Value="n" />
        <Setter Property="ContentStringFormat" Value="" />
    </Style>

<TabControl>
    <TabItem Header="{Binding Content, RelativeSource={RelativeSource Self}}">
        <local:MyTestClass />
    </TabItem>
</TabControl>

This TabItem will now display "This is my formatted string" in the header, and the content will be "this is my non-formatted string".

There a couple things to keep in mind. Typically these properties would be used only in an HeaderedItemsControl context. The HeaderStringFormat would not be bound in this way, and instead will have the default binding provided by the ItemContainer of the HeaderedItemsControl. For instance if you set the ItemsSource property of the TabItem, then it will automatically wire up the header and the content binding for you, and all you have to do is supply the formatting value you want.

Last, but not least, I was able to get everything working properly with a GroupBox and TabItem, but not so much luck with an expander and I'm not sure why. The expander handles the ContentStringFormat properly, but not the HeaderContentStringFormat. This is suprising considering that the both inherit from HeaderContentControl.

Micah