Click here to Skip to main content
15,868,164 members
Articles / Desktop Programming / WPF

A WPF Font Picker (with Color)

Rate me:
Please Sign up or sign in to vote.
5.00/5 (12 votes)
19 Jan 2013CPOL4 min read 86.9K   3.6K   34   5
Unsatisfied by the WPF Font Pickers available, I decided to write one on my own (well, almost...)

Image 1 

Introduction 

Surprisingly, the Windows Presentation Framework (WPF) comes with no predefined dialog forms, so for instance there is no Font Dialog or Color Dialog in the default toolbox. Since I needed one, I decided to write one on my own. Well, not entirely on my own: as any smart programmer knows, nothing is built from scratch anymore. So I must begin with some...

Credits

My WPF Font Picker is based on the remix of two different works: a Font Chooser in XAML and a Color Combobox control. The first one is the Pure XAML Font Chooser by Cheng Norris [1]. The second one is the Color Combobox Picker by Muhammed Sudheer [2].

Without these two noticeable pieces of software, my WPF Font Picker would have never seen the light.

Requisites

I needed in first place a pluggable WPF library, so that I can easily add it to any of my projects. I decided to build a "WPF User Control" in the form of two different reusable controls (the Color Picker and the Font Picker) and a Window Dialog in the fashion of Windows Forms Task Dialogs.

The Font Picker had to allow the user to choose a font with every option: family, style, size, weight and stretch. Also, the Font Picker had to let the user choose the font foreground color. 

An important requisite, that was missing from the works quoted in the credits, was that the dialog form should be able to be initialized with a "current" font. It is easy to understand why: the typical scenario is that the user selects font, then she is not satisfied and clicks again to display the dialog: she does not want a "default" font again, she wants to modify the font she selected in the first attempt. 

Using the code

The DLL library that contains Font Picker is called ColorFont and it is composed by two XAML User Controls:

  • ColorFontChooser
  • ColorPicker
and by one XAML Window (the Dialog):
  • ColorFontDialog
so that it can be easily referenced in a .NET project and be used as a standard dialog form:
C#
ColorFont.ColorFontDialog fntDialog = new ColorFont.ColorFontDialog();
fntDialog.Owner = this;
fntDialog.Font = FontInfo.GetControlFont(this.txtText);
if (fntDialog.ShowDialog() == true)
{
    FontInfo selectedFont = fntDialog.Font;
    if (selectedFont != null)
    {
        FontInfo.ApplyFont(this.txtText, selectedFont);
    }
}

where FontInfo can be seen as an equivalent of FileInfo class, that is a class holding all the properties of a Font (color included):

C#
public class FontInfo
{
    public FontFamily Family { get; set; }
    public double Size { get; set; }
    public FontStyle Style { get; set; }
    public FontStretch Stretch { get; set; }
    public FontWeight Weight { get; set; }
    public SolidColorBrush BrushColor { get; set; }

    #region Static Utils

    public static string TypefaceToString(FamilyTypeface ttf)
    {
        StringBuilder sb = new StringBuilder(ttf.Stretch.ToString());
        sb.Append("-");
        sb.Append(ttf.Weight.ToString());
        sb.Append("-");
        sb.Append(ttf.Style.ToString());
        return sb.ToString();
    }

    public static void ApplyFont(Control control, FontInfo font)
    {
        control.FontFamily = font.Family;
        control.FontSize = font.Size;
        control.FontStyle = font.Style;
        control.FontStretch = font.Stretch;
        control.FontWeight = font.Weight;
        control.Foreground = font.BrushColor;
    }

    public static FontInfo GetControlFont(Control control)
    {
        FontInfo font = new FontInfo();
        font.Family = control.FontFamily;
        font.Size = control.FontSize;
        font.Style = control.FontStyle;
        font.Stretch = control.FontStretch;
        font.Weight = control.FontWeight;
        font.BrushColor = (SolidColorBrush)control.Foreground;
        return font;
    }
    #endregion

    public FontInfo()
    {
    }

    public FontInfo(FontFamily fam, double sz, FontStyle style, 
                    FontStretch strc, FontWeight weight, SolidColorBrush c)
    {
        this.Family = fam;
        this.Size = sz;
        this.Style = style;
        this.Stretch = strc;
        this.Weight = weight;
        this.BrushColor = c;
    }

    public FontColor Color
    {
        get
        {
            return AvailableColors.GetFontColor(this.BrushColor);
        }
    }

    public FamilyTypeface Typeface
    {
        get
        {
            FamilyTypeface ftf = new FamilyTypeface();
            ftf.Stretch = this.Stretch;
            ftf.Weight = this.Weight;
            ftf.Style = this.Style;
            return ftf;
        }
    }

}

Color Picker User Control

The Color Picker User Control is nothing but a custom drop down list exposing the System.Windows.Media.Colors enumeration. The XAML part is heavily taken from [2], so I'd send you there for the details.

