C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

フォントによFontDialog表示時の初期値がおかしくなる

[トピック内 10 記事 (1 - 10 表示)]  << 0 >>

■103383 / inTopicNo.1)  フォントによFontDialog表示時の初期値がおかしくなる
  
□投稿者/ アブサン (4回)-(2024/10/17(Thu) 10:01:55)

分類:[C#] 

こんにちは

C# + .Net8 WinForm で開発しています。

FontDialog で質問です。

私の環境に「Nirmala UI」というフォントがあります。
FontDialogで見ると、スタイルに
「中細」「標準」「太字」「中細 斜体」「斜体」「太字 斜体」
と表示されています。

「中細 斜体」を選択すると
  フォントファミリ名「Nirmala UI Semilight」
  スタイルは「Italic」
が返ります。

しかし、このフォントをそのままFontDialogに初期表示させようとすると
フォント名欄に「Nirmala UI」が表示されず空欄になります。

挙動の確認のため以下のようにし、
1で表示されるフォントダイアログの操作で「Nirmala UI」「中細 斜体」を選択しましたが
2で表示されるフォントダイアログでは上記の事象となりました。

using (FontDialog fd = new FontDialog())
{
fd.ShowDialog(this); //1
fd.ShowDialog(this); //2
}

原因がわからないのですが、回避方法などがあれば教えてほしいです。
引用返信 編集キー/
■103384 / inTopicNo.2)  Re[1]: フォントによFontDialog表示時の初期値がおかしくなる
□投稿者/ 魔界の仮面弁士 (3804回)-(2024/10/17(Thu) 10:58:59)
2024/10/17(Thu) 11:00:11 編集(投稿者)

No103383 (アブサン さん) に返信
> 私の環境に「Nirmala UI」というフォントがあります。

手元の環境で、Windows Explorer から
 C:\Windows\Fonts\Nirmala UI\
を開いてみると、「太字」「中細」「標準」の 3 つしか表示されませんでした。

上記は物理ディレクトリではなく仮想フォルダーであり、各フォントの実態としては
 C:\Windows\Fonts\NirmalaB.ttf
 C:\Windows\Fonts\NirmalaS.ttf
 C:\Windows\Fonts\Nirmala.ttf
であることを確認できます。
https://learn.microsoft.com/ja-jp/typography/font-list/nirmala-ui


> FontDialogで見ると、スタイルに
> 「中細」「標準」「太字」「中細 斜体」「斜体」「太字 斜体」
> と表示されています。

using var fd = new FontDialog { AllowSimulations = false };
fd.ShowDialog(this);
fd.ShowDialog(this);

とすることで、本来の選択肢である「中細」「標準」「太字」のみとなり、
エミュレートされていた「中細 斜体」が選択されてしまう状態を防ぐことができるかと。
引用返信 編集キー/
■103400 / inTopicNo.3)  Re[2]: フォントによFontDialog表示時の初期値がおかしくなる
□投稿者/ 魔界の仮面弁士 (3809回)-(2024/10/17(Thu) 21:51:17)
No103384 (魔界の仮面弁士) に追記
> using var fd = new FontDialog { AllowSimulations = false };
> fd.ShowDialog(this);
> fd.ShowDialog(this);
> とすることで、本来の選択肢である「中細」「標準」「太字」のみとなり、
> エミュレートされていた「中細 斜体」が選択されてしまう状態を防ぐことができるかと。

AllowSimulations = false だけでなく
FontMustExist = true も指定せねばならないようです。

FontMustExist = false のままですと、
選択肢にないフォントやスタイルも指定できてしまいます。
引用返信 編集キー/
■103403 / inTopicNo.4)  Re[3]: フォントによFontDialog表示時の初期値がおかしくなる
□投稿者/ アブサン (5回)-(2024/10/18(Fri) 10:35:22)
No103400 (魔界の仮面弁士 さん) に返信

ご回答ありがとうございます!

教えていただいた以下を設定することにより、「中細」「標準」「太字」のみとなりました。
しかし「中細」を選択しても同様の事象になりました。
「標準」「太字」だと期待値通りの動作です。

AllowSimulations = false
FontMustExist = true

私の環境のせいかとも考え、他PCでも試しましたが同様になりました。
ちなみにFontDialogで「中細」を選択して返ってきたFontで文字の描画は出来ています。

もし何か他にも情報がありましたら、お教えいただけると助かります。
よろしくお願いいたします。
引用返信 編集キー/
■103406 / inTopicNo.5)  Re[4]: フォントによFontDialog表示時の初期値がおかしくなる
□投稿者/ KOZ (476回)-(2024/10/19(Sat) 06:28:08)
2024/10/20(Sun) 21:22:16 編集(投稿者)
No103403 (アブサン さん) に返信

