How do I highlight all occurrences of a list of words in a text.
For example, i do have a list of strings ("if", "else", "then", "while", "true").
I do need to find them in a TextBox and highlight them (foreground + background color).
Examples as how it should look:
The current approach is to override TextBox and do "something" in the OnTextChange event.
see this question:
How to highlight portion of a Rich TextBox WPF control if there are more than 2 spaces?
and this open source control:
http://wpfsyntax.codeplex.com/
I'm actually using some approaches using the RichTextBox but im making steps slowly.
Realized how i mark things there are still some bugs. For instance, everything gets
marked after the first character to mark. So it looks just like that:
pos is the position of the character i want to mark (+1 for just one character), in OnTextChange
MarkForeground(pos + 2, pos + 2 + 1, Colors.Green); // +2 for some awkward wpf bug probably ;)
private void MarkForeground(int start, int end, Color col)
{
TextPointer startPointer = this.Document.ContentStart.GetPositionAtOffset(start);
TextPointer endPointer = this.Document.ContentStart.GetPositionAtOffset(end);
if (startPointer != null && endPointer != null)
{
TextRange range = new TextRange(startPointer, endPointer);
range.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(col));
}
}
Related
I have a WPF Rich Text Box control where I am replacing the text with new text of the same length and I simply want to get the original caret position before I replace the text, replace the text and then reset the caret position.
I think that the problem is that it uses a TextPointer that is tied to the RichTextBox and is reset when I clear the original text so that when reapply it the position has changed. What I actually want to do is to reset it to either the character position index in the original text or I am happy to deal with Lined and Columns.
I have scoured the internet for an answer to what should be a very simple problem and nothing seems to answer it. What I really want is an X/Y coordinate or anything that will help.
You can use GetOffsetToPosition() and GetPositionAtOffset() to save the relative position of the Caret and to restore it.
In other words, assuming that the RichTextBox initializes like this:
RichTextBox rtb;
int paragraphIndex = -1;
int indexInParagraph;
public MainWindow()
{
InitializeComponent();
rtb = new RichTextBox();
rtb.Document = new FlowDocument();
Paragraph para = new Paragraph(new Run("some text some text some text."));
rtb.Document.Blocks.Add(para);
// sets the caret at a specific (random) position in the paragraph:
rtb.CaretPosition = para.ContentStart.GetPositionAtOffset(5);
this.Content = rtb;
}
Note the three private fields in the class.
You should save the caret's paragraph index and caret index in the paragraph, before you replace the text:
public void SaveCaretState()
{
//enumerate and get the paragraph index
paragraphIndex = -1;
foreach (var p in rtb.Document.Blocks)
{
paragraphIndex++;
if (p == rtb.CaretPosition.Paragraph)
break;
}
//get index relative to the start of the paragraph:
indexInParagraph = rtb.CaretPosition.Paragraph.ElementStart.GetOffsetToPosition(rtb.CaretPosition);
}
and restore it whenever you liked:
public void RestoreCaretState(MouseEventArgs e)
{
// you might need to insure some conditions here (paragraph should exist and ...)
Paragraph para = rtb.Document.Blocks.ElementAt(paragraphIndex) as Paragraph;
rtb.CaretPosition = para.ElementStart.GetPositionAtOffset(indexInParagraph);
}
Please note that its a simple example and there might be other Blocks in RichTextBox.Document. However, the idea and implementation is not that much different.
I'm trying to implement the Application.Find command for the WPF richtextbox. Let's say I'm searching for "expert". Sounds easy enough. But due to the nature of wpf, if every other letter in "expert" is bolded, then the richtextbox contains e*x*p*e*r*t* and that means six runs exist. I have a starting textPointer. What I'm trying to figure out is how to get the ending textPointer so that I can create a TextRange that I can use to create the Selection.
In this example, the starting textpointer is in the first run, and the ending textpointer should be in the last run. Is there a simple way to generate a textpointer if you know the run and the offset within the run? I tried generating it using a offset from the first textpointer but that did not work because the offset was not within the first run.
As a relative newbie to the WPF richtextbox, this one has me stumped. I imagine that this problem has already been tackled and solved. I did find one partial solution but it only worked on a single run and does not address the multiple run situation.
The idea is to find the offset of the first character (IndexOf) and then to find the TextPointer at this index (but by counting only text characters).
public TextRange FindTextInRange(TextRange searchRange, string searchText)
{
int offset = searchRange.Text.IndexOf(searchText, StringComparison.OrdinalIgnoreCase);
if (offset < 0)
return null; // Not found
var start = GetTextPositionAtOffset(searchRange.Start, offset);
TextRange result = new TextRange(start, GetTextPositionAtOffset(start, searchText.Length));
return result;
}
TextPointer GetTextPositionAtOffset(TextPointer position, int characterCount)
{
while (position != null)
{
if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
{
int count = position.GetTextRunLength(LogicalDirection.Forward);
if (characterCount <= count)
{
return position.GetPositionAtOffset(characterCount);
}
characterCount -= count;
}
TextPointer nextContextPosition = position.GetNextContextPosition(LogicalDirection.Forward);
if (nextContextPosition == null)
return position;
position = nextContextPosition;
}
return position;
}
This is how to use the code:
TextRange searchRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
TextRange foundRange = FindTextInRange(searchRange, "expert");
foundRange.ApplyPropertyValue(TextElement.ForegroundProperty, new SolidColorBrush(Colors.Red));
I found a more complete solution to be here on this GitHub page.
https://github.com/manasmodak/WpfSearchAndHighlightText
It is able to deal with the \n and \r just fine and didn't have the errors I was dealing with from other solutions.
I am creating an extended RichTextBox control, where I am drawing icons next to the lines of text. I am using GetPositionFromCharIndex to find the current line and next line Y coordinate, so that the height and middle of the current line can be determined. This method obviously stops working when the last line is encountered because there is no next line. Is there some way to determine the height of the last line of text? Note, the font size can vary from line to line.
Not easy to do. Maybe there is an API call for this, but I'm not 100% sure.
A hacky way to accomplish this is to use an off-screen RichTextBox control and transfer the RTF property to it, which just happens to append an extra \par at the end for you, so there is an extra line now:
using (RichTextBox r = new RichTextBox()) {
r.SelectAll();
r.SelectedRtf = richTextBox1.Rtf;
for (int i = 1; i < r.Lines.Length; i++) {
Point p1 = r.GetPositionFromCharIndex(r.GetFirstCharIndexFromLine(i - 1));
Point p2= r.GetPositionFromCharIndex(r.GetFirstCharIndexFromLine(i));
int height = p2.Y - p1.Y;
MessageBox.Show(String.Format("Line #{0} height = {1}", i - 1, height));
}
}
I'm trying create a user control using the silverlight 5 richtextbox. I need to be able to insert "inline ui" and can't work out how to get the current cursor position.
I trigger my code like this:
this.GetAbsolutePos(this.richText.Selection.Start);
The guts of this method is here:
private int GetAbsolutePos(TextPointer textPointer)
{
int index = 0;
TextPointer pos = this.richText.ContentStart;
while (pos.CompareTo(textPointer) != 0)
{
if (pos.IsAtInsertionPosition)
{
index++;
}
pos = pos.GetNextInsertionPosition(LogicalDirection.Forward);
}
return index;
}
Given the following text in a richtextbox control....
If the cursor is between 5 & 6 on the first line, then the above function correctly returns 5. But as the cursor is further into the text the position becomes more inaccurate. ie between 5 & 6 on the second line returns 16 and the third line returns 27.
It also becomes more difficult as I'm inserting inline elements at these positions, which then count as a "symbol" and further cause the count to go wrong.
This image shows what is happening when I "insert" the inline ui between 5 & 6 on each line.
Just for completeness here is the Xaml from richtext.Xaml (I've removed all the extra attributes from the Section/Paragraph elements to make it clearer)
<Section>
<Paragraph>
<Run>1234567890</Run>
<LineBreak />
<Run>1234567890</Run>
<LineBreak />
<Run>1234567890</Run>
</Paragraph>
</Section>
Based on the remarks on this page MSDN Silverlight TextPointer Class
Symbol - For TextPointer operations, any of the following is considered to be a symbol:
An opening or closing tag for a TextElement element.
A UIElement element contained within an InlineUIContainer. Note that
a UIElement is always counted as exactly one symbol. Any additional
content or elements contained by the UIElement are not considered
symbols.
Each 16-bit Unicode character inside of a text Run element.
I think I need to "know" what kind of "symbol" I am currently on but cannot figure out how.
Seems like it should be easy, but working with TextPointers seems very unintuitive.
I had an idea to parse the Xaml to find the cursor position but that seems like a real hack.
Any help would be appreciated.
Thanks
In the end we just just manually adjust the index by the number of SubstituteEdits (our representation of the UIElement) that are before the text point. Its still flaky and we have other issues with our control, but it's good enough for the moment.
Cheers
Tim
private int GetAbsolutePos(SubstituteEdit newSub)
{
int index = 0;
TextPointer textPointer = this.richText.Selection.Start;
TextPointer caretWhere = this.richText.ContentStart;
while (true)
{
caretWhere = caretWhere.GetNextInsertionPosition(LogicalDirection.Forward);
if (caretWhere == null || caretWhere.CompareTo(textPointer) == 0)
{
break;
}
index++;
}
foreach (SubstituteEdit sub in this.Substitutes)
{
if (sub.Position < index && sub != newSub)
{
index--;
}
}
return index;
}
I am not sure if this will help, but I ended up letting the user insert where the selection is instead of caret position and it seems to work well. Here is some of my code:
InlineUIContainer MyUI = new InlineUIContainer();
TextBlock tblx = new TextBlock() { Text = addedItem.Title, FontWeight = FontWeights.Bold };
MyUI.Child = tblx;
Paragraph myParagraph = new Paragraph();
myParagraph.Inlines.Add(MyUI);
rtb.Selection.Insert(myParagraph);
I've got a custom (and getting complex) TabControl. It's a gathering of many sources, plus my own wanted features. In it is a custom Panel to show the headers of the TabControl. Its features are to compress the size of the TabItems until they reached their minimum, and then activates scrolling features (in the Panel, again). There is also another custom panel to hold a single button, that renders on the right of the TabItems (it's a "new tab" button).
It all works great, until I try to animate the scrolling.
Here are some relevant snippets :
In the CustomTabPanel (C#, overriding Panel and implementing IScrollInfo):
private readonly TranslateTransform _translateTransform = new TranslateTransform();
public void LineLeft()
{
FirstVisibleIndex++;
var offset = HorizontalOffset + _childRects[0].Width;
if (offset < 0 || _viewPort.Width >= _extent.Width)
offset = 0;
else
{
if (offset + _viewPort.Width > _extent.Width)
offset = _extent.Width - _viewPort.Width;
}
_offset.X = offset;
if (_scrollOwner != null)
_scrollOwner.InvalidateScrollInfo();
//Animate the new offset
var aScrollAnimation = new DoubleAnimation(_translateTransform.X, -offset,
new Duration(this.AnimationTimeSpan), FillBehavior.HoldEnd) { AccelerationRatio = 0.5, DecelerationRatio = 0.5 };
aScrollAnimation.Completed += ScrollAnimationCompleted;
_translateTransform.BeginAnimation(TranslateTransform.XProperty, aScrollAnimation , HandoffBehavior.SnapshotAndReplace);
//End of animation
// These lines are the only ones needed if we remove the animation
//_translateTransform.X = -offset;
//InvalidateMeasure();
}
void ScrollAnimationCompleted(object sender, EventArgs e)
{
InvalidateMeasure();
}
the _translateTransform is initialized in the constructor :
base.RenderTransform = _translateTransform;
Again, everything is fine if I remove the animation part and just replace it with the commented out lines at the end.
I must also point out that the problem is NOT with the animation itself. That part works out well. The problem is about when I remove some tab items : all the layout then screws up. The TranslateTransformation seems to hold on some wrong value, or something.
Thanks in advance.
Well. As it's often the case, I kept working on the thing, and... answered myself.
Could still be useful for other people, so here was the catch. In the line :
var aScrollAnimation = new DoubleAnimation(_translateTransform.X, -offset, new Duration(this.AnimationTimeSpan), FillBehavior.HoldEnd)
{ AccelerationRatio = 0.5, DecelerationRatio = 0.5 };
the FillBehavior should have been FillBehavior.Stop.
As easy as that!