I just added a custom event that the control raises each time a new color is selected, bubbling the original 'DropDownClosed' event; and a Property, "SelectedColor", that is able to communicate the color picked by the user. 

C#
public partial class ColorPicker : UserControl
{
    private ColorPickerViewModel viewModel;

    public static readonly RoutedEvent ColorChangedEvent = 
        EventManager.RegisterRoutedEvent(
           "ColorChanged", RoutingStrategy.Bubble, 
           typeof(RoutedEventHandler), typeof(ColorPicker));

    public static readonly DependencyProperty SelectedColorProperty = 
        DependencyProperty.Register(
           "SelectedColor", typeof(FontColor), 
           typeof(ColorPicker), new UIPropertyMetadata(null));

    public ColorPicker()
    {
        InitializeComponent();
        this.viewModel = new ColorPickerViewModel();
        this.DataContext = this.viewModel;
    }

    public event RoutedEventHandler ColorChanged
    {
        add { AddHandler(ColorChangedEvent, value); }
        remove { RemoveHandler(ColorChangedEvent, value); }
    }

    public FontColor SelectedColor
    {
        get 
        {
            FontColor fc = (FontColor)this.GetValue(SelectedColorProperty);
            if (fc == null)
            {
                fc = AvailableColors.GetFontColor("Black");
            }
            return fc;
        }

        set 
        {
            this.viewModel.SelectedFontColor = value;
            SetValue(SelectedColorProperty, value);
        }
    }

    private void RaiseColorChangedEvent()
    {
        RoutedEventArgs newEventArgs = new RoutedEventArgs(ColorPicker.ColorChangedEvent);
        RaiseEvent(newEventArgs);
    }

    private void superCombo_DropDownClosed(object sender, EventArgs e)
    {
        this.SetValue(SelectedColorProperty, this.viewModel.SelectedFontColor);
        this.RaiseColorChangedEvent();
    }

    private void superCombo_Loaded(object sender, RoutedEventArgs e)
    {
        this.SetValue(SelectedColorProperty, this.viewModel.SelectedFontColor);
    }

}

Font Chooser User Control

As I said before, the Font Chooser User Control is a little modification of [1]. The original control did not sort the list of fonts. I added this feature by modifying the CollectionViewSource resource as follows:

XML
<CollectionViewSource Source="{Binding Source={x:Static Fonts.SystemFontFamilies}}" 
 x:Key="familyCollection">
     <CollectionViewSource.SortDescriptions>
           <scm:SortDescription PropertyName="Source" Direction="Ascending" />
     </CollectionViewSource.SortDescriptions>
 </CollectionViewSource> 

where the "scm:" namespace is defined as:

XML
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase" 

In this case too, I added some logic to handle the "ColorChanged" event defined above, and to expose the selected font as an object of class FontInfo.

C#
public partial class ColorFontChooser : UserControl
{
    public ColorFontChooser()
    {
        InitializeComponent();
        this.colorPicker.SelectedColor = new SolidColorBrush(Colors.Black);
        this.txtSampleText.IsReadOnly = true;
    }

    public FontInfo SelectedFont
    {
        get
        {
            return new FontInfo(this.txtSampleText.FontFamily,
                this.txtSampleText.FontSize,
                this.txtSampleText.FontStyle,
                this.txtSampleText.FontStretch,
                this.txtSampleText.FontWeight,
                this.colorPicker.SelectedColor);
        }
    }

    private void colorPicker_ColorChanged(object sender, RoutedEventArgs e)
    {
        this.txtSampleText.Foreground = this.colorPicker.SelectedColor;
    }
} 

Dialog Window

The Font Picker is served as a normal Dialog window, returning "true" if the user presses "OK". In this case the programmer can fetch the selected color by retrieving the property "Font" of the Dialog class, and apply it to any WPF Control by using the utility static method "ApplyFont" defined in the FontInfo class.

The following is the XAML source code of the dialog window:

XML
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:ColorFont" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="ColorFont.ColorFontDialog"
        Title="Select Font" Height="380" Width="592" WindowStartupLocation="CenterOwner" ResizeMode="NoResize" Icon="Resources/colorfont_icon.png" Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" Loaded="Window_Loaded_1">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="40" />
        </Grid.RowDefinitions>
        <local:ColorFontChooser x:Name="colorFontChooser" Grid.Row="0" Margin="0,0,6,0" d:LayoutOverrides="Width, Height" />
        <Grid Grid.Row="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="406"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <StackPanel Grid.Column="1" Orientation="Horizontal" d:LayoutOverrides="Margin">
                <Button x:Name="btnOk" Width="85" Margin="4,8" Content="OK" IsDefault="True" Click="btnOk_Click"/>
                <Button Width="70" Margin="4,8" Content="Cancel" IsCancel="True"/>
            </StackPanel>
        </Grid>
    </Grid>
