C# losing Font Style in RichTextBox after deleting lines - c#

I have a RichTextBox for a simple chat where I add lines programmatically.
I make the usernames bold and the messages in regular style.
After some lines I want to delete the first lines to keep the chat in a acceptably length. But when I do so I lose the text format and everything appears bold. What am I doing wrong and how can I fix this?
EDIT
I could solve the problem where I wasn't able to delete the first line.
I had to set the the ReadOnly property to false. Even though I was able to add new lines it prevented deleting lines. So the following code works to delete lines. Thanks to #TaW!
if (ChatText.Lines.Length >= 10)
{
int p = 0; int count = 0;
do
{
p = ChatText.Text.IndexOf("\n\r");
if (p >= 0)
{
ChatText.SelectionStart = p;
ChatText.SelectionLength = 2; // length of "\n\r"
ChatText.SelectedText = "\n";
count++;
}
}
while(p >= 0);
int nll = 1; // <<=== pick the length of your new line character(s)!!
int pS = ChatText.Lines.Take(0).Select(x => x.Length + nll).Sum() - nll;
int pL = ChatText.Lines.Take(1).Select(x => x.Length + nll).Sum() - nll;
if (pS < 0) { pS = 0; pL++; }
ChatText.SelectionStart = pS;
ChatText.SelectionLength = pL - pS;
ChatText.Cut();
}
//////////////////////////////////
// now add new lines
//////////////////////////////////
string[] chatstr;
// string text is given as method parameter
chatstr = text.Split(new string[] { ": " }, 2, StringSplitOptions.None);
// go to the end of the text
ChatText.SelectionStart = ChatText.Text.Length;
ChatText.SelectionLength = 0;
// make text bold
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Bold);
// add username (chatstr[0]) and colon
ChatText.AppendText(chatstr[0] + ": ");
// make text regular
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Regular);
// add message (chatstr[1])
ChatText.AppendText(chatstr[1] + "\n");
// and finaly scroll down
ChatText.ScrollToCaret();
So deleting lines works and new lines are added as intended. Finaly!
solved :)

