RichTextBox and Inserting at Caret Positions - wpf

Here is the deal: I have a RichTextBox control and it works fine. The problem is that there is a button "Insert Current DateTime" which adds/injects the current datetime into the RichTextBox. The user can enter the datetime anywhere where the caret is pointing. This involves complicated string manipulation and stuff.
Any ideas how to get the current caret position. Whenever I get RichTextBox.CaretPositon it seems it is pointing to the start of the RichTextBox and not where the actual caret is.
UPDATE 1:
The date time button click code:
private void DateTimeStampButton_Click(object sender, RoutedEventArgs e)
{
//TextRange tr = new TextRange(textBox.Selection.Start, textBox.Selection.End);
var tr = new TextRange(textBox.Document.ContentStart, textBox.Document.ContentEnd);
if(tr.Text.Length == 2)
{
if(tr.Text == "\r\n")
{
tr.Text = tr.Text.TrimStart(new[] { '\r', '\n' });
}
}
textBox.CaretPosition.InsertTextInRun(DateTime.Now.ToShortDateString() + " " + DateTime.Now.ToShortTimeString() + ": ");
DateTimeStampButton.Focusable = false;
}
private void SharpRichTextBox_LostFocus(object sender, RoutedEventArgs e)
{
SetValue(TextProperty, Text);
var binding = BindingOperations.GetBinding(this, TextProperty);
if (binding == null) return;
if (binding.UpdateSourceTrigger == UpdateSourceTrigger.Default || binding.UpdateSourceTrigger == UpdateSourceTrigger.LostFocus)
{
// if (TextProperty != null) BindingOperations.GetBindingExpression(this, TextProperty).UpdateSource();
}
}
public string Text
{
get
{
var newValue = new TextRange(Document.ContentStart, Document.ContentEnd).Text.RemoveNewLineAndReturn();
return newValue;
}
set
{
if (!String.IsNullOrEmpty(value))
{
SetValue(TextProperty, value.RemoveNewLineAndReturn());
Document.Blocks.Clear();
Document.Blocks.Add(new Paragraph(new Run(value)));
OnPropertyChanged("Text");
}
}
}
UPDATE 2:
Turned out the problem was with the DateTime button being Focusable. I turned it to be not focusable and it worked as expected. When focus was lost on the RichTextBox it was resetting the caret position. It happened only once since in the code the btn_DateTime was dynamically being set as Focusable = false. I placed Focusable = false in XAML and everything worked fine from the start.

I'm using this code to successfully do what you are attempting:
private void insertNowButton_Click(object sender, RoutedEventArgs e)
{
//NOTE: The caret position does not change.
richTextBox1.CaretPosition.InsertTextInRun(DateTime.Now.ToString());
}
EDIT: Addressing Update 1
private void DateTimeStampButton_Click(object sender, RoutedEventArgs e)
{
var tr = new TextRange(textBox.Document.ContentStart, textBox.Document.ContentEnd);
if (tr.Text.Length == 2)
{
if (tr.Text == "\r\n")
{
tr.Text = tr.Text.TrimStart(new[] { '\r', '\n' });
}
}
/* Changing the text is the only way I can get the date to insert at the beginning */
tr.Text = "I need a beer at ";
textBox.CaretPosition.InsertTextInRun(DateTime.Now.ToString());
}
It looks like SetValue is changing the text so based on my test that actually changing the text resets the caret, I would agree with you that SetValue is causing the problem...

I tried this solution with WPFToolkit.Extended RichTextBox and it didn't work for me.
However I found another one and thought it would be good to post it in here in case someone else could use it.
My problem was also that the after I clicked a button that is supposed to append text at the caret location, it instead adds it at the beginning of the RichTextBox.
So The solution I found is similar to the one in here -
RichTextBox CaretPosition physical location
Instead of using CaretPosition I used RichTextBox.Selection.Start.InsertTextInRun("SomeText").
It considered the selection's start as the caret position even though no selection was made and therefore was good enough for me.
I hope someone will find this useful :)

This worked for me:
private void InsertText(String text, RichTextBox rtb)
{
rtb.CaretPosition = rtb.CaretPosition.GetPositionAtOffset(0, LogicalDirection.Forward);
rtb.CaretPosition.InsertTextInRun(text);
}
I found the code here:
How do I move the caret a certain number of positions in a WPF RichTextBox?

Related

Why the GetCharIndexFromPosition() doesn't return properly for last character of textbox?