FontDialog に初期表示されるフォントは LOGFONT構造体によります。

Nirmala UI で中細フォントを選択するには LOGFONT 構造体を初期化するときに

lfFaceName : "Nirmala UI"
lfWeight   : 350

と指定する必要があるのですが、FontDialog では指定できないので、
FontDialog、または CommonDialog 継承したクラスを作成し、RunDialog を
オーバーライドする必要があるでしょう。

CommonDialog を継承したクラスを書いておきます。

using System.Runtime.InteropServices;

#nullable disable

public class FontDialogEx : CommonDialog
{
    public Font Font { get; set; }
    public int FontWeight { get; set; }

    public FontDialogEx() {
        Reset();
    }

    public override void Reset() {
        Font = Control.DefaultFont;
    }

    // フォント名からスタイルを示すキーワードを削除
    private static string GetBaseFontName(string fontName) {
        string[] fontStyles = { "Semilight", "Bold", "Italic", "Regular", "Light", "Medium" };
        foreach (var style in fontStyles) {
            if (fontName.Contains(style)) {
                fontName = fontName.Replace(style, "").Trim();
                break;
            }
        }
        return fontName;
    }

    protected override bool RunDialog(nint hwndOwner) {
        NativeMethods.LOGFONT logFont = new();
        Font.ToLogFont(logFont);
        logFont.lfFaceName = GetBaseFontName(logFont.lfFaceName);
        logFont.lfWeight = FontWeight;

        NativeMethods.CHOOSEFONT cf = new();
        IntPtr logFontPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(NativeMethods.LOGFONT)));
        try {
            Marshal.StructureToPtr(logFont, logFontPtr, false);
            cf.lpLogFont = logFontPtr;
            cf.hwndOwner = hwndOwner;
            cf.hDC = IntPtr.Zero;
            cf.hInstance = NativeMethods.GetModuleHandle(null);
            cf.Flags = NativeMethods.CF_INITTOLOGFONTSTRUCT;
            var result = NativeMethods.ChooseFont(cf);
            if (result) {
                logFont = Marshal.PtrToStructure<NativeMethods.LOGFONT>(logFontPtr);
                Font = Font.FromLogFont(logFont);
                FontWeight = logFont.lfWeight;
            }
            return result;

        } finally {
            if (logFontPtr != IntPtr.Zero) {
                Marshal.FreeCoTaskMem(logFontPtr);
            }
        }
    }

    static class NativeMethods
    {
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public class LOGFONT
        {
            public int lfHeight;
            public int lfWidth;
            public int lfEscapement;
            public int lfOrientation;
            public int lfWeight;
            public byte lfItalic;
            public byte lfUnderline;
            public byte lfStrikeOut;
            public byte lfCharSet;
            public byte lfOutPrecision;
            public byte lfClipPrecision;
            public byte lfQuality;
            public byte lfPitchAndFamily;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            public string lfFaceName;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public class CHOOSEFONT
        {
            public int lStructSize = Marshal.SizeOf(typeof(CHOOSEFONT));
            public IntPtr hwndOwner;
            public IntPtr hDC;
            public IntPtr lpLogFont;
            public int iPointSize = 0;
            public int Flags;
            public int rgbColors;
            public IntPtr lCustData = IntPtr.Zero;
            public IntPtr lpfnHook;
            public string lpTemplateName = null;
            public IntPtr hInstance;
            public string lpszStyle = null;
            public short nFontType = 0;
            public short ___MISSING_ALIGNMENT__ = 0;
            public int nSizeMin;
            public int nSizeMax;
        }

        [DllImport("comdlg32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool ChooseFont([In, Out] CHOOSEFONT cf);

        [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr GetModuleHandle(string modName);

        public const int CF_INITTOLOGFONTSTRUCT = 0x00000040;
    }
}

FontDialog と同等の機能を持たせたい場合は .NET Framework のソース
https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/FontDialog.cs,c5fa98b3849668dd
が参考になると思います。



引用返信 編集キー/
■103407 / inTopicNo.6)  Re[5]: フォントによFontDialog表示時の初期値がおかしくなる
□投稿者/ KOZ (477回)-(2024/10/19(Sat) 08:30:15)
No103406 (KOZ) に返信
> lfFaceName : "Nirmala UI"
> lfWeight : 350
>

lfFaceName は Nirmala UI Semilight のままで大丈夫のようです。
GetBaseFontName は不要で flWeight を指定するだけでよさそう。
引用返信 編集キー/
■103411 / inTopicNo.7)  Re[6]: フォントによFontDialog表示時の初期値がおかしくなる
□投稿者/ KOZ (479回)-(2024/10/20(Sun) 21:44:15)
2024/10/21(Mon) 03:21:27 編集(投稿者)
Font.FromLogFont で作成したフォントから ToLogFont で LOGFONT クラスを取得すると、
lfWeight がフォントの標準値になってしまうことが原因のようです。

var logFont = new NativeMethods.LOGFONT {
    lfFaceName = "Nirmala UI",
    lfWeight = 350,
    lfHeight = -9
};
var font = Font.FromLogFont(logFont);
var logFont2 = new NativeMethods.LOGFONT();
font.ToLogFont(logFont2);
Debug.WriteLine($"{logFont2.lfWeight}"); // 400 になってしまう

GDI+ のバグのような気します。

NET8 から System.Drawing.Interop.LOGFONT 構造体が追加されたので、うまく使ってください。
https://learn.microsoft.com/en-us/dotnet/api/system.drawing.interop.logfont?view=net-8.0


引用返信 編集キー/
■103414 / inTopicNo.8)  Re[7]: フォントによFontDialog表示時の初期値がおかしくなる
□投稿者/ アブサン (6回)-(2024/10/21(Mon) 12:56:54)
No103411 (KOZ さん) に返信