Never change the Text of a RichtTextBox if it contains any formatting.
Changing the Lines property (by Skip) is just another way to change the Text.
Instead only use the functions the RTB provides: Always start by selecting the portion you want to format, then apply one or more of the functions and/or set one or more of the properties..:
To delete portions use Cut.
Here is a function that will delete a number of entire lines:
void DeleteLines(RichTextBox rtb, int fromLine, int count)
{
int p1 = rtb.GetFirstCharIndexFromLine(fromLine);
int p2 = rtb.GetFirstCharIndexFromLine(fromLine + count);
rtb.SelectionStart = p1;
rtb.SelectionLength = p2 - p1;
bool readOnly = rtb.ReadOnly; // allow change even when the RTB is readonly
rtb.ReadOnly = false; ;
rtb.Cut();
rtb.ReadOnly = readOnly;
}
Trying to keept the formatting alive yourself is a tedious and error-prone waste of your time.
In addition to font properties you would also have to resore all other things you can set with the SelectedXXX properties, like colors, alignment, spacing etc etc..
To delete the first 3 lines use:
DeleteLines(yourRTB, 0, 3);
To restrict the text to 10 lines use:
DeleteLines(yourRTB, 0, yourRTB.Lines.Length - 10);
Note that the function above should have a few checks for valid input; I left them out as the checks somehow need a decision what to do, if count or fromLine if greater than Lines.Length or if fromLine is negative..
While we are at it, here is how to append a bold line:
yourRTB.SelectionStart = yourRTB.Text.Length;
yourRTB.SelectionLength = 0;
using (Font font = new Font(yourRTB.SelectionFont, FontStyle.Bold))
yourRTB.SelectionFont = font;
yourRTB.AppendText(yourNewLine + textOfNewLine);
Of course it really shold go into a reuseable function that the the bolding as a parameter..
Update:
since you are using WordWrap you may prefer this function. It deletes the actual lines, not the visible ones:
void DeleteLinesWW(RichTextBox rtb, int fromLine, int count)
{
int nll = 1; // <<=== pick the length of your new line character(s)!!
int pS = rtb.Lines.Take(fromLine).Select(x => x.Length + nll).Sum() - nll;
int pL = rtb.Lines.Take(fromLine + count).Select(x => x.Length + nll).Sum() - nll;
if (pS < 0) { pS = 0; pL++; }
rtb.SelectionStart = pS;
rtb.SelectionLength = pL - pS ;
bool readOnly = rtb.ReadOnly;
rtb.ReadOnly = false; // allow change even when the RTB is readonly
rtb.Cut();
rtb.ReadOnly = readOnly;
}
A word on NewLine: Do note that I have not used the Environment.NewLine constant as it not really a good idea. If you add multiline text to the RichTextBox in the designer and then look at it you will see that it uses simple '\n' new lines, no returns, no NL-CR, just '\n'. So this seems to be the generic way in a winforms RTB and I recommend using it..
The new function relies on all lines having a newline of the same length!
To make sure you can use this replacement function:
int RTBReplace(RichTextBox rtb, string oldText, string newText)
{
int p = 0; int count = 0;
do
{
p = richTextBox1.Text.IndexOf(oldText);
if (p >= 0)
{
richTextBox1.SelectionStart = p;
richTextBox1.SelectionLength = oldText.Length;
richTextBox1.SelectedText = newText;
count ++;
}
}
while (p >= 0);
return count;
}
Calling it like this:
RTBReplace(yourrichTextBox, "\r\n", "\n");
Update 2:
Here is an example how to add your chat lines:
private void button1_Click(object sender, EventArgs e)
{
string cLine = "Taw: Hello World"; // use your own lines!
var chatstr = cLine.Split(new string[] { ": " }, 2, StringSplitOptions.None);
AppendLineBold(yourrichTextBox, "\n" + chatstr[0], true);
AppendLineBold(yourrichTextBox, chatstr[1], false);
yourrichTextBox.ScrollToCaret();
}
void AppendLineBold(RichTextBox rtb, string text, bool bold)
{
rtb.SelectionStart = richTextBox1.Text.Length;
rtb.SelectionLength = 0;
using (Font font = new Font(rtb.SelectionFont,
bold ? FontStyle.Bold : FontStyle.Regular))
rtb.SelectionFont = font;
rtb.AppendText(text);
}
Update 3:
Looks like the ReadOnly property disallows the use of Cut. So we need to temporatily allow changes.
Funny: SelectedText can't be set either, but AppendText works fine..

To keep text formatting, you can also try the following (it's a little shorter and should also do the trick)
string text = "Username: hello this is a chat message";
// delete the first line when after 10 lines
if (ChatText.Lines.Length >= 10)
{
ChatText.SelectionStart = 0; // set SelectionStart to the beginning of chat text (RichTextBox)
ChatText.SelectionLength = ChatText.Text.IndexOf("\n", 0) + 1; // select the first line
ChatText.SelectedText = ""; // replace by an empty string
ChatText.SelectionStart = ChatText.Text.Length; // set SelectionStart to text end to make SelectionFont work for appended text
}
// split the string in chatstr[0] = username, chatstr[1] = message
string[] chatstr = text.Split(new string[] { ": " }, 2, StringSplitOptions.None);
// make the username bold
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Bold);
ChatText.AppendText(chatstr[0] + ": ");
// make the message regular
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Regular);
ChatText.AppendText(chatstr[1] + Environment.NewLine);
ChatText.ScrollToCaret();

Related

Remove characters before character “|”

