カスタムコントロールでIMEコンポジションウィンドウの表示位置を指定する

カスタムコントロールでIMEコンポジションウィンドウの表示位置を指定するC#のコードを紹介します。

概要

IMEの変換結果を表示するコンポジションウィンドウを指定した位置に表示するプログラムを作成します。

プログラム例

コード

カスタムコントロールのコードは以下です。

CustomControl.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ImeWindow
{
  public partial class CustomControl : Control
  {
    public const int WM_IME_COMPOSITION = 0x010F;
    public const int WM_IME_NOTIFY = 0x0282;
    public const int WM_CHAR = 0x0102;
    public const int WM_LBUTTONDOWN = 0x00000201;
    public const int WM_GETDLGCODE = 0x0087;

    public const int GCS_RESULTSTR = 0x0800;    // 変換確定後文字取得に使用する値(ひらがな)
    public const int GCS_COMPSTR = 0x0008;      // IME入力中文字取得に使用する値(ひらがな)

    public const int IMN_SETOPENSTATUS = 0x0008;

    //IME
    [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 int ImmSetCompositionFont(IntPtr hIMC, [In, Out] IntPtr lplf);

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

    [DllImport("Imm32.dll", CharSet = CharSet.Unicode)]
    public static extern int ImmGetCompositionStringW(IntPtr hIMC, int dwIndex, byte[] lpBuf, int dwBufLen);

    public const Int32 CS_VREDRAW = 0x01;
    public const Int32 CS_HREDRAW = 0x02;

    public enum CompositionFormStyle : uint
    {
      CFS_DEFAULT = 0x0000,
      CFS_RECT = 0x0001,
      CFS_POINT = 0x0002,
      CFS_FORCE_POSITION = 0x0020,
      CFS_CANDIDATEPOS = 0x0040,
      CFS_EXCLUDE = 0x0080
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
      public int x;
      public int y;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
      public int _Left;
      public int _Top;
      public int _Right;
      public int _Bottom;
    }

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

    //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;

    // if we specify CharSet.Auto instead of CharSet.Ansi, then the string will be unreadable
    [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;
    }




    public CustomControl() 
    {
      InitializeComponent();
    }
    public CustomControl(IContainer container) 
    {
      container.Add(this);
      InitializeComponent();
    }

    protected override CreateParams CreateParams {
      get
      {
        CreateParams cp = base.CreateParams;
        cp.ExStyle |= 0x02000000; 
        cp.ClassStyle = cp.ClassStyle & ~(CS_VREDRAW | CS_HREDRAW);
        return cp;
      }
    }

    protected override void InitLayout()
    {
      base.InitLayout();
      ifImmSetCompositionWindow(0, 0, this.Font);//IMEの変換場所をセット(graphicsの作成後に実行する必要がある)
    }


    protected override void WndProc(ref Message m)
    {
      // ウィンドウメッセージの処理
      switch (m.Msg) {
        case WM_IME_COMPOSITION:
          WMImeComposition(ref m);
          break;
        case WM_IME_NOTIFY:
          WMImeNotify(ref m);
          break;
        case WM_CHAR:
          break;
        case WM_LBUTTONDOWN:
          WMLButtonDown(ref m);
          break;
        case WM_GETDLGCODE://矢印キーなどの特殊キーの取得
          WMGetDlgCode(ref m);
          break;
        default:
          break;
      }
      base.WndProc(ref m);
    }

    private void WMGetDlgCode(ref Message message)
    {
      message.Result = (IntPtr)(-1); //ダイアログ関係の特殊キー (矢印、TAB)をすべてブロック
    }

    private void WMLButtonDown(ref Message message)
    {
      this.Focus();
      int x = unchecked((short)(long)message.LParam);
      int y = unchecked((short)((long)message.LParam >> 16));
      ifImmSetCompositionWindow(x, y, this.Font);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
      base.OnPaint(e);
      if (DesignMode == true) {
        Pen p = new Pen(Color.Gray);
        p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
        e.Graphics.DrawRectangle(p, new Rectangle(0, 0, Width - 1, Height - 1));
      }
    }
     

    private void WMImeComposition(ref Message m)
    {
      IntPtr Imc = ImmGetContext(this.Handle);
      int L = ImmGetCompositionStringW(Imc, GCS_COMPSTR, null, 0);
      ImmReleaseContext(Handle, Imc);
    }

    private void WMImeNotify(ref Message m)
    {
      switch ((int)m.WParam) {
        case IMN_SETOPENSTATUS:
          //IMEのステータスが変更されたとき
          IntPtr Imc = ImmGetContext(this.Handle);
          //入力コンテキストを解放し、コンテキスト内の関連メモリのロックを解除します。
          ImmReleaseContext(this.Handle, Imc);
          break;
      }
    }


    ///////////////////////////////////////////////////////////////////////////////
    //IME表示位置の補正とフォント表示の補正
    private int ifImmSetCompositionWindow(int x, int y, Font font)
    {
      //if not HandleAllocated then Exit
      if (this.Handle == 0) return -1;

      IntPtr ImmHandle = ImmGetContext(this.Handle);//指定されたウィンドウに関連付けられている入力コンテキストを取得します。
      if (ImmHandle != IntPtr.Zero) {
        if (font != null) {
          LOGFONT logFont = new LOGFONT();
          font.ToLogFont(logFont); // FontからLOGFONTを取得

          // LOGFONTをアンマネージメモリにコピー
          IntPtr logFontPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(LOGFONT)));
          Marshal.StructureToPtr(logFont, logFontPtr, false);

          // IMEのフォントを設定
          ImmSetCompositionFont(ImmHandle, logFontPtr);

          // メモリを解放
          Marshal.FreeHGlobal(logFontPtr);

          /*
          //IMEの文字のフォントを設定する
          IntPtr hHGlobalLOGFONT = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(LOGFONT)));
          IntPtr pLogFont = GlobalLock(hHGlobalLOGFONT);
          LOGFONT logFont = new LOGFONT();
          //this.Font.ToLogFont(logFont);
          logFont.lfHeight = this.Font.Height;//
          logFont.lfFaceName = this.Font.Name;
          logFont.lfItalic = this.Font.Italic;
          //logFont. = this.Font.Italic;

          Marshal.StructureToPtr(logFont, pLogFont, false);
          GlobalUnlock(hHGlobalLOGFONT);
          ImmSetCompositionFont(ImmHandle, hHGlobalLOGFONT);
          Marshal.FreeHGlobal(hHGlobalLOGFONT);
          */
        }

        COMPOSITIONFORM cform = new COMPOSITIONFORM();
        cform.dwStyle = (uint)CompositionFormStyle.CFS_POINT;
        cform.ptCurrentPos.x = x;
        cform.ptCurrentPos.y = y;
        if (Convert.ToBoolean(0) == ImmSetCompositionWindow(ImmHandle, ref cform)) {
          //Failure
          new Exception();
        }

        //入力コンテキストを解放し、コンテキスト内の関連メモリのロックを解除します。
        ImmReleaseContext(this.Handle, ImmHandle);
      }
      return 0;
    }

  }
}

