Raj on July 22nd, 2008

Word’s font selection combobox displays a preview of each font. It’s a great feature which makes it much easier for a user to select a font. Instead of going through each font one-by-one to see what the font looks like, the user can see the results for all installed fonts in one fell swoop.

Accomplishing this feat using C# in .NET 1.0 and 1.1 was no easy feat. The length of many older code samples easily reached into the hundreds of lines. .NET 2.0 introduced new controls and features that made the job much easier.

The C# .NET 2.0 solution only requires one class and less than 40 lines of code. All we need to do is handle several of the events thrown by the ToolStripComboBox’s underlying ComboBox control. My solution extends the ToolStripComboBox, but you can accomplish the same thing without extending the control.

Extending the ToolStripComboBox

[System.ComponentModel.DesignerCategory ( "code" )]
[ToolStripItemDesignerAvailability ( ToolStripItemDesignerAvailability.All )]
public class ToolStripFontComboBox : ToolStripComboBox
{
    protected int iDropWidth = 0;

// . . .

[System.ComponentModel.DesignerCategory ( "code" )]
This line instructs the IDE to restrict this class to the code view. Without this line, double-clicking the file from the Solution Explorer brings up the Designer view, which is completely unnecessary for this project.

[ToolStripItemDesignerAvailability ( ToolStripItemDesignerAvailability.All )]
This line instructs the IDE to show the ToolStripFontComboBox control in the Designer when editing ToolStrips. using ToolStripItemDesignerAvailability.All results in our control showing up in all Toolstrip-based controls, including DropDowns and ContextMenus.

iDropWidth stores the width of the largest item. It’s value will be calculated in the MeasureItem event handler.

Constructor

public ToolStripFontComboBox ()
{
    // Set the draw mode so we can take over item drawing
    this.ComboBox.DrawMode = DrawMode.OwnerDrawVariable;
    this.AutoCompleteMode = AutoCompleteMode.Append;

    // Handle the events
    this.ComboBox.DropDown += new EventHandler ( DropDown );
    this.ComboBox.MeasureItem += new MeasureItemEventHandler ( MeasureItem );
    this.ComboBox.DrawItem += new DrawItemEventHandler ( DrawItem );

    // Create the list of fonts, and populate the ComboBox with that list
    PopulateFonts();
}

We need to to set the underlying ComboBox’s DrawMode to DrawMode.OwnerDrawVariable or DrawMode.OwnerDrawFixed. OwnerDrawVariable means that each item can have its own, variable size. OwnerDrawFixed means that each item will have the same, fixed size.

Setting the DrawMode to one of the OwnerDraw… options allows us to handle the the underlying ComboBox’s item drawing events. It’s also possible to just use the default DrawMode, but then we’d have to override the underlying ComboBox’s protected OnPaint event. This isn’t possible with a ToolStripComboBox, because the control inherits from the ToolStripItem class instead of the ComboBox class. Overriding the OnPaint event is also a masochistic endeavor best left to C++ programmers and other self-flaggelators.

We’re only going to handle the DropDown, MeasureItem, and DrawItem events thrown by the underlying ComboBox. Those event handlers will be defined in our class.

Finally, we call the PopulateFonts() method, which will search for installed fonts and then populate the ComboBox with the names of those fonts.

Handling the Events

protected void DropDown ( object sender, EventArgs e )
{
    this.ComboBox.DropDownWidth = iDropWidth;
}

A ComboBox throws the DropDown event when the DropDown portion is displayed. In this event, we set the width of the DropDown. We’re doing this here to make sure the width of the DropDown is properly set when we know we’re going to use it. Setting this property here allows us to account for changes to the ComboBox’s Items collection, and makes the control more usable because we don’t have to unnecessarily restrict access to properties that would be exposed by a normal ToolStripComboBox.

protected void MeasureItem ( object sender, MeasureItemEventArgs e )
{
    if ( e.Index > -1 )
    {
        string szFont = Items[e.Index].ToString ();
        Graphics g = this.ComboBox.CreateGraphics ();
        SizeF size = g.MeasureString ( szFont, new Font ( szFont, this.Font.Size ) );

        // Set the Item’s Width, and iDropWidth if the item has a greater width
        e.ItemWidth= (int)size.Width;
        if (e.ItemWidth > iDropWidth) { iDropWidth = e.ItemWidth; }

        // If .NET gives you problems drawing fonts with different heights, set a maximum height
        e.ItemHeight = (int)size.Height;
        if ( e.ItemHeight > 20 ) { e.ItemHeight = 20; }
    }
}

A ComboBox throws the MeasureItem event whenever it needs to measure the size of an item for drawing. In the code above, the ComboBox has already been populated with fonts. Each item contains the name of the font. The MeasureItem event occurs before drawing, so the event does not pass us a Graphics object. We need to create our own. We then use the Graphics object to measure the size of a string, containing the name of a font, rendered using that same font. After we’ve retrieved the size, we run a few checks just to make sure everything is kosher. We also set the iDropWidth variable to the size of the widest item.

protected void DrawItem ( object sender, DrawItemEventArgs e )
{
    // DrawBackground handles drawing the background (i.e,. hot-tracked v. not)
    // It uses the system colors (Bluish, and and white, by default)
    // same as calling e.Graphics.FillRectangle ( SystemBrushes.Highlight, e.Bounds );
    e.DrawBackground ();

    if ( e.Index > -1 )
    {
        string szFont = Items[e.Index].ToString ();
        Font fFont = new Font ( szFont, this.Font.Size );

        Rectangle rectDraw = e.Bounds;

        if ( ( e.State && DrawItemState.Default ) == e.State )
        {
            e.Graphics.DrawString ( szFont, fFont, SystemBrushes.WindowText ), rectDraw );
        }
        else
        {
            e.Graphics.DrawString ( szFont, fFont, SystemBrushes.HighlightText ), rectDraw );
        }
    }
    // Uncomment this if you actually like the way the focus rectangle looks
    //e.DrawFocusRectangle ();
}

