如何设置用作字体选择器的 ComboBox 的 SelectedValue?

How to set the SelectedValue of a ComboBox used as Font selector?

我有一个组合框,我在里面填充了字体。我使用的方法是 link。我将在这里分享那个问题的答案。

public YourForm()
{
    InitializeComponent();

    ComboBoxFonts.DrawItem += ComboBoxFonts_DrawItem;           
    ComboBoxFonts.DataSource = System.Drawing.FontFamily.Families.ToList();
}

private void ComboBoxFonts_DrawItem(object sender, DrawItemEventArgs e)
{
    var comboBox = (ComboBox)sender;
    var fontFamily = (FontFamily)comboBox.Items[e.Index];
    var font = new Font(fontFamily, comboBox.Font.SizeInPoints);

    e.DrawBackground();
    e.Graphics.DrawString(font.Name, font, Brushes.Black, e.Bounds.X, e.Bounds.Y);
}

现在我只对这段代码做了 1 处更改,那就是:

cmbFonts.DrawMode = DrawMode.OwnerDrawFixed;

加载字体没有问题,它可以正常工作,但我尝试在我的表单加载时设置 selectedvalue。例如,我尝试设置名为“arial”的字体。为此,我使用了这个:

var dataSource = cmbFonts.DataSource as List<FontFamily>;
int res = -1;
try
{
  res = dataSource.IndexOf(new FontFamily(StaticVariables.FontName));
}
catch { }
if (res != -1)
  cmbFonts.SelectedIndex = res;

现在当我这样做时我得到 System.ArgumentOutOfRangeException 错误,因为我没有添加任何项目到 Combobox 我绑定了 DataSource 因此当我尝试设置 SelectedIndex我得到这个错误,我知道,我也试过这个:

cmbFonts.SelectedValue = StaticVariables.FontName;

但是当我 运行 我的代码在 Visual studio 中带有断点时,我发现我的 SelectedValue 从未改变。在执行该行之前我看到了 null,在执行之后我仍然在 SelectedValue 中看到了 null,我检查了我的 StaticVariables.FontName 变量并且字体显示在那里。

我也尝试过使用 combobox.Text 属性 但没有运气,就像 SelectedValue,之前是空字符串,在我用断点跳过它之后仍然是一样的。

长话短说: 我尝试 select 在我填充了 DataSource

的组合框中加载表单上的项目

这是一个自定义的所有者绘制的 ComboBox class(此处命名为 FontListCombo)显示 compatible系统字体系列,代表每个 ComboBox Item 使用 FontFamily 名称作为 Item Text 和相应的字体来绘制 Item 的文本(实际上是 classic ComboBox Font 选择器)。

自定义控件会在 运行 时自动填充系统中的可用字体列表。它还对 WM_FONTCHANGE 消息做出反应(当系统字体池更改时广播;例如,将字体添加到 Fonts 文件夹或从中删除),以更新字体列表并反映更改(也避免尝试使用不再存在的字体)。

ComboBox Items 的文本是使用 TextRenderer.DrawText() instead of Graphics.DrawString() 绘制的,因为前者在此上下文中提供更清晰的结果。

ComboBox.Items 集合由 FontObjectclass 个对象的集合表示,publicclass 它存储了每个 FontFamily 的一些属性,并且还公开了一些内部使用的静态方法 return Font 对象或 FontFamily 对象,调用自定义 ComboBox 的相应 public 方法:

  • GetSelectedFont(SizeInPoints, FontStyle) 方法 return 来自当前 ComboBox.SelectedItem 的 Font 对象。
  • GetSelectedFontFamily() return 来自当前 ComboBox.SelectedItem 的 FontFamily 对象。

它还会覆盖 ToString(),成为 return 其属性值的摘要。

这种对象容器更适合这里:将 FontFamily 对象存储为 ConboBox 项肯定会产生不同类型的问题,最明显和最悲惨的是一些 FontFamily 对象随着时间的推移甚至变得无效在它们被存储之后。这些对象一开始并不是要永久存储的,所以这并不奇怪。

如示例所示,从ComboBox.SelecteItem获取当前的Font和FontFamily:
(这里,ComboBox 实例被命名为 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();
}

FontObject class 存储了 FontFamily 的一些重要细节,例如 Cell Ascent, the Cell Descent, the EM Size and the Line Spacing.
此处描述了有关如何使用这些功能的一些详细信息:


Fonts and text metrics

它是这样工作的:


FontListCombo 自定义控件:

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