カスタムコントロールでキャレットとIMEコンポジションウィンドウを表示して、キーボードの入力を受け取るコードと実行結果

カスタムコントロールでキャレットとIMEコンポジションウィンドウを表示して、キーボードの入力を受け取るコードと実行結果を紹介します。

概要

こちらの記事ではカスタムコントロールで IMEコンポジションウィンドウの位置を指定するコードを紹介しました。 この記事では、キャレットを表示してキーボードの入力を受け取るコードを紹介します。

キャレットの表示

IMEコンポジションウィンドウの位置を指定するコードにキャレットの表示コードを追加します。

コード

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace ImeCompositionWindowAndCaret
{
  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;

    //Caret
    [DllImport("user32.dll")]
    static extern bool CreateCaret(IntPtr hWnd, IntPtr hBitmap, int nWidth, int nHeight);

    [DllImport("user32.dll")]
    static extern bool DestroyCaret();

    [DllImport("user32.dll")]
    static extern bool SetCaretPos(int X, int Y);

    [DllImport("user32.dll")]
    static extern bool ShowCaret(IntPtr hWnd);

    [DllImport("user32.dll")]
    static extern bool HideCaret(IntPtr hWnd);

    private bool caretCreated = false;
    private Point CaretPos = new Point(0, 0);

    //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 |= 0x08000000; // WS_EX_NOACTIVATE (フォーカスを受け取らないコントロールの場合)
        cp.ExStyle |= 0x02000000; //追加 2023-01-03
        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);
          //bool r = ImmGetOpenStatus(Imc); //IMEが開いている時は not 0 (True)
          //入力コンテキストを解放し、コンテキスト内の関連メモリのロックを解除します。アプリケーションで ImmGetContext 関数を呼び出したら、必ず対応する ImmReleaseContext 関数を呼び出さなければなりません。
          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);
        }

        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();
        }

        //入力コンテキストを解放し、コンテキスト内の関連メモリのロックを解除します。アプリケーションで ImmGetContext 関数を呼び出したら、必ず対応する ImmReleaseContext 関数を呼び出さなければなりません。
        ImmReleaseContext(this.Handle, ImmHandle);
      }
      return 0;
    }


    //キャレット
    protected override void OnMouseClick(MouseEventArgs e)
    {
      base.OnMouseClick(e);
      CreateShowCaret(e.X, e.Y);
      CaretPos.X = e.X;
      CaretPos.Y = e.Y;
    }

    protected override void OnLostFocus(EventArgs e)
    {
      base.OnLostFocus(e);
      CaretHide();
    }

    protected override void OnGotFocus(EventArgs e)
    {
      base.OnGotFocus(e);
      // フォーカスを取得したときにキャレットを再表示
      CreateShowCaret(CaretPos.X, CaretPos.Y); // 必要に応じて適切な位置を指定
    }

    public void CreateShowCaret(int x, int y)
    {
      if (!caretCreated) {
        if (!CreateCaret(this.Handle, IntPtr.Zero, 2, 16)) {
          throw new InvalidOperationException("キャレットの作成に失敗しました。");
        }
        caretCreated = true;
      }
      SetCaretPos(x, y);
      ShowCaret(this.Handle);
    }

    public void CaretHide()
    {
      if (caretCreated) {
        HideCaret(this.Handle);
        DestroyCaret();
        caretCreated = false;
      }
    }

  }
}

UI

下図のUIを作成します。フォームにカスタムコントロールを配置します。
カスタムコントロールでキャレットとIMEコンポジションウィンドウを表示して、キーボードの入力を受け取るコードと実行結果:画像1

解説

キャレットを表示するメソッドと非表示にするメソッドを記述します。
キャレットの表示や非表示の詳細はこちらの記事を参照してください。

    public void CreateShowCaret(int x, int y)
    {
      if (!caretCreated) {
        if (!CreateCaret(this.Handle, IntPtr.Zero, 2, 16)) {
          throw new InvalidOperationException("キャレットの作成に失敗しました。");
        }
        caretCreated = true;
      }
      SetCaretPos(x, y);
      ShowCaret(this.Handle);
    }

    public void CaretHide()
    {
      if (caretCreated) {
        HideCaret(this.Handle);
        DestroyCaret();
        caretCreated = false;
      }
    }


マウスクリックでクリックした位置にキャレットを表示します。 また、フォーカスを失った際にキャレットを非表示にし、フォーカスを受け取った場合にはキャレットを表示します。

    protected override void OnMouseClick(MouseEventArgs e)
    {
      base.OnMouseClick(e);
      CreateShowCaret(e.X, e.Y);
      CaretPos.X = e.X;
      CaretPos.Y = e.Y;
    }

    protected override void OnLostFocus(EventArgs e)
    {
      base.OnLostFocus(e);
      CaretHide();
    }

    protected override void OnGotFocus(EventArgs e)
    {
      base.OnGotFocus(e);
      // フォーカスを取得したときにキャレットを再表示
      CreateShowCaret(CaretPos.X, CaretPos.Y); // 必要に応じて適切な位置を指定
    }

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。
カスタムコントロールでキャレットとIMEコンポジションウィンドウを表示して、キーボードの入力を受け取るコードと実行結果:画像2

カスタムコントロールをクリックします。クリックした位置にキャレットが表示されます。
カスタムコントロールでキャレットとIMEコンポジションウィンドウを表示して、キーボードの入力を受け取るコードと実行結果:画像3

フォームがフォーカスを失うとキャレットは非表示になります。
カスタムコントロールでキャレットとIMEコンポジションウィンドウを表示して、キーボードの入力を受け取るコードと実行結果:画像4

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