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,
Related
I'm trying to make a Reddit Formatter tool for whenever you have a text with just one line break to add another and make a new paragraph. Here in StackOverflow it's the same, you have to press the enter key twice to start a new paragraph. It'd go from:
Roses are red
Violets are Blue
to
Roses are red
Violets are Blue
The code below works: it detects enter characters by checking every character from the text you've input in the textbox, starting from the end, and replaces them with a double one after clicking a button
private void button1_Click(object sender, EventArgs e)
{
for (int i = textBox1.Text.Length - 1; i >= 0; i--)
{
if (textBox1.Text[i] == '\u000A')
{
textBox1.Text = textBox1.Text.Insert(i, "\r\n\r\n");
}
}
}
It's great, but I don't want to add more than one enter character if it's already a double. I don't want to go from
Roses are red
Violets are Blue
to
Roses are red
Violets are Blue
because it's already working as the first example. It just adds more lines infinitely if you keep pressing the button.
I've tried with this:
private void button1_Click(object sender, EventArgs e)
{
for (int i = textBox1.Text.Length - 1; i >= 0; i--)
{
if (textBox1.Text[i] == '\u000A' && textBox1.Text[i - 1] != '\u000A')//if finds a SINGLE new line
{
textBox1.Text = textBox1.Text.Insert(i, "\r\n\r\n");
}
}
}
But it doesn't work? It's basically the same but also checks if the previous one is an enter character too
What am I doing wrong? I'm really confused because it should work... The output is exactly the same as the first code
Thank you in advance
Let's break the question down into 2 parts
Part #1: What am I doing wrong
Your code checks for 2 consecutive \n characters
if (textBox1.Text[i] == '\u000A' && textBox1.Text[i - 1] != '\u000A')
But you always end up finding the \r character at [i-1] when you find \n at [i]. In short your check only works to detect a single \n but never more than 1 consecutive EOLNs
Part #2: Best way to do this
RegularExpressions are the best way to handle such things. It not only makes the parsing part easy to read/write (if you know regex), but also retains flexibility when the pattern changes (again if you know regex)
The below line should do what you need
textBox1.Text = Regex.Replace(textBox1.Text, "(?:\r\n)+", "\r\n\r\n");
Let me explain the regex to you
(?:xxx) This is just a regular bracket (ignore the xxx, as that is just a placeholder) to group together things without capturing them
+ The plus sign after the bracket tells the engine to capture one or more instances of the item preceding it which in this case is `(?:\r\n)`
So as you would have realized, we are looking for one or more instances of \r\n and replacing it with just one instance of \r\n
Good Afternoon. I am new to stack overflow as a poster but have referenced it for years. I have been researching this problem of mine for about 2 weeks and while I've seen solutions that are close I still am left with an issue.
I am writing a C# gui that reads in an assembly code file and highlights different text items for further processing via another program. My form has a RichTextBox that the text is displayed in. In the case below I am trying to select the text at the location of the ‘;’ until the end of the line and change the text to color red. Here is the code that I am using.
Please note: The files that are read in by the program are of inconsistent length, not all lines are formatted the same so I cannot simply search for the ';' and operate on that.
On another post a member has given an extension method for AppendText which I have gotten to work perfectly except for the original text is still present along with my reformatted text. Here is the link to that site:
How to use multi color in richtextbox
// Loop that it all runs in
Foreach (var line in inArray)
{
// getting the index of the ‘;’ assembly comments
int cmntIndex = line.LastIndexOf(';');
// getting the index of where I am in the rtb at this time.
int rtbIndex = rtb.GetFirstCharIndexOfCurrentLine();
// just making sure I have a valid index
if (cmntIndex != -1)
{
// using rtb.select to only select the desired
// text but for some reason I get it all
rtb.Select(cmntIndex + rtbIndex, rtb.SelectionLength);
rtb.SelectionColor = Color.Red;
}
}
Below is the sample assembly code from a file in it's original form all the text is black:
;;TAG SOMETHING, SOMEONE START
ASSEMBLY CODE ; Assembly comments
ASSEMBLY CODE ; Assembly comments
ASSEMBLY CODE ; Assembly comments
;;TAG SOMETHING, SOMEONE FINISH
When rtb.GetFirstCharIndexOfCurrentLine() is called it returns a valid index of the RTB and I imagine that if I add the value returned by line.LastIndexOf(';') I will then be able to just select the text above that looks like ; Assembly comments and turn it red.
What does happen is that the entire line turns red.
When I use the AppendText method above I get
ASSEMBLY CODE (this is black) ; Assembly comments (this is red) (the rest is black) ASSEMBLY CODE ; Assembly comments
The black code is the exact same code as the recolored text. In this case I need to know how to clear the line in the RTB and/or overwrite the text there. All the options that I have tried result in deletion of those lines.
Anywho, I'm sure that was lengthy but I'm really stumped here and would greatly appreciate advice.
I hope I've understood you correctly.
This loops over each line in the richtextbox, works out which lines are the assembly comments, then makes everything red after the ";"
With FOREACH loop as requested
To use a foreach loop you simply need to keep track of the index manually like so:
// Index
int index = 0;
// Loop over each line
foreach (string line in richTextBox1.Lines)
{
// Ignore the non-assembly lines
if (line.Substring(0, 2) != ";;")
{
// Start position
int start = (richTextBox1.GetFirstCharIndexFromLine(index) + line.LastIndexOf(";") + 1);
// Length
int length = line.Substring(line.LastIndexOf(";"), (line.Length - (line.LastIndexOf(";")))).Length;
// Make the selection
richTextBox1.SelectionStart = start;
richTextBox1.SelectionLength = length;
// Change the colour
richTextBox1.SelectionColor = Color.Red;
}
// Increase index
index++;
}
With FOR loop
// Loop over each line
for(int i = 0; i < richTextBox1.Lines.Count(); i++)
{
// Current line text
string currentLine = richTextBox1.Lines[i];
// Ignore the non-assembly lines
if (currentLine.Substring(0, 2) != ";;")
{
// Start position
int start = (richTextBox1.GetFirstCharIndexFromLine(i) + currentLine.LastIndexOf(";") + 1);
// Length
int length = currentLine.Substring(currentLine.LastIndexOf(";"), (currentLine.Length - (currentLine.LastIndexOf(";")))).Length;
// Make the selection
richTextBox1.SelectionStart = start;
richTextBox1.SelectionLength = length;
// Change the colour
richTextBox1.SelectionColor = Color.Red;
}
}
Edit:
Re-reading your question I'm confused as to whether you wanted to make the ; red as well.
If you do remove the +1 from this line:
int start = (richTextBox1.GetFirstCharIndexFromLine(i) + currentLine.LastIndexOf(";") + 1);
Private Sub RichTextBox1_Click(sender As Object, e As EventArgs) Handles RichTextBox1.Click
Dim MyInt1 As Integer
Dim MyInt2 As Integer
' Reset your RTB back color to white at each click
RichTextBox1.SelectionBackColor = Color.White
' Define the nth first character number of the line you clicked
MyInt1 = RichTextBox1.GetFirstCharIndexOfCurrentLine()
' use that nth to find the line number in the RTB
MyInt2 = RichTextBox1.GetLineFromCharIndex(MyInt1)
'Select the line using an array property of RTB (RichTextBox1.Lines())
RichTextBox1.Select(MyInt1, RichTextBox1.Lines(MyInt2).Length)
' This line would be for font color change : RichTextBox1.SelectionColor = Color.Maroon
' This one changes back color :
RichTextBox1.SelectionBackColor = Color.Yellow
End Sub
' There are a few bugs inherent to the rtb.select method
' It bugs if a line wraps, or fails on an "http" line... probably more.
(I just noticed the default stackoverflow.com character colors on my above code are not correct for comment lines and others.)
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
I'm trying to create an "snippet" from a paragraph. I have a long paragraph of text with a word hilighted in the middle. I want to get the line containing the word before that line and the line after that line.
I have the following piece of information:
The text (in a string)
The lines are deliminated by a NEWLINE character \n
I have the index into the string of the text I want to hilight
A couple other criteria:
If my word falls on first line of the paragraph, it should show the 1st 3 lines
If my word falls on the last line of the paragraph, it should show the last 3 lines
Should show the entire paragraph in the degenative cases (the paragraph only has 1 or 2 lines)
Here's an example:
This is the 1st line of CAT text in the paragraph
This is the 2nd line of BIRD text in the paragraph
This is the 3rd line of MOUSE text in the paragraph
This is the 4th line of DOG text in the paragraph
This is the 5th line of RABBIT text in the paragraph
Example, if my index points to BIRD, it should show lines 1, 2, & 3 as one complete string like this:
This is the 1st line of CAT text in the paragraph
This is the 2nd line of BIRD text in the paragraph
This is the 3rd line of MOUSE text in the paragraph
If my index points to DOG, it should show lines 3, 4, & 5 as one complete string like this:
This is the 3rd line of MOUSE text in the paragraph
This is the 4th line of DOG text in the paragraph
This is the 5th line of RABBIT text in the paragraph
etc.
Anybody want to help tackle this?
In my opinion this is an excellent opportunity to use the StringReader class:
Read your text line by line.
Keep your lines in some kind of buffer (e.g., a Queue<string>), dropping lines you don't need after a given number of lines have been read.
Once your "needle" is found, read one more line (if possible) and then just return what's in your buffer.
In my opinion, this has some advantages over the other approaches suggested:
Since it doesn't utilize String.Split, it doesn't do more work than you need -- i.e., reading the entire string looking for the characters to split on, and creating an array of the substrings.
In fact, it doesn't necessarily read the entire string at all, since once it finds the text it's looking for it only goes as far as necessary to get the desired number of padding lines.
It could even be refactored (very easily) to be able to deal with any textual input via a TextReader -- e.g., a StreamReader -- so it could even work with huge files, without having to load the entire contents of a given file into memory.
Imagine this scenario: you want to find an excerpt of text from a text file that contains the entire text from a novel. (Not that this is your scenario -- I'm just speaking hypothetically.) Using String.Split would require that the entire text of the novel be split according to the delimiter you specified, whereas using a StringReader (well, in this case, a StreamReader) would only require reading until the desired text was found, at which point the excerpt would be returned.
Again, I realize this isn't necessarily your scenario -- just suggesting that this approach provides scalability as one of its strengths.
Here's a quick implementation:
// rearranged code to avoid horizontal scrolling
public static string FindSurroundingLines
(string haystack, string needle, int paddingLines) {
if (string.IsNullOrEmpty(haystack))
throw new ArgumentException("haystack");
else if (string.IsNullOrEmpty(needle))
throw new ArgumentException("needle");
else if (paddingLines < 0)
throw new ArgumentOutOfRangeException("paddingLines");
// buffer needs to accomodate paddingLines on each side
// plus line containing the needle itself, so:
// (paddingLines * 2) + 1
int bufferSize = (paddingLines * 2) + 1;
var buffer = new Queue<string>(/*capacity*/ bufferSize);
using (var reader = new StringReader(haystack)) {
bool needleFound = false;
while (!needleFound && reader.Peek() != -1) {
string line = reader.ReadLine();
if (buffer.Count == bufferSize)
buffer.Dequeue();
buffer.Enqueue(line);
needleFound = line.Contains(needle);
}
// at this point either the needle has been found,
// or we've reached the end of the text (haystack);
// all that's left to do is make sure the string returned
// includes the specified number of padding lines
// on either side
int endingLinesRead = 0;
while (
(reader.Peek() != -1 && endingLinesRead++ < paddingLines) ||
(buffer.Count < bufferSize)
) {
if (buffer.Count == bufferSize)
buffer.Dequeue();
buffer.Enqueue(reader.ReadLine());
}
var resultBuilder = new StringBuilder();
while (buffer.Count > 0)
resultBuilder.AppendLine(buffer.Dequeue());
return resultBuilder.ToString();
}
}
Some example input/output (with text containing your example input):
Code:
Console.WriteLine(FindSurroundingLines(text, "MOUSE", 1);
Output:
This is the 2nd line of BIRD text in the paragraph
This is the 3rd line of MOUSE text in the paragraph
This is the 4th line of DOG text in the paragraph
Code:
Console.WriteLine(FindSurroundingLines(text, "BIRD", 1);
Output:
This is the 1st line of CAT text in the paragraph
This is the 2nd line of BIRD text in the paragraph
This is the 3rd line of MOUSE text in the paragraph
Code:
Console.WriteLine(FindSurroundingLines(text, "DOG", 0);
Output:
This is the 4th line of DOG text in the paragraph
Code:
Console.WriteLine(FindSurroundingLines(text, "This", 2);
Output:
This is the 1st line of CAT text in the paragraph
This is the 2nd line of BIRD text in the paragraph
This is the 3rd line of MOUSE text in the paragraph
This is the 4th line of DOG text in the paragraph
This is the 5th line of RABBIT text in the paragraph
Using the LINQ extension methods to get the right strings:
string[] lines = text.Split('\n');
// Find the right line to work with
int position = 0;
for (int i = 0; i < lines.Count(); i++)
if (lines[i].Contains(args[0]))
position = i - 1;
// Get in range if we had a match in the first line
if (position == -1)
position = 0;
// Adjust the line index so we have 3 lines to work with
if (position > lines.Count() - 3)
position = lines.Count() - 3;
string result = String.Join("\n", lines.Skip(position).Take(3).ToArray());
This can of course be optimized a bit by quitting the for loop as soon as the index has been found, and probably a number of other things. You can probably even LINQify so you never need to actually store that extra array, but I can't think of a good way to do that right now.
An alternative for the checks on position could be something like position = Math.Max(0,Math.Min(position, lines.Count() - 3)); - which would handle both of them at once.
There are a few ways one can handle this:
First Method:
Use String.IndexOf() and String.LastIndexOf().
You can find where the current selected word is by using TextBox.SelectionStart(). Then simply look for LastIndexOf from the selection location looking for the '\n' to find the previous line (don't grab the first lastindexof from the selection, once you find one...do it again from that location so you get the beginning of that line). Then do the same from the selection point only using IndexOf to find the '\n' to get the end of the line. Once again, don't use the first one you find, repeat it starting from the first found location to get the second line's end. Then simply substring the text with the area you found.
Second Method: Use String.Split() by the '\n' character (creates an array of strings, each one containing a different line from the text in order of array index). Find the index of the line the text is in, and then simply grab from the String[index] for the line before, including, and after. Hopefully this two methods are clear enough for you to figure out your coding. If you are still stuck, let me know.
Alright. Lemme have a crack,
I think the first thing I would do is split everything into arrays. Simply because then we have a simple way to "count" the lines.
string[] lines = fullstring.Split('\n');
Once we have that, Unfortunately I don't know of any indexof that goes through each point in an array. There probably is one, but without trawling through the internet, I would simply go
int i = -1;
string animal = 'bird';
foreach(string line in lines)
{
i++;
if(line.indexof(animal) > -1) break;
}
// we will need a if(i == -1) then we didn't find the animal etc
Ok so then, We now have the line. All we need to do, is...
if(i == 0)
{
writeln(lines[0);
writeln(lines[1]);
etc
}
else
if(i == lines.count - 1)
{
//this means last array index
}
else
{
//else we are in the middle. So just write out the i -1, i, i+1
}
I know that is messy as hell. But that's how I would solve the issue.
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