FatalExecutionEngineError when adding inlines in the CoerceValueCallback of a TextBlock

2

I am trying to create a TextBlock control that formats the text it is bound to in some way. To achieve that, I tried to use the CoerceValueCallback of a class deriving from TextBlock to add the neccesary inlines, and then ignore the text. Something like:

public class BuggyTextBlock : TextBlock
{
    static BuggyTextBlock()
    {
        TextProperty.OverrideMetadata(typeof(BuggyTextBlock),
                      new FrameworkPropertyMetadata((PropertyChangedCallback)null,
                                                    CoerceText));
    }

    private static object CoerceText(DependencyObject sender, object value)
    {
        BuggyTextBlock tb= (BuggyTextBlock)sender;                       
        tb.Inlines.Add(new Run("Hello World")); // FatalExecutionEngineError here
        return string.Empty;
    }
}

Now, when I use this control as soon as I change the Text property (either directly or by databinding) I get the FatalExecutionEngineError. It does not matter if I use tb.Inlines.Clear() before or not, or wether I try to return null or string.Empty.

Is this really a CLR bug (like the error text implies) or am I doing something silly here?

Edit:

The MDA message reads

FatalExecutionEngineError was detected Message: The runtime has encountered a fatal error. The address of the error was at 0xe7376797, on thread 0x156c. The error code is 0x80131623. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.

c#
wpf
asked on Stack Overflow Apr 17, 2013 by Jens • edited Apr 17, 2013 by Jens

2 Answers

1

MainWindow

XAML

xmlns:obj='clr-namespace:Jens' 
Height="350" Width="525" Loaded="Window_Loaded">

<obj:BuggyTextBlock Background="Gray" Width="100" Height="50" x:Name="myBug">

</obj:BuggyTextBlock>

Code-behind

    public MainWindow()
    {
        InitializeComponent();
        myBug.Text = "blubb";
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {

        myBug.Text = "new blubb";
    }

your code

public class BuggyTextBlock : TextBlock
{
    static BuggyTextBlock()
    {
        TextProperty.OverrideMetadata(typeof(BuggyTextBlock),
                      new FrameworkPropertyMetadata((PropertyChangedCallback)null,
                                                    CoerceText));
    }

    private static object CoerceText(DependencyObject sender, object value)
    {
        // 1. value == blubb
        // 2. value == new blubb
        // and here it comes i don't know why but it get called 3 Times
        // 3. value == Hello WorldHello World <-- FatalExecutionEngineError here

        BuggyTextBlock tb = (BuggyTextBlock)sender; 
        tb.Inlines.Add(new Run("Hello World")); // FatalExecutionEngineError here
        return string.Empty;
    }
}

Edit also if you do

    private static object CoerceText(DependencyObject sender, object value)
    {
        BuggyTextBlock tb = (BuggyTextBlock)sender; 

        tb.Text = value //<- watch here you will get a StackOverflowException

        return string.Empty;
    }
answered on Stack Overflow Apr 17, 2013 by WiiMaxx • edited Oct 6, 2014 by a_hardin
1

You can work around this situation with the following:

class HighlightTextBlock : TextBlock
{
    static HighlightTextBlock()
    {
        TextProperty.OverrideMetadata(
            typeof(HighlightTextBlock),
            new FrameworkPropertyMetadata("", null, (d, e) => ((HighlightTextBlock)d).OnCoerceTextPropertyValue(e)));
    }

    private bool _isUpdating;

    private object OnCoerceTextPropertyValue(object baseValue)
    {
        if (_isUpdating)
        {
            return string.Empty;
        }

        // TODO if it is possible to modify baseValue directly, return that modified value here

        var condition = SomeLogicToDetermineWhetherWeShouldUpdate();

        _isUpdating = true;

        Inlines.Clear();

        // TODO add inlines here

        _isUpdating = false;

        return baseValue;
    }
}

This will trigger automatically in most situations. If you need to manually coerce the value at any time (such as in response to another property updating), just use:

InvalidateProperty(TextProperty);
answered on Stack Overflow Jun 18, 2020 by Drew Noakes

User contributions licensed under CC BY-SA 3.0