DrawItem() is the the meat, cheese, and potatoes of our project. It handles all the work of drawing our fonts names in their respective fonts. This version draws the background and the font string, but it does not draw the focus rectangle. I don’t like the way the focus rectangle looks, but if you do, just uncomment the function call.

e.DrawBackground() draws the background color for the ComboBox using the System.Highlight color. In the default Windows themes, this color is a shade of blue, but some themes change that color. This function call only saves us a line of code, since it’s a trivial matter to draw a rectangle. (See the comment in the code.)

First, we retrieve the font’s name from the ComboBox (each item is the name of an installed font) and create the font object. We’ll be using the font object to draw the font’s name. We then get the bounding rectangle for drawing.

The color we’ll use to draw the text depends on the item’s state. We use the SystemColors.WindowText color to draw any item that isn’t currently selected or hot-tracked by the mouse. Hot-tracked items are drawn using the SystemColors.HighlightText color.

We determine the item’s state by checking e.State and comparing it to the DrawItemState enumeration. DrawItemState enumerates many possible values, but as as configured in our extended control, the ToolStripComboBox will only send 3 types of states: e.Default, e.HotLight, and e.Selected. For our purposes, it only matters whether the item is in its default state or not, so we’ll only test for the default state. Each item can have several states at once (for example, an item can be selected and hot-tracked at the same time), so we can’t just test for equality. Instead, we have to use a bit comparison to test only the state we want. To do this, we bitwise AND the DrawItemState item with the e.State we were passed, and then compare the result with the state we’re checking for.

Finally, we draw the font string. We don’t need to create our own Graphics object to do the drawing because the DrawItemEventArgs object passed as e includes the Graphics object the ComboBox is using to draw the items.

Generating the List of Fonts

For the purposes of simplicity, this tutorial only illustrates the .NET way to search for installed fonts. I might get around to posting the Windows API way to do it later. The benefit of the .NET way is simplicity. The Windows API way offers more control. For example, it’s (relatively) easy to select just Western fonts through the API, but doing this in .NETisn’t possible, as far as I know.

public void PopulateFonts ()
{
    foreach ( FontFamily ff in FontFamily.Families )
    {
        if ( ff.IsStyleAvailable ( FontStyle.Regular ) )
        {
            this.Items.Add ( ff.Name );
        }
    }
    if ( Items.Count > 0 )
        this.SelectedIndex = this.FindString ( "Times" );
}

The FontFamily class takes care of all the work for us. It contains a list of all the fonts installed on the system in the FontFamily.Members field. In case you’re wondering, in Windows terminology, font families are what we commonly refer to as fonts. The font family name is the font name we commonly associate with fonts. Font, in this context, actually refers to the specific font/font-style/font-size combination. For example, “Times New Roman” is a font family, while “Times New Roman” in bold and size 12pt is a font.

The code above only checks if each font family has the regular font style available. However, for most document editing applications, it’s a good idea to also check for the Bold and Italic font styles, and only add font families which also include those styles. Windows can emulate the underline style on if a font family doesn’t include its own underline style.

Closing Remarks

To use this code, just grab it and finish up the class definition by adding a closing bracket. This class uses the System, System.Drawing, and System.Windows.Forms namespaces.

Share and Enjoy:
  • Digg
  • del.icio.us
  • Mixx
  • Google

Tags: , , , , ,

3 Responses to “Creating a FontCombBox for the ToolStrip in C#”

  1. Thats a good article. Nice use of ComboBox control. Even we run a blog on programming languages (http://codelog.blogial.com) where we write about C# ui controls. Thanks.

  2. Code fails to compile generating an error in the DrawItem code segment. I’m not quite sure how to handle the “&amp” which may be the source of the problem. If someone could help me solve the problem I would appreciate it.

    Steps taken:

    1) Created a new Windows project and added a form with a toolStrip control.

    2) Added a comboBox control in the toolStrip.

    3) Added a new Class to the project ‘ToolStripCs.cs’ and copied/pasted the above code into the new Class.

    Errors imediately occured in the DrawItem method. Being somewhat of a C# novice I cannot seem to correct the code to make it compile.

    TIA dKoder

  3. The “&amp” is a WordPress bug that keeps replacing the && (AND operator) in the C# code with the HTML code for the & symbol.

Leave a Reply