public partial class Form1 : Form
{
TextBox textBox;
public Form1()
{
InitializeComponent();
textBox = new TextBox() { Height = 30, Width = 200, Text = "Syncfusion Software", Font = new Font("Arial", 11) };
textBox.MouseMove += textBox_MouseMove;
this.Controls.Add(textBox);
}
void textBox_MouseMove(object sender, MouseEventArgs e)
{
var selectionStart = textBox.GetCharIndexFromPosition(e.Location);
textBox.SelectionStart = selectionStart;
textBox.SelectionLength = 0;
}
}
Here is my code, this is simple sample that i tried to get clear information about GetCharIndexFromPosition() method of TextBox.
In Mouse move, i get the char index position using the current mouse position of the textbox and set selection start of the textbox based on it. So that if i mouse move, then the selection start or caret position will be set based on the mouse move. But there was an issue with when mouse moved at the end of text, selection start is not set to last. it sets last but before.
For example, if a text box contains text "stack", then if mouse position is after the "k" then caret position should be at the end, but it displayed inbetween "c" and "k". GetCharIndexPosition() doesn't returns value properly for last character. Let me know solution for this
Thanks in Advance.
Regards,
Venkatesan R
This is a known documented behavior. The Remarks section of the GetCharIndexFromPosition method documentation contains the following Important note:
If the specified location is not within the client rectangle of the control, or is beyond the last character in the control, the return value is the index of the last character.
The workaround is to use the reverse method GetPositionFromCharIndex to adjust the returned index.
Something like this
void textBox_MouseMove(object sender, MouseEventArgs e)
{
var charIndex = textBox.GetCharIndexFromPosition(e.Location);
var charPosition = textBox.GetPositionFromCharIndex(charIndex);
if (e.Location.X > charPosition.X) charIndex++;
textBox.Select(charIndex, 0);
}
P.S. As a side note, I have no idea what this method is trying to achieve, but for sure it prevents the standard text selection by mouse behavior.
This ended up really bothering me, so I expanded Ivan Stoev's idea to a rather overengineered method that calculates the pixel width of the last character and divides it by two to accurately emulate the same behaviour as on the other characters.
The method was written for a drag/drop scenario, where the selection is adjusted to the drop position while hovering the mouse over.
// Cached, so it doesn't get recalculated on each moved pixel.
private Char _textBoxLastChar = '\0';
private Int32 _textBoxLastCharHalfWidth = 0;
private void TextBox_DragOver(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(DataFormats.UnicodeText))
return;
TextBox tb = sender as TextBox;
if (tb == null)
return;
Int32 textLen = tb.Text.Length;
if (textLen > 0 && _textBoxLastChar != tb.Text[textLen - 1])
{
_textBoxLastChar = tb.Text[textLen - 1];
_textBoxLastCharHalfWidth = (Int32)Math.Round(GetStringWidth(_textBoxLastChar.ToString(), tb.Font) / 2);
}
Point localPoint = tb.PointToClient(new Point(e.X, e.Y));
Int32 index = tb.GetCharIndexFromPosition(localPoint);
// fix for fact it returns the last char position when you go outside text bounds.
Int32 charPosition = tb.GetPositionFromCharIndex(index).X;
if (textLen != 0 && index == textLen - 1 && localPoint.X > charPosition + _textBoxLastCharHalfWidth)
index++;
if (!tb.Focused)
tb.Focus();
tb.SelectionStart = index;
tb.SelectionLength = 0;
}
public static Double GetStringWidth(String text, Font f)
{
//create a bmp / graphic to use MeasureString on
Single areaSize = f.Size * 20;
using (Bitmap b = new Bitmap(1, 1))
using (Graphics g = Graphics.FromImage(b))
{
SizeF sizeOfString = g.MeasureString(text, f, new SizeF(areaSize, areaSize), StringFormat.GenericTypographic);
return sizeOfString.Width;
}
}
Of course, if you ever change the font or font size of the text box, you'll have to reset _textBoxLastChar back to '\0'.

Detect when caret position changes in RichTextBox

