RichTextbox Find - c#

I found a good solution for search and highlight text in RichTextbox LINK
And this solution works fine, but I found a very nasty bug, when searching for the last character in any text, such as "Hello World" and if you try to type a letter "d" in search field, program will glitch in endless cycle in here
while ((index = this.Find(findWhat, startSearch, findoptions)) > -1)
{
isfind = true;
this.SelectionBackColor = highlightColor;
startSearch = index + 1;
}
How to fix this bug? Or maybe help me find another solution how to find and highlight text in RichTextBox.

Yes, it is buggy. You have to add an extra check to ensure it doesn't start the search beyond the end of the text. Like this:
int max = this.TextLength;
while (startSearch < max &&
(index = this.Find(findWhat, startSearch, findoptions)) > -1) {
isFind = true;
this.SelectionBackColor = highlightColor;
startSearch = index + 1;
}

Related

Apply last non empty string to empty string?

A file is read in. Looks for lines that have a number that beings with an S The lines that do not have an S are maintained. Saves to an array. I am then populating an existing gridview with the same amount of lines.
As a place holder I have set the blank lines to *** This is where I'm stuck. I need the empty strings to be populated with the last non empty string.
So for example if the readout is:
1
2
3
Empty
Empty
Empty
4
Empty
6
I'd want it displayed as:
1
2
3
3
3
3
4
4
6
I can't figure out how to do that. I've been searching all day for examples but can only find ways of grabbing either the first or last number of my array is all. Here is my code.
var sLines = File.ReadAllLines(cboPartProgram.Text)
.Where(s => !s.StartsWith("'"))
.Select(s => new
{
SValue = Regex.Match(s, "(?<=S)[\\d.]*").Value,
})
.ToArray();
string LastSValue = "";
string Value = "";
for (int i = 0; i < sLines.Count(); i++)
{
if (sLines[i].SValue == "")
{
LastSValue = "***";
Value = LastSValue;
}
else
{
Value = (sLines[i].SValue);
}
}
Ok I think I got it.
for (int i = 0; i < sLines.Length; i++)
{
if (sLines[i].SValue == "" && i > 0)
{
foreach (var empt in sLines[i].SValue)
{
LastSValue = sLines[i - 1].SValue;
Value = LastSValue;
}
}
else
{
Value = (sLines[i].SValue);
}
On a side note, when I copy my code I use the code option above to format it, but I notice someone always has to correct my spacing. Its copied straight from the IDE but there are always spaced each line that I guess shouldn't be. Is there a different way I should do it?
UPDATE
If I should ask this as a new question let me know, but it's so dependent on this that I thought I should keep it here.
Using the code I posted above that does what I needed it too. I've been trying to edit this so that if there is NO previous number, so for example if there if line 1 has no number but the rest do, then just apply the string "NA" otherwise still do what the code above does to the rest of the lines.
I guess maybe the best way would be to just take the results from the above code, and if there are any empty spaces left, apply "NA" but I can't figure it out.
In your example, you just need to take the value of the row before to fill the current value. Something like the following :
for (int i = 0; i < sLines.Length; i++)
{
if (sLines[i].SValue == "" && i > 0)
{
sLines[i].SValue = sLines[i-1].SValue;
}
else
{
sLines[i].SValue = sLines[i].SValue;
}
}
Your example has one more issue but currently I'll focus only on gathering the "last non empty" string.
If you look at your example you can spot few things that could potentially help you finding solution. These are for loop and reference to original list that stays intact.
For my example I'll use Linq because it will be much easier.
First of all I'll copy all from before for loop ( if that makes sense :D ) :
var sLines = File.ReadAllLines(cboPartProgram.Text)
.Where(s => !s.StartsWith("'"))
.Select(s => new
{
SValue = Regex.Match(s, "(?<=S)[\\d.]*").Value,
})
.ToArray();
string LastSValue = "";
string Value = "";
Just because it's okay and will work for now.
With your for loop I'll make modifications :
for (int i = 0; i < sLines.Count(); i++)
{
// `i` is representing current "index" of processed "word"
// we can use this to find last "valid" element
// string notEmpty = sLines.Take(i).LastOrDefault(word => !string.IsNullOrEmpty(word));
// but since you want to assign this to `Value` and there can be not empty string at `i` index
// we can make it in one line :
Value = string.IsNullOrEmpty(sLines[i]) ? sLines.Take(i).LastOrDefault(word => !string.IsNullOrEmpty(word)) : sLines[i].SValue;
// instead of your previous logic :
//if (sLines[i].SValue == "")
//{
// LastSValue = "***";
// Value = LastSValue;
//}
//else
//{
// Value = (sLines[i].SValue);
//}
}
Another problem which I think you'll face is that first value ( judging by the input ) can also be empty. Which will throw exception in my example. This will also be impossible to fit this kind of solution because there's no previous value ( at all ).
From what I understand, if you want to store the result in Value and do something else with it inside the loop (instead of changing it in the array), what you probably want is this:
for (int i = 0; i < sLines.Count(); i++)
{
if (sLines[i].SValue == "")
{
Value = LastSValue;
}
else
{
Value = (sLines[i].SValue);
LastSValue = Value;
}
// use Value
}
I would also suggest using sLines.Length instead of Count(), which is made for sequences where the length isn't known in advance - it's supposed to literally count the elements one by one. In this case it would probably be optimized but if you know you're dealing with an array, it's a good idea to ask for the length directly.
EDIT:
To get "NA" if there's no previous number, just initialize LastSValue to this value before the loop:
string LastSValue = "NA";
That way, if Value is empty and there was not LastSValue set before, it will still be "NA".
EDIT2:
A solution similar to the one from #Cubi, to change it in place:
for (int i = 0; i < sLines.Length; i++)
{
if (sLines[i].SValue == "")
sLines[i].SValue = i > 0 ? sLines[i-1].SValue : "NA";
}

Richtextbox Lines.Length == -1

I am trying to number each line in a RichTextBox. I have gotten the number of lines using Lines.Length. If I start typing when it is blank that will turn to 1, but if I press backspace 2-3 times then start typing it returns 0.
(I know pointless) Here is the function that return the number of lines and you can see (my failed attempt) where I tried to compare it with the TextLength to correct the value.
private int LineCount()
{
int textLength = MainIOControl.TextLength;
int lineCount = MainIOControl.Lines.Length - 1;
return (textLength == 0 && lineCount == 0) ? 0 : lineCount;
}
This return the correct value if you just start typing, but not if you press backspace a couple times when it is empty.
Am I simple overlooking something obvious?
Try calculating it yourself instead of using the Lines property:
return MainIOControl.Text.Length - MainIOControl.Text.Replace(Environment.NewLine, string.Empty).Length;
This one may work better for your situation:
private int LineCount()
{
return MainIOControl.Text.Length - MainIOControl.Text.Replace("\n", string.Empty).Length + 1;
}

RichTextBox - sorting lines randomly

I want to write an application which sorts randomly line of text which I copy from a source and paste into RichTextBox area.
However, there is one condition - text is formatted (some words are in bold, underline etc.). So any suggestions? How should it look like?
I think I should use RichTextBox.Rtf or something but I am really a beginner and I appreciate every hint or example code.
Thanks
It is a bit tricky. You can retrieve the formatted RTF text lines like this
string[] rtfLines = new string[richTextBox1.Lines.Length];
for (int i = 0; i < rtfLines.Length; i++) {
int start = richTextBox1.GetFirstCharIndexFromLine(i);
int length = richTextBox1.Lines[i].Length;
richTextBox1.Select(start, length);
rtfLines[i] = richTextBox1.SelectedRtf;
}
Now you can shuffle the lines like this
var random = new Random();
rtfLines = rtfLines.OrderBy(s => random.NextDouble()).ToArray();
Clear the RichtTextBox
richTextBox1.Text = "";
Inserting the lines is best done in reverse order because it is easier to select the beginning of the text
// Insert the line which will be the last line.
richTextBox1.Select(0, 0);
richTextBox1.SelectedRtf = rtfLines[0];
// Prepend the other lines and add a line break.
for (int i = 1; i < rtfLines.Length; i++) {
richTextBox1.Select(0, 0);
// Replace the ending "}\r\n" with "\\par }\r\n". "\\par" is a line break.
richTextBox1.SelectedRtf =
rtfLines[i].Substring(0, rtfLines[i].Length - 3) + "\\par }\r\n";
}
The task seems not complicated(if I understand it correctly).
Get your clipboard into string then parse into array- use Split().
Then determine how many randon events you need and iterate through every word ; generate random number for each iteration(which should match the amount of events), intersect that number with one of the events and apply that case to that particular word. Maybe not the most efficient way to do it, but that's what comes to my mind

Faster Way to Color All Occurences in a RichTextBox in C#

I have a RichTextBox, and there are about more than 1000 occurrences of a specified search string.
I use the following function to color all the occurrences:
public void ColorAll(string s)
{
rtbxContent.BeginUpdate();
int start = 0, current = 0;
RichTextBoxFinds options = RichTextBoxFinds.MatchCase;
start = rtbxContent.Find(s, start, options);
while (start >= 0)
{
rtbxContent.SelectionStart = start;
rtbxContent.SelectionLength = s.Length;
rtbxContent.SelectionColor = Color.Red;
rtbxContent.SelectionBackColor = Color.Yellow;
current = start + s.Length;
if (current < rtbxContent.TextLength)
start = rtbxContent.Find(s, current, options);
else
break;
}
rtbxContent.EndUpdate();
}
But I found it's very slow.
However, if I color all the occurrences of another word, which has less number of occurrences in the same text, I found it's very fast.
So I guess the slowness is from (these two line might get UI refresh involved):
rtbxContent.SelectionColor = Color.Red;
rtbxContent.SelectionBackColor = Color.Yellow;
Is there a faster way of doing the same job, such as, I do the coloring in the memory, and then I display the result at one-go?
Do I make myself clear?
Thanks.
The amount of time it takes is directly proportional to the number of occurances.
It is probably the Find that is using the most time. You could replace this line:
start = rtbxContent.Find(s, start + s.Length, options);
with this:
start = rtbxContent.Find(s, current, options);
Since you have computed current to equal start + s.Length
You could also store s.Length is a variable so you do not need to count all the characters in a string each time. The same goes for rtbxContent.TextLength.
There is a faster way.
Use regex to find the matches then highlight in the richtextbox
if (this.tBoxFind.Text.Length > 0)
{
try
{
this.richTBox.SuspendLayout();
this.Cursor = Cursors.WaitCursor;
string s = this.richTBox.Text;
System.Text.RegularExpressions.MatchCollection mColl = System.Text.RegularExpressions.Regex.Matches(s, this.tBoxFind.Text);
foreach (System.Text.RegularExpressions.Match g in mColl)
{
this.richTBox.SelectionColor = Color.White;
this.richTBox.SelectionBackColor = Color.Blue;
this.richTBox.Select(g.Index, g.Length);
}
}
finally
{
this.richTBox.ResumeLayout();
this.Cursor = Cursors.Default;
}
}
The string search is linear. If you find Find method to be slow, maybe you can use third party tool to do the searching for you. All you need is index of the pattern in a string.
Maybe this will help you. You should time the difference and use the faster one.
You're on the right track that Winforms' slow RichTextBox implementation is to blame. You also did well to use the BeginUpdate and EndUpdate methods (I'm guessing you took those from here?). But alas, that isn't enough.
A couple of solutions:
1: Try writing the RTF directly to the textbox. This is a fairly messy, complicated format, but luckily, I have created an answer here which will do the trick.
2: This highly rated external project also looks well worth a look: http://www.codeproject.com/Articles/161871/Fast-Colored-TextBox-for-syntax-highlighting

Errata in Extreme Programming Adventures in C#?

I'm trying to work my way through Ron Jeffries's Extreme Programming Adventures in C#. I am stuck, however, in Chapter 3 because the code does not, and cannot, do what the author says it does.
Basically, the text says that I should be able to write some text in a word-wrap enabled text box. If I then move the cursor to an intermediate line and hit enter, the code should re-display the lines before the cursor, add a couple of lines and a set of HTML paragraph tags, then append the rest of the lines. The code doesn't match the text because it uses the textbox.lines property. Well, no matter how many word-wrapped lines there are in a text box, there's only ONE line in the Lines property until you hit a carriage return. So, the statement that the code should, "Copy the rest of the lines into the buffer" appears wrong to me.
I'd appreciate anybody having experience with the book telling me what I'm reading, or doing, wrong!
Thanks.
EoRaptor
Try emailing Ron Jeffries directly. I have the book - somewhere, but I don't remember it not working. His email address is ronjeffries at acm dot org and put [Ron] in the subject line.
(And for those wondering - his email info was right from his website Welcome page)
I've also just started this book and had exactly the same problem although the code you have included looks further along than where I am.
The 'subscript out of range' occurred for 2 reasons, first as Ron explains he was just testing and so returned a hard-coded value of 3 before he wrote the CursorLine() function, which means you I think at least 4? lines of text which as you say need to be pasted in, or maybe set the text to this value before running, also as you say they need to have carriage returns to make txtbox.Lines return an array of strings.
The second reason occurs even after CursorLine() has been implemented but only happens if the text box is empty as txtbox.Lines returns string[0] but I think Ron is implementing a 'User Story' which says that when text has been entered and user presses enter, so not sure if he fixes this later, but will probably find out!
The author's do state that they are learning C# and will show the development wart's and all, which is one of the reasons I have chosen to study this book as I think it is encouraging me to develop projects. I also try to do the code first before looking at his solutions to see if I'm thinking the same way, but maybe I know C# a little better than I give myself credit for, or I'm completly crap, but I've noticed a few things, first he says that overriding OnKeyDown() doesn't work, but I think he must have got confused and tried to do in Form, instead of deriving from TextBox and overriding there.
This was my code when reading the 'User Story':
int curPos = txtbox.SelectionStart;
string Wrd = Environment.NewLine + "<P></P>" + Environment.NewLine;
txtbox.SelectedText = Wrd;
int pl = Environment.NewLine.Length + 3; // "<P>" length is 3
// Put text cursor inbetween <P> tags
txtbox.SelectionStart = curPos + pl;
It works differently to Ron's code, but was just my interpretation of the 'User Story' and not sure how should act if text is selected or wether to split line if text cursor in the middle etc.
Also in 'My Adventures' in Extreme Programming Adventures in C#
txtbox.GetLineFromCharIndex(txtbox.SelectionStart)
gets the cursor line position and doesn't matter if no carriage returns or resized,
as far as I can tell, I done little test with:
txtbox.GetLineFromCharIndex(txtbox.TextLength)
which returns the total amount of lines, which will vary if you resize the text box.
Using C# I always look for solutions which already exsist and people may slate me for this but I think MS have created a great language with great components which do what you expect them to do, so don't have to re-create the wheel each time.
Although like I say it's early days in this book and perhaps these simple solutions aren't extensible enough and maybe Ron's taking that into account, although he did mention just get it working then worry about that later is more the XP way.
Warren.
print("using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace NotepadOne {
public class TextModel {
private String[] lines;
private int selectionStart;
private int cursorPosition;
public TextModel() {
}
public String[] Lines {
get {
return lines;
}
set {
lines = value;
}
}
public int SelectionStart {
get {
return selectionStart;
}
set {
selectionStart = value;
}
}
public int CursorPosition {
get {
return cursorPosition;
}
set {
cursorPosition = value;
}
}
public void InsertControlPText() {
lines[lines.Length - 1] += "ControlP";
}
public void InsertParagraphTags() {
int cursorLine = CursorLine();
String[] newlines = new String[lines.Length + 2];
for (int i = 0; i <= cursorLine; i++) {
newlines[i] = lines[i];
}
newlines[cursorLine + 1] = "";
newlines[cursorLine + 2] = "<P></P>";
for (int i = cursorLine + 1; i < lines.Length; i++) {
newlines[i + 2] = lines[i];
}
lines = newlines;
selectionStart = NewSelectionStart(cursorLine + 2);
}
private int CursorLine() {
int length = 0;
int lineNr = 0;
foreach (String s in lines) {
if (length <= SelectionStart && SelectionStart <= length + s.Length + 2) {
break;
length += s.Length + Environment.NewLine.Length;
lineNr++;
}
lineNr++;
}
return lineNr;
}
private int NewSelectionStart(int cursorLine) {
int length = 0;
for (int i = 0; i < cursorLine; i++) {
length += lines[i].Length + Environment.NewLine.Length;
}
return length + 3;
}
}
}
");
The InsertParagraphTags method is called by pressing the enter key in the textbox.
BTW, the break here is that there is a subscript out of range error if you try to hit enter at the end of the text. I'm sure I could figure out how to get around this but then my code won't look like his code; which is what I'm trying to learn.
Randy

Categories

Resources