ご返信ありがとうございます。

コードまで載せて頂いて大変助かります!
試してみて結果報告します。
引用返信 編集キー/
■103415 / inTopicNo.9)  Re[8]: フォントによFontDialog表示時の初期値がおかしくなる
□投稿者/ アブサン (7回)-(2024/10/24(Thu) 09:51:15)
No103411 (KOZ さん) に返信

提示していただいたコードを参考にして
「中細」「細字」等が初期表示されるようになりました。

コードのGetBaseFontNameでスタイル判定方法もわかりましたので
Semilight、Lightの場合にlfWeightを変えるようにしました。

大変助かりました!
ありがとうございました!
解決済み
引用返信 編集キー/
■103416 / inTopicNo.10)  Re[9]: フォントによFontDialog表示時の初期値がおかしくなる
□投稿者/ KOZ (480回)-(2024/10/24(Thu) 19:41:51)
2024/10/25(Fri) 06:25:05 編集(投稿者)
No103415 (アブサン さん) に返信
> コードのGetBaseFontNameでスタイル判定方法もわかりましたので
> Semilight、Lightの場合にlfWeightを変えるようにしました。

解決した後ですが、中太や極太などもある上、Light の場合、290 や 300 の値をとることがあるため、
フォント名と lfWeight の Dictionary を作るといいと思います。

// スタイル名
private enum WeightStyles
{
    THIN = 100,
    EXTRALIGHT = 200,
    ULTRALIGHT = 200,
    LIGHT = 300,
    SEMILIGHT = 350,
    NORMAL = 400,
    REGULAR = 400,
    MEDIUM = 500,
    SEMIBOLD = 600,
    DEMIBOLD = 600,
    BOLD = 700,
    EXTRABOLD = 800,
    ULTRABOLD = 800,
    HEAVY = 900,
    BLACK = 900
}

// ダイアログで選択したとき フォント名と lfWeight の組み合わせを登録して次回から使用する。
// 既知のフォントはあらかじめ登録しておく
private static readonly Dictionary<string, int> dicWeight
               = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase) {
                   { "Microsoft JhengHei Light", 290 },
                   { "Microsoft JhengHei UI Light", 290 }
                   { "Segoe UI Light", 300 },
                   { "Segoe UI Semibold", 600 },
                   { "Segoe UI Black", 900 }
               };

private static NativeMethods.LOGFONT GetLogFont(Font font) {
    // 既知のフォント名から lfWeight を設定する
    var logFont = new NativeMethods.LOGFONT();
    font.ToLogFont(logFont);
    lock (dicWeight) {
        if (dicWeight.TryGetValue(logFont.lfFaceName, out int weight)) {
            logFont.lfWeight = weight;
            return logFont;
        }
    }
    // フォント名からスタイルを検索して lfWeight を設定する
    var faceName = logFont.lfFaceName.ToUpper();
    foreach (WeightStyles style in Enum.GetValues(typeof(WeightStyles))) {
        if (faceName.IndexOf(" " + style.ToString()) >= 0) {
            logFont.lfWeight = (int)style;
            break;
        }
    }
    return logFont;
}

private void UpdateFont(NativeMethods.LOGFONT logFont) {
    // LOGFONT から Font を作成する
    Font = Font.FromLogFont(logFont);

    // Font から LOGFONT を取り出す
    var gdipLogFont = GetLogFont(Font);

    Debug.Print($"{logFont.lfFaceName} {logFont.lfWeight}:{gdipLogFont.lfWeight}");

    // lfWeight が違う場合は保存する
    if (gdipLogFont.lfWeight != logFont.lfWeight) {
        lock (dicWeight) {
            dicWeight[logFont.lfFaceName] = logFont.lfWeight;
        }
    }
}

解決済み
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