Iterate through RichTextBox making specific words bold - c#

I wrote a Vacuum tube Cross reference program.
I can't figure out how to iterate through the RichTextBox1 to make the requested tube value Bold.
Existing output when searching for 6AU8A
Tube Cross1 Cross2 Cross3 Qty Location
6AU8 6AU8A 6BH8 6AW8 2 PM BOX 3
6AU8A 6AU8 6BH8 6AW8 6 BOX 9
6BA8A 6AU8 6AU8A 8AU8A 1 BOX 11
6CX8 6AU8A 6EB8 6GN8 2 BOX 16
6EH5 6AU8A 2081 6AW8A# 1 BOX 19
6GH8 6EA8 6GH8A 6AU8A 2 BOX 23
6GH8A 6GH8 6EA8 6AU8A 10 BOX 22
6GH8A 6GH8 6EA8 6AU8A 5 BOX 23
So, I need any occurrence for search term (6AU8A in this example) in Bold.
Using VS 2019, Windows Application, compiled for .NET 4.8.1, runs on Windows 7 & 10 PC's.
public int FindMyText(string text)
{
length = text.Length;
// Initialize the return value to false by default.
int returnValue = -1;
// Ensure that a search string has been specified and a valid start point.
if (text.Length > 0)
{
// Obtain the location of the first character found in the control
// that matches any of the characters in the char array.
int indexToText = richTextBox1.Find(text);
// Determine whether the text was found in richTextBox1.
if (indexToText >= 0)
{
// Return the location of the character.
returnValue = indexToText;
start = indexToText;
}
}
return returnValue;
}

Once you call Find(), the value will be SELECTED (if it exists). Then you can change the Font to include Bold. The Find() function has a "Start" position, which allows you to find the NEXT occurrence. So you start at 0, then add the length of the search string to get the next starting position. If the value returned is -1, then there were no more matches and you can stop.
Looks something like this:
private void button1_Click(object sender, EventArgs e)
{
BoldText("6AU8A"); // get your input from somewhere...
}
private void BoldText(String value)
{
int startAt = 0;
int indexToText = richTextBox1.Find(value, startAt, RichTextBoxFinds.None);
while (indexToText != -1)
{
richTextBox1.SelectionFont = new Font(richTextBox1.SelectionFont, FontStyle.Bold);
startAt = startAt + value.Length;
indexToText = richTextBox1.Find(value, startAt, RichTextBoxFinds.None);
}
}
Here it is in action:

If you actually need to use a RichTextBox for this, you could use a simple Regex to match one or more terms, then use Index and Length properties of each Match to perform the selection and change the Font and/or other styles (in the code here, optionally, the color)
Pass a collection of terms to match (containing one or more terms) to the method:
string[] parts = { "6AU8A", "6GH8" };
Highlight(richTextBox1, parts, FontStyle.Bold);
// also specifying a Color
Highlight(richTextBox1, parts, FontStyle.Bold, Color.Red);
// or deselect previous matches
Highlight(richTextBox1, parts, FontStyle.Regular);
The Regex only matches complete sequences, e.g., passing 6GH8, it won't partially highlight 6GH8A
If you prefer a partial selection, remove both the \b boundaries in the pattern
using System.Text.RegularExpressions;
private void Highlight(RichTextBox rtb, IEnumerable<string> terms, FontStyle style, Color color = default)
{
color = color == default ? rtb.ForeColor : color;
string pattern = $#"\b(?:{string.Join("|", terms)})\b";
var matches = Regex.Matches(rtb.Text, pattern, RegexOptions.IgnoreCase);
using (var font = new Font(rtb.Font, style)) {
foreach (Match m in matches) {
rtb.Select(m.Index, m.Length);
rtb.SelectionColor = color;
rtb.SelectionFont = font;
};
}
}

Related

How can I write on a new Line in a MultiLine Textbox?

