System.StackOverflowException при удалении InlineUIContainer
Итак, у меня есть RichTextBox, который может содержать один или несколько InlineUIContainer, содержащих небольшой UserControl. Когда один из них удаляется, возникает исключение StackruException (ни один из моих кодов не запускается между нажатием Backspace и исключением).
Исключение возникает во внутреннем коде, мне удалось восстановить StackTrace благодаря WinDbg, и вот важная часть результата:
...
00bce210 51048111 System.Windows.Markup.Primitives.MarkupWriter.RecordNamespaces(Scope, System.Windows.Markup.Primitives.MarkupObject, System.Windows.Markup.IValueSerializerContext, Boolean)
00bce25c 51048111 System.Windows.Markup.Primitives.MarkupWriter.RecordNamespaces(Scope, System.Windows.Markup.Primitives.MarkupObject, System.Windows.Markup.IValueSerializerContext, Boolean)
00bce2a8 51048111 System.Windows.Markup.Primitives.MarkupWriter.RecordNamespaces(Scope, System.Windows.Markup.Primitives.MarkupObject, System.Windows.Markup.IValueSerializerContext, Boolean)
00bce2f4 51048111 System.Windows.Markup.Primitives.MarkupWriter.RecordNamespaces(Scope, System.Windows.Markup.Primitives.MarkupObject, System.Windows.Markup.IValueSerializerContext, Boolean)
00bce340 51048111 System.Windows.Markup.Primitives.MarkupWriter.RecordNamespaces(Scope, System.Windows.Markup.Primitives.MarkupObject, System.Windows.Markup.IValueSerializerContext, Boolean)
00bce38c 51048111 System.Windows.Markup.Primitives.MarkupWriter.RecordNamespaces(Scope, System.Windows.Markup.Primitives.MarkupObject, System.Windows.Markup.IValueSerializerContext, Boolean)
00bce3d8 5104829a System.Windows.Markup.Primitives.MarkupWriter.WriteItem(System.Windows.Markup.Primitives.MarkupObject)
00bce3f0 51047ebd System.Windows.Markup.Primitives.MarkupWriter.SaveAsXml(System.Xml.XmlWriter, System.Windows.Markup.Primitives.MarkupObject)
00bce41c 51047e15 System.Windows.Markup.Primitives.MarkupWriter.SaveAsXml(System.Xml.XmlWriter, System.Object)
00bce428 51024246 System.Windows.Markup.XamlWriter.Save(System.Object, System.IO.TextWriter)
00bce43c 510241c6 System.Windows.Markup.XamlWriter.Save(System.Object)
00bce46c 51132783 System.Windows.Documents.TextTreeDeleteContentUndoUnit.CopyObjectNode(System.Windows.Documents.TextTreeObjectNode, ContentContainer ByRef)
00bce484 511325c8 System.Windows.Documents.TextTreeDeleteContentUndoUnit.CopyContent(System.Windows.Documents.TextTreeNode, System.Windows.Documents.TextTreeNode)
00bce4ac 5113290e System.Windows.Documents.TextTreeDeleteContentUndoUnit.CopyElementNode(System.Windows.Documents.TextTreeTextElementNode, ContentContainer ByRef)
00bce4dc 51132632 System.Windows.Documents.TextTreeDeleteContentUndoUnit.CopyContent(System.Windows.Documents.TextTreeNode, System.Windows.Documents.TextTreeNode)
00bce504 511323a0 System.Windows.Documents.TextTreeDeleteContentUndoUnit..ctor(System.Windows.Documents.TextContainer, System.Windows.Documents.TextPointer, System.Windows.Documents.TextPointer)
00bce52c 51406de0 System.Windows.Documents.TextTreeUndo.CreateDeleteContentUndoUnit(System.Windows.Documents.TextContainer, System.Windows.Documents.TextPointer, System.Windows.Documents.TextPointer)
00bce548 50729cc7 System.Windows.Documents.TextContainer.DeleteContentInternal(System.Windows.Documents.TextPointer, System.Windows.Documents.TextPointer)
00bce57c 50728c51 System.Windows.Documents.TextRangeEdit.DeleteContentBetweenPositions(System.Windows.Documents.TextPointer, System.Windows.Documents.TextPointer)
00bce590 50728bdc System.Windows.Documents.TextRangeEdit.DeleteEquiScopedContent(System.Windows.Documents.TextPointer, System.Windows.Documents.TextPointer)
00bce5c0 50728ae7 System.Windows.Documents.TextRangeEdit.DeleteParagraphContent(System.Windows.Documents.ITextPointer, System.Windows.Documents.ITextPointer)
00bce5dc 50fe4172 System.Windows.Documents.TextRangeEditTables.DeleteContent(System.Windows.Documents.TextPointer, System.Windows.Documents.TextPointer)
00bce620 50fa7fa1 System.Windows.Documents.TextPointer.System.Windows.Documents.ITextPointer.DeleteContentToPosition(System.Windows.Documents.ITextPointer)
00bce644 5103afb6 System.Windows.Documents.TextRangeBase.SetText(System.Windows.Documents.ITextRange, System.String)
00bce68c 50fdf012 System.Windows.Documents.TextSelection.System.Windows.Documents.ITextRange.set_Text(System.String)
00bce6b0 50fdb214 System.Windows.Documents.TextEditorTyping.OnBackspace(System.Object, System.Windows.Input.ExecutedRoutedEventArgs)
А затем еще 13000 вызовов MarkupWriter.RecordNamespaces до исключения. Я провел небольшое исследование, и когда InlineUIContainer удаляется, фреймворк пытается сериализовать его как XAML, чтобы иметь возможность выполнить команду Undo. Но в этом случае сериализация зацикливается на вызове MarkupWriter.RecordNamespaces.
Поскольку это внутренний код, я не знаю, как предотвратить этот цикл. Мне приходит в голову полностью предотвратить сериализацию этого InlineUIContainer, но я не нашел способа сделать это.
Есть один?
РЕДАКТИРОВАТЬ:
По запросу, вот определение RichTextBox и UserControl.
CurveFormulaTextBox.xaml
<UserControl x:Class="StruCAT.Views.CurveFormulaTextBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:prop="clr-namespace:StruCAT.Properties"
xmlns:viewModels="clr-namespace:StruCAT.ViewModels"
x:Name="ThisCurveFormulaTextBox"
d:DataContext="{x:Type viewModels:ParameterViewModel}"
DataContextChanged="UserControl_DataContextChanged"
mc:Ignorable="d">
<DockPanel>
<RichTextBox x:Name="RichTextBox"
MinWidth="250"
Padding="1"
VerticalContentAlignment="Center"
IsDocumentEnabled="True"
LostFocus="RichTextBox_LostFocus"
PreviewKeyDown="RichTextBox_PreviewKeyDown"
SelectionChanged="RichTextBox_SelectionChanged">
<RichTextBox.Document>
<FlowDocument LineHeight="24" PageWidth="10000">
<Paragraph x:Name="Paragraph" Padding="0,0,0,0" />
</FlowDocument>
</RichTextBox.Document>
</RichTextBox>
<Popup x:Name="InsertionPopup"
AllowsTransparency="True"
IsOpen="{Binding IsFocused, ElementName=RichTextBox, Mode=OneWay}"
PlacementTarget="{Binding ElementName=RichTextBox}">
<StackPanel>
<StackPanel.Effect>
<DropShadowEffect BlurRadius="2"
Opacity="0.5"
ShadowDepth="0" />
</StackPanel.Effect>
<Polygon HorizontalAlignment="Center"
Fill="White"
Points="4,0 0,8, 8,8" />
<Border Margin="1,0,1,1"
Padding="1"
Background="White"
BorderThickness="0"
CornerRadius="3">
<StackPanel Orientation="Horizontal">
<Button Margin="0"
Padding="1,1,0,0"
Content="{StaticResource CurveIcon}"
PreviewMouseLeftButtonDown="InsertCurve"
Style="{StaticResource CustomButton}"
ToolTip="{x:Static prop:Resources.AddCurve}" />
<Button Margin="0"
Content="{StaticResource VariableIcon}"
PreviewMouseLeftButtonDown="InsertVariable"
Style="{StaticResource CustomButton}"
ToolTip="{x:Static prop:Resources.AddVariable}" />
</StackPanel>
</Border>
</StackPanel>
</Popup>
</DockPanel>
</UserControl>
CurveFormulaTextBox.xaml.cs
using StruCAT.ViewModels;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
namespace StruCAT.Views
{
/// <summary>
/// Logique d'interaction pour CurveFormulaTextBox.xaml
/// </summary>
public partial class CurveFormulaTextBox : UserControl
{
public CurveFormulaTextBox()
{
InitializeComponent();
}
public bool Working { get; set; } = false;
public string Formula
{
get { return (string)GetValue(FormulaProperty); }
set { SetValue(FormulaProperty, value); }
}
public static readonly DependencyProperty FormulaProperty = DependencyProperty.Register("Formula", typeof(string), typeof(CurveFormulaTextBox), new PropertyMetadata("", FormulaChanged));
private static void FormulaChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var cftb = (CurveFormulaTextBox)sender;
if (e.NewValue != null && !cftb.Working)
cftb.FillParagraph();
}
private void UpdateFormula()
{
Working = true;
var formula = "";
foreach (var inline in Paragraph.Inlines)
{
if (inline.GetType() == typeof(Run))
formula += ((Run)inline).Text;
else if (inline.GetType() == typeof(InlineUIContainer) && ((InlineUIContainer)inline).Child.GetType() == typeof(KeywordComboBox))
{
var kw = ((KeywordComboBox)((InlineUIContainer)inline).Child).SelectedKeyword;
if (kw != null)
formula += "{" + kw.Model.Type + ";" + kw.Model.ID + "}";
}
}
Formula = formula;
Working = false;
}
public void FillParagraph()
{
Working = true;
foreach (var match in Regex.Matches(Formula, "{.*?}|[^{}]+"))
{
if (match.ToString().StartsWith("{"))
{
var targetType = match.ToString().Replace("{", "").Replace("}", "").Trim().Split(';')[0];
var keywordID = match.ToString().Replace("{", "").Replace("}", "").Trim().Split(';')[1];
var cbo = CreateKCB(targetType);
cbo.SelectedKeyword = (DataContext as ParameterViewModel).Parent.Parent.Parent.Keywords.FirstOrDefault(x => x.Model.ID == keywordID);
Paragraph.Inlines.Add(cbo);
}
else
{
Paragraph.Inlines.Add(new Run(match.ToString()));
}
}
Working = false;
}
private KeywordComboBox CreateKCB(string type)
{
var kcb = new KeywordComboBox() { Type = type, DataContext = this.DataContext };
kcb.SelectionChanged += () => UpdateFormula();
return kcb;
}
private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) {
FillParagraph();
}
private void RichTextBox_LostFocus(object sender, RoutedEventArgs e)
{
UpdateFormula();
}
private void RichTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter) e.Handled = true;
}
private void RichTextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
Rect positionRect = RichTextBox.CaretPosition.GetCharacterRect(LogicalDirection.Backward);
InsertionPopup.HorizontalOffset = positionRect.Left - ((FrameworkElement)InsertionPopup.Child).ActualWidth / 2.0;
UpdateFormula();
}
private void InsertCurve(object sender, MouseButtonEventArgs e)
{
new InlineUIContainer(CreateKCB("Curve"), RichTextBox.CaretPosition);
}
private void InsertVariable(object sender, MouseButtonEventArgs e)
{
new InlineUIContainer(CreateKCB("Float"), RichTextBox.CaretPosition);
}
}
}
KeywordComboBox.xaml
<UserControl x:Class="StruCAT.Views.KeywordComboBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:StruCAT.ViewModels"
x:Name="ThisKeywordCombobox"
Margin="1,1,1,-6"
d:DataContext="{x:Type viewModels:ParameterViewModel}"
mc:Ignorable="d">
<DockPanel Height="22">
<Border Background="WhiteSmoke"
BorderBrush="Silver"
BorderThickness="1,1,0,1"
CornerRadius="2,0,0,2"
UseLayoutRounding="True">
<ContentPresenter DockPanel.Dock="Left">
<ContentPresenter.Style>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Content" Value="{StaticResource VariableIcon}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Type, ElementName=ThisKeywordCombobox}" Value="Curve">
<Setter Property="Content" Value="{StaticResource CurveIcon}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
</Border>
<ComboBox Padding="3,3,0,3"
DisplayMemberPath="Name"
IsEnabled="{Binding Path=ItemsSource, RelativeSource={RelativeSource Self}, Converter={StaticResource EmptyToFalseConverter}}"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedKeyword, ElementName=ThisKeywordCombobox, UpdateSourceTrigger=PropertyChanged}"
SelectionChanged="ComboBox_SelectionChanged">
<ComboBox.ItemsSource>
<MultiBinding Converter="{StaticResource KeywordsFilter}">
<Binding Path="Parent.Parent.Parent.Keywords" />
<Binding ElementName="ThisKeywordCombobox" Path="Type" />
<Binding Path="Parent.Parent.KeywordViewModel" />
<Binding Path="Parent.Keywords" />
<!-- USED ONLY TO TRIGGER UPDATE -->
<Binding Path="Parent.Parent.Parent.Keywords.Count" />
<Binding Path="Parent.Keywords.Count" />
</MultiBinding>
</ComboBox.ItemsSource>
</ComboBox>
</DockPanel>
</UserControl>
KeywordComboBox.xaml.cs
using StruCAT.ViewModels;
using System.Windows;
using System.Windows.Controls;
namespace StruCAT.Views
{
/// <summary>
/// Logique d'interaction pour KeywordComboBox.xaml
/// </summary>
public partial class KeywordComboBox : UserControl
{
public KeywordComboBox()
{
InitializeComponent();
}
public KeywordViewModel SelectedKeyword
{
get { return (KeywordViewModel)GetValue(SelectedKeywordProperty); }
set { SetValue(SelectedKeywordProperty, value); }
}
public static readonly DependencyProperty SelectedKeywordProperty = DependencyProperty.Register("SelectedKeyword", typeof(KeywordViewModel), typeof(KeywordComboBox), new PropertyMetadata(null));
public string Type
{
get { return (string)GetValue(TypeProperty); }
set { SetValue(TypeProperty, value); }
}
public static readonly DependencyProperty TypeProperty = DependencyProperty.Register("Type", typeof(string), typeof(KeywordComboBox), new PropertyMetadata("Float"));
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectionChanged();
}
public delegate void SelectionChange();
public event SelectionChange SelectionChanged;
}
}
Первое решение, представленное в комментарии, который исчез с момента (?), Состоит в том, чтобы полностью предотвратить команду Undo, установив UndoLimit RichTextBox в 0. Это эффективно предотвращает исключение при удалении InlineUIContainer. Если другого решения нет, воспользуюсь этим.
1 ответ
Старый вопрос, но просто чтобы дать представление о том, что происходит. Вот метод, найденный здесь (см. ниже). Когда вы удаляете из RichTextBox, объект сериализуется в xaml, чтобы его можно было восстановить, когда пользователь нажимает кнопку отмены (Ctrl + Z). Чтобы сериализовать объект, он перебирает все свойства объекта. Проблема здесь заключается в
RecordNamespaces
recurses , что не является проблемой для простых объектов, но когда у вас есть циклическая ссылка , например, дочерний объект имеет ссылку на своего родителя и наоборот, рекурсия никогда не остановится. В вашем случае кажется, что вы содержите
Parent
свойство, которое заставляет меня поверить, что у вас есть циклическая ссылка в коде.
private bool RecordNamespaces(Scope scope, MarkupObject item, IValueSerializerContext context, bool
lastWasString)
{
// I removed some lines to show just the main part
// Note how it recurses on the last line I showed
foreach (MarkupProperty property in item.Properties)
{
if (property.IsComposite)
{
bool isCollection = IsCollectionType(property.PropertyType);
foreach (MarkupObject subItem in property.Items)
lastWasString = RecordNamespaces(scope, subItem, context, lastWasString || isCollection);
}
}
Теперь, чтобы решить эту проблему, у вас есть 2 варианта:
- Установить
UndoLimit
из RichTextBox в0
как вы уже упоминали. - При использовании убедитесь, что его содержимое не содержит циклических ссылок (в вашем случае установка
DataContext
кParameterViewModel
вызывает эту проблему)
- Кстати, содержимое
InlineUIContainer
должны быть сериализуемыми, то есть у вас не может быть общих свойств, требуется конструктор без параметров по умолчанию и т. д., поэтому существует множество ограничений.