如何在 FlowDocument WPF 中以编程方式在现有段落的中间添加段落?
How to add Paragraph in the middle of existing Paraghaph programmaticaly in FlowDocument WPF?
我有一个测试应用程序,我试图插入一个可编辑的段落,以便用户可以在那里写信息(也许它可以通过 运行 实现,我以段落为例,如果你知道如何将 运行 添加到 运行 中,那会很棒)。我 不想 使用 richtextbox 主要有两个原因:
用户无法编辑文档的任何其他部分
Flowdocument有分页
对于我现在所做的,我有这个:文本框和流程文档,其中一段 (aaaaa bbb cccc) 由 xaml 创建,另一段由代码
创建
我的可编辑段落到文档末尾。我想要的是用它代替 examle 的“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);
}
超级原始,我只是在测试它,但我无法解决该怎么做,请帮助
一个非常简单的解决方案是使用 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);
}
}
我有一个测试应用程序,我试图插入一个可编辑的段落,以便用户可以在那里写信息(也许它可以通过 运行 实现,我以段落为例,如果你知道如何将 运行 添加到 运行 中,那会很棒)。我 不想 使用 richtextbox 主要有两个原因:
用户无法编辑文档的任何其他部分
Flowdocument有分页 对于我现在所做的,我有这个:文本框和流程文档,其中一段 (aaaaa bbb cccc) 由 xaml 创建,另一段由代码
创建我的可编辑段落到文档末尾。我想要的是用它代替 examle 的“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);
}
超级原始,我只是在测试它,但我无法解决该怎么做,请帮助
一个非常简单的解决方案是使用 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);
}
}