I am trying to implement very simple text formatting functionality for a RichTextBox in WPF. This just consists of a few bold, italic, etc ToggleButtons just above the RichTextBox. See image below, but ignore the top TextBox - the RichTextBox is the bigger one at the bottom.
Toggling formatting for either a selection or at the caret position (for text that will be typed in) is not a problem, as I'm doing this:
private void BoldButton_Checked(object sender, RoutedEventArgs e)
{
this.SetSelectionBold(true);
}
private void BoldButton_Unchecked(object sender, RoutedEventArgs e)
{
this.SetSelectionBold(false);
}
private void SetSelectionBold(bool isBold)
{
var selection = this.RichText.Selection;
if (selection != null)
{
selection.ApplyPropertyValue(TextElement.FontWeightProperty, isBold ? FontWeights.Bold : FontWeights.Normal);
}
}
However, if the user moves the caret somewhere else (e.g. from bold text to normal text) then I'd like the ToggleButtons to reflect that state, in much the same way as it works in Word. Is it possible to detect when the caret position changes, and take action accordingly?
Hook yourself into SelectionChanged event and get current caret position, and test if the property exists on that selection?
In the event, probably you want something like:
var selection = richTextBox.Selection;
if(selection != null)
{
if(selection.GetPropertyValue(TextElement.FontWeightProperty) == FontWeights.Bold)
// todo; enable your button
}
If that event is not triggered by caret positioning(the document doesn't say anything about that),
you probably need to inherit from RichTextBox and override OnSelectionChanged, after that you need to actually generate your own Caret, eg:
var currentCaretPlusOne = new TextRange(richTextBox.CaretPosition,
richTextBox.CaretPosition+1);
if(currentCaretPlusOne != null)
{
if(currentCaretPlusOne.GetPropertyValue(TextElement.FontWeightProperty)
== FontWeights.Bold)
// todo; enable your button
}

WPF TextBox AcceptsReturn = True

In WPF Application I have a textbox.
I set its AcceptsReturn Property to true.
So, I can enter data in multiple lines.
When user press enter in the textbox I want to check :
1) Is the cursor on the last line?
2) If cursor is on the last line then check if thatLine.Text = Nothing?
Something like this?
private void TextBoxOnTextChanged(object sender, TextChangedEventArgs e)
{
TextBox tb = sender as TextBox;
if (tb == null)
{
return;
}
string[] lines = tb.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.None);
if (tb.CaretIndex >= tb.Text.Length - lines.Last().Length)
{
// cursor is on last line
if (string.IsNullOrEmpty(lines.Last()))
{
// cursor is on last line and line is empty
}
}
}
ok is in c# but i don't know the vb syntax..
if you need a translation to vb: http://www.developerfusion.com/tools/convert/csharp-to-vb/ ;-)

WPF RichTextbox remove Foreground information from TextRange

