0

所以,我有一个 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 时的异常。如果没有其他解决方案,我将使用此解决方案。

4

1 回答 1

0

老问题,但只是为了说明正在发生的事情。这是方法RecordNamespaces,可在此处找到(见下文)。当您InlineUIContainer从富文本框中删除一个对象时,该对象将序列化为 xaml,以便在用户按下撤消 (Ctrl + Z) 时将其恢复。为了序列化对象,它遍历对象的所有属性。这里的问题是RecordNamespaces recurses,这对于简单对象来说不是问题,但是当你有一个循环引用时,比如一个 child 引用了它的 parent,反之亦然,递归将永远不会停止。在您的情况下,您似乎ParameterViewModel包含一个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);
        }
    }

现在为了解决这个问题,你有两个选择:

  1. UndoLimitrichtextbox 设置0为您已经提到的。
  2. 使用时InlineUIContainer,请确保其内容不包含任何循环引用(在您的情况下,设置DataContexttoParameterViewModel会导致此问题)
  • 附带说明, 的内容InlineUIContainer必须是可序列化的,这意味着您不能拥有通用属性,需要默认的无参数构造函数等,因此有很多限制。
于 2021-12-31T13:18:52.540 回答