13

I have a rich text box that may contain a string that has elements of bold, italics, or even different fonts and sizes. If I select the entire string, including all of the differences, how can I "Bold" that string without converting the entire string to the generic font with just a "bold" attribute?

For example: I want to turn "This is some text" into "This is some text"

Note that "is some" remained italicized and "text" remained a different font.

What I currently have is quite simplistic:

private void tsBold_Click(object sender, EventArgs e)
{
    if (rtb.SelectionFont == null) return;

    Font f;

    if (tsBold.Checked)
        f = new Font(rtb.SelectionFont, FontStyle.Bold);
    else
        f = new Font(rtb.SelectionFont, FontStyle.Regular);

    rtb.SelectionFont = f;

    rtb.Focus();
}

Of course, this is going to apply the exact same font to the entire selection. Is there any way to just append "bold" to the existing font(s)?

ANSWER While the "official" answer below is just the tip of the iceberg, it was the push I needed in the right direction. Thank you for the tip.

Here's my official fix:

I added this to my RichTextBox object:

    /// <summary>
    ///     Change the richtextbox style for the current selection
    /// </summary>
    public void ChangeFontStyle(FontStyle style, bool add)
    {
        //This method should handle cases that occur when multiple fonts/styles are selected
        // Parameters:-
        //  style - eg FontStyle.Bold
        //  add - IF true then add else remove

        // throw error if style isn't: bold, italic, strikeout or underline
        if (style != FontStyle.Bold
            && style != FontStyle.Italic
            && style != FontStyle.Strikeout
            && style != FontStyle.Underline)
            throw new System.InvalidProgramException("Invalid style parameter to ChangeFontStyle");

        int rtb1start = this.SelectionStart;
        int len = this.SelectionLength;
        int rtbTempStart = 0;

        //if len <= 1 and there is a selection font then just handle and return
        if (len <= 1 && this.SelectionFont != null)
        {
            //add or remove style 
            if (add)
                this.SelectionFont = new Font(this.SelectionFont, this.SelectionFont.Style | style);
            else
                this.SelectionFont = new Font(this.SelectionFont, this.SelectionFont.Style & ~style);

            return;
        }

        using (EnhancedRichTextBox rtbTemp = new EnhancedRichTextBox())
        {
            // Step through the selected text one char at a time    
            rtbTemp.Rtf = this.SelectedRtf;
            for (int i = 0; i < len; ++i)
            {
                rtbTemp.Select(rtbTempStart + i, 1);

                //add or remove style 
                if (add)
                    rtbTemp.SelectionFont = new Font(rtbTemp.SelectionFont, rtbTemp.SelectionFont.Style | style);
                else
                    rtbTemp.SelectionFont = new Font(rtbTemp.SelectionFont, rtbTemp.SelectionFont.Style & ~style);
            }

            // Replace & reselect
            rtbTemp.Select(rtbTempStart, len);
            this.SelectedRtf = rtbTemp.SelectedRtf;
            this.Select(rtb1start, len);
        }
        return;
    }

I then changed the click methods to use the following pattern:

    private void tsBold_Click(object sender, EventArgs e)
    {
        enhancedRichTextBox1.ChangeFontStyle(FontStyle.Bold, tsBold.Checked);

        enhancedRichTextBox1.Focus();
    }
Jerry
  • 4,507
  • 9
  • 50
  • 79
  • Why is this so hard in WinForms? It's a trivial [`EM_SETCHARFORMAT`](http://msdn.microsoft.com/en-us/library/bb774230(v=VS.85).aspx) at the Win32 level. Since you have the window handle at hand, can't you do it that way? – David Heffernan Mar 16 '11 at 23:48
  • Please do not post an answer as an update to your question. You are allowed to self-answer your own question. Also, that way people can upvote your answer. – BCdotWEB Jul 27 '18 at 11:24

5 Answers5

7

RTB does not support this well. You cannot even discover the range of characters within the selection that has the same font style. Start by checking the SelectionFont property first, it will be null if the selection contains a mix of styles. If that's the case, you'll have to iterate the selection one character at a time by setting the SelectionStart and SelectionLength properties, read the SelectionFont until it changes. Apply the changed font to the range you discovered.

Check this answer for a way to keep this reasonably quick and flicker-free.

Note that implementing an editor with RTB is a favorite topic at codeproject.com. Borrowing code, if not the entire project, is a good way to lessen the pain.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • To keep mine "flicker free" I just used a code-generated RTB object, then updated the results in the main RTB when it was done. Very fast, and no flicker. – Jerry Mar 16 '11 at 15:32
  • 1
    Not a bad idea, very expensive though. You're doubling the amount of memory you consume. – Hans Passant Mar 16 '11 at 15:41
3

To make a text selection bold while keeping its formatting intact, use this:

if (rtb.SelectionFont !=null)
    rtb.SelectionFont = new Font(rtb.SelectionFont, rtb.SelectionFont.Style | FontStyle.Bold);

To unBold text selection while keeping its formatting intact, use this:

if (rtb.SelectionFont !=null)
    rtb.SelectionFont = new Font(rtb.SelectionFont, rtb.SelectionFont.Style & ~FontStyle.Bold);

Note that above code will only work, if all the selected text has same formatting (font size, style etc). This is detected by checking the SelectionFont property first, it will be null if the selection contains a mix of styles.

