2

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 :)

Community
  • 1
  • 1
St. Helen
  • 99
  • 1
  • 9
  • The correct way is not to `Skip` lines or to reformat the surrounding lines but __first to select__ the lines you want to delete and __then use `Cut`__ to delete them. – TaW Apr 30 '16 at 06:23
  • Sorry but I dont understand. How do I do that? I can't find a `Cut` method for `string[]`. – St. Helen Apr 30 '16 at 06:45
  • See my answer. Cut is one of the many methods of the RichTextBox needed to manipulate its content and its formatting.. – TaW Apr 30 '16 at 06:49
  • The `IndexOutOfRangeException` is because there is no text after the `": "`. I inserted a check for this in the new answer. – ib11 Apr 30 '16 at 07:37
  • No text after the ":" should never happen since it is not possible to send empty messages. I guess I'm messing around with the WordWrap property. – St. Helen Apr 30 '16 at 07:51
  • Delete text by first setting the SelectionStart and SelectionLength properties, then setting SelectionText = "". – Hans Passant Apr 30 '16 at 14:29
  • _ChatText.SelectionLength = 3; // length of "\n\r"_ Um, it really ought to be 2, right? (And keeping the Replacement in a reuaseable function is preferrabel, too, imo; also: If you control the linefeeds you can simply set the `nll` variable to 2..) - Also do look at my final example for a nice method to add your chat lines! Your code still doesn't follow the rules: First Select then format! – TaW Apr 30 '16 at 16:58
  • Yes 2 is right. I also added the select. Everything works fine now. I'd recommend to use seperate functions too but I wanted to keep it simple until it works. I'm not sure if I understood why you suggest giving `nll` 2 but I'll figure it out. Thank you very much! Gonna check how I can mark the question "answered". – St. Helen Apr 30 '16 at 17:19
  • nll should be the length of your linefeeds. If they are "\r\n" it is 2 if it is "\n" or "\r" it must be 1. Both work but they must not be mixed.. – TaW Apr 30 '16 at 17:23
  • Ah ok. I understand. I'm fine with replacing all possible `"\n\r"` with `"\n"` so I never have to take care of that again. – St. Helen Apr 30 '16 at 17:26

2 Answers2

4

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..

