Here's a custom owner-drawn ComboBox class (here, named FontListCombo
) that shows compatible System Font Families, representing each ComboBox Item using the FontFamily name as the Item Text and the corresponding Font to draw the Item's text (a classic ComboBox Font selector, in practice).
The Custom Control automatically fills itself, at run-time, with the list of available Fonts in the System. It also react to WM_FONTCHANGE
messages (broadcast when the System Font pool changes; e.g., a Font is added to or removed from the Fonts
folder), to updated the Font list and reflect the change (to also avoid to try and use a Font that doesn't exist anymore).
The text of the ComboBox Items is drawn using TextRenderer.DrawText() instead of Graphics.DrawString(), since the former grants sharper results in this context.
The ComboBox.Items
collection is represented by a collection of FontObject
class objects, a public class that stores some of the properties of each FontFamily and also exposes a couple of static methods used internally to return a Font object or a FontFamily object, calling the corresponding public methods of the custom ComboBox:
- The
GetSelectedFont(SizeInPoints, FontStyle)
method return a Font object from the current ComboBox.SelectedItem
.
GetSelectedFontFamily()
return the FontFamily object from the current ComboBox.SelectedItem
.
It also overrides ToString()
, to return a summary of values of its properties.
This kind of object container is a better fit here: storing FontFamily object as the ConboBox Items would, most surely, create different sorts of problems, the most visible and tragic is that some FontFamily objects become invalid over time or even right after they have been stored. These objects are not meant to be stored permanently to begin with, so it's not exactly a surprise.
As shown in the example, to get the current Font and FontFamily from the ComboBox.SelecteItem
:
(here, the ComboBox instance is named cboFontList
)
private void cboFontList_SelectionChangeCommitted(object sender, EventArgs e)
{
Font font = cboFontList.GetSelectedFont(this.Font.SizeInPoints, FontStyle.Regular);
FontFamily family = cboFontList.GetSelectedFontFamily();
string fontDetails = (cboFontList.SelectedItem as FontListCombo.FontObject).ToString();
}
The FontObject class stores some important details of the FontFamily, such as the Cell Ascent, the Cell Descent, the EM Size and the Line Spacing.
Some details on how to use these features are described here:
Properly draw text using Graphics Path
Fonts and text metrics
This is how it works:

FontListCombo Custom Control:
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
[DesignerCategory("Code")]
public class FontListCombo : ComboBox
{
private List<FontObject> fontList = null;
public FontListCombo() {
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.DrawMode = DrawMode.OwnerDrawVariable;
}
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
if (!DesignMode) GetFontFamilies();
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
if ((Items.Count == 0) || e.Index < 0) return;
e.DrawBackground();
var flags = TextFormatFlags.Left | TextFormatFlags.VerticalCenter;
using (var family = new FontFamily(this.GetItemText(Items[e.Index])))
using (var font = new Font(family, 10F, FontStyle.Regular, GraphicsUnit.Point)) {
TextRenderer.DrawText(e.Graphics, family.Name, font, e.Bounds, this.ForeColor, flags);
}
e.DrawFocusRectangle();
base.OnDrawItem(e);
}
protected override void OnMeasureItem(MeasureItemEventArgs e) {
base.OnMeasureItem(e);
e.ItemHeight = this.Font.Height + 4;
}
private void GetFontFamilies()
{
this.fontList = new List<FontObject>();
fontList.AddRange(FontFamily.Families
.Where(f => f.IsStyleAvailable(FontStyle.Regular))
.Select(f => new FontObject(f)).ToArray());
this.DisplayMember = "FamilyName";
this.ValueMember = "EmHeight";
this.DataSource = fontList;
}
public FontFamily GetSelectedFontFamily()
{
if (this.SelectedIndex < 0) return null;
return FontObject.GetSelectedFontFamily((FontObject)this.SelectedItem);
}
public Font GetSelectedFont(float sizeInPoints, FontStyle style)
{
if (this.SelectedIndex < 0) return null;
return FontObject.GetSelectedFont((FontObject)this.SelectedItem, sizeInPoints, style);
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg) {
case WM_FONTCHANGE: // The System Font pool has changed
GetFontFamilies();
break;
}
}
public class FontObject
{
public FontObject(FontFamily family) { GetFontFamilyInfo(family); }
public string FamilyName { get; set; }
public int EmHeight { get; set; }
public int CellAscent { get; set; }
public int CellDescent { get; set; }
public int LineSpacing { get; set; }
private void GetFontFamilyInfo(FontFamily family)
{
this.FamilyName = family.Name;
this.EmHeight = family.GetEmHeight(FontStyle.Regular);
this.CellAscent = family.GetCellAscent(FontStyle.Regular);
this.CellDescent = family.GetCellDescent(FontStyle.Regular);
this.LineSpacing = family.GetLineSpacing(FontStyle.Regular);
}
internal static FontFamily GetSelectedFontFamily(FontObject fobj)
=> new FontFamily(fobj.FamilyName);
internal static Font GetSelectedFont(FontObject fobj, float sizeInPoints, FontStyle style)
=> new Font(GetSelectedFontFamily(fobj), sizeInPoints, style);
public override string ToString()
{
var sb = new StringBuilder();
sb.AppendLine(this.FamilyName);
sb.AppendLine($"Em Height: {this.EmHeight}");
sb.AppendLine($"Cell Ascent: {this.CellAscent}");
sb.AppendLine($"Cell Descent: {this.CellDescent}");
sb.AppendLine($"Line Spacing: {this.LineSpacing}");
return sb.ToString();
}
}
}