sorry for my bad english... The default for a RichTextBox content is to inherit the Foreground color from the RichTextBox itself. That's nice, but if I set a specific Foreground color to some part of my text, that part does not inherit the Foreground anymore, obviously. How can I make my "colored" text inherit the Foreground again? I'm trying to do something like the "Automatic" color from Office Word but after I have set a specific color to a TextRange, I do not know how to unset it :/
TextRange.ClearAllProperties() does what I need, but also erases other properties like FontSize and FontFamily...
TextRange.ApplyPropertyValue(ForegroundProperty, DependencyProperty.UnsetValue) also does not do the trick...
You can also unset it by setting the property to null (this worked for me clearing out the background, for example removing highlighting)
TextRange.ApplyPropertyValue(TextElement.BackgroundProperty, null);
This seemed almost impossible to achieve since there is no "RemovePropertyValue" method. I also tried with span and got the same exception as you did so I made a method that collects all the Paragraphs within the TextRange and made a span for each separetly.. less than ideal, I know.. Anyway, it works for a small example but might be pretty hard to work with for something more complex.
private List<Span> m_spanList = new List<Span>();
private void c_setForegroundButton_Click(object sender, RoutedEventArgs e)
{
TextPointer textPointerStart = c_richTextBox1.Selection.Start;
TextPointer textPointerEnd = c_richTextBox1.Selection.End;
TextRange textRange = new TextRange(textPointerStart, textPointerEnd);
SetForeground(textRange);
}
private void c_clearForegroundButton_Click(object sender, RoutedEventArgs e)
{
foreach (Span span in m_spanList)
{
span.ClearValue(Span.ForegroundProperty);
}
}
public void SetForeground(TextRange textRange)
{
List<Paragraph> spannedParagraphs = new List<Paragraph>();
if (textRange.Start.Paragraph != null)
{
TextRange curRange = null;
Block cur = textRange.Start.Paragraph;
do
{
spannedParagraphs.Add(cur as Paragraph);
// Get next range
curRange = new TextRange(cur.ContentStart, cur.ContentEnd);
} while ((textRange.End.Paragraph == null || !curRange.Contains(textRange.End.Paragraph.ContentEnd)) && (cur = cur.NextBlock) != null);
}
if (spannedParagraphs.Count == 1)
{
Span span = new Span(c_richTextBox1.Selection.Start, c_richTextBox1.Selection.End);
span.Foreground = Brushes.Red;
m_spanList.Add(span);
}
else
{
for (int i = 0; i < spannedParagraphs.Count; i++)
{
if (i == spannedParagraphs.Count - 1)
{
Paragraph paragraph = spannedParagraphs[i];
// For some reason I get an exception here when I try this..
//m_span = new Span(paragraph.ElementStart, c_richTextBox1.Selection.End);
c_richTextBox1.Selection.Select(paragraph.ElementStart, c_richTextBox1.Selection.End);
Span span = new Span(c_richTextBox1.Selection.Start, c_richTextBox1.Selection.End);
span.Foreground = Brushes.Red;
m_spanList.Add(span);
}
else if (i == 0)
{
Paragraph paragraph = spannedParagraphs[i];
Span span = new Span(c_richTextBox1.Selection.Start, paragraph.ElementEnd);
span.Foreground = Brushes.Red;
m_spanList.Add(span);
}
else
{
Paragraph paragraph = spannedParagraphs[i];
Span span = new Span(paragraph.ElementStart, paragraph.ElementEnd);
span.Foreground = Brushes.Red;
m_spanList.Add(span);
}
}
}
}
If you look at the code of method TextRange.ApplyPropertyValue in the .NET Reference Source, you'll see that in the end it calls DependencyObject.SetValue on a collection of Inlines and Blocks, and DependencyObject.SetValue treats the value DependencyProperty.UnsetValue specially by effectively clearing the local value for the property.
The problem is that they didn't think of that when implementing TextRange.ApplyPropertyValue: it checks the passed property value against the property type, and in case of a reference type, it makes sure the passed value is either null or inherits from the same class, thus preventing us from passing DependencyProperty.UnsetValue.
One solution I found to implement a way of clearing local values of a TextRange for dependency properties of a reference type is the following:
// We declare a marker brush to be detected later in the TextRange.
var markerBrush = new SolidColorBrush();
// First we ask the TextRange implementation to set our marker brush on its content.
// Using ApplyPropertyValue here takes care of splitting inlines when necessary to make
// sure that only the selected text gets affected.
range.ApplyPropertyValue(TextElement.ForegroundProperty, markerBrush);
// Now, we search the text range for every Inline that has our brush set as the foreground
// brush, and we clear the Foreground dependency property.
var position = range.Start;
while (position != null && range.Contains(position))
{
if (position.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart &&
position.Parent is Inline inline &&
inline.ReadLocalValue(TextElement.ForegroundProperty) == _foregroundClearBrush)
inline.ClearValue(TextElement.ForegroundProperty);
position = position.GetNextContextPosition(LogicalDirection.Forward);
}

Remove images from RichTextBox FlowDocument

I have a WPF application on which the user can paste some data from Word inside a RichTextBox... but if that word data has an image, I need to remove it, how can I accomplish that?
Since the FlowDocument is xml, maybe doing some linq magic could do it, but I don't know how.
There is a tool called WordtoXAML Converter (http://wordtoxaml.codeplex.com). You can use that to convert your Word document contents to XAML, use regular expression matching to identify the images and then strip them out.
The following code will do what you want. While it can be a bit wasteful (it looks through the entire document instead of just the bit that was just pasted), it is the only way to do it, as sometimes the RichTextBox is inaccurate when it indicates the recently painted range:
public class MyTextBox : RichTextBox
{
public MyTextBox()
{
CommandBindings.Add(new CommandBinding(ApplicationCommands.Paste, Paste));
}
protected virtual void Paste(object sender, ExecutedRoutedEventArgs e)
{
Paste();
foreach (var image in FindImages())
{
if (image.SiblingInlines != null)
{
image.SiblingInlines.Remove(image);
}
}
}
IEnumerable<InlineUIContainer> FindImages()
{
var result = new List<InlineUIContainer>();
var blocks = Document.Blocks;
for (TextPointer position = blocks.FirstBlock.ElementStart; position != null && position.CompareTo(blocks.LastBlock.ElementEnd) != 1; position = position.GetNextContextPosition(LogicalDirection.Forward))
{
InlineUIContainer element = position.Parent as InlineUIContainer;
if (element != null && element.Child is Image)
{
result.Add(element);
}
}
return result;
}
}

Resources