IME変換領域(ウィンドウ)のフォントを設定する - ImmSetCompositionFont の呼び出し - C#

IME変換領域(ウィンドウ)のフォントの変更やフォントサイズの変更をするコードを紹介します。

概要

IMEの変換領域のフォントを変更する場合は、WindowsAPIのImmSetCompositionFont()関数を呼び出します。

コード例

Windows API 宣言

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public class LOGFONT
    {
      public int lfHeight;
      public int lfWidth;
      public int lfEscapement;
      public int lfOrientation;
      public FontWeight lfWeight;
      [MarshalAs(UnmanagedType.U1)]
      public bool lfItalic;
      [MarshalAs(UnmanagedType.U1)]
      public bool lfUnderline;
      [MarshalAs(UnmanagedType.U1)]
      public bool lfStrikeOut;
      public FontCharSet lfCharSet;
      public FontPrecision lfOutPrecision;
      public FontClipPrecision lfClipPrecision;
      public FontQuality lfQuality;
      public FontPitchAndFamily lfPitchAndFamily;
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = LF_FACESIZE * 2)]
      public string lfFaceName;
    }

    [DllImport("kernel32.dll")]
    public static extern IntPtr GlobalLock(IntPtr hMem);

    [DllImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GlobalUnlock(IntPtr hMem);

    [DllImport("imm32.dll")]
    public static extern int ImmSetCompositionFont(IntPtr hIMC, [In, Out] IntPtr lplf);

呼び出し部

  IntPtr hHGlobalLOGFONT = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(LOGFONT)));
  IntPtr pLogFont = GlobalLock(hHGlobalLOGFONT);
  LOGFONT logFont = new LOGFONT();
  myFont.ToLogFont(logFont);
  Marshal.StructureToPtr(logFont, pLogFont, false);
  GlobalUnlock(hHGlobalLOGFONT);
  ImmSetCompositionFont(ImmHandle, hHGlobalLOGFONT);
  Marshal.FreeHGlobal(hHGlobalLOGFONT);
上記のコードでは、myFont.ToLogFont(logFont); で正しくフォント変換ができない問題があります。フォント名が反映されず、「MS P ゴシック」で描画される動作になります。
その場合は、
  myFont.ToLogFont(logFont);
  logFont.Name = myFont.Name;
とすると、フォント名も反映されます。

サンプルプログラム

サンプルプログラムを紹介します。

UI

下図のUIを作成します。PanelとButtonをフォームに配置します。

コード