I have to make a program, where you're able to type in a minimum value and a maximum value. Then all the numbers from the min. to max. that are even should be showed in a multiline textbox.
But when the even number gets written into the textbox, it always overwrites the number which was written into the textbox before.
I tried Enviroment.NewLine and also this \r\n thing, but I probably used it wrong.
private void cmdstart_Click(object sender, EventArgs e)
{
for (int i = Convert.ToInt32(textBox1.Text); i <= Convert.ToInt32(textBox2.Text); i++)
{
int a = i % 2;
if (a == 0)
{
textBox3.Text = Convert.ToString(i);
}
}
}
In the end, its supposed to output all even numbers from the min. to max. in a multiline textbox. Each number should be on a new line.
It happens because you overwrite it each time.
Try the following code:
textBox3.Text += i.ToString()+Environment.NewLine;
Make sure that you have set Multiline property to true on the textBox3.
You can set it through properties window after selecting textbox3 or you can write below line in the form constructor after initializeComponents is done.
textBox3.Multiline = true;
Once this is done, Environment.NewLine or \r\n both should work.
You can test it by yourself, if you write multiline text
Input:
1
2
3
4
5
6
7
to your textbox using designer and Text property, you can see that it generates something like this:
txt value:
"1\r\n2\r\n3\r\n4\r\n5\r\n6\r\n7"
Code:
string txt = multiLineTextBox.Text;
So if you add \r\n to your existing text, it will append text to new line:
for (int i = Convert.ToInt32(textBox1.Text); i <= Convert.ToInt32(textBox2.Text); i++)
{
int a = i % 2;
if (a == 0)
{
textBox3.Text = $"{textBox3.Text}{i}\r\n";
// or using string.Format for older versions
//textBox3.Text = string.Format("{0}{1}\r\n", textBox3.Text, i);
}
}

Unity Auto-Centering Text

I'm making a spelling game for kids in Unity. I've got it so when the right letter is clicked, the letter is highlighted. I couldn't figure out how to highlight a specific char on the fly in a Text field, so I just put a highlighted version of the text behind the regular, and have the regular char disappear when clicked instead. Now, I need this text centered in the beginning, but the problem is it auto centers whenever a char disappears, misaligning the text.
Any ideas on how to stop getting it to auto center?
newWord = new StringBuilder (startWord.text);
i = 0;
if (Letter.gameObject.GetComponent<MoveLetter> ().checkIfClicked (startWord.text[i]))
{
newWord [i] = ' ';
startWord.text = newWord.ToString();
Destroy (Letter);
letters.Remove (Letter);
i++;
}
My answer will try to help you fix your first problem : highlighting characters.
I would advise you to not duplicate text to show the highlighted characters. Instead, use rich text
You can change the color of individual characters by surrounding them with the color tag, as follow :
<color=#ffff00ff>H</color><color=#ffff00ff>E</color>LP
This string in the text attribute of your Text component will make the H and E letters yellow, while the other letters will stay black (or whatever color you have chosen for the Text component)
Here is a function you can use to highlight specific letters of a given string :
using System;
// ....
public void Foo()
{
// Give the indices of the letters of the string, starting at 0
GetComponent<UnityEngine.UI.Text>().text = Highlight("HELP", "#ffff00ff", 3, 1, 2);
// Will output :
// H<color=#ffff00ff>E</color><color=#ffff00ff>L</color><color=#ffff00ff>P</color>
}
private string Highlight(string text, string color, params int[] indices)
{
Array.Sort(indices);
for (int i = indices.Length - 1 ; i >= 0; i--)
{
if( indices[i] == text.Length - 1 )
text = String.Format( "{0}<color={1}>{2}</color>", text.Substring(0, indices[i]), color, text[indices[i]] ) ;
else if( indices[i] == 0 )
text = String.Format( "<color={0}>{1}</color>{2}", color, text[indices[i]], text.Substring(1));
else if( indices[i] < text.Length )
text = String.Format( "{0}<color={1}>{2}</color>{3}", text.Substring(0, indices[i]), color, text[indices[i]], text.Substring(indices[i] + 1));
}
return text;
}
I'm not sure exactly how Shadow works (I'm a newbie), but maybe you could try to put a Shadow component on the right letter when clicked and that way it may be easier to make it work.
EDIT:
Here, you could do it like this. Dunno how efficient it is, but I'm sure you can control it from code somehow. Add 4 components and cast a shadow in each direction
http://imgur.com/gD4zs9N
#Hellium answer is the right one: using rich text is probably your best solution.
However there are two other possibilities I can think of right now:
keep your 2 text elements (one being the background and the other being the overlay) and use a monospaced font: this way the background could be "HELP" while the overlay would be " LP" (simply make sure both are the same size/alignment)
use TextMesh Pro which is now part of Unity :)
Hope this helps,

