none
ImmSetConversionStatus (かそれに変わる方法で) をWindows8/10で使いたい RRS feed

  • 質問

  • VS2010 VC++MFC で開発しています。

    ImmSetConversionStatus を使って入力箇所によって入力モードを変えるようなプログラムを作っています。

    Windows7では問題なかったのが 8や10になって全角カナ/半角カナが上手く設定出来なくなりました。
    (いまのところ 8/10でも  ひらがな/半角英数 の切り替えだけ行うと上手く設定できるようです) 

    Windwos8/10でシステムの設定など変えずにプログラムだけでIMEのモードを正しく変える方法がありましたらご教授ください。

    よろしくお願いします。

    2016年8月23日 17:53

回答

  • コードありがとうございます。こちらでも再現しました。

    WM_IME_NOTIFY メッセージを確認すると、WM_KILLFOCUS の時点では IME が既に閉じているような動きになっていました。こうなると、WM_KILLFOCUS で入力モードの取得を行うのが難しいので、入力モードの取得は入力モードが変わった直後、(WM_IME_NOTIFY の IMN_SETCONVERSIONMODE や WM_IME_NOTIFY の IMN_SETOPENSTATUS)で行ったほうがよさそうです。

    ただ、手元のプログラムではユーザーが IME を閉じたときと、フォーカス移動の際に IME が閉じたときの区別がつかない・・・。うーんどうしたもんか・・・

    2016年8月25日 4:55