I have a software which needs to remove all of the characters before "|".
For example input
" text needs to removed | Text needs to stay "
An example output will be
"Text needs to stay"
I have the code down below. It works for single-line text but doesn't work on multiple lines. (only removes the text on the first line rest of them stays the same)
I need to make it work with multiple lines. Any ideas?
string input = richTextBox.Text;
string output = input.Substring(input.IndexOf('|') + 1);
richTextBox1.Text = output;
You could do it easily using the Lines property and a temporary List<string> to store the result of substring
List<string> newLines = new List<string>();
foreach (string s in richTextBox1.Lines)
{
// If you want only the lines with the | remove the else block
int x = s.IndexOf('|');
if(x > -1)
newLines.Add(s.Substring(x + 1).Trim());
else
newLines.Add(s);
}
richTextBox1.Lines = newLines.ToArray();
string output = "";
var myArray = input.Split("\r\n");
foreach(var ar in myArray)
if(ar.Length > 0)
output+= ar.Substring(0, ar.IndexOf('|')) + "\r\n";
Oups! i returned the first part, but i suppose you got the point
What about using LINQ for this.
E.g.:
List<string> lines = yourString.Split("\n"); //Add \r if needed
List<string> smallerLines = lines.Select(x => x.Skip(x.IndexOf('|')+1));
If needed you can always create one new string of the output:
string finalString = String.Join(String.Empty, smallerLines);
string input = richTextBox1.Text;
int len = richTextBox1.Lines.Length;
string output = "";
for (int i = 0; i <len; i++)
{
if(i!=len-1)
{
output += richTextBox1.Lines[i].Substring(input.IndexOf('|') + 1) +
Environment.NewLine;
}
else
{
output += richTextBox1.Lines[i].Substring(input.IndexOf('|') + 1);
}
}
richTextBox1.Text = output;

Problems with reading text and display in textboxs in c#

