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.
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;
}
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);
User contributions licensed under CC BY-SA 3.0