superjos
superjos

Reputation: 12695

Apply ValueConverter in style when source comes from x:Static resx file

After quite some search and reading other questions & posts, I was not able to find how to solve this. Note: I'm relatively new to WPF (not to binding in general).

Here's what I'm after:

  1. I'd like to have all TextBlock controls in a window to be styled in some way.
  2. The style should also apply a ValueConverter to make text all uppercase.
  3. Finally, each TextBlock Text could come either from binding to a view-model property, or from binding to a resource string from a .resx file

Here's an excerpt of what I'm playing with:

<!-- in Window resources -->
<help:AllCapsStringConverter x:Key="AllCaps"/>

<Style TargetType="TextBlock">
    <Setter Property="Foreground" Value="Brown" />
    <Setter Property="Text">
        <Setter.Value>
            <Binding>
                <Binding.Converter>
                    <help:AllCapsStringConverter />
                </Binding.Converter>
                <!-- also tried adding:
                <Binding.RelativeSource>
                    <RelativeSource Mode="Self" />
                </Binding.RelativeSource>
                -->
            </Binding>

            <!-- also tried with: 
            <Binding Converter="{StaticResource AllCaps}"/>

            <Binding Path="Text" Converter="{StaticResource AllCaps}"/>
            -->
        </Setter.Value>
    </Setter>
</Style>

<!-- in Window content -->
<TextBlock Text="{x:Static resx:MyResources.MyTitle}" />

Here's the value converter, which on its own has proved to be working:

class AllCapsStringConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value != null && value is string)
        {
            return ((string)value).ToUpper();
        }
        else
        {
            return value;
        }
    }

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

TextBlocks get the foreground color, but no conversion kicks in.

I was able to apply the converter locally to the single TextBlock, but I don't want to apply that to all TextBlocks around the window:

<TextBlock>
    <TextBlock.Text>
        <Binding Source="{x:Static resx:MyResources.MyTitle}"
                 Converter="{StaticResource AllCaps}"/>
    </TextBlock.Text>
</TextBlock>
<!-- which is the same as -->
<TextBlock Style="{StaticResource CustomerInfoTitleStyle}" 
           Text="{Binding Source={x:Static resx:MyResources.MyTitle}, Converter={StaticResource AllCaps}}" />

Upvotes: 0

Views: 2511

Answers (2)

Simon
Simon

Reputation: 71

Your converter is not working because your TextBlock is overriding the Text property of the Style, which includes the converter you have added to the binding.

For example:

<Grid.Resources>

    <Style x:Key="MyTextBlockStyle" TargetType="TextBlock">
        <Setter Property="Foreground" Value="Red"/>
        <Setter Property="FontSize" Value="16"/>
        <Setter Property="Text" Value="You won't see this."></Setter>
    </Style>

</Grid.Resources>

<TextBlock Text="You will see this." Style="{StaticResource MyTextBlockStyle}"/>

Hopefully from the above you can see why your approach is not working.

A better solution would just be to set the value of Text with the value converter on the TextBlock, rather than in the Style.

If you don't want to do that, one common cheat you could use to get around this could be to bind the TextBlock's Text property to the Tag property, like so:

<Grid.Resources>

    <local:AllCapsConverter x:Key="AllCaps"/>

    <Style x:Key="MyTextBlockStyle" TargetType="TextBlock">
        <Setter Property="Foreground" Value="Red"/>
        <Setter Property="FontSize" Value="16"/>
        <Setter Property="Text" Value="{Binding Tag, RelativeSource={RelativeSource Self}, Converter={StaticResource AllCaps}}"/>
    </Style>

</Grid.Resources>

<TextBlock Tag="You will see this." Style="{StaticResource MyTextBlockStyle}"/>

I have a strong dislike of this approach, but it does get you what you want. I would prefer you just use the converter when you set the binding on the TextBlock.

lukegv's approach is another alternative. However, there is no need to use a Label, as you are overriding the template and (similar to my example above) you are just binding to the Content property of the Label. You could just as easily get what you need from a ContentControl.

<Grid.Resources>

    <local:AllCapsConverter x:Key="AllCaps"/>

    <Style x:Key="MyContentControl" TargetType="ContentControl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate>
                    <TextBlock Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource AllCaps}}"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</Grid.Resources>

<ContentControl Style="{StaticResource MyContentControl}" Content="You will see this too!"/>

I'm not a particularly huge fan of this idea either though, as you lose access to all the other TextBlock properties. For example, if I wanted to set the FontWeight property of my TextBlock then I would be stuffed.

Upvotes: 2

Lukas K&#246;rfer
Lukas K&#246;rfer

Reputation: 14493

Try to use a 'Label' and build a template, because a 'TextBlock' is not a control.

<Style x:Key="AllCapsLabel" TargetType="{x:Type Label}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Label">
                <TextBlock Foreground="Brown" Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource AllCaps}}" />
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

And use it this way:

<Label Style="{StaticResource AllCapsLabel}" Content="whatever you want" />

If the content is plain text (aka a 'String'), the text should always be uppercase.

Upvotes: 0

Related Questions