How to display a 500 word output into a window/message box?

In a Windows form applications I want to get a wordy (500 word) output into a window/mini window (not just into a textbox control like in tools). For example:
I am trying to execute the output of pkg.pkgs*. This could generate a huge output in my windows form application. Please give me an idea on it. I don't want messagebox.show().
You can create a overload constructor to your window and pass the wordy into that.
public class Window1 : Form
{
public Window1(string wordy)
{
textbox.text = wordy;
}
}
You can call the window as
Form wi = new Window1(message);
wi.showdialog();
In case you can't create your own MessageBox with text area, I suggest trimming the wordy text. We can try two modes:
Trim on white space, e.g. My favorite wordy text is it into My favorite... in order to spare words
However, it's not always possible/reasonable. If we want, say, just 10 symbols in My favorite wordy text is it we'd rather not left My... but My favorit...
As a criterium for modes switching, let use a rule of thumb that the text trimmed should be at least of 2/3 of maximum possible length. Implementation:
public static string TrimWordyText(string source, int totalLength = 200) {
if (string.IsNullOrEmpty(source))
return source;
else if (source.Length <= totalLength)
return source;
// let's try trimming on white space (sparing mode)
int index = 0;
for (int i = 0; i <= totalLength; ++i) {
char ch = source[i];
if (char.IsWhiteSpace(ch))
index = i;
}
// can we save at least 2/3 of text by splitting on white space
if (index > totalLength * 2 / 3)
return source.Substring(0, index) + "...";
else
return source.Substring(0, totalLength) + "...";
}
....
// "My favorite..."
myTextBox.Text = TrimWordyText("My favorite wordy text is it", 15);
// "My favorit..."
myTextBox.Text = TrimWordyText("My favorite wordy text is it", 10);

Highlighting a line in a RichTextBox1, line number = a variable

I have a variable, lets say it = 5, and then I would like the line number 5 to be highlighted "blue" in my RichTextBox1. is that possible at all?
Or should I use something like a ListBox, DataGridView etc.
This will highlight the text in a given line in a RichTextBox if WordWrap is off:
void highLightALine(RichTextBox rtb, int line, Color hiLight)
{
int i1 = rtb.GetFirstCharIndexFromLine(line);
int i2 = rtb.GetFirstCharIndexFromLine(line + 1);
if (i2 < 0) i2 = rtb.Text.Length;
rtb.SelectionStart = i1;
rtb.SelectionLength = i2 - i1;
rtb.SelectionBackColor = hiLight;
}
Note that if WordWrap is true it will still highlight the line but only as far as it is visible. Its continuation on the next line will not be changed.
Also note that only Text can be highlighted. Trailing empty space can't be highlighted afaik. Here is an example of trying to owner-draw an RTB subclass..

How to parse a numbered sequence from a List of filenames?

