GetNumberOfConsoleFonts() работает некорректно

103
13 июня 2021, 12:40

В своём консольном проекте на C# я решил реализовать возможность смены шрифта консоли средствами самой программы. Алгоритм действий:

  1. С помощью WinAPI-функции GetNumberOfConsoleFonts() получить количество доступных для консоли шрифтов.
  2. С помощью WinAPI-функции GetConsoleFontInfo() получить их индексы.
  3. Последовательное применение WinAPI-функций SetConsoleFont() и GetCurrentConsoleFontEx() для каждого индекса, чтобы получить более детальную информацию о шрифтах.
  4. На основе полученной информации организовать диалог с выбором шрифта.

Проблема в том, что GetNumberOfConsoleFonts() постоянно возвращает 0, делая все другие шаги бессмысленными. В чём может быть причина? Можно ли как-нибудь иначе получить количество доступных для консоли шрифтов или их индексы?

[DllImport("kernel32.dll", SetLastError = true)] 
static extern uint GetNumberOfConsoleFonts();
//Использование:
uint fontsCount = GetNumberOfConsoleFonts();

У меня установлены Windows 10 и Visual Studio 2019.

Дополнение от 26.08.2019:
В общем, я так и не нашёл способа заставить функции GetNumberOfConsoleFonts, GetConsoleFontInfo и SetConsoleFont заработать. Пока вместо этого использую следующий алгоритм:

  1. Из ветки реестра HKEY_CURRENT_USER\Software\Microsoft\Shared Tools\Panose (похоже, содержит имена и некоторые параметры шрифтов) получаю все пары Имя:Значение.
  2. Выполняю отсеивание, оставляя только те, в которых четвёртый байт равен 0x09 (возможно именно это указывает на моноширинность шрифта).
  3. Для каждого из оставшихся имён шрифтов последовательно применяю SetCurrentConsoleFontEx и GetCurrentConsoleFontEx, чтобы проверить может ли данный шрифт использоваться в консоли (если свойство FaceName структуры CONSOLE_FONT_INFO_EX что передаётся в SetCurrentConsoleFontEx совпадает с свойством FaceName структуры CONSOLE_FONT_INFO_EX что возвращается GetCurrentConsoleFontEx, значит консоль может использовать этот шрифт).
  4. Из тех, что были успешно применены, формирую список, который и использую в диалоге выбора шрифта.
Answer 1

В Windows 10 Microsoft внесли много улучшений в консоль. Это связано главным образом не с WinRT, а с появлением .NET Core и Windows Subsystem for Linux, которые оживили интерес к *NIX-софту, а он по большей части является именно консольным. Одно из таких улучшений - возможность использовать в консоли любой моноширинный шрифт. Видимо, именно из-за этого недокументированная функция GetNumberOfConsoleFonts теперь возвращает 0 - никаких особых "консольных шрифтов" больше не существует, и она потеряла свой смысл.

На более ранних версиях Windows она работает, но особой необходимости в ней нет, так как список поддерживаемых шрифтов можно получить из ветки реестра HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console\TrueTypeFont (она тоже не документированная, но на основе реестра список шрифтов построить вроде попроще, чем по предложенному алгоритму). По умолчанию, в ней только Consolas и Lucida Console. В нее можно добавить и другие моноширинные шрифты, удовлетворяющие данным условиям - и они в какой-то мере будут работать - но это, скорее всего, плохая идея (см. Why are console windows limited to Lucida Console and raster fonts?).

На Windows 10 же список поддерживаемых консолью шрифтов - это просто список всех моноширинных шрифтов. Помимо реестра, его можно получить стандартными средствами GDI/GDI+:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Drawing.Text;
class Program
{     
    public static bool IsFixedPitch(Graphics graphics, FontFamily fam)
    {        
        Font font=new Font(fam, 10);
        using (font)
        {
            IntPtr hDC = graphics.GetHdc();
            TEXTMETRIC metrics;
            IntPtr hFont = font.ToHfont();
            try
            {
                IntPtr hPreviousFont = SelectObject(hDC, hFont);
                bool res = GetTextMetrics(hDC, out metrics);
                if (res == false) throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
                SelectObject(hDC, hPreviousFont);
            }
            finally
            {
                DeleteObject(hFont);
                graphics.ReleaseHdc(hDC);
            }
            return (metrics.tmPitchAndFamily & TMPF_FIXED_PITCH) == 0;
        }
    }
    [DllImport("Gdi32.dll")]
    static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
    [DllImport("Gdi32.dll", SetLastError = true)]
    static extern bool GetTextMetrics(IntPtr hdc, out TEXTMETRIC lptm);
    [DllImport("Gdi32.dll")]
    static extern bool DeleteObject(IntPtr hdc);
    [StructLayout(LayoutKind.Sequential)]
    internal struct TEXTMETRIC
    {
        public int tmHeight;
        public int tmAscent;
        public int tmDescent;
        public int tmInternalLeading;
        public int tmExternalLeading;
        public int tmAveCharWidth;
        public int tmMaxCharWidth;
        public int tmWeight;
        public int tmOverhang;
        public int tmDigitizedAspectX;
        public int tmDigitizedAspectY;
        public char tmFirstChar;
        public char tmLastChar;
        public char tmDefaultChar;
        public char tmBreakChar;
        public byte tmItalic;
        public byte tmUnderlined;
        public byte tmStruckOut;
        public byte tmPitchAndFamily;
        public byte tmCharSet;
    }
    const byte TMPF_FIXED_PITCH = 0x01;    
    static void Main(string[] args)
    { 
        InstalledFontCollection coll = new InstalledFontCollection();
        Graphics g = Graphics.FromHwnd(IntPtr.Zero);
        using (g)
        {
            foreach (var family in coll.Families)
            {
                if (IsFixedPitch(g, family))
                {
                    Console.WriteLine(family.GetName(0));
                }
            }
        }
        Console.ReadKey();
    }    
}
READ ALSO
System.Xml C# как сделать необязательное для сериализации поле

System.Xml C# как сделать необязательное для сериализации поле

Допустим у меня есть класс User, в классе User есть string? поле CountryПри сериализации файла создаётся

115
WPF Binding в значение - динамический биндинг

WPF Binding в значение - динамический биндинг

вопросик есть, как можно передать команде сразу два параметра, при этом что один из параметров состояние текущего элемента

103
Работа приложения с Windows Form в фоновом режиме

Работа приложения с Windows Form в фоновом режиме

У меня есть рабочее приложение, написанное для себя, которое перемещает файлыДля финального завершения осталось доделать, чтобы оно работало...

97
Сохранить видео поток VLC C#

Сохранить видео поток VLC C#

Требуется сохранить видео поток на жесткий диск, который транслируется на форму(WindowsForms) через библиотеку VLCИмеется ip и порт источника трансляции(Tcp)

75