すべての返信

  • #include <InputScope.h>
    
    HMODULE hMSCTF = LoadLibrary(TEXT("msctf.dll"));
    typedef HRESULT(WINAPI *SETINPUTSCOPE)(IN HWND, IN InputScope);
    SETINPUTSCOPE pfnSetInputScope = (SETINPUTSCOPE)GetProcAddress(hMSCTF, "SetInputScope");
    
    if (pfnSetInputScope)
    {
    	(*pfnSetInputScope)(hEdit, IS_KATAKANA_HALFWIDTH); // 半角カナ
    	//(*pfnSetInputScope)(hEdit, IS_KATAKANA_FULLWIDTH); // 全角カナ
    }
    
    FreeLibrary(hMSCTF);


    Windows 8以降では上記のようにすることで、半角カナ/全角カナの切り替えができるようです。

    参考サイト: https://msdn.microsoft.com/ja-jp/library/windows/apps/xaml/ms629025.aspx

    2016年8月23日 22:08
  • ちなみにVisual C++ 2010から実装されたautodecltype()を使用するとちょっとだけ簡単に書けます。

    #include <InputScope.h>
    
    HMODULE hMSCTF = LoadLibrary(TEXT("msctf.dll"));
    auto setInputScope = (decltype(SetInputScope)*)GetProcAddress(hMSCTF, "SetInputScope");
    
    if (setInputScope)
    {
    	setInputScope(hEdit, IS_KATAKANA_HALFWIDTH); // 半角カナ
    	//setInputScope(hEdit, IS_KATAKANA_FULLWIDTH); // 全角カナ
    }
    
    FreeLibrary(hMSCTF);

    2016年8月23日 23:00
  • #include <InputScope.h>
    
    HMODULE hMSCTF = LoadLibrary(TEXT("msctf.dll"));
    typedef HRESULT(WINAPI *SETINPUTSCOPE)(IN HWND, IN InputScope);
    SETINPUTSCOPE pfnSetInputScope = (SETINPUTSCOPE)GetProcAddress(hMSCTF, "SetInputScope");
    
    if (pfnSetInputScope)
    {
    	(*pfnSetInputScope)(hEdit, IS_KATAKANA_HALFWIDTH); // 半角カナ
    	//(*pfnSetInputScope)(hEdit, IS_KATAKANA_FULLWIDTH); // 全角カナ
    }
    
    FreeLibrary(hMSCTF);


    Windows 8以降では上記のようにすることで、半角カナ/全角カナの切り替えができるようです。

    参考サイト: https://msdn.microsoft.com/ja-jp/library/windows/apps/xaml/ms629025.aspx


    ありがとうございます。 試してみます。
    2016年8月23日 23:30
  • ちなみにVisual C++ 2010から実装されたautodecltype()を使用するとちょっとだけ簡単に書けます。

    #include <InputScope.h>
    
    HMODULE hMSCTF = LoadLibrary(TEXT("msctf.dll"));
    auto setInputScope = (decltype(SetInputScope)*)GetProcAddress(hMSCTF, "SetInputScope");
    
    if (setInputScope)
    {
    	setInputScope(hEdit, IS_KATAKANA_HALFWIDTH); // 半角カナ
    	//setInputScope(hEdit, IS_KATAKANA_FULLWIDTH); // 全角カナ
    }
    
    FreeLibrary(hMSCTF);

    ありがとうございます。 これも 試してみます。  
    2016年8月23日 23:30
  • SetInputScope ありがとうございました。

    現在の InputScopeを取得する、 GetInputScopeのようなFunctionが見つからないようですが、そうゆうFunctionはありますでしょうか ?

    よろしくお願い致します。

    2016年8月24日 0:03
  • SetInputScopeのドキュメントには

    To remove the input scope association, pass IS_DEFAULT to this parameter.

    An application must call this method, passing in IS_DEFAULT to the hwnd parameter, to remove the input scope association before the window is destroyed.

    等の記述がありますが、これでは足りませんか?

    2016年8月24日 0:43
  • SetInputScopeのドキュメントには

    To remove the input scope association, pass IS_DEFAULT to this parameter.

    An application must call this method, passing in IS_DEFAULT to the hwnd parameter, to remove the input scope association before the window is destroyed.

    等の記述がありますが、これでは足りませんか?

    ありがとうございます。

    やりたい事は 入力領域(CEdit)ごとに設定した入力モードを保持して、再度その入力領域にフォーカスが来たら
    保持していた入力モードを復活させます。

    以前は ImmGetConversionStatus/ImmSetConversionStatus で上手く動いていました。
    ImmGetConversionStatus のような InputScope GetInputScope(HWND) があればいいのですが

    よろしくお願い致します。

    2016年8月24日 1:02
  • ImmGetConversionStatus 関数については、Windows 8 以降でも動作する認識ですがどうでしょうか?

    CEdit から他にフォーカスが移る直前に ImmGetConversionStatus 関数にて、CEdit の入力モードを保持し、再度フォーカスが来た時に保持しておいた入力モードの IME_CMODE_KATAKANA、IME_CMODE_FULLSHAPE などのフラグをみて、SetInputScope 関数を呼び出すのはいかがでしょうか?

    2016年8月24日 1:32
  • ImmGetConversionStatus 関数については、Windows 8 以降でも動作する認識ですがどうでしょうか?

    CEdit から他にフォーカスが移る直前に ImmGetConversionStatus 関数にて、CEdit の入力モードを保持し、再度フォーカスが来た時に保持しておいた入力モードの IME_CMODE_KATAKANA、IME_CMODE_FULLSHAPE などのフラグをみて、SetInputScope 関数を呼び出すのはいかがでしょうか?

    ありがとうございます。

    丁度その方法で進めていました。 結果が出ましたらご報告致します。

    2016年8月24日 1:52
  • ImmGetConversionStatus 関数については、Windows 8 以降でも動作する認識ですがどうでしょうか?

    CEdit から他にフォーカスが移る直前に ImmGetConversionStatus 関数にて、CEdit の入力モードを保持し、再度フォーカスが来た時に保持しておいた入力モードの IME_CMODE_KATAKANA、IME_CMODE_FULLSHAPE などのフラグをみて、SetInputScope 関数を呼び出すのはいかがでしょうか?

    ありがとうございます。

    丁度その方法で進めていました。 結果が出ましたらご報告致します。

    この方法で進めていますがなかなか上手く行きませんでした。


    setInputScope に
     IS_ALPHANUMERIC_HALFWIDTH, IS_HIRAGANA, IS_ALPHANUMERIC_FULLWIDTH,  IS_KATAKANA_FULLWIDTH, IS_KATAKANA_HALFWIDTH
    などを設定しても
    ImmGetConversionStatus は全て ひらがな (conv=H19) を返すようです。

    setInputScopeでを設定した後、 画面からIMEの入力モードを別のモードに変更しても ImmGet系で得られる情報はsetInputScapeで設定したままのようです。

    上手く動かすには、この2点の問題を解消する必要があるのですが、
    いい方法がありましたらご教授お願いします。


    2016年8月24日 4:18
  • 読みづらいので上げなおしました。

    SetInputScope とImmGetConversionStatus で進めていますがなかなか上手く行きませんでした。


    setInputScope に
     IS_ALPHANUMERIC_HALFWIDTH, IS_HIRAGANA, IS_ALPHANUMERIC_FULLWIDTH,  IS_KATAKANA_FULLWIDTH, IS_KATAKANA_HALFWIDTH
    などを設定しても
    ImmGetConversionStatus は全て ひらがな (conv=H19) を返すようです。

    setInputScopeでを設定した後、 画面からIMEの入力モードを別のモードに変更しても ImmGet系で得られる情報はsetInputScapeで設定したままのようです。

    上手く動かすには、この2点の問題を解消する必要があるのですが、
    いい方法がありましたらご教授お願いします。

    2016年8月24日 4:33
  • ちょっと調べて分かったことです。

    SetInputScope 関数を使用すると、指定されたコントロールに入力モードが覚えられ、ユーザーが入力モードを変更しても次回のコントロールのフォーカス時にSetInputScope 関数で指定した入力モードに戻ってしまうようです。これを解除するには、SetInputMode(hEdit, IS_DEFAULT);というようにIS_DEFAULTを指定します。

    ImmGetConversionStatus 関数は、ImmGetContext 関数で取得した戻り値を指定しますが、ImmGetContext の引数のウィンドウハンドルは無視されるようで、現在のフォーカスコントロールの入力モードの状態が取得されるようです。(これはWindows 7 までは IME モードがスレッド単位で保持されていましたが、Windows 8 以降ではユーザー単位で保持されるように仕様が変更されたためだと思います)
    フォーカスがあるコントロールの IME 入力モードは SetInputMode で変更したかユーザーが変更したかどうかによらず私の環境(Win10 x64)では正しく取得できました。

    [フォーカスを得たときの処理]
    1.内部的保持している入力モードを見て SetInputMode で入力モードを設定する

    [フォーカスが失われる直前の処理]
    1.フォーカスがあるうちに ImmGetConversionStatus 関数で現在の入力モードを取得
    2.SetInputMode で入力モードを変更した場合は, SetInputMode(hEdit, IS_DEFAULT);を呼び出して覚えられている入力モードを解除する。

    上記のような処理でもう一度、動作をご確認できますでしょうか?

    2016年8月24日 5:14
  • ちょっと調べて分かったことです。

    SetInputScope 関数を使用すると、指定されたコントロールに入力モードが覚えられ、ユーザーが入力モードを変更しても次回のコントロールのフォーカス時にSetInputScope 関数で指定した入力モードに戻ってしまうようです。これを解除するには、SetInputMode(hEdit, IS_DEFAULT);というようにIS_DEFAULTを指定します。

    ImmGetConversionStatus 関数は、ImmGetContext 関数で取得した戻り値を指定しますが、ImmGetContext の引数のウィンドウハンドルは無視されるようで、現在のフォーカスコントロールの入力モードの状態が取得されるようです。(これはWindows 7 までは IME モードがスレッド単位で保持されていましたが、Windows 8 以降ではユーザー単位で保持されるように仕様が変更されたためだと思います)
    フォーカスがあるコントロールの IME 入力モードは SetInputMode で変更したかユーザーが変更したかどうかによらず私の環境(Win10 x64)では正しく取得できました。

    [フォーカスを得たときの処理]
    1.内部的保持している入力モードを見て SetInputMode で入力モードを設定する

    [フォーカスが失われる直前の処理]
    1.フォーカスがあるうちに ImmGetConversionStatus 関数で現在の入力モードを取得
    2.SetInputMode で入力モードを変更した場合は, SetInputMode(hEdit, IS_DEFAULT);を呼び出して覚えられている入力モードを解除する。

    上記のような処理でもう一度、動作をご確認できますでしょうか?

    ありがとうございます。 やってみてご報告します。
    2016年8月24日 6:11
  • ちょっと調べて分かったことです。

    SetInputScope 関数を使用すると、指定されたコントロールに入力モードが覚えられ、ユーザーが入力モードを変更しても次回のコントロールのフォーカス時にSetInputScope 関数で指定した入力モードに戻ってしまうようです。これを解除するには、SetInputMode(hEdit, IS_DEFAULT);というようにIS_DEFAULTを指定します。

    ImmGetConversionStatus 関数は、ImmGetContext 関数で取得した戻り値を指定しますが、ImmGetContext の引数のウィンドウハンドルは無視されるようで、現在のフォーカスコントロールの入力モードの状態が取得されるようです。(これはWindows 7 までは IME モードがスレッド単位で保持されていましたが、Windows 8 以降ではユーザー単位で保持されるように仕様が変更されたためだと思います)
    フォーカスがあるコントロールの IME 入力モードは SetInputMode で変更したかユーザーが変更したかどうかによらず私の環境(Win10 x64)では正しく取得できました。

    [フォーカスを得たときの処理]
    1.内部的保持している入力モードを見て SetInputMode で入力モードを設定する

    [フォーカスが失われる直前の処理]
    1.フォーカスがあるうちに ImmGetConversionStatus 関数で現在の入力モードを取得
    2.SetInputMode で入力モードを変更した場合は, SetInputMode(hEdit, IS_DEFAULT);を呼び出して覚えられている入力モードを解除する。

    上記のような処理でもう一度、動作をご確認できますでしょうか?

    ありがとうございます。 やってみてご報告します。
    なんども済みません。 まだ上手く動かないのでもう少し教えてください。
    >[フォーカスが失われる直前の処理]
    私の方では CEditのサブクラスのOnKillFocus内で CEdit::OnKillFocusを呼ぶ前に
    ImmGetContext -> ImmGetOpenStatus -> ImmGetConversionStatus で入力モードを得ようとしていますが、
    やはり、画面操作で変更した入力モードが上手く取れません。 (SetInputScopeで変更したモードが返ってきます)
    なにか間違っている箇所はありますでしょうか ?

    >[フォーカスが失われる直前の処理]
    >1.フォーカスがあるうちに ImmGetConversionStatus 関数で現在の入力モードを取得
    >2.SetInputModeで入力モードを変更した場合は, SetInputMode(hEdit, IS_DEFAULT);を呼び出して入力モードを元に戻す。

    ソフト起動時に入力モードを変更していますので SetInputModeが必ず使っています。
     SetInputMode(hEdit, IS_DEFAULT) を呼び出すタイミングが分からないのですが、どのタイミングで呼べばよいでしょうか?

    よろしくお願いいたします。


    2016年8月24日 7:06
  • 簡単なサンプルを作りました。 modeの初期値はIS_ALPHANUMERIC_HALFWIDTHにしてますので
    CEditの初めは半角英数で表示されて、画面操作で入力モードをひらがなに変えて CEditのフォーカスを外すと OnKillFocusに来るのですが
    この場合 半角英数のままに設定されているようですのでImmGetConversionStatusまで来ないでImmGetOpenStatusでエラーになります。

    setInputScope を数箇所にいれてみたのですが結果は同じでした。
    なにか悪いところをご指摘いただけると大変助かります。

    void CTestEdit::OnKillFocus(CWnd* pNewWnd)
    {
    	HIMC hImc = ImmGetContext(m_hWnd);
    
    	//setInputScope(hEdit, IS_DEFAULT); 
    
    	BOOL bOpen =  ::ImmGetOpenStatus(hImc);
    	if ( bOpen )
    	{
    		DWORD conv = 0, sent = 0;
    		//setInputScope(hEdit, IS_DEFAULT);
    		if ( ::ImmGetConversionStatus(hImc, &conv, &sent) )
    		{
    			switch (conv)
    			{
    			case 25://全角ひらかな
    				mode = IS_HIRAGANA;
    				break;
    			case 27	//全角カタカナ
    				mode = IS_KATAKANA_FULLWIDTH;
    				break;
    			case 19	//半角カタカナ
    				mode = IS_KATAKANA_HALFWIDTH;
    				break;
    			case 24:	//全角英数
    				mode = IS_ALPHANUMERIC_FULLWIDTH;
    				break;
    			default:
    				mode = IS_ALPHANUMERIC_HALFWIDTH;
    				break;
    			}
    		}
    		else
    		{
    			mode = IS_ALPHANUMERIC_HALFWIDTH;
    		}
    	}
    	else
    	{
    		mode = IS_ALPHANUMERIC_HALFWIDTH;
    	}
    
    	::ImmReleaseContext(m_hWnd, hImc);
    
    	CEdit::OnKillFocus(pNewWnd);
    }
    
    
    void CTestEdit::OnSetFocus(CWnd* pOldWnd)
    {
    	CEdit::OnSetFocus(pOldWnd);
    
    	// TODO : ここにメッセージ ハンドラ コードを追加します。
    	setInputScope(m_hWnd, mode); 
    }
    
    

    2016年8月24日 12:10
  • コードありがとうございます。こちらでも再現しました。

    WM_IME_NOTIFY メッセージを確認すると、WM_KILLFOCUS の時点では IME が既に閉じているような動きになっていました。こうなると、WM_KILLFOCUS で入力モードの取得を行うのが難しいので、入力モードの取得は入力モードが変わった直後、(WM_IME_NOTIFY の IMN_SETCONVERSIONMODE や WM_IME_NOTIFY の IMN_SETOPENSTATUS)で行ったほうがよさそうです。

    ただ、手元のプログラムではユーザーが IME を閉じたときと、フォーカス移動の際に IME が閉じたときの区別がつかない・・・。うーんどうしたもんか・・・

    2016年8月25日 4:55
  • コードありがとうございます。こちらでも再現しました。

    WM_IME_NOTIFY メッセージを確認すると、WM_KILLFOCUS の時点では IME が既に閉じているような動きになっていました。こうなると、WM_KILLFOCUS で入力モードの取得を行うのが難しいので、入力モードの取得は入力モードが変わった直後、(WM_IME_NOTIFY の IMN_SETCONVERSIONMODE や WM_IME_NOTIFY の IMN_SETOPENSTATUS)で行ったほうがよさそうです。

    ただ、手元のプログラムではユーザーが IME を閉じたときのメッセージと、フォーカス移動の際に IME が閉じたときの区別がつかない・・・。うーんどうしたもんか・・・


    お試し大変ありがとうございました。

    InputScopeとImm系の共存はなかなか難しいですね。

    エンドユーザーはATOKを入れている場合があるようですので、クライアントの意向でInputScopeがNGになりました。

    Windows8/10でもコントロールパネルの「アプリウィンドウごとに異なる入力方式を設定する」をONにするとImm系が使えるようですので、とりえずはプログラムからその値をONにする方針となりました。

    みなさま 大変ありがとうございました。

    2016年8月25日 5:13
  • Windows8/10でもコントロールパネルの「アプリウィンドウごとに異なる入力方式を設定する」をONにするとImm系が使えるようですので、とりえずはプログラムからその値をONにする方針となりました。

    その方針で大丈夫なんでしょうか?
    ユーザーの設定を勝手に書き換えるものですので、アプリに対する不満につながりかねないと思うのですが…。
    (少数顧客、あるいはそのアプリ専用と言える環境など、困らない場合は読み捨ててください)

    2016年8月25日 13:34
    モデレータ
  • Windows8/10でもコントロールパネルの「アプリウィンドウごとに異なる入力方式を設定する」をONにするとImm系が使えるようですので、とりえずはプログラムからその値をONにする方針となりました。

    その方針で大丈夫なんでしょうか?
    ユーザーの設定を勝手に書き換えるものですので、アプリに対する不満につながりかねないと思うのですが…。
    (少数顧客、あるいはそのアプリ専用と言える環境など、困らない場合は読み捨ててください)

    あまりいい方法ではないですが、ATOK+IME両方使えるようにするには、現状この方法しか考えられません。

    一応影響少なくするためアプリ終了時に、設定を戻すようにしました。

    2016年8月25日 15:50