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を愛用