My intention is using File.ReadAllText to read a text file line by line. After that, I will check each string array if it contains the keyword that I expected, I will take the whole string out and display it into a textbox. So here is my code :
OpenFileDialog fopen = new OpenFileDialog();
fopen.Filter = "(All type)|*.*";
fopen.ShowDialog();
if(fopen.FileName != "")
{
textBox1.Text = fopen.FileName;
string save = fopen.FileName;
string save1 = save.Split('.')[0];
//string readtext = File.ReadAllText(save);
//string[] readtext1 = readtext.Split('\n');
string[] readline = File.ReadAllLines(save);
int lines = readline.Count();
textBox2.Text = readtext;
for (int i = 0; i < lines; i++ )
{
if (readline[i].Contains("CPL"))
{
int len = readline[i].Length;
textBox3.Text = readline[i].Substring(2, len - 4);
textBox3.AppendText(Environment.NewLine);
}
}
The problem is : if the input file look like
<>something<>
<>something1<>
<>something2<>
<>something3CPL<>
<>something4CPL<>
<>something5CPL<>
The output is always just the last string array. (here is something5CPL).
What I expected is
something3CPL
something4CPL
something5CPL
Can anybody tell me what is wrong with my code?
Thank you.
You're assigning (overwriting) the text in the textbox each iteration, so it'll only hold the last value you get from the file:
textBox3.Text = readline[i].Substring(2, len - 4);
Instead, use the same technique (appending) as you did with the Environment.Newline:
textBox3.AppendText(readline[i].Substring(2, len - 4));
This will keep adding the new values onto the end of the textbox's existing text, as you want.
You need to append the text each instead of setting the text in each iteration:
textBox3.AppendText(readline[i].Substring(2, len - 4));
May be in place of
textBox3.Text = readline[i].Substring(2, len - 4);
use
textBox3.Text += readline[i].Substring(2, len - 4);
As a textbox you can't see the results vertically.
Anyway this is the solution:
OpenFileDialog fopen = new OpenFileDialog();
fopen.Filter = "(All type)|*.*";
fopen.ShowDialog();
if(fopen.FileName != "")
{
textBox1.Text = fopen.FileName;
string save = fopen.FileName;
string save1 = save.Split('.')[0];
//string readtext = File.ReadAllText(save);
//string[] readtext1 = readtext.Split('\n');
string[] readline = File.ReadAllLines(save);
int lines = readline.Count();
textBox2.Text = readtext;
for (int i = 0; i < lines; i++ )
{
if (readline[i].Contains("CPL"))
{
int len = readline[i].Length;
textBox3.Text += (readline[i].Substring(2, len - 4) + " ");
}
}

Change SelectedRTF

I have a richTextBox and a Regex with some words. Once, I find all the words I want to change their color to blue. I can use SelectionColor = Blue, but when it comes to coloring thousands of words it becomes quite slow.
After some search, I read that changing the RTF of the richTextBox is a faster way to change the text (e.g. it's size and/or color).
Here is my unfinished code:
MatchCollection matches = myRegex.Matches(richTextBox.text);
foreach (Match match in matches)
{
richTextBox.Select(match.Index, match.Length);
string addColor = #"{\colortbl ;\red0\green0\blue255;}" + Environment.NewLine;
richTextBox.SelectionColor = Color.Blue; //Must be replaced
}
I also found out that in every case (in my case, the entire text uses the same font and has the same size, only the color of some words changes) the SelectedRtf is:
{\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset0 Consolas;}}
\uc1\pard\lang1033\f0\fs18 word} // richTextBox.SelectedRtf
Moreover, using the Selection.Color = Blue changes the SelectedRtf to:
{\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset0 Consolas;}}
{\colortbl ;\red0\green0\blue255;} // The addColor string!
\uc1\pard\lang1033\f0\fs18 word}
To get the above string, I use this: richTextBox.SelectedRtf.Insert(59, addColor), so what I need to do is to replace SelectedRtf with that. However, after some attempts, nothing seems to happen. The color of the words remains the same. Any ideas?
Yes, it is possible and about twice as fast than the 'regular' way..:
Changing 30k words in a 3M text takes 28 seconds over 60 seconds before..
Here are the steps I would recommend, assuming your words are identifyable in the richTextBox.Rtf (*):
You could create your own color table, but it seems safer to let the system do it for you: I cheat by coloring the 1st letter before and resetting it after coloring the matches..
I pre- and postfix the search word by the rtf code for a foreground color index into the table. My code assumes that there is only one extra color in addition to the default one.
If you have more you should keep track and/ or analyze the colortable..
Here is a RTF reference, btw..
I do the replacement with the RegEx in my RichTextBox RTB like this:
string search "find me!";
RTB.SelectionStart = 0;
RTB.SelectionLength = 1;
RTB.SelectionColor = Color.HotPink;
Regex RX = new Regex(search);
MatchCollection matches = RX.Matches(RTB.Rtf);
RTB.Rtf = RX.Replace(RTB.Rtf, "\\cf1 " + search + "\\cf0 ");
RTB.SelectionStart = 0;
RTB.SelectionLength = 1;
RTB.SelectionColor = RTB.ForeColor;
(*) Note that modifying the Rtf property like this assumes that your search texts are identifiable in the Rtf. You can and should check this by comparing the matches count when searching the Rtf and the Text ! when they don't agree you probably need to use the 'regular' way..
Note that this only deals with Colors. For Font sizes etc you will have to add \fn (which index into the stylesheet) commands in a similar way..
Update: I have wrapped the code above in an expanded function, also taking care of more colors, word boundaries and some checks..:
int colorWords(RichTextBox RTB, String searchWord, Color color)
{
string wordChar = #"\w*"; // or #"\b*" for stricter search
Regex RX = new Regex(wordChar + searchWord + wordChar);
RTB.SelectionStart = 0;
RTB.SelectionLength = 0;
RTB.SelectedText = "~"; // insert a dummy character
RTB.SelectionStart = 0;
RTB.SelectionLength = 1;
RTB.SelectionColor = color; // and color it
MatchCollection matches = null;
matches = RX.Matches(RTB.Text);
int textCount = matches.Count;
matches = RX.Matches(RTB.Rtf);
// we should not find more in the rtf code, less is ok
if (textCount < matches.Count) return -1;
if (matches.Count <= 0) return 0;
List<Color> colors = getRtfColorTable(RTB);
int cIndex = 1;
Color cRGB = Color.FromArgb(255, color);
if (colors.Contains(cRGB) )
cIndex = colors.FindIndex(x => x == cRGB) + 1;
RTB.Rtf = RX.Replace(RTB.Rtf, "\\cf" + cIndex + " " + searchWord + "\\cf0 ");
RTB.SelectionStart = 0;
RTB.SelectionLength = 1;
RTB.Cut(); // remove the dummy
return matches.Count;
}
Here is a function that pulls out the current colors from the Rtf color table. (Hopefully, the full spec is not exactly very small and tackling it with two simple IndexOf is a little optimistic.. ;-)
List<Color> getRtfColorTable(RichTextBox RTB)
{ // \red255\green0\blue0;
List<Color> colors = new List<Color>();
string tabString = #"\colortbl ;";
int ct0 = RTB.Rtf.IndexOf(tabString);
if (ct0 >= 0)
{
ct0 += tabString.Length;
int ct1 = RTB.Rtf.IndexOf(#"}", ct0);
var table = RTB.Rtf.Substring(ct0, ct1 - ct0).Split(';');
foreach(string t in table)
{
var ch = t.Split('\\');
if (ch.Length == 4)
{
int r = Convert.ToInt16(ch[1].Replace("red", ""));
int g = Convert.ToInt16(ch[2].Replace("green", ""));
int b = Convert.ToInt16(ch[3].Replace("blue", ""));
colors.Add(Color.FromArgb(255, r, g, b));
}
}
}
return colors;
}
The example was called like this:
colorWords(RTB, "<DIR>", Color.SaddleBrown);
colorWords(RTB, "Verzeichnis", Color.BlueViolet);
colorWords(RTB, "2012", Color.OrangeRed);

Delete the last line of rich Text Box?

I like to delete last line of richtextbox which has ended with ; semicolumn. I like to delete this line until ; semicolumn that comes before the last semicolumn.
Example:
hello do not delete this line;
hello this sentence will continue...
untill here;
result should be:
hello do not delete this line;
My Code:
private void button1_Click_1(object sender, EventArgs e) {
List<string> myList = richTextBox1.Lines.ToList();
if (myList.Count > 0) {
myList.RemoveAt(myList.Count - 1);
richTextBox1.Lines = myList.ToArray();
richTextBox1.Refresh();
}
}
Found the solution here:
RichTextBox1.Lines = RichTextBox1.Lines.Take(RichTextBox1.Lines.Length - 3).ToArray();
Use this:
var last = richTextBox1.Text.LastIndexOf(";");
if (last > 0)
{
richTextBox1.Text = richTextBox1.Text.Substring(0, last - 1);
var beforelast = richTextBox1.Text.LastIndexOf(";");
richTextBox1.Text = richTextBox1.Text.Substring(0, beforelast + 1);
}
else
{
richTextBox1.Text = "";
}
You did not specify the other scenarios(i.e, when the string does not contain ";")
this code removes the string starting at ";" just before the last ";" to the last ";".
It removes the last semicolon and texts after that, then finds the new last ";". finally removes the text after this ";"
For those that find this question after all these years...
The solutions that use the .Text property or .Lines property end up removing the formatting from the existing text. Instead use something like this to preserve formatting:
var i = textBox.Text.LastIndexOf("\n");
textBox.SelectionStart = i;
textBox.SelectionLength = o.TextLength - i + 1;
textBox.SelectedText = "";
Note that if your textbox is in ReadOnly mode, you can't modify SelectedText. In that case you need to set and reset ReadOnly like this:
textBox.ReadOnly = false;
textBox.SelectedText = "";
textBox.ReadOnly = true;
I'm not sure exactly how the rich text box works, but something like
input = {rich text box text}
int index = text.lastIndexOf(";");
if (index > 0)
{
input = input.Substring(0, index);
}
// put input back in text box
How about that ?
string input = "your complete string; Containing two sentences";
List<string> sentences = s.Split(';').ToList();
//Delete the last sentence
sentences.Remove(sentences[sentences.Count - 1]);
string result = string.Join(" ", sentences.ToArray());
int totalcharacters = yourrtb.Text.Trim().Length;
int totalLines = yourrtb.Lines.Length;
string lastLine = yourrtb.Lines[totalLines - 1];
int lastlinecharacters = lastLine.Trim().Length;
yourrtb.Text = yourrtb.Text.Substring(0, totalcharacters - lastlinecharacters);

Copy+Paste DataGridViewSelectedCellCollection to/from Clipboard

Assume that I have a DataGridView which is populated by a number of different strings (different length, numbers and plain text) in Cells.
Want I want to do is to copy and paste these strings, which could be any selection of Cells.
My approach to copy is:
if (e.Control && e.KeyCode == Keys.C)
{
// copy
DataGridViewSelectedCellCollection tmpCells = this.MyDataGridView.SelectedCells;
Clipboard.SetDataObject(tmpCells);
}
Which is working properly.
My approach to paste is:
if (e.Control && e.KeyCode == Keys.V)
{
// paste
IDataObject dataInClipboard = Clipboard.GetDataObject();
string stringInClipboard = (string)dataInClipboard.GetData(DataFormats.Text);
char[] rowSplitter = { '\r', '\n' };
char[] columnSplitter = { '\t' };
string[] rowsInClipboard = stringInClipboard.Split(rowSplitter, StringSplitOptions.RemoveEmptyEntries);
int r1 = this.MyDataGridView.SelectedCells[0].RowIndex;
int c1 = this.MyDataGridView.SelectedCells[0].ColumnIndex;
int r2 = this.MyDataGridView.SelectedCells[this.MyDataGridView.SelectedCells.Count-1].RowIndex;
int c2 = this.MyDataGridView.SelectedCells[this.MyDataGridView.SelectedCells.Count-1].ColumnIndex;
int r = Math.Min(r1, r2); // Do not care if selection was taken by drag mouse up or down, always start from min
int c = Math.Min(c1, c2); // Do not care if selection was taken by drag mouse left or right, always start from min
for (int iRow = 0; iRow < rowsInClipboard.Length; ++iRow )
{
string[] valuesInRow = rowsInClipboard[iRow].Split(columnSplitter);
for (int iCol = 0; iCol < valuesInRow.Length; ++iCol )
{
if (this.MyDataGridView.ColumnCount-1 >= c + iCol)
{
DataGridViewCell DGVC = (this.MyDataGridView.Rows[r + iRow].Cells[c + iCol]);
DGVC.Value = valuesInRow[iCol];
}
}
}
}
}
Which works fine UNLESS the string itself DOES NOT contain any delimiter I specified with rowSplitter and columnSplitter. But this unfortunately is the case very often. It then separates the string and expands it to the next cell.
Example:
Cell[n] = {"This string contains a new line delimiter \n but should use only one cell."}
Will be pasted to:
Cell[n] = {"This string contains a new line delimiter"};
Cell[n+1] = {"but should use only one cell."}
So my question is: is it possible to restore the DataGridViewSelectedCellCollection as it was copied to the clipboard before? Just casting from object to DataGridViewSelectedCellCollection will not work:
DataGridViewSelectedCellCollection DGSCC = (DataGridViewSelectedCellCollection)dataInClipboard; // compiles, but throws exception at runtime
Do I have any other option but parsing each string by a defined formatting?
You will have to define own format for clipboard, which will do what default one can't do for you.
Simplest solution in this specific case is to convert multi-line breaks into \n and then convert back when you paste, but in any case it means no more
DataGridViewSelectedCellCollection tmpCells = this.MyDataGridView.SelectedCells;
Clipboard.SetDataObject(tmpCells);
but
DataGridViewSelectedCellCollection tmpCells = this.MyDataGridView.SelectedCells;
string result = "";
foreach(DataGridViewCell cell in tempCells)
// ... try to replicate what default clipboard text representation does
// change line breaks
Clipboard.SetDataObject(result.Replace("\xd\xa", "\\n"));
and paste will be:
IDataObject dataInClipboard = Clipboard.GetDataObject();
string stringInClipboard = dataInClipboard.GetData(DataFormats.Text).ToString().Replace("\\n", "\xd\xa");

Categories

Resources