TaW
  • 53,122
  • 8
  • 69
  • 111
  • Thank you but somehow it still don't works. Now the first line (the one that should be deleted) gets bold (the username and the message) to the point where WordWrap inserts a line break. And new lines are completely regular. Also the first line is not deleted :( – St. Helen Apr 30 '16 at 07:16
  • @St.Helen -- Make sure you call the method properly. And then the new lines must be formatted as you normally do. – ib11 Apr 30 '16 at 07:23
  • I just used it inside the `if` part instead of calling a function. I used 0 for `fromLine` and 1 for `fromLine + count`. Should work as well? I didn't changed anything below the `if` so I wonder why new lines are completely regular. – St. Helen Apr 30 '16 at 07:32
  • A little hard to tell just how your code looks now. I don't see __any__ code in your post that does __any selecting__. This may be the problem! - Without it you just can't format the RTB properly!! Appended text will get the formatting of the text right before it, __if and only if__ that text was formatted correctly! If otoh the formatting happend to stop before the line break it will __not__ carry over.. Also see my update with a few examples. – TaW Apr 30 '16 at 07:59
  • @TaW I only want to delete the first line everytime the lines are above my limit (10 in this case). So 0 and 1 should be fine. It still behaves like described above. No line is deleted. Edit: should I edit my OP again to show the entire code? – St. Helen Apr 30 '16 at 08:10
  • Hm, `if (ChatText.Lines.Length >= 10) DeleteLines(ChatText, 0, ChatText.Lines.Length - 10);` should do the job. No? – TaW Apr 30 '16 at 08:14
  • Isn't it the same as `DeleteLines(RTB, 0, 1);` ? Both doesn't work. – St. Helen Apr 30 '16 at 08:30
  • Well, it is close. And both should work. They do here. Did you try to debug it to see what the values for p1 and p2 are? – TaW Apr 30 '16 at 08:36
  • p1 = 0 and p2 = 26 every time the `if` is `true` – St. Helen Apr 30 '16 at 08:44
  • Well, that sounds ok. And nothing gets deleted?? Please try to isolate the DeleteLines call in a button to test it. If it works from there something else must be going on in the code afterwards.. – TaW Apr 30 '16 at 08:52
  • Huh? Do you mean `private void button1_Click(object sender, EventArgs e) { DeleteLines(YourRichTextBox1, 0, 1); }` doesn't delete the first line??? Sure? Can you see all lines? Are there any other RTBs around? – TaW Apr 30 '16 at 09:15
  • Exactly the same behaviour as before. No line deleted instead the first line appears bold until the line break by WordWrap and no bold text on new lines at all. – St. Helen Apr 30 '16 at 09:18
  • That sounds impossible. There is no bolding or formatting in the function, just a Cut. Do try it on a fresh richtTextBox to see that is works just fine. Then investigate what else might be going on.. Any events you have coded for the RTB? – TaW Apr 30 '16 at 09:23
  • No events at all. I'm just adding text :( Everything works fine until I try to delete the first line. – St. Helen Apr 30 '16 at 09:28
  • Oh, are you relying on 'line breaks' by wordWrap? These are no real line breaks and will disappear when you resize the RTB.. But the function will still honor them as the GetFirstCharIndexFromLine does. Could that play a part? – TaW Apr 30 '16 at 09:28
  • Yes I do so the text fits into the RTB and the user don't have to use the horizontal scrollbar all the time. – St. Helen Apr 30 '16 at 09:32
  • I tried using only very short text so no additional line break by WordWrap happens. Then I set WordWrap to false. Still the same behaviour. It's getting pretty frustrating now :/ – St. Helen Apr 30 '16 at 09:50
  • That is really weird. Did you try a freshly added RichTextBox with just a few lines of text and a button to remove the 1st line? (I'm still working on the new version, even if it has nothing to do with your problems.. The length of a line break is the problem here..) – TaW Apr 30 '16 at 09:54
  • OK, the new function seems to work now; but it really can't be the problem when your tests show no deletions at all.. – TaW Apr 30 '16 at 10:20
  • @TaW Where do you get `oldText` and `richTextBox1` from in `DeleteLinesWW`? Edit: And do I have to set `SelectionStart` or something to the end of the entire text before I add a new line (after deleting the first)? – St. Helen Apr 30 '16 at 15:19
  • Whoops, that 1st line doesn't belong here. (It was just a way to allow me to rweset the text while testing.) Removed. No, When using AppendText there is no need to set the SelectionStart. – TaW Apr 30 '16 at 15:25
  • But I set `SelectionFont` to bold before I use `AppendText`. So far your code seems to catch at least the entire first line now. But it still don't delete it. The first line is now completely bold and new lines still appear completely regular. – St. Helen Apr 30 '16 at 15:41
  • @TaW I've found the reason for 1 part of the problem. The RTB had `ReadOnly = true;`. When I change it to false the first line gets deleted. Sorry I really wasn't aware of this! And I guess I have to toggle it while adding and deleting lines to prevent direct input by the user. But new lines added are still completely regular. – St. Helen Apr 30 '16 at 16:29
  • I have added an example how to add one of your lines. Any `SelectionXXX` property setting __makes no sense__ (at best!) unless you select the target __first__ using `SectionStart` __and__ `SelectionLength`. Unless there is any text selected setting `SelectionFont` is useless. At best. It also may well mess up the formats you have.. – TaW Apr 30 '16 at 16:39
  • Ah, yes, ReadOnly prevents Cut. You can easily adapt the functions, see the update! – TaW Apr 30 '16 at 16:42
  • Thank you very much! I'm going to make a break now. I need a pause. I'll look into adding lines later and come back then. Please see the update in my OP. I'll update it again when I'm done. Strange behaviour of `ReadOnly` though. – St. Helen Apr 30 '16 at 16:58
0

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();