</Window>

Notice how the Window makes use of the User Controls defined above, as objects belonging to the "local:" namespace.

The C# code behind the XAML is trivial, as it handles the OK button setting the Font property that exposes the font selected by the user.

The interesting part regards how the dialog "synchronizes" its controls to display a font suggested by the user. First, the font is set calling the Font property. Then, when the window is loaded, the Font specifications are stored in the FontInfo class, and the color, the family, the typeface and the size of the specified font are passed to the controls. 

C#
public partial class ColorFontDialog : Window
{
    private FontInfo selectedFont;

    public ColorFontDialog()
    {
        this.selectedFont = null; // Default
        InitializeComponent();
    }

    public FontInfo Font
    {
        get
        {
            return this.selectedFont;
        }

        set
        {
            FontInfo fi = value;
            this.selectedFont = fi;
        }
    }

    private void SyncFontName()
    {
        string fontFamilyName = this.selectedFont.Family.Source;
        int idx = 0;
        foreach (var item in this.colorFontChooser.lstFamily.Items)
        {
            string itemName = item.ToString();
            if (fontFamilyName == itemName)
            {
                break;
            }
            idx++;
        }
        this.colorFontChooser.lstFamily.SelectedIndex = idx;
        this.colorFontChooser.lstFamily.ScrollIntoView(this.colorFontChooser.lstFamily.Items[idx]);
    }

    private void SyncFontSize()
    {
        double fontSize = this.selectedFont.Size;
        this.colorFontChooser.fontSizeSlider.Value = fontSize;
    }

    private void SyncFontColor()
    {        
        int colorIdx = AvailableColors.GetFontColorIndex(this.Font.Color);
        this.colorFontChooser.colorPicker.superCombo.SelectedIndex = colorIdx;
        // Fix by Scout.DS_at_gmail.com. Thanks!
        this.colorFontChooser.txtSampleText.Foreground = this.Font.Color.Brush;
        this.colorFontChooser.colorPicker.superCombo.BringIntoView();
    }

    private void SyncFontTypeface()
    {
        string fontTypeFaceSb = FontInfo.TypefaceToString(this.selectedFont.Typeface);
        int idx = 0;
        foreach (var item in this.colorFontChooser.lstTypefaces.Items)
        {
            FamilyTypeface face = item as FamilyTypeface;
            if (fontTypeFaceSb == FontInfo.TypefaceToString(face))
            {
                break;
            }
            idx++;
        }
        this.colorFontChooser.lstTypefaces.SelectedIndex = idx;
    }

    private void btnOk_Click(object sender, RoutedEventArgs e)
    {
        this.Font = this.colorFontChooser.SelectedFont;
        this.DialogResult = true;
    }

    private void Window_Loaded_1(object sender, RoutedEventArgs e)
    {
        this.SyncFontColor();
        this.SyncFontName();
        this.SyncFontSize();
        this.SyncFontTypeface();
    }
}

Conclusions

I personally find rather annoying that the WPF base library comes with no predefined dialog forms, and that even the basic File Dialog is a wrapper to the Windows Forms one. Is there a reason for that? I frankly do not know, anyway I am looking forward to a new WPF version with the dialogs in there. For the time being, my humble Font Picker could be of some use.

History

May 4th, 2012: Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Italy Italy
Alessio Saltarin is Certified IT Architect and Senior Software Developer. He writes articles and book reviews for some italian magazines. He is the author of "Ruby e Rails" book.

Comments and Discussions

 
QuestionWant to attribute code to you in project we are working on. Pin
Andrew Stampor4-Sep-12 3:09
Andrew Stampor4-Sep-12 3:09 
QuestionColor not retained after second call to dialog Pin
gwrowe19-Jun-12 9:13
gwrowe19-Jun-12 9:13 
If you open the dialog and select a color, the text in the text box changes to the desired color. However, if you open the dialog again and click OK (even if you don't change any of the properties of the font), the text in the text box reverts to black. This appears to be because ColorPicker.SelectedColorProperty is not initialized when the dialog opens. I managed to fix this by adding the line

C#
this.colorFontChooser.colorPicker.SetValue(ColorPicker.SelectedColorProperty, colorFontChooser.colorPicker.viewModel.SelectedFontColor);


after line 63 in ColorFontDialog.xaml.cs, but this requires making both the ColorPickerViewModel class and the viewModel field in ColorPicker public, which isn't the prettiest solution.
AnswerRe: Color not retained after second call to dialog Pin
Alessio Saltarin29-Jun-12 2:58
Alessio Saltarin29-Jun-12 2:58 
QuestionDownload Pin
Member 12149095-Jun-12 0:10
Member 12149095-Jun-12 0:10 
AnswerRe: Download Pin
Alessio Saltarin12-Jun-12 1:06
Alessio Saltarin12-Jun-12 1:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.