WPF/XAML: How to make all text upper case in TextBlock?
Asked Answered
N

6

19

I want all characters in a TextBlock to be displayed in uppercase

<TextBlock Name="tbAbc"
           FontSize="12"
           TextAlignment="Center"
           Text="Channel Name"
           Foreground="{DynamicResource {x:Static r:RibbonSkinResources.RibbonGroupLabelFontColorBrushKey}}" />

The strings are taken through Binding. I don't want to make the strings uppercase in the dictionary itself.

Nicolanicolai answered 25/7, 2014 at 12:39 Comment(2)
see C# string format flag or modifier to lowercase paramEarthman
Possible duplicate of WPF/XAML: how to make all text upper case / capital?Overactive
A
48

Or use

Typography.Capitals="AllSmallCaps"

in your TextBlock definition.

See here: MSDN - Typography.Capitals

EDIT:

This does not work in Windows Phone 8.1, only in Windows 8.1 ...

Affectionate answered 4/2, 2015 at 14:2 Comment(2)
This doesn't work, capitals will be large capitals and lower case characters will be small capitals.Ugh
This breaks when using custom font.Stedt
K
17

Implement a custom converter.

using System.Globalization;
using System.Windows.Data;
// ...
public class StringToUpperConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value != null && value is string )
        {
            return ((string)value).ToUpper();
        }

        return value;
    }

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

Then include that in your XAML as a resource:

<local:StringToUpperConverter  x:Key="StringToUpperConverter"/>

And add it to your binding:

Converter={StaticResource StringToUpperConverter}
Knawel answered 25/7, 2014 at 12:51 Comment(4)
You may not want to use "value is string" in there. Try if (value != null) { return value.ToString().ToUpper(culture); }Quodlibet
@Quodlibet - I prefer converters that work with a specific type only. If it doesn't get that type, return the value untouched. That might be a little cautious when it expects a string, but this is the pattern I prefer. Not that your suggestion wouldn't work perfectly well.Knawel
Just a friendly heads-up. If value is null, then value is string will equate to false so your null check can be removed. You can also remove your cast and use the following syntax instead: if(value is string stringValue){ return stringValue.ToUpper(); }. I've also added my own answer that uses a single-line null-coalescing approach as well as making the converter a subclass of MarkupExtension so you don't need to add your converter to your resources. You can just use it directly as needed right in the binding. Feel free to check it out! :)Vachell
@MarkA.Donohoe - always appreciate friendly improvements, and will take a look at markup extensions for future applications also. Thanks for the info and +1 on your answer also.Knawel
P
10

You can use an attached property like this:

public static class TextBlock
{
    public static readonly DependencyProperty CharacterCasingProperty = DependencyProperty.RegisterAttached(
        "CharacterCasing",
        typeof(CharacterCasing),
        typeof(TextBlock),
        new FrameworkPropertyMetadata(
            CharacterCasing.Normal,
            FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.NotDataBindable,
            OnCharacterCasingChanged));

    private static readonly DependencyProperty TextProxyProperty = DependencyProperty.RegisterAttached(
        "TextProxy",
        typeof(string),
        typeof(TextBlock),
        new PropertyMetadata(default(string), OnTextProxyChanged));

    private static readonly PropertyPath TextPropertyPath = new PropertyPath("Text");


    public static void SetCharacterCasing(DependencyObject element, CharacterCasing value)
    {
        element.SetValue(CharacterCasingProperty, value);
    }

    public static CharacterCasing GetCharacterCasing(DependencyObject element)
    {
        return (CharacterCasing)element.GetValue(CharacterCasingProperty);
    }

    private static void OnCharacterCasingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is System.Windows.Controls.TextBlock textBlock)
        {
            if (BindingOperations.GetBinding(textBlock, TextProxyProperty) == null)
            {
                BindingOperations.SetBinding(
                    textBlock,
                    TextProxyProperty,
                    new Binding
                    {
                        Path = TextPropertyPath,
                        RelativeSource = RelativeSource.Self,
                        Mode = BindingMode.OneWay,
                    });
            }
        }
    }

    private static void OnTextProxyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        d.SetCurrentValue(System.Windows.Controls.TextBlock.TextProperty, Format((string)e.NewValue, GetCharacterCasing(d)));

        string Format(string text, CharacterCasing casing)
        {
            if (string.IsNullOrEmpty(text))
            {
                return text;
            }

            switch (casing)
            {
                case CharacterCasing.Normal:
                    return text;
                case CharacterCasing.Lower:
                    return text.ToLower();
                case CharacterCasing.Upper:
                    return text.ToUpper();
                default:
                    throw new ArgumentOutOfRangeException(nameof(casing), casing, null);
            }
        }
    }
}

