65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF 字体选择器(带颜色)

starIconstarIconstarIconstarIconstarIcon

5.00/5 (12投票s)

2012年5月4日

CPOL

4分钟阅读

viewsIcon

90560

downloadIcon

3686

由于对可用的 WPF 字体选择器不满意,我决定自己编写一个(好吧,几乎...)

 

介绍 

令人惊讶的是,Windows Presentation Framework (WPF) 没有任何预定义的对话框窗体,例如,默认工具箱中没有字体对话框颜色对话框。由于我需要一个,所以我决定自己编写一个。好吧,也不是完全自己编写的:正如任何聪明的程序员都知道的那样,现在没有任何东西是从头开始构建的。所以,我必须从一些...开始

致谢

我的 WPF 字体选择器 基于对两项不同工作的混音:XAML 中的字体选择器颜色组合框 控件。第一个是 Cheng Norris纯 XAML 字体选择器 [1]。第二个是 Muhammed Sudheer使用 WPF 的颜色组合框选择器 [2]

如果没有这两个值得注意的软件,我的 WPF 字体选择器 永远不可能问世。

先决条件

我首先需要一个可插拔的 WPF 库,这样我就可以轻松地将其添加到我的任何项目中。我决定构建一个 "WPF 用户控件",采用两个不同的可重用控件(颜色选择器字体选择器)和类似 Windows 窗体任务对话框的窗口对话框的形式。

字体选择器 必须允许用户选择具有所有选项的字体:字体系列、样式、大小、粗细和拉伸。此外,字体选择器 必须允许用户选择字体的前景色。

一个重要的前提是,在信用中引用的作品中缺少的一点是,对话框窗体应该能够使用 "当前 "字体进行初始化。这很容易理解为什么:典型的情况是用户选择字体,然后她不满意并再次单击以显示对话框:她不希望再次出现 "默认 "字体,她希望修改她在第一次尝试中选择的字体。

使用代码

包含字体选择器的 DLL 库名为 ColorFont,它由两个 XAML 用户控件组成

  • ColorFontChooser
  • ColorPicker
以及一个 XAML 窗口(对话框)
  • ColorFontDialog
这样就可以在 .NET 项目中轻松引用它,并将其用作标准对话框窗体
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);
    }
}

其中 FontInfo 可以被视为 FileInfo 类的等价物,即一个保存字体所有属性(包括颜色)的类

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;
        }
    }

}

颜色选择器用户控件

颜色选择器用户控件 仅仅是一个自定义下拉列表,公开 System.Windows.Media.Colors 枚举。XAML 部分很大程度上取自 [2],所以我会让你去那里了解细节。

我只是添加了一个自定义事件,该控件每次选择新颜色时都会引发该事件,冒泡原始的 'DropDownClosed' 事件;以及一个属性 "SelectedColor",它能够传达用户选择的颜色。

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);
    }

}

字体选择器用户控件

正如我之前所说,字体选择器用户控件[1] 的一个小修改。原始控件没有对字体列表进行排序。我通过如下修改 CollectionViewSource 资源添加了此功能

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

其中 "scm:" 命名空间定义为

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

在这种情况下,我也添加了一些逻辑来处理上面定义的 "ColorChanged" 事件,并将选定的字体公开为 FontInfo 类的对象。

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;
    }
} 

对话框窗口

字体选择器 作为普通对话框窗口提供,如果用户按下 "确定" 则返回 "true"。在这种情况下,程序员可以通过检索对话框类的 "Font" 属性来获取选定的颜色,并通过使用在 FontInfo 类中定义的实用静态方法 "ApplyFont" 将其应用于任何 WPF 控件。

以下是对话框窗口的 XAML 源代码

<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>

请注意窗口如何使用上面定义的用户控件,作为属于 "local:" 命名空间的对象。

XAML 后的 C# 代码很简单,因为它处理了“确定”按钮,设置了 Font 属性,该属性公开了用户选择的字体。

有趣的部分涉及对话框如何 "同步" 其控件以显示用户建议的字体。首先,通过调用 Font 属性设置字体。然后,当窗口加载时,Font 规范存储在 FontInfo 类中,并且指定字体的颜色、系列、字样和大小将传递给控件。

 
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();
    }
}

结论

我个人觉得 WPF 基础库没有任何预定义的对话框窗体非常烦人,即使基本的文件对话框也是 Windows 窗体的包装器。这是有原因的吗?我坦率地说,我不知道,无论如何,我期待一个包含对话框的新 WPF 版本。目前,我谦虚的字体选择器可能会有一些用处。

历史

2012 年 5 月 4 日:初始发布。

© . All rights reserved.