Как программно добавить абзац в середине существующего абзаца в FlowDocument WPF?

У меня есть тестовое приложение, в котором я пытаюсь вставить редактируемый абзац, чтобы пользователь мог писать туда информацию (возможно, это можно реализовать только с помощью «Выполнить», я взял абзац только для примера, если вы знаете, как добавить «Выполнить» в «Выполнить», это будет будь великолепен). Я НЕ ХОЧУ использую для этого richtextbox по двум основным причинам:

  1. Пользователь не может редактировать никакие другие части документа

  2. Flowdocument имеет пагинацию Для того, что я сделал сейчас, у меня есть это: текстовое поле и потоковый документ с одним абзацем (aaaaa bbb cccc), созданным xaml, и одним, созданным кодом

    Мой редактируемый абзац идет в конец документа. Я хочу поставить его вместо «bbb», например. Так что он должен как-то найти "bbb" из всего документа, заменить его и поставить на это место мой абзац

Я пытался:

  • Пробежаться по всем блокам, найти текст, который мне нужен, и удалить его из абзаца, но бесполезно, потому что я не могу заменить строку абзацем или выполнить
  • Найти индекс текста, который я хочу, но я все равно ничего не могу с ним поделать, потому что мне нужен TextPointer
  • Преобразовать int в TextPointer, но в документации сказано, что я иду в неправильном направлении и не сохраняю
  • Найдите контроллер курсора для FlowDocument и установите для него индекс, который мне нужен, но ему все еще нужен TextPointer Так что мне действительно нужна помощь, потому что я не вижу других вариантов

Вот мой 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);
        }

Это очень сырой, я просто тестировал его, но я не могу решить, как это сделать, пожалуйста, помогите

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
38
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Довольно простым решением было бы использовать TextBlock, а затем вставить TextBox в позицию, которую вы хотите отредактировать.

Следующий пример позволяет EditableTextBlock расширить TextBlock, чтобы расширить TextBlock поведение.
Установка свойства EditableTextBlock.EditableTextRange определяет положение и диапазон в тексте, которые должны быть доступны для редактирования. Полный отображаемый текст можно получить, как обычно, обратившись к унаследованному свойству EditableTextBlock.Text.
Установка свойства EditableTextBlock.EditableTextRange вызовет появление TextBox в указанной позиции. Затем отредактированное значение фиксируется нажатием клавиши Войти или щелчком Button рядом с TextBox.
Затем значок TextBox исчезнет, ​​а отредактированный текст снова станет доступен только для чтения.
Чтобы упростить обработку контента, 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);
  }
}

у меня получилось, благодаря вам!

Liromanz 04.04.2022 19:21

Другие вопросы по теме