Search code examples
c#wpftextblockinvalidoperationexception

Programatically adding a TextDecoration to a TextBlock causes InvalidOperationException


I'm trying to add a TextDecoration (e.g. Strikethrough) to a TextBlock via a data bound property.

Minimal example:

public partial class TextDecoTest : Window
    {
        public TextDecoTest()
        {
            DataContext = this;
            InitializeComponent();
            TextDeco.Add(TextDecorations.Strikethrough); //Exception here
        }    


        public TextDecorationCollection TextDeco
        {
            get { return (TextDecorationCollection)GetValue(TextDecoProperty); }
            set { SetValue(TextDecoProperty, value); }
        }    

        public static readonly DependencyProperty TextDecoProperty =
            DependencyProperty.Register("TextDeco", typeof(TextDecorationCollection), typeof(TextDecoTest), new PropertyMetadata(new TextDecorationCollection()));


    }

XAML:

<Window>
    <TextBlock Text="Test" TextDecorations="{Binding TextDeco}" />
</Window>

However, adding of a TextDecoration fails with:

Specified value of type 'System.Windows.TextDecorationCollection' must have IsFrozen set to false to modify.

Okay, but since you can't really unfreeze controls in WPF, I'm a bit stumped. It works if I create a new TextDecorationCollection, add the decoration and then replace the entire collection, but this isn't really feasible in our case and also feels wrong. I found several other mentions of this issue, but they seem to refer to a bug that has been fixed with .NET 4.5 (we're using 4.6.1).


Solution

  • The four static properties in the TextDecorations class are already of type TextDecorationCollection, e.g.

    public static TextDecorationCollection Strikethrough { get; }
    

    Apparently they aren't meant to be combined.

    So instead of adding any of them to your TextDeco property, just set the property like this:

    TextDeco = TextDecorations.Strikethrough;
    

    You may however combine two TextDecorations like this:

    TextDeco = new TextDecorationCollection(
        Enumerable.Concat(TextDecorations.Strikethrough, TextDecorations.Underline));
    

    Or more, by recursively calling Concat:

    TextDeco = new TextDecorationCollection(
        Enumerable.Concat(TextDecorations.Strikethrough,
            Enumerable.Concat(TextDecorations.Underline, TextDecorations.OverLine)));