I would like to automatically parse a range of numbered sequences from an already sorted List<FileData> of filenames by checking which part of the filename changes.
Here is an example (file extension has already been removed):
First filename: IMG_0000
Last filename: IMG_1000
Numbered Range I need: 0000 and 1000
Except I need to deal with every possible type of file naming convention such as:
0000 ... 9999
20080312_0000 ... 20080312_9999
IMG_0000 - Copy ... IMG_9999 - Copy
8er_green3_00001 .. 8er_green3_09999
etc.
I would like the entire 0-padded range e.g. 0001 not just 1
The sequence number is 0-padded e.g. 0001
The sequence number can be located anywhere e.g. IMG_0000 - Copy
The range can start and end with anything i.e. doesn't have to start with 1 and end with 9999
Numbers may appear multiple times in the filename of the sequence e.g. 20080312_0000
Whenever I get something working for 8 random test cases, the 9th test breaks everything and I end up re-starting from scratch.
I've currently been comparing only the first and last filenames (as opposed to iterating through all filenames):
void FindRange(List<FileData> files, out string startRange, out string endRange)
{
string firstFile = files.First().ShortName;
string lastFile = files.Last().ShortName;
...
}
Does anyone have any clever ideas? Perhaps something with Regex?
If you're guaranteed to know the files end with the number (eg. _\d+), and are sorted, just grab the first and last elements and that's your range. If the filenames are all the same, you can sort the list to get them in order numerically. Unless I'm missing something obvious here -- where's the problem?
Use a regex to parse out the numbers from the filenames:
^.+\w(\d+)[^\d]*$
From these parsed strings, find the maximum length, and left-pad any that are less than the maximum length with zeros.
Sort these padded strings alphabetically. Take the first and last from this sorted list to give you your min and max numbers.
Firstly, I will assume that the numbers are always zero-padded so that they are the same length. If not then bigger headaches lie ahead.
Secondly, assume that the file names are exactly the same apart from the increment number component.
If these assumptions are true then the algorithm should be to look at each character in the first and last filenames to determine which same-positioned characters do not match.
var start = String.Empty;
var end = String.Empty;
for (var index = 0; index < firstFile.Length; index++)
{
char c = firstFile[index];
if (filenames.Any(filename => filename[index] != c))
{
start += firstFile[index];
end += lastFile[index];
}
}
// convert to int if required
edit: Changed to check every filename until a difference is found. Not as efficient as it could be but very simple and straightforward.
Here is my solution. It works with all of the examples that you have provided and it assumes the input array to be sorted.
Note that it doesn't look exclusively for numbers; it looks for a consistent sequence of characters that might differ across all of the strings. So if you provide it with {"0000", "0001", "0002"} it will hand back "0" and "2" as the start and end strings, since that's the only part of the strings that differ. If you give it {"0000", "0010", "0100"}, it will give you back "00" and "10".
But if you give it {"0000", "0101"}, it will whine since the differing parts of the string are not contiguous. If you would like this behavior modified so it will return everything from the first differing character to the last, that's fine; I can make that change. But if you are feeding it a ton of filenames that will have sequential changes to the number region, this should not be a problem.
public static class RangeFinder
{
public static void FindRange(IEnumerable<string> strings,
out string startRange, out string endRange)
{
using (var e = strings.GetEnumerator()) {
if (!e.MoveNext())
throw new ArgumentException("strings", "No elements.");
if (e.Current == null)
throw new ArgumentException("strings",
"Null element encountered at index 0.");
var template = e.Current;
// If an element in here is true, it means that index differs.
var matchMatrix = new bool[template.Length];
int index = 1;
string last = null;
while (e.MoveNext()) {
if (e.Current == null)
throw new ArgumentException("strings",
"Null element encountered at index " + index + ".");
last = e.Current;
if (last.Length != template.Length)
throw new ArgumentException("strings",
"Element at index " + index + " has incorrect length.");
for (int i = 0; i < template.Length; i++)
if (last[i] != template[i])
matchMatrix[i] = true;
}
// Verify the matrix:
// * There must be at least one true value.
// * All true values must be consecutive.
int start = -1;
int end = -1;
for (int i = 0; i < matchMatrix.Length; i++) {
if (matchMatrix[i]) {
if (end != -1)
throw new ArgumentException("strings",
"Inconsistent match matrix; no usable pattern discovered.");
if (start == -1)
start = i;
} else {
if (start != -1 && end == -1)
end = i;
}
}
if (start == -1)
throw new ArgumentException("strings",
"Strings did not vary; no usable pattern discovered.");
if (end == -1)
end = matchMatrix.Length;
startRange = template.Substring(start, end - start);
endRange = last.Substring(start, end - start);
}
}
}

Categories

Resources