下記のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace ImeComposition
{
  public partial class FormMain : Form
  {
    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
      public int _Left;
      public int _Top;
      public int _Right;
      public int _Bottom;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
      public int x;
      public int y;
    }
    
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public class LOGFONT
    {
      public int lfHeight;
      public int lfWidth;
      public int lfEscapement;
      public int lfOrientation;
      public FontWeight lfWeight;
      [MarshalAs(UnmanagedType.U1)]
      public bool lfItalic;
      [MarshalAs(UnmanagedType.U1)]
      public bool lfUnderline;
      [MarshalAs(UnmanagedType.U1)]
      public bool lfStrikeOut;
      public FontCharSet lfCharSet;
      public FontPrecision lfOutPrecision;
      public FontClipPrecision lfClipPrecision;
      public FontQuality lfQuality;
      public FontPitchAndFamily lfPitchAndFamily;
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = LF_FACESIZE * 2)]
      public string lfFaceName;
    }

    //LOGFONT
    public enum FontWeight : int
    {
      FW_DONTCARE = 0,
      FW_THIN = 100,
      FW_EXTRALIGHT = 200,
      FW_LIGHT = 300,
      FW_NORMAL = 400,
      FW_MEDIUM = 500,
      FW_SEMIBOLD = 600,
      FW_BOLD = 700,
      FW_EXTRABOLD = 800,
      FW_HEAVY = 900,
    }
    public enum FontCharSet : byte
    {
      ANSI_CHARSET = 0,
      DEFAULT_CHARSET = 1,
      SYMBOL_CHARSET = 2,
      SHIFTJIS_CHARSET = 128,
      HANGEUL_CHARSET = 129,
      HANGUL_CHARSET = 129,
      GB2312_CHARSET = 134,
      CHINESEBIG5_CHARSET = 136,
      OEM_CHARSET = 255,
      JOHAB_CHARSET = 130,
      HEBREW_CHARSET = 177,
      ARABIC_CHARSET = 178,
      GREEK_CHARSET = 161,
      TURKISH_CHARSET = 162,
      VIETNAMESE_CHARSET = 163,
      THAI_CHARSET = 222,
      EASTEUROPE_CHARSET = 238,
      RUSSIAN_CHARSET = 204,
      MAC_CHARSET = 77,
      BALTIC_CHARSET = 186,
    }
    public enum FontPrecision : byte
    {
      OUT_DEFAULT_PRECIS = 0,
      OUT_STRING_PRECIS = 1,
      OUT_CHARACTER_PRECIS = 2,
      OUT_STROKE_PRECIS = 3,
      OUT_TT_PRECIS = 4,
      OUT_DEVICE_PRECIS = 5,
      OUT_RASTER_PRECIS = 6,
      OUT_TT_ONLY_PRECIS = 7,
      OUT_OUTLINE_PRECIS = 8,
      OUT_SCREEN_OUTLINE_PRECIS = 9,
      OUT_PS_ONLY_PRECIS = 10,
    }
    public enum FontClipPrecision : byte
    {
      CLIP_DEFAULT_PRECIS = 0,
      CLIP_CHARACTER_PRECIS = 1,
      CLIP_STROKE_PRECIS = 2,
      CLIP_MASK = 0xf,
      CLIP_LH_ANGLES = (1 << 4),
      CLIP_TT_ALWAYS = (2 << 4),
      CLIP_DFA_DISABLE = (4 << 4),
      CLIP_EMBEDDED = (8 << 4),
    }
    public enum FontQuality : byte
    {
      DEFAULT_QUALITY = 0,
      DRAFT_QUALITY = 1,
      PROOF_QUALITY = 2,
      NONANTIALIASED_QUALITY = 3,
      ANTIALIASED_QUALITY = 4,
      CLEARTYPE_QUALITY = 5,
      CLEARTYPE_NATURAL_QUALITY = 6,
    }
    [Flags]
    public enum FontPitchAndFamily : byte
    {
      DEFAULT_PITCH = 0,
      FIXED_PITCH = 1,
      VARIABLE_PITCH = 2,
      FF_DONTCARE = (0 << 4),
      FF_ROMAN = (1 << 4),
      FF_SWISS = (2 << 4),
      FF_MODERN = (3 << 4),
      FF_SCRIPT = (4 << 4),
      FF_DECORATIVE = (5 << 4),
    }

    const int LF_FACESIZE = 32;

    [DllImport("kernel32.dll")]
    public static extern IntPtr GlobalLock(IntPtr hMem);

    [DllImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GlobalUnlock(IntPtr hMem);

    [DllImport("imm32.dll")]
    public static extern IntPtr ImmGetContext(IntPtr hWnd);

    [DllImport("imm32.dll")]
    public static extern IntPtr ImmReleaseContext(IntPtr hWnd, IntPtr context);

    [DllImport("Imm32.dll")]
    public static extern bool ImmGetOpenStatus(IntPtr hIMC);

    [DllImport("imm32.dll")]
    public static extern bool ImmSetCompositionWindow(IntPtr hIMC, ref COMPOSITIONFORM lpCompForm);

    public struct COMPOSITIONFORM
    {
      public uint dwStyle;
      public POINT ptCurrentPos;
      public RECT rcArea;
    } 

    [DllImport("imm32.dll")]
    public static extern int ImmSetCompositionFont(IntPtr hIMC, [In, Out] IntPtr lplf);

    [DllImport("imm32.dll")]
    public static extern int ImmSetCompositionStringW(IntPtr himc, int dwIndex, IntPtr lpComp, int dw, int lpRead, int dw2);
     

    const int SCS_SETSTR                = 0x00000009;  //変換文字列か読み文字列のいずれか、またはその両方の属性を設定
    const int SCS_CHANGEATTR            = 0x00000011;  //変換文字列か読み文字列のいずれか、またはその両方の文節情報を設定
    const int SCS_CHANGECLAUSE          = 0x00000024;  //IMEにRECONVERTSTRING 構造体に格納された文字列を逆変換するよう指示
    const int SCS_SETRECONVERTSTRING    = 0x00010000;  //IMEにRECONVERTSTRING構造体を調整するよう指示
    const int SCS_QUERYRECONVERTSTRING  = 0x00020000;

    const int CFS_POINT = 0x0002;


    public FormMain()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      panel1.Focus();
      panel1.ImeMode = System.Windows.Forms.ImeMode.Hiragana;

      string setStr = "あいうえお";
      
      IntPtr Imc = ImmGetContext(this.Handle);

      //Window
      COMPOSITIONFORM compform = new COMPOSITIONFORM();
      compform.dwStyle = CFS_POINT;
      compform.ptCurrentPos.x = 0;
      compform.ptCurrentPos.y = 0;
      ImmSetCompositionWindow(Imc, ref compform);

      //Font
      Font myFont = new Font("MS ゴシック", 20, FontStyle.Bold);
      IntPtr hHGlobalLOGFONT = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(LOGFONT)));
      IntPtr pLogFont = GlobalLock(hHGlobalLOGFONT);
      LOGFONT logFont = new LOGFONT();
      myFont.ToLogFont(logFont);
      logFont.Name = myFont.Name; //追加
      Marshal.StructureToPtr(logFont, pLogFont, false);
      GlobalUnlock(hHGlobalLOGFONT);
      ImmSetCompositionFont(Imc, hHGlobalLOGFONT);
      Marshal.FreeHGlobal(hHGlobalLOGFONT);

      //String
      int length = Encoding.Unicode.GetByteCount(setStr);
      IntPtr stringPointer = (IntPtr)Marshal.StringToHGlobalUni(setStr);
      ImmSetCompositionStringW(Imc, SCS_SETSTR, stringPointer, length, 0, 0);
      Marshal.FreeHGlobal(stringPointer);
      
      ImmReleaseContext(this.Handle, Imc);
    }
  }
}

解説

  panel1.Focus();
  panel1.ImeMode = System.Windows.Forms.ImeMode.Hiragana;
上記コードにより、panel1にフォーカスを移します。また、IMEのモードをひらがな入力モードにします。

  IntPtr Imc = ImmGetContext(this.Handle);
IMEの操作にあたりIMEのコンテキストハンドルを取得します。

      COMPOSITIONFORM compform = new COMPOSITIONFORM();
      compform.dwStyle = CFS_POINT;
      compform.ptCurrentPos.x = 0;
      compform.ptCurrentPos.y = 0;
      ImmSetCompositionWindow(Imc, ref compform);
COMPOSITIONFORM構造体を作成し、IMEウィンドウの位置を指定します。ImmSetCompositionWindow()関数にCOMPOSITIONFORMを与えIMEのウィンドウ位置を設定します。上記のコードでは(0,0)の左上にウィンドウ位置を設定します。

      //Font
      Font myFont = new Font("MS ゴシック", 20, FontStyle.Bold);
      IntPtr hHGlobalLOGFONT = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(LOGFONT)));
      IntPtr pLogFont = GlobalLock(hHGlobalLOGFONT);
      LOGFONT logFont = new LOGFONT();
      myFont.ToLogFont(logFont);
      logFont.Name = myFont.Name; //追加
      Marshal.StructureToPtr(logFont, pLogFont, false);
      GlobalUnlock(hHGlobalLOGFONT);
      ImmSetCompositionFont(Imc, hHGlobalLOGFONT);
      Marshal.FreeHGlobal(hHGlobalLOGFONT);
ImmSetCompositionFont()関数を呼び出し、IMEウィンドウに表示されるフォントを指定します。
ImmSetCompositionFont()関数はLOGFONT構造体のポインタを与えます。ポインタのメモリ確保はMarshal.AllocHGlobal()メソッドを呼び出しアンマネージ メモリを割り当てます。割り当てたアンマネージ メモリは使用後に Marshal.FreeHGlobal()メソッドを呼び出し開放します。
Font(System.Drawing.Font)からLOGFONTへの変換は、FontクラスのToLogFont()メソッドを用います。LOGFONTに変換後にMarshal.StructureToPtr()を呼び出し、マネージ オブジェクトとして作成したLOGFONTをアンマネージ メモリで確保したLOGFONTにコピー(マーシャリング)します。

      //String
      int length = Encoding.Unicode.GetByteCount(setStr);
      IntPtr stringPointer = (IntPtr)Marshal.StringToHGlobalUni(setStr);
      ImmSetCompositionStringW(Imc, SCS_SETSTR, stringPointer, length, 0, 0);
      Marshal.FreeHGlobal(stringPointer);
ImmSetCompositionStringW()関数を呼び出し、IMEウィンドウに表示されるテキストを設定します。マネージ型のstringは直接APIに渡せないため、Marshal.StringToHGlobalUni()メソッドを呼び出しアンマネージ型のIntPtrに変換してImmSetCompositionStringW()関数に渡しています。

  ImmReleaseContext(this.Handle, Imc);
すべての処理が終わったのちIMEのコンテキストハンドルを開放します。

実行結果

上記のプロジェクトを実行します。下図のウィンドウが表示されます。


[Button1]をクリックします。panel1の左上にIMEのウィンドウが表示されます。フォントサイズも20で表示されていることがわかります。

補足:どうしてマーシャリングが必要なのか

LOGFONT構造体は、もともとの定義は構造体(struct)で"typedef struct tagLOGFONTW{.......}LOGFONTW"で定義されていますが、今回のサンプルではclassで定義しています。classの場合は作成したオブジェクトはマネージオブジェクトのため、そのままWindows APIに渡すことはできません。APIの引数として渡すには、アンマネージメモリにコピーしてからAPIに渡す必要があります。LOGFONTをstructで定義すればアンマネージメモリの確保は不要になりますが、System.Drawing.FontのToLogFont()メソッドはobjectを引数にとるため、ToLogFontは使えず、System.Drawing.FontからLOGFONTへのコピーが煩雑になります。
System.Drawing.FontのToLogFont()メソッドを利用しないのであれば、以下の定義、コードでも動作します。

先のコードのLOGFONTをclassからstructに変更します。
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct LOGFONT
    {
      public int lfHeight;
      public int lfWidth;
      public int lfEscapement;
      public int lfOrientation;
      public FontWeight lfWeight;
      [MarshalAs(UnmanagedType.U1)]
      public bool lfItalic;
      [MarshalAs(UnmanagedType.U1)]
      public bool lfUnderline;
      [MarshalAs(UnmanagedType.U1)]
      public bool lfStrikeOut;
      public FontCharSet lfCharSet;
      public FontPrecision lfOutPrecision;
      public FontClipPrecision lfClipPrecision;
      public FontQuality lfQuality;
      public FontPitchAndFamily lfPitchAndFamily;
      [MarshalAs(UnmanagedType.ByValTStr, SizeConst = LF_FACESIZE * 2)]
      public string lfFaceName;
    }

ImmSetCompositionFontのAPIのインポート宣言の第二引数を"IntPtr"から"ref LOGFONT"に変更します。
    [DllImport("imm32.dll", EntryPoint = "ImmSetCompositionFont")]
    public static extern int ImmSetCompositionFont(IntPtr hIMC, ref LOGFONT lplf);

ImmSetCompositionFont()の呼び出しを下記に変更します。ToLogFontメソッドは利用できないため、LOGFONTの各項目に値を代入します。
  //String
  LOGFONT logFont = new SLOGFONT();
  logFont.lfHeight = -27;
  logFont.lfFaceName = "MS 明朝";
  ImmSetCompositionFont(Imc, ref logFont);

著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
掲載日: 2014-10-19
iPentec all rights reserverd.