WPF 字体选择器(带颜色)





5.00/5 (12投票s)
由于对可用的 WPF 字体选择器不满意,我决定自己编写一个(好吧,几乎...)
介绍
令人惊讶的是,Windows Presentation Framework (WPF) 没有任何预定义的对话框窗体,例如,默认工具箱中没有字体对话框或颜色对话框。由于我需要一个,所以我决定自己编写一个。好吧,也不是完全自己编写的:正如任何聪明的程序员都知道的那样,现在没有任何东西是从头开始构建的。所以,我必须从一些...开始
致谢
我的 WPF 字体选择器 基于对两项不同工作的混音:XAML 中的字体选择器和 颜色组合框 控件。第一个是 Cheng Norris 的 纯 XAML 字体选择器 [1]。第二个是 Muhammed Sudheer 的 使用 WPF 的颜色组合框选择器 [2]。
如果没有这两个值得注意的软件,我的 WPF 字体选择器 永远不可能问世。
先决条件
我首先需要一个可插拔的 WPF 库,这样我就可以轻松地将其添加到我的任何项目中。我决定构建一个 "WPF 用户控件",采用两个不同的可重用控件(颜色选择器和字体选择器)和类似 Windows 窗体任务对话框的窗口对话框的形式。
字体选择器 必须允许用户选择具有所有选项的字体:字体系列、样式、大小、粗细和拉伸。此外,字体选择器 必须允许用户选择字体的前景色。
一个重要的前提是,在信用中引用的作品中缺少的一点是,对话框窗体应该能够使用 "当前 "字体进行初始化。这很容易理解为什么:典型的情况是用户选择字体,然后她不满意并再次单击以显示对话框:她不希望再次出现 "默认 "字体,她希望修改她在第一次尝试中选择的字体。
使用代码
包含字体选择器的 DLL 库名为 ColorFont,它由两个 XAML 用户控件组成
- ColorFontChooser
- ColorPicker
- ColorFontDialog
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 日:初始发布。