Now to do this with all text in richtextbox,

Now to Bold/unBold the entire text of richtextbox while keeping other formatting intact, you need to loop through all the characters of the richtextbox and apply Bold/unBold one by one. Here is the complete code:

private void tsBold_Click(object sender, EventArgs e)
{
    //Loop through all the characters of richtextbox
    for (int i = 0; i < rtb.TextLength; i++)
    {
        //Select current character
        rtb.Select(i, 1);

        if (tsBold.Checked)
            //Make the selected character Bold
            rtb.SelectionFont = new Font(rtb.SelectionFont, rtb.SelectionFont.Style | FontStyle.Bold);
        else
            //Make the selected character unBold
            rtb.SelectionFont = new Font(rtb.SelectionFont, rtb.SelectionFont.Style & ~FontStyle.Bold);
    }
}

If you need to toggle the existing state of Bold (i.e. make non-bold text Bold and make bold text unBold), use this instead:

        if (rtb.SelectionFont.Style.ToString().Contains("Bold")) //If the selected character is Bold
            //Make the selected character unBold
            rtb.SelectionFont = new Font(rtb.SelectionFont, rtb.SelectionFont.Style & ~FontStyle.Bold);
        else //If the selected character is unBold
            //Make the selected character Bold
            rtb.SelectionFont = new Font(rtb.SelectionFont, rtb.SelectionFont.Style | FontStyle.Bold);
ePandit
  • 2,905
  • 2
  • 24
  • 15
0

In case you want also to change its font family and font size you could use this method: Hans is right, you have to iterate each character.

  private void ChangeFontStyleForSelectedText(string familyName, float? emSize, FontStyle? fontStyle, bool? enableFontStyle)
    {
        _maskChanges = true;
        try
        {
            int txtStartPosition = txtFunctionality.SelectionStart;
            int selectionLength = txtFunctionality.SelectionLength;
            if (selectionLength > 0)
                using (RichTextBox txtTemp = new RichTextBox())
                {
                    txtTemp.Rtf = txtFunctionality.SelectedRtf;
                    for (int i = 0; i < selectionLength; ++i)
                    {
                        txtTemp.Select(i, 1);
                        txtTemp.SelectionFont = RenderFont(txtTemp.SelectionFont, familyName, emSize, fontStyle, enableFontStyle);
                    }

                    txtTemp.Select(0, selectionLength);
                    txtFunctionality.SelectedRtf = txtTemp.SelectedRtf;
                    txtFunctionality.Select(txtStartPosition, selectionLength);
                }
        }
        finally
        {
            _maskChanges = false;
        }
    }

      /// <summary>
    /// Changes a font from originalFont appending other properties
    /// </summary>
    /// <param name="originalFont">Original font of text</param>
    /// <param name="familyName">Target family name</param>
    /// <param name="emSize">Target text Size</param>
    /// <param name="fontStyle">Target font style</param>
    /// <param name="enableFontStyle">true when enable false when disable</param>
    /// <returns>A new font with all provided properties added/removed to original font</returns>
    private Font RenderFont(Font originalFont, string familyName, float? emSize, FontStyle? fontStyle, bool? enableFontStyle)
    {
        if (fontStyle.HasValue && fontStyle != FontStyle.Regular && fontStyle != FontStyle.Bold && fontStyle != FontStyle.Italic && fontStyle != FontStyle.Underline)
            throw new System.InvalidProgramException("Invalid style parameter to ChangeFontStyleForSelectedText");

        Font newFont;
        FontStyle? newStyle = null;
        if (fontStyle.HasValue)
        {
            if (fontStyle.HasValue && fontStyle == FontStyle.Regular)
                newStyle = fontStyle.Value;
            else if (originalFont != null && enableFontStyle.HasValue && enableFontStyle.Value)
                newStyle = originalFont.Style | fontStyle.Value;
            else
                newStyle = originalFont.Style & ~fontStyle.Value;
        }

        newFont = new Font(!string.IsNullOrEmpty(familyName) ? familyName : originalFont.FontFamily.Name,
                            emSize.HasValue ? emSize.Value : originalFont.Size,
                            newStyle.HasValue ? newStyle.Value : originalFont.Style);
        return newFont;
    }

For more explanations you could go to: http://how-to-code-net.blogspot.ro/2014/01/how-to-make-custom-richtextbox-control.html

Alexa Adrian
  • 1,778
  • 2
  • 23
  • 38
0

I am yet to test it in terms of Font object references consuming more memory however

This should work

        if(rtbCaseContent.SelectedText.Length > 0 ) 
        {
            // calculate font style 
            FontStyle style = FontStyle.Underline;
            Font selectedFont = rtbCaseContent.SelectionFont;

            if (rtbCaseContent.SelectionFont.Bold == true)
            {
                style |= FontStyle.Bold;
            }
            if (rtbCaseContent.SelectionFont.Italic == true)
            {
                style |= FontStyle.Italic;
            }

            rtbCaseContent.SelectionFont = new Font(selectedFont,style);
        }           
Addy
  • 731
  • 5
  • 15
0

if you want to apply more FontStyle's on the same text you can use bitwise operators | and ~ | adds a new style and ~ removes an existing style for example

Font aFont=new Font(aPrototypeFont, anotherFont.Style | FontStyle.Bold); 
Iancu Vlad
  • 41
  • 2
  • 2