Classes Extension Project: RtfEditor

.

Environment: MS Visual C# .NET

Description

I’ve been needing a way to edit RTF formatted text via the RichTextBox control that is part of the C# libraries. There are no built-in ways to update the text, however. So, I thought that it would be nice to make a UserControl that includes the basic formatting options of the RichTextBox with the control itself.

Here is a shot of the test container for the control called RtfEditor:

As you can see, I’ve included all the basics that you would normally see for a quick text editor. The only thing that’s a part of RTF formatting that I think I’ve left out is the bulleting stuff. The ‘tool bar’ isn’t really; I did this just to make things simple and to allow easy usage with the combo boxes.

It’s not too fancy, but it works. I’m sure someone can add on what they need.

The Code

I entered into MS Visual Studio and created a Control Library project in the project wizard; then, I named it RtfEditor.

This comes up with a blank control surface to add your controls onto. I added the RichText box, the four combo boxes, and the six check boxes (with their appearance set as Button).

When the Component Designer is done generating its code, the first part of the class looked like this.

using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;

namespace RtfEditor
{
  /// <summary>
  /// Summary description for RtfEditor.
  /// </summary>
  public class RtfEditor : System.Windows.Forms.UserControl
  {
    private System.Windows.Forms.GroupBox groupBox1;
    private System.Windows.Forms.GroupBox groupBox2;
    public  System.Windows.Forms.RichTextBox m_textBox;
    private System.Windows.Forms.ComboBox m_fontName;
    private System.Windows.Forms.ComboBox m_fontSize;
    private System.Windows.Forms.CheckBox m_bold;
    private System.Windows.Forms.CheckBox m_italic;
    private System.Windows.Forms.CheckBox m_under;
    private System.Windows.Forms.CheckBox m_left;
    private System.Windows.Forms.CheckBox m_center;
    private System.Windows.Forms.CheckBox m_right;
    private System.Windows.Forms.GroupBox groupBox3;
    private System.Windows.Forms.ComboBox m_fore;
    private System.Windows.Forms.ComboBox m_back;
    private System.Windows.Forms.FontDialog fontDialog1;
    private System.ComponentModel.IContainer components;

You see the variables that make up the UserControl. Notice that I’ve made the RichTextBox a public field. I could have also made it into a parameter—probably should have—but this allows easy access to the load, save, and RTF methods of the base control.

I then added some arrays for doing the color drop-downs for the foreground and background colors and then also added the tooltips.

I’ve only included a few colors. Feel free to add any other colors you wish to use, a color picker dialog, or an automated way of creating these arrays. I’ve included them like this for easy understanding.

private Brush[] m_brushes = new Brush[] { Brushes.White,
                                          Brushes.Black,
                                          Brushes.Blue,
                                          Brushes.Green,
                                          Brushes.Red,
                                          Brushes.Yellow };
private Color[] m_colors = new Color[] { Color.White, Color.Black,
                                         Color.Blue, Color.Green,
                                         Color.Red, Color.Yellow };
private string[] m_colornames = new string[] { "White", "Black",
                                               "Blue", "Green",
                                               "Red", "Yellow" };
private System.Windows.Forms.ToolTip m_toolTip;

Here is the constructor.

public RtfEditor()
{
  // This call is required by the Windows.Forms Form Designer.
  InitializeComponent();

  foreach( System.Drawing.FontFamily ff in
           System.Drawing.FontFamily.Families )
  {
    if( ff.Name.Equals("Verdana"))
      m_fontName.SelectedIndex = m_fontName.Items.Add( ff.Name);
    else
      m_fontName.Items.Add( ff.Name);
  }

  m_fontSize.SelectedIndex = 3;

  m_fore.Items.AddRange( m_colornames);
  m_back.Items.AddRange( m_colornames);

  m_fore.SelectedIndex = 1;
  m_back.SelectedIndex = 0;

  m_fore.DrawItem += new DrawItemEventHandler(
                         OnColorComboDrawItem);
  m_back.DrawItem += new DrawItemEventHandler(
                         OnColorComboDrawItem);

  m_toolTip.SetToolTip( m_textBox, "This is your current
                                    document.");
  m_toolTip.SetToolTip( m_fontName, "This is the type of font for
                                     your text.");
  m_toolTip.SetToolTip( m_fontSize, "This is the size of your
                                     text.");
  m_toolTip.SetToolTip( m_bold,   "Make your text bold.");
  m_toolTip.SetToolTip( m_italic, "Make your text italisized.");
  m_toolTip.SetToolTip( m_under,  "Make your text right aligned.");
  m_toolTip.SetToolTip( m_left,   "Make your text left aligned.");
  m_toolTip.SetToolTip( m_center, "Make your text centered.");
  m_toolTip.SetToolTip( m_right,  "Make your text right aligned.");
  m_toolTip.SetToolTip( m_fore,   "Text foreground color.");
  m_toolTip.SetToolTip( m_back,   "Text background color.");

  m_textBox.SelectionChanged += new EventHandler(
                                    OnTxtSelectionChanged);
}

I call the standard InitializeComponent() that all forms need. Then, I add the list of available font families. I like the Verdana font, so I made that the default selection in the control. I also like 12 as the standard point size so I made that the default selection in the font size combo box.

I then add the arrays to the foreground and background color comboboxes and set their default settings. I then tap into the event handlers for the DrawItem method of the these two combo boxes. This will allow me to draw the colors in the combo boxes as needed; this turned out to be much easier than in VC++.

I set the tool tips, and finally set up the event handler that will handle changing the control states as the cursor moves around the text window.

Next comes the Draw Item event method. I just draw the colors into the item area as needed. Most of the work is handled by the framework.

void OnColorComboDrawItem( object sender, DrawItemEventArgs e)
{
  if( e.Index != -1)
    e.Graphics.FillRectangle( m_brushes[ e.Index], e.Bounds);
}

I’m just filling the rectangle of the item boundary with the brush/color of the particular item index that is being updated. (I’m going to skip over some of the code, such as the Dispose and InitializeComponents, because these are well documented elsewhere.)

Next, we start the section of event handlers associated with the controls that I’ve added to our UserControl.

First is the Font Name combo box. When the selection changes, this I what I do.

#region Handle Control Events
private void m_fontName_SelectedIndexChanged(object sender,
                                             System.EventArgs e)
{
  if( m_fontSize.SelectedIndex == -1 ||
      m_fontName.SelectedIndex == -1)
  return

  FontStyle style = m_textBox.SelectionFont.Style;
  float size = m_textBox.SelectionFont.Size;

  m_textBox.SelectionFont = new Font(
    m_fontName.Items[ m_fontName.SelectedIndex].ToString(),
                      size, style);

  m_textBox.Focus();
}

First, I make sure that some item is selected in both the Font Name and the Font Size combo boxes. I get the current font style at the cursor position. Because I’m only changing the font name at this position, I also get the size. I create the new font based on the new parameters and set the font to this at the current cursor position in the RichTextBox. If you read the documentation for the RichTextBox, you will see that all the ‘Selection*’ parameters are set/get and when they are set they will either change the value at the current cursor position if nothing is selected, or change the selected text format. I always set the focus back to the text box so we can keep right on typing after a format change.

I do the same thing for the size combo box, save off the font style and font family name, and create a new font for the current ‘selection.’

private void m_fontSize_SelectedIndexChanged(object sender,
                                             System.EventArgs e)
{
  if( m_fontSize.SelectedIndex == -1 ||
      m_fontName.SelectedIndex == -1)
  return

  FontStyle style = m_textBox.SelectionFont.Style;
  string name = m_textBox.SelectionFont.FontFamily.Name;

  m_textBox.SelectionFont = new Font(
    name,
    float.Parse(m_fontSize.Items[ m_fontSize.SelectedIndex].
                                  ToString()), style);

  m_textBox.Focus();
}

The event handlers all work in pretty much the same way as each other. I change the style based upon whether the button is checked or unchecked.

private void m_bold_CheckedChanged(object sender,
                                   System.EventArgs e)
{
  if( m_fontSize.SelectedIndex == -1 ||
      m_fontName.SelectedIndex == -1)
  return

  FontStyle style = FontStyle.Regular;

  if( m_bold.Checked )
    style |= FontStyle.Bold;
  if( m_italic.Checked )
   style |= FontStyle.Italic;
  if( m_under.Checked )
    style |= FontStyle.Underline;

  string name = m_textBox.SelectionFont.FontFamily.Name;
  float size = m_textBox.SelectionFont.Size;

  m_textBox.SelectionFont = new Font( name, size, style);

  m_textBox.Focus();
}

private void m_italic_CheckedChanged(object sender,
                                     System.EventArgs e)
{
  if( m_fontSize.SelectedIndex == -1 ||
      m_fontName.SelectedIndex == -1)
  return

  FontStyle style = FontStyle.Regular;

  if( m_bold.Checked )
    style |= FontStyle.Bold;
  if( m_italic.Checked )
    style |= FontStyle.Italic;
  if( m_under.Checked )
    style |= FontStyle.Underline;

  string name = m_textBox.SelectionFont.FontFamily.Name;
 float size = m_textBox.SelectionFont.Size;

  m_textBox.SelectionFont = new Font( name, size, style);

  m_textBox.Focus();
}

private void m_under_CheckedChanged(object sender,
                                    System.EventArgs e)
{
  if( m_fontSize.SelectedIndex == -1 ||
      m_fontName.SelectedIndex == -1)
  return

  FontStyle style = FontStyle.Regular;

  if( m_bold.Checked )
    style |= FontStyle.Bold;
  if( m_italic.Checked )
    style |= FontStyle.Italic;
  if( m_under.Checked )
    style |= FontStyle.Underline;

  string name = m_textBox.SelectionFont.FontFamily.Name;
  float size = m_textBox.SelectionFont.Size;

  m_textBox.SelectionFont = new Font( name, size, style);

  m_textBox.Focus();
}

Similarly to the combo box event handlers, on all three of these I update the current font based upon the saved off size, family name, and the new style based on the state of the check boxes.

With the alignment check boxes, it’s again pretty much the same procedure, except here I’m updating the alignment of the current ‘selection.’ Also, since I want these three controls to act like a radio control group, I turn off the other two buttons when one is turned on.

private void m_left_CheckedChanged(object sender,
                                   System.EventArgs e)
{
  if( m_left.Checked)
  {
    m_textBox.SelectionAlignment = HorizontalAlignment.Left;

    m_center.Checked = false;
    m_right.Checked  = false;
  }
  m_textBox.Focus();
}

private void m_center_CheckedChanged(object sender,
                                     System.EventArgs e)
{
  if( m_center.Checked)
  {
    m_textBox.SelectionAlignment = HorizontalAlignment.Center;

    m_left.Checked = false;
    m_right.Checked = false;
  }
  m_textBox.Focus();
}

private void m_right_CheckedChanged(object sender,
                                    System.EventArgs e)
{
  if( m_right.Checked)
  {
    m_textBox.SelectionAlignment = HorizontalAlignment.Right;

    m_center.Checked = false;
    m_left.Checked   = false;
  }
  m_textBox.Focus();
}

These need to be turned into a radio control group, really, to give the proper behavior.

The final part of this section is again very similar functionality. Here I handle events from the fore- and background color changes.

private void m_fore_SelectedIndexChanged(object sender,
                                         System.EventArgs e)
{
  m_textBox.SelectionColor = m_colors[ m_fore.SelectedIndex];
  m_textBox.Focus();
}

private void m_back_SelectedIndexChanged(object sender,
                                         System.EventArgs e)
{
  m_textBox.BackColor = m_colors[ m_back.SelectedIndex];
  m_textBox.Focus();
}
#endregion

It’s pretty simple, really. The selection color is updated or the background color is updated. Notice that there is no formatting option in the RichTextBox to change the background of only a part of the text; instead, you can only change the background color of the entire document. I know that RTF formatting allows for this, but for some reason this particular control doesn’t handle it. And, adding that functionality is beyond the scope of this article.

Finally, we get to the crux of this little operation.

public void OnTxtSelectionChanged( object sender, EventArgs e)
{
  if( m_textBox.SelectionAlignment == HorizontalAlignment.Center)
  {
    m_center.Checked = true;
  }
  if( m_textBox.SelectionAlignment == HorizontalAlignment.Left)
  {
    m_left.Checked = true;
  }
  if( m_textBox.SelectionAlignment == HorizontalAlignment.Right)
  {
    m_right.Checked = true;
  }

  int index = m_fontSize.Items.IndexOf( m_textBox.SelectionFont.
                                        Size.ToString() );
  m_fontSize.SelectedIndex = index;

  index = m_fontName.Items.IndexOf( m_textBox.SelectionFont.
                                    FontFamily.Name);
  m_fontName.SelectedIndex = index;

  for( int i = 0; i < m_colors.Length; i++)
  {
    if( m_colors[i].Equals( m_textBox.SelectionColor))
    m_fore.SelectedIndex = i;
  }

  if( m_textBox.SelectionFont.Bold)
    m_bold.Checked = true;
  else
    m_bold.Checked = false;

  if( m_textBox.SelectionFont.Italic)
    m_italic.Checked = true;
  else
    m_italic.Checked = false;

  if( m_textBox.SelectionFont.Underline)
    m_under.Checked = true;
  else
    m_under.Checked = false;

  }
}

This method handles the event as the cursor or selection changes in the text box. This allows me to update the control states as different text comes under the control of the cursor. I get the current state of the selection and update the controls appropriately. I get the alignment and font states. Here you could easily add the bulleting stuff too.

Deployment

To use this control in your form, compile the project and update your Toolbox control in VStudio by ‘Customizing’ it. Select the Projects tab and browse to the DLL that is created by the compilation. The user control will then show up on your Toolbox as another control you may add to a form.

Conclusion

This was a fun little experiment for someone who is pretty new to C# but is really getting into it. I hope this helps people out.

Downloads

Download demo project – 36 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read