Then usage in xaml will look like:

<StackPanel>
    <TextBox x:Name="TextBox" Text="abc" />
    <TextBlock local:TextBlock.CharacterCasing="Upper" Text="abc" />
    <TextBlock local:TextBlock.CharacterCasing="Upper" Text="{Binding ElementName=TextBox, Path=Text}" />
    <Button local:TextBlock.CharacterCasing="Upper" Content="abc" />
    <Button local:TextBlock.CharacterCasing="Upper" Content="{Binding ElementName=TextBox, Path=Text}" />
</StackPanel>
Plate answered 23/3, 2018 at 6:54 Comment(4)
This is a great solution if the textblock in question is already using a converter.Bloodfin
Great solution as it doesn't imply you to change the content of the element.Alleviation
This is a great solution because it can solely be controlled via external styles/property setters: <Setter Property="local:TextBlock.CharacterCasing" Value="Upper" />Messalina
BTW, you don't have to nest your if statements. You can use an && operator to first check if it's a TextBlock and if so, check its text binding. Also, in newer versions of C#, they recommend using pattern matching against null instead of equality checking. (i.e. instead of if(bla == null){ ... } they suggest if(bla is null){ ... } (or is not null if that's what you need). Not sure if it's just for readability or if it even alters the IL, but that seems to be the direction of all their newest/latest code.Vachell
C
4

If it's not a big deal you could use TextBox instead of TextBlock like this:

<TextBox CharacterCasing="Upper" IsReadOnly="True" />
Constantine answered 25/7, 2014 at 13:13 Comment(4)
It's not really an acceptable answer though because the question asks about TextBlock.Mokas
@StephenDrew I was not asking him to accept my answer, just letting him know that he should accept one ( the one that helped him the most - that's all about this site, right?). In my opinion, the one with the converter is a good answer. (mine is just an alternative).Constantine
On TextBox, setting the character casing property to true only affects manually typed text. So setting IsReadOnly to true at the same time eliminates the CharacterCasing properties usefulness to this particular use-case. I didn't know the CharacterCasing property existed on TextBox though so I learned something here!Characteristically
@Characteristically is correct, CharacterCasing="Upper" on the TextBox element will only apply to newly typed text into the element. It won't apply it to an already existing "text" value unfortunatelyBase
V
4

While there's already a great answer here that uses a converter, I'm providing an alternative implementation that simplifies the conversion to a single line (thanks to null coalescing), as well as making it a subclass of MarkupExtension so it's easier to use in XAML.

Here's the converter...

using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;

namespace IntuoSoft.Wpf.Converters {

    [ValueConversion(typeof(string), typeof(string))]
    public class CapitalizationConverter : MarkupExtension, IValueConverter {

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            => (value as string)?.ToUpper() ?? value; // If it's a string, call ToUpper(), otherwise, pass it through as-is.

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            => throw new NotSupportedException();

        public override object ProvideValue(IServiceProvider serviceProvider) => this;
    }
}

And here's how you use it (Note: This assumes the above namespace is prefixed with is in your XAML):

<TextBlock Text={Binding SomeValue, Converter={is:CapitalizationConverter}}" />

Because it's a MarkupExtension subclass, you can simply use it right where/when it's needed. No need to define it in the resources first.

Vachell answered 28/2, 2021 at 3:22 Comment(0)
M
2

I use a character casing value converter:

class CharacterCasingConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var s = value as string;
        if (s == null)
            return value;

        CharacterCasing casing;
        if (!Enum.TryParse(parameter as string, out casing))
            casing = CharacterCasing.Upper;

        switch (casing)
        {
            case CharacterCasing.Lower:
                return s.ToLower(culture);
            case CharacterCasing.Upper:
                return s.ToUpper(culture);
            default:
                return s;
        }
    }

    object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}
Mokas answered 25/9, 2014 at 14:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.