I have a test app where i'm trying to insert an editable paragraph so user can write info there (maybe it can be realized just with Run, i took paragraph just for example, if you know how to add Run into Run, that will be great). I DON'T WANT to use richtextbox for it for two main reasons:
User can't edit any other parts of document
Flowdocument has pagination For what i've done now, i have this: textbox and flowdocument with one paragraph (aaaaa bbb cccc) created by xaml and one created by code
My editable paragraph going to the end of document. What i want is to put it instead of "bbb" for examle. So it must somehow find "bbb" from all document, replace it, and put in that place my paragraph
I've tried to:
- Run through all blocks, find text that i need and remove it from paragraph, but no use because i can't replace string with paragraph or with run
- Find index of text i want but i still can't do nothing with it because i need a TextPointer
- Convert int to TextPointer but documentation said i'm going to a wrong and unsave direction
- Find cursor controller for FlowDocument and set it to index i need but it still needs a TextPointer So i really need help because i can't see no other options
Here is my xaml
<Grid x:Name="grid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<FlowDocumentReader Grid.Row="1">
<FlowDocument x:Name="DocumentReader"/>
</FlowDocumentReader>
</Grid>
And here is my xaml.cs without any bad code with my attempts to set paragrahp inside a paragraph - just textbox and editable paragraph
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);
}
It is super raw, i was just testing it, but i can't resolve how to do it, please help
CodePudding user response:
A quite simple solution would be to use a TextBlock
and then inline a TextBox
at the position you like to edit.
The following example lets EditableTextBlock
extend TextBlock
to extend the TextBlock
behavior.
Setting a EditableTextBlock.EditableTextRange
property defines the position and range in the text which should be made editable. The complete displayed text can be obtained by accessing the inherited EditableTextBlock.Text
property as usual.
Setting the EditableTextBlock.EditableTextRange
property will trigger a TextBox
to appear at the specified position. The edited value is then committed by pressing the Enter key or by clicking the Button
next to the TextBox
.
The TextBox
will then disappear and the edited text will become read-only again.
To simplify the content handling, the EditableTextBlock
maintains a single Run to display the content.
The implementation is very simple and should serve you as a useful starting point.
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>
Usage example
MainWindow.xaml
<Window>
<local:EditableTextBlock x:Name="Document" />
</Window>
MainWindow.xaml.cs
partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded #= onl oaded;
}
private void onl oaded(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);
}
}