所以,我有一个 RichTextBox,它可以包含一个或多个 InlineUIContainer,其中包含一个小的 UserControl。当其中一个被删除时,直接抛出 StackOverflowException (我的代码没有在 Backspace 按下和异常之间运行)。
内部代码中引发了异常,感谢 WinDbg,我设法恢复了 StackTrace,这是结果的重要部分:
...
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)
然后再调用 MarkupWriter.RecordNamespaces 13000 次,直到出现异常。我做了一些研究,当 InlineUIContainer 被删除时,框架会尝试将其序列化为 XAML,以便能够执行撤消命令。但在这种情况下,序列化在 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);
}
}
}
关键字组合框.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>
关键字组合框.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;
}
}
注释中提供的第一个解决方案(自 (?) 以来已消失)是通过将 RichTextBox 的 UndoLimit 设置为 0 来完全阻止 Undo 命令。它有效地防止了删除 InlineUIContainer 时的异常。如果没有其他解决方案,我将使用此解决方案。