如何在 FlowDocument WPF 中以编程方式在现有段落的中间添加段落?

How to add Paragraph in the middle of existing Paraghaph programmaticaly in FlowDocument WPF?

我有一个测试应用程序,我试图插入一个可编辑的段落,以便用户可以在那里写信息(也许它可以通过 运行 实现,我以段落为例,如果你知道如何将 运行 添加到 运行 中,那会很棒)。我 不想 使用 richtextbox 主要有两个原因:

  1. 用户无法编辑文档的任何其他部分

  2. Flowdocument有分页 对于我现在所做的,我有这个:文本框和流程文档,其中一段 (aaaaa bbb cccc) 由 xaml 创建,另一段由代码

    创建

    我的可编辑段落到文档末尾。我想要的是用它代替 examle 的“bbb”。所以它必须以某种方式从所有文档中找到“bbb”,替换它,然后把我的段落放在那个地方

我试过:

这是我的 xaml

<Grid x:Name="grid">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <FlowDocumentReader Grid.Row="1">
        <FlowDocument x:Name="DocumentReader"/>
    </FlowDocumentReader>

</Grid>

这是我的 xaml.cs 没有任何错误代码,我尝试在段落内设置 paragrahp - 只是文本框和可编辑的段落

Dictionary<string, Paragraph> paragraphs = new Dictionary<string, Paragraph>();
        private string text = $"{{\rtf1\ansi\ansicpg1252\uc1\htmautsp\deff2{{\fonttbl{{\f0\fcharset0 Times New Roman;}}{{\f2\fcharset0 Palatino Linotype;}}}}{{\colortbl\red0\green0\blue0;\red255\green255\blue255;}}\loch\hich\dbch\pard\plain\ltrpar\itap0{{\lang1033\fs21\f2\cf0 \cf0\ql{{\f2 {{\ltrch aaaaa bbb ccc}}\li0\ri0\sa0\sb0\fi0\ql\par}}\r\n}}\r\n}}";
        public MainWindow()
        {
            InitializeComponent();
            
            //this is how data loads in flowdocument in my actual programm
            TextRange textRange = new TextRange(DocumentReader.ContentStart, DocumentReader.ContentEnd);
            using (MemoryStream stream = new MemoryStream(ASCIIEncoding.Default.GetBytes(text)))
            {
                textRange.Load(stream, DataFormats.Rtf);
            }

            //this is what i was testing
            var parag = new Paragraph { Name = "paragName" };
            parag.Inlines.Add(new Run("as"));
            paragraphs.Add("paragName", parag);
            DocumentReader.Blocks.Add(parag);

            var txt = new TextBox{Tag = "paragName" };
            txt.TextChanged += (sender, args) =>
            {
                paragraphs.First(x => (string)x.Key == txt.Tag).Value.Inlines.Clear();
                paragraphs.First(x => (string)x.Key == txt.Tag).Value.Inlines.Add(new Run((sender as TextBox).Text));
            };
            grid.Children.Add(txt);
        }

超级原始,我只是在测试它,但我无法解决该怎么做,请帮助

一个非常简单的解决方案是使用 TextBlock 然后在您要编辑的位置内联 TextBox

以下示例让 EditableTextBlock 扩展 TextBlock 以扩展 TextBlock 行为。
设置 EditableTextBlock.EditableTextRange 属性 定义文本中应可编辑的位置和范围。完整的显示文字可以像往常一样访问继承的EditableTextBlock.Text属性
设置 EditableTextBlock.EditableTextRange 属性 将触发 TextBox 出现在指定位置。然后通过按 Enter 键或单击 TextBox.
旁边的 Button 来提交编辑的值 然后 TextBox 将消失,编辑的文本将再次变为 read-only。
为了简化内容处理,EditableTextBlock 维护了一个 运行 显示内容。
实现非常简单,应该可以作为一个有用的起点。

TextRange.cs

public readonly struct TextRange : IEquatable<TextRange>
{
  public TextRange(int index, int length)
  {
    // TODO::Throw ArgumentException if values are out of range (e.g. < 0)

    this.Index = index;
    this.Length = length;
  }

  public bool Equals(TextRange other) => this.Index.Equals(other.Index) && this.Length.Equals(other.Length);
  public int Index { get; }
  public int Length { get; }
}

EditableTextBlock.cs

public class EditableTextBlock : TextBlock
{
  public TextRange EditableTextRange
  {
    get => (TextRange)GetValue(EditableTextRangeProperty);
    set => SetValue(EditableTextRangeProperty, value);
  }

  public static readonly DependencyProperty EditableTextRangeProperty = DependencyProperty.Register(
    "EditableTextRange", 
    typeof(TextRange), 
    typeof(EditableTextBlock), 
    new PropertyMetadata(default(TextRange), OnTextRangeChanged));

  public static void SetText(UIElement attachedElement, string value)
    => attachedElement.SetValue(TextProperty, value);

  public static string GetText(UIElement attachedElement)
    => attachedElement.GetValue(TextProperty) as string;

  public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached(
    "Text",
    typeof(string),
    typeof(EditableTextBlock),
    new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
  public static RoutedUICommand CommitChangesCommand { get; }

  static EditableTextBlock()
  {
    DefaultStyleKeyProperty.OverrideMetadata(typeof(EditableTextBlock), new FrameworkPropertyMetadata(typeof(EditableTextBlock)));
    CommitChangesCommand = new RoutedUICommand(
      "Commit edit changes",
      nameof(CommitChangesCommand),
      typeof(EditableTextBlock),
      new InputGestureCollection() 
        { 
          new KeyGesture(Key.Enter) 
        });
  }

  public EditableTextBlock()
  {
    var editableElementContentTemplate = Application.Current.Resources["EditableElementTemplate"] as DataTemplate;
    if (editableElementContentTemplate == null)
    {
      throw new InvalidOperationException("Define a DataTemplate named "EditableElementTemplate" in App.xaml");
    }

    var editableContent = new ContentPresenter() { ContentTemplate = editableElementContentTemplate };
    this.EditableElement = new InlineUIContainer(editableContent);

    this.CommandBindings.Add(new CommandBinding(CommitChangesCommand, ExecuteCommitChangesCommand));
  }

  private static void OnTextRangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    => (d as EditableTextBlock).OnTextRangeChanged((TextRange)e.OldValue, (TextRange)e.NewValue);

  private void ExecuteCommitChangesCommand(object sender, ExecutedRoutedEventArgs e)
  {
    var documentTextBuilder = new StringBuilder();
    foreach (Inline documentElement in this.Inlines)
    {
      documentTextBuilder.Append(documentElement is Run run ? run.Text : GetText((documentElement as InlineUIContainer).Child));
    }
    var readOnlyDocument = new Run(documentTextBuilder.ToString());
    this.Inlines.Clear();
    this.Inlines.Add(readOnlyDocument);
  }

  protected virtual void OnTextRangeChanged(TextRange oldTextRange, TextRange newTextRange)
  {
    Inline documentContent = this.Inlines.FirstInline;
    if (documentContent is Run run && newTextRange.Index < run.Text.Length)
    {
      string newPreceedingReadOnlyRangeText = run.Text.Substring(0, newTextRange.Index);
      var newReadOnlyElement = new Run(newPreceedingReadOnlyRangeText);
      this.Inlines.InsertBefore(documentContent, newReadOnlyElement);

      string newEditableRangeText = run.Text.Substring(newTextRange.Index, newTextRange.Length);
      SetText(this.EditableElement.Child, newEditableRangeText);
      this.Inlines.InsertAfter(documentContent, this.EditableElement);
      this.Inlines.Remove(documentContent);

      string remainingReadOnlyRangeText = run.Text.Substring(newTextRange.Index + newTextRange.Length);
      var remainingReadOnlyElement = new Run(remainingReadOnlyRangeText);
      this.Inlines.InsertAfter(this.EditableElement, remainingReadOnlyElement);
    }
    else // Append
    {
      string newEditableRangeText = String.Empty;
      SetText(this.EditableElement.Child, newEditableRangeText);
      this.Inlines.Add(this.EditableElement);
    }
  }

  private InlineUIContainer EditableElement { get; }
}

App.xaml

<Application xmlns:system="clr-namespace:System;assembly=netstandard">
  <Application.Resources>
    <DataTemplate x:Key="EditableElementTemplate"
                  DataType="{x:Type system:String}">
      <StackPanel Orientation="Horizontal">
        <TextBox Text="{Binding RelativeSource={RelativeSource AncestorType=ContentPresenter}, Path=(local:EditableTextBlock.Text), UpdateSourceTrigger=PropertyChanged}" />
        <Button Command="{x:Static local:EditableTextBlock.CommitChangesCommand}"
                Content="Ok" />
      </StackPanel>
    </DataTemplate>
  </Application.Resources>
</Application>

使用示例

MainWindow.xaml

<Window>
  <local:EditableTextBlock x:Name="Document" />
</Window>

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public MainWindow()
  {
    InitializeComponent();

    this.Loaded #= OnLoaded;
  }

  private void OnLoaded(object sender, EventArgs e)
  {
    var documentText = "This is some random text.";
    this.Document.Text = documentText;

    int editableTextIndex = this.Document.Text.IndexOf("random");
    int editableTextLength = "random".Length;
    this.Document.EditableTextRange = new TextRange(editableTextIndex, editableTextLength);
  }
}