Detecting if a given font style exists in C#
Describes how to use the FontFamily object to determine if a given font style exists
This article shows how to use the built in ownerdraw functionality of a standard Windows Forms ComboBox control to display a WYSIWYG font list.
To start, we'll create a new class, and inherit this from the
ComboBox
control.
We are going to use variable ownerdraw for this sample, as it
gives us a little more flexibility without having to mess around
with the ItemHeight
property. We'll add a constructor, and set
the ownerdraw mode here. Also, we'll add a new version of the
DrawMode
property, which we'll both hide and disable the value
persistence. We always want the font list to be sorted, so for
now we'll do the same with the Sorted
property.
public FontComboBox()
{
this.DrawMode = DrawMode.OwnerDrawVariable;
this.Sorted = true;
}
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), EditorBrowsable(EditorBrowsableState.Never)]
public new DrawMode DrawMode
{
get { return base.DrawMode; }
set { base.DrawMode = value; }
}
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), EditorBrowsable(EditorBrowsableState.Never)]
public new bool Sorted
{
get { return base.Sorted; }
set { base.Sorted = value; }
}
In order to avoid continuously creating and destroying font
objects, we'll create a internal cache of fonts. When it's time
to draw the control, the cache will be queried - if the
requested font exists, it will be returned, otherwise the font
will be created and added to the cache. This will be done via
the GetFont
method below.
protected virtual Font GetFont(string fontFamilyName)
{
lock (_fontCache)
{
if (!_fontCache.ContainsKey(fontFamilyName))
{
Font font;
font = this.GetFont(fontFamilyName, FontStyle.Regular);
if (font == null)
font = this.GetFont(fontFamilyName, FontStyle.Bold);
if (font == null)
font = this.GetFont(fontFamilyName, FontStyle.Italic);
if (font == null)
font = this.GetFont(fontFamilyName, FontStyle.Bold | FontStyle.Italic);
_fontCache.Add(fontFamilyName, font);
}
}
return _fontCache[fontFamilyName];
}
protected virtual Font GetFont(string fontFamilyName, FontStyle fontStyle)
{
Font font;
try
{
font = new Font(fontFamilyName, this.PreviewFontSize, fontStyle);
}
catch
{
font = null;
}
return font;
}
Note: Whilst testing the control, I discovered that some of the fonts installed on the development system only had bold or italic styles. The original version of this method, which always attempts to get the normal style would cause a crash.
Due to this, I changed the method to try and access the normal style, and if that failed, to try the other styles. Perhaps there is a better way of doing this, but I leave that as an exercise for the future.
As we don't want the font size of the dropdown list to necessarily match that of the display/edit portion, we'll add a new property named `PreviewFontSize*.
public event EventHandler PreviewFontSizeChanged;
[Category("Appearance"), DefaultValue(12)]
public int PreviewFontSize
{
get { return _previewFontSize; }
set
{
_previewFontSize = value;
this.OnPreviewFontSizeChanged(EventArgs.Empty);
}
}
protected virtual void OnPreviewFontSizeChanged(EventArgs e)
{
if (PreviewFontSizeChanged != null)
PreviewFontSizeChanged(this, e);
this.CalculateLayout();
}
When certain actions occur, such as this property changing, we want to calculate the height of items in the dropdown list.
private void CalculateLayout()
{
this.ClearFontCache();
using (Font font = new Font(this.Font.FontFamily, (float)this.PreviewFontSize))
{
Size textSize;
textSize = TextRenderer.MeasureText("yY", font);
_itemHeight = textSize.Height + 2;
}
}
In order to avoid slowing the control down without reason, we'll delay loading the list of font families until there is a reason, either when the control's text has changed, or when the control gets focus.
This will be done by creating a LoadFontFamilies
method which
will be called by overriding OnGotFocus
and OnTextChanged
.
public virtual void LoadFontFamilies()
{
if (this.Items.Count == 0)
{
Cursor.Current = Cursors.WaitCursor;
foreach (FontFamily fontFamily in FontFamily.Families)
this.Items.Add(fontFamily.Name);
Cursor.Current = Cursors.Default;
}
}
protected override void OnGotFocus(EventArgs e)
{
this.LoadFontFamilies();
base.OnGotFocus(e);
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
if (this.Items.Count == 0)
{
int selectedIndex;
this.LoadFontFamilies();
selectedIndex = this.FindStringExact(this.Text);
if (selectedIndex != -1)
this.SelectedIndex = selectedIndex;
}
}
Drawing an overdraw ComboBox is done by overriding the
OnDrawItem
method. However, as we have told the control we are
doing variable sized ownerdraw, we also need to override
OnMeasureItem
. This method allows us to define the size for
each item, or in the case of this control to set the height of
each item to match the pixel height calculated for the value of
the PreviewFontSize
property.
protected override void OnMeasureItem(MeasureItemEventArgs e)
{
base.OnMeasureItem(e);
if (e.Index > -1 && e.Index < this.Items.Count)
{
e.ItemHeight = _itemHeight;
}
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
base.OnDrawItem(e);
if (e.Index > -1 && e.Index < this.Items.Count)
{
e.DrawBackground();
if ((e.State & DrawItemState.Focus) == DrawItemState.Focus)
e.DrawFocusRectangle();
using (SolidBrush textBrush = new SolidBrush(e.ForeColor))
{
string fontFamilyName;
fontFamilyName = this.Items[e.Index].ToString();
e.Graphics.DrawString(fontFamilyName, this.GetFont(fontFamilyName), textBrush, e.Bounds, _stringFormat);
}
}
}
The actual drawing is very simple - we use the built in drawing
for the background and focus rectangle, and then use the
Graphics
object to draw the text using the GetFont
method
explained above.
You might notice that the above code is referencing a previously
defined StringFormat
object. This is created using the below
method.
protected virtual void CreateStringFormat()
{
if (_stringFormat != null)
_stringFormat.Dispose();
_stringFormat = new StringFormat(StringFormatFlags.NoWrap);
_stringFormat.Trimming = StringTrimming.EllipsisCharacter;
_stringFormat.HotkeyPrefix = HotkeyPrefix.None;
_stringFormat.Alignment = StringAlignment.Near;
_stringFormat.LineAlignment = StringAlignment.Center;
if (this.IsUsingRTL(this))
_stringFormat.FormatFlags |= StringFormatFlags.DirectionRightToLeft;
}
private bool IsUsingRTL(Control control)
{
bool result;
if (control.RightToLeft == RightToLeft.Yes)
result = true;
else if (control.RightToLeft == RightToLeft.Inherit && control.Parent != null)
result = IsUsingRTL(control.Parent);
else
result = false;
return result;
}
As we are creating a large number of objects, we need to clean
these up in the controls Dispose
method.
protected override void Dispose(bool disposing)
{
this.ClearFontCache();
if (_stringFormat != null)
_stringFormat.Dispose();
base.Dispose(disposing);
}
protected virtual void ClearFontCache()
{
if (_fontCache != null)
{
foreach (string key in _fontCache.Keys)
_fontCache[key].Dispose();
_fontCache.Clear();
}
}
The control as it stands is a basic example, and depending on your application's needs, it could be further expanded. For example:
The full source of the class is below.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Text;
using System.Windows.Forms;
namespace Cyotek.Windows.Forms
{
public class FontComboBox : ComboBox
{
#region Private Member Declarations
private Dictionary<string, Font> _fontCache;
private int _itemHeight;
private int _previewFontSize;
private StringFormat _stringFormat;
public FontComboBox()
{
_fontCache = new Dictionary<string, Font>();
this.DrawMode = DrawMode.OwnerDrawVariable;
this.Sorted = true;
this.PreviewFontSize = 12;
this.CalculateLayout();
this.CreateStringFormat();
}
public event EventHandler PreviewFontSizeChanged;
protected override void Dispose(bool disposing)
{
this.ClearFontCache();
if (_stringFormat != null)
_stringFormat.Dispose();
base.Dispose(disposing);
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
base.OnDrawItem(e);
if (e.Index > -1 && e.Index < this.Items.Count)
{
e.DrawBackground();
if ((e.State & DrawItemState.Focus) == DrawItemState.Focus)
e.DrawFocusRectangle();
using (SolidBrush textBrush = new SolidBrush(e.ForeColor))
{
string fontFamilyName;
fontFamilyName = this.Items[e.Index].ToString();
e.Graphics.DrawString(fontFamilyName, this.GetFont(fontFamilyName), textBrush, e.Bounds, _stringFormat);
}
}
}
protected override void OnFontChanged(EventArgs e)
{
base.OnFontChanged(e);
this.CalculateLayout();
}
protected override void OnGotFocus(EventArgs e)
{
this.LoadFontFamilies();
base.OnGotFocus(e);
}
protected override void OnMeasureItem(MeasureItemEventArgs e)
{
base.OnMeasureItem(e);
if (e.Index > -1 && e.Index < this.Items.Count)
{
e.ItemHeight = _itemHeight;
}
}
protected override void OnRightToLeftChanged(EventArgs e)
{
base.OnRightToLeftChanged(e);
this.CreateStringFormat();
}
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
if (this.Items.Count == 0)
{
int selectedIndex;
this.LoadFontFamilies();
selectedIndex = this.FindStringExact(this.Text);
if (selectedIndex != -1)
this.SelectedIndex = selectedIndex;
}
}
public virtual void LoadFontFamilies()
{
if (this.Items.Count == 0)
{
Cursor.Current = Cursors.WaitCursor;
foreach (FontFamily fontFamily in FontFamily.Families)
this.Items.Add(fontFamily.Name);
Cursor.Current = Cursors.Default;
}
}
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), EditorBrowsable(EditorBrowsableState.Never)]
public new DrawMode DrawMode
{
get { return base.DrawMode; }
set { base.DrawMode = value; }
}
[Category("Appearance"), DefaultValue(12)]
public int PreviewFontSize
{
get { return _previewFontSize; }
set
{
_previewFontSize = value;
this.OnPreviewFontSizeChanged(EventArgs.Empty);
}
}
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), EditorBrowsable(EditorBrowsableState.Never)]
public new bool Sorted
{
get { return base.Sorted; }
set { base.Sorted = value; }
}
private void CalculateLayout()
{
this.ClearFontCache();
using (Font font = new Font(this.Font.FontFamily, (float)this.PreviewFontSize))
{
Size textSize;
textSize = TextRenderer.MeasureText("yY", font);
_itemHeight = textSize.Height + 2;
}
}
private bool IsUsingRTL(Control control)
{
bool result;
if (control.RightToLeft == RightToLeft.Yes)
result = true;
else if (control.RightToLeft == RightToLeft.Inherit && control.Parent != null)
result = IsUsingRTL(control.Parent);
else
result = false;
return result;
}
protected virtual void ClearFontCache()
{
if (_fontCache != null)
{
foreach (string key in _fontCache.Keys)
_fontCache[key].Dispose();
_fontCache.Clear();
}
}
protected virtual void CreateStringFormat()
{
if (_stringFormat != null)
_stringFormat.Dispose();
_stringFormat = new StringFormat(StringFormatFlags.NoWrap);
_stringFormat.Trimming = StringTrimming.EllipsisCharacter;
_stringFormat.HotkeyPrefix = HotkeyPrefix.None;
_stringFormat.Alignment = StringAlignment.Near;
_stringFormat.LineAlignment = StringAlignment.Center;
if (this.IsUsingRTL(this))
_stringFormat.FormatFlags |= StringFormatFlags.DirectionRightToLeft;
}
protected virtual Font GetFont(string fontFamilyName)
{
lock (_fontCache)
{
if (!_fontCache.ContainsKey(fontFamilyName))
{
Font font;
font = this.GetFont(fontFamilyName, FontStyle.Regular);
if (font == null)
font = this.GetFont(fontFamilyName, FontStyle.Bold);
if (font == null)
font = this.GetFont(fontFamilyName, FontStyle.Italic);
if (font == null)
font = this.GetFont(fontFamilyName, FontStyle.Bold | FontStyle.Italic);
if (font == null)
font = (Font)this.Font.Clone();
_fontCache.Add(fontFamilyName, font);
}
}
return _fontCache[fontFamilyName];
}
protected virtual Font GetFont(string fontFamilyName, FontStyle fontStyle)
{
Font font;
try
{
font = new Font(fontFamilyName, this.PreviewFontSize, fontStyle);
}
catch
{
font = null;
}
return font;
}
protected virtual void OnPreviewFontSizeChanged(EventArgs e)
{
if (PreviewFontSizeChanged != null)
PreviewFontSizeChanged(this, e);
this.CalculateLayout();
}
}
}
Like what you're reading? Perhaps you like to buy us a coffee?
# DotNetKicks.com
# DotNetShoutout
# wmjordan
# Richard Moss
# Roy
# Richard Moss