解説

カスタムコントロールでマウスの左ボタンがクリックされると、ifImmSetCompositionWindow() メソッドを呼び出します。
ifImmSetCompositionWindow メソッドではコントロールのFontから LOGFONTオブジェクトを作成し、ImmSetCompositionFont() 関数を呼び出して、 IMEコンポジションウィンドウのフォントを設定します。また、ImmSetCompositionWindow() 関数を呼び出し、 クリックした位置のx,y座標の位置に、IMEコンポジションウィンドウを移動します。

UI

下図のUIを作成します。
フォームにカスタムコントロールを配置します。
カスタムコントロールでIMEコンポジションウィンドウの表示位置を指定する:画像1

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。
カスタムコントロールでIMEコンポジションウィンドウの表示位置を指定する:画像2

ウィンドウ内のカスタムコントロールをクリックします。次に、IMEをオンにしてキー入力します。 IMEのコンポジションウィンドウがクリックした位置に表示され、変換文字列が表示されます。
カスタムコントロールでIMEコンポジションウィンドウの表示位置を指定する:画像3

カスタムコントロールでIMEコンポジションウィンドウの表示位置を指定する:画像4

コンポジションウィンドウを指定した位置に表示できました。

AuthorPortraitAlt
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
作成日: 2024-12-21
Copyright © 1995–2025 iPentec all rights reserverd.