- PR -

C#での独自コンポーネントへのIMEからの文字入力の方法

投稿者投稿内容
冬星
会議室デビュー日: 2005/12/11
投稿数: 6
投稿日時: 2005-12-11 02:30
はじめまして。C#でテキストエディタを作ってみようと挑戦しています。

System.Windows.Forms.UserControl の派生クラス myctrl をつくり、
その上に System.Windows.Forms.Panel の派生クラス txtarea をつくってカレットや文字を表示させています。

ASCII文字の入力は、myctrl に System.Windows.Forms.KeyPressEventHandler などを用意してキー入力を受け取り処理できましたが、日本語の文字が入力できません。

そこで、IME制御に挑戦しているのですが、IME入力窓を開くことすらできずにおります。

具体的には myctrl.GotFocus イベントハンドラ内で WIN32API ImmGetContext,ImmSetOpenStatus,ImmReleaseContext の順で呼び出してみたのですが、ImmGetContext に this.Handle を渡してみても 0 しか返されず入力コンテキストが取得できません。myctrl.Focused は確かに true になっているのですが…。WIN32 API の GetFocus() の戻り値をセットしたりもしてみたのですが ImmGetContext は 0 しか返してくれません。

独自に作ったコンポーネントの上に IME の変換窓を開いて確定した文字列を手に入れるにはどうすればよいのでしょうか?方法が根本的に間違っているでしょうか。

それぞれの API の宣言は下のようにしています:
 [DllImport("Imm32.dll", CharSet = CharSet.Auto)]
 private static extern bool ImmSetOpenStatus(IntPtr hIMC, bool fOpen);
 [DllImport("Imm32.dll", SetLastError=true)]
 public static extern IntPtr ImmGetContext(IntPtr hWnd);






じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2005-12-11 10:20
引用:

冬星さんの書き込み (2005-12-11 02:30) より:

System.Windows.Forms.UserControl の派生クラス myctrl をつくり、
その上に System.Windows.Forms.Panel の派生クラス txtarea をつくってカレットや文字を表示させています。
ASCII文字の入力は、myctrl に System.Windows.Forms.KeyPressEventHandler などを用意してキー入力を受け取り処理できましたが、日本語の文字が入力できません。

そこで、IME制御に挑戦しているのですが、IME入力窓を開くことすらできずにおります。


ごめんなさい、ひとつ教えてください。
何を目的として「IME 制御」に挑戦しているのでしょうか?
その前の文脈からは、何が目的で Key イベントを捉えているのかが書かれていない為、判りません。
目的によっては IME の制御をしなくて済むように思えます。

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
冬星
会議室デビュー日: 2005/12/11
投稿数: 6
投稿日時: 2005-12-11 15:50
ご質問ありがとうございます。

引用:

ごめんなさい、ひとつ教えてください。
何を目的として「IME 制御」に挑戦しているのでしょうか?
その前の文脈からは、何が目的で Key イベントを捉えているのかが書かれていない為、判りません。
目的によっては IME の制御をしなくて済むように思えます。


はい。そもそもIME制御をしようとしているのは、作っているテキストエディタコンポーネントの文字入力で、IMEからの全角文字の入力が可能にしたいからです。

なぜ、テキストエディタコンポーネントをゼロから(UserControlから)作っているのかと言えば、当初RichTextを拡張して仕様を満たそうとしたのですが、だんだん困難になってきて、自前で全部作らざるを得ないのではないかと考えたからです。

どのような仕様のテキストエディタを作ろうとしているかですが:
・文法を別途定義して、文法に従って'<','>',識別子などの構文上の強調表示。
・括弧入力時、対応する括弧の点滅表示。
・改行コード、EOFマークの画像表示。
・折り返し、非折り返し表示。
・行番号、行ルーラー、桁ルーラー表示。
・無限回数の Undo/Redo への対応。(その際、強調色分け表示処理に Undo/Redo 処理が影響されないこと。)
・xml, xhtml, html の対応するノードレベルの Expand, Collapse 表示。( C# の IDE がもっているような、エディタの左側の +,- ボタンでの開閉。)

これらを、 RichText を継承して拡張することで実装できるかやっているうちに、拡張では無理じゃないかと思い始めたため、独自コンポーネントのスクラッチを試みはじめ、調べたところ UserControl から派生させたクラスでそれが実現できそうだと考えて試している…というのが現状です。

とりあえず、IME窓を開くこと自体が出来なかったので、まずは ACSII 文字だけでも入力できるようにサンプルを書いたため、 KeyPress, KeyDown イベントをフックしてキーコードを取得して、文字を入力するようになっています。(この方法が ACSII 文字の入力方法として適切かどうかはわかりません。キー入力を捉えて ACSII 文字入力を実現する他の適切な方法がわからなかったんです。)

UserControl 派生クラスと、その上に表示した Panel の組み合わせでテキストエディタの実現をするというやり方は、 C# の唯一のソースの拝見できるサンプルである SharpDevelop のエディタコンポーネントを読んで、理解できる範囲で参考にしました。
冬星
会議室デビュー日: 2005/12/11
投稿数: 6
投稿日時: 2005-12-11 21:40

UserControl 派生クラスのコンストラクタの中で、新規に入力コンテキストを生成して 表示用の Panel インスタンス ( edarea ) のハンドルと関連付けしたら、 m_hIMC に新規に作った入力コンテキストが返されるようになりました。
引用:

m_hIMC = ImmCreateContext();
if (m_hIMC != null) {
IntPtr oldhIMC = ImmAssociateContext(edarea.Handle, m_hIMC);
}



そこで、下のような imeOpen 関数を書いて UserControl 派生クラスの GotFocus イベントプロシージャ内で imeOpen(true, edarea.Handle); として呼び出すようにしたら、 ImmGetContext() も 0 でない hIMC を返すようになり、戻り値的には全ての呼び出しが正常に呼び出せるようになりました。
引用:

public void imeOpen(bool bOpen, IntPtr hWnd)
{
//IntPtr hIMC = m_hIMC;
IntPtr hIMC = ImmGetContext(hWnd);
bool bRet = false;
if (hIMC != null) {
if (ImmGetOpenStatus(hIMC) != bOpen) {
bRet = ImmSetOpenStatus(hIMC, bOpen);
}
if (bOpen == true) {
LOGFONT lf = new LOGFONT();
lf.lfHeight = 9;
lf.lfFaceName = "MS ゴシック";
IntPtr hFont = CreateFontIndirect(lf);
if (IntPtr.Zero != hFont) {
DeleteObject(hFont);
}
bRet = ImmSetCompositionFont(hIMC, ref lf);
COMPOSITIONFORM cf;
cf.dwStyle = CFS_POINT;
cf.ptCurrentPos = m_caretPos;
cf.rcArea.left = 0;
cf.rcArea.top = 0;
cf.rcArea.right = 0;
cf.rcArea.bottom = 0;
bRet = ImmSetCompositionWindow(hIMC, ref cf);
}
//
ImmReleaseContext(hWnd, hIMC);
}
}



ですが、相変わらず IME の文字入力窓は一向開く気配がありません…。orz
じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2005-12-12 00:39
ならば、IME を制御するという考えは不自然です。
通常 "入力を受け付けるコントロール" は、TextBox か RichTextBox から継承します。

# もしこれ以外の方法で入力を受け付けるコントロールを作ったとしても、
# 制御しなければならないポイントは結局同じで、遠回りしているとしか思えません。

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
冬星
会議室デビュー日: 2005/12/11
投稿数: 6
投稿日時: 2005-12-12 01:50
入れ違いで、じゃんぬねっとさんの最初のご質問が気になって、実験をしておりました。

[実験]
A. SharpDevelop1.1 + .Net Framework1.1runtime + .Net1.1SDK
B. SharpDevelop2.0(corsavy) + .Net Framework2.0runtime + .Net2.0SDK(english)

[結果]
A. IME制御のコードを全て外しても全角半角キーによりIME窓が開き全角入力できました。
B. IME制御コードの有無に関わらずIME窓は一切開きませんでした。

結果から言えるのは A. の構成でなら「何もしなくても全角入力が出来る。」ということでした…が、なぜこうなるのかわかりません。…なぜだろう……。(^^;

引用:

じゃんぬねっとさんの書き込み (2005-12-12 00:39) より:
ならば、IME を制御するという考えは不自然です。
通常 "入力を受け付けるコントロール" は、TextBox か RichTextBox から継承します。

# もしこれ以外の方法で入力を受け付けるコントロールを作ったとしても、
# 制御しなければならないポイントは結局同じで、遠回りしているとしか思えません。


すみません、おっしゃっていることの意図をつかみたいと真剣に思っているのですが、本質的な部分でさっぱりわかりません。(汗)クラスとは、扱うべきデータ構造を適切な形でもち、それに対する必要な処理機能を備えているモジュールのようなものだと考えているのですが、そうすると今回のように TextBox 自身の文字表示機能もデータ構造も一切使わず、自前で表示し自前でデータを取り扱うにも関わらず、TextBox から派生させることの自然さ(メリット??)とは、何なのでしょうか?

SharpDevelop のエディタコンポーネントは、なぜ TextBox から派生させず UserControl と Panel で実装されていたのでしょう…。(作者しか知らない?)また、独自コンポーネントを作る土台に使われるらしい UserControl から派生させてコンポーネントを作るケースは、「入力を受け付けないコントロール」の場合に限るのが普通だという風に考えると良いのでしょうか。

あ…もしかして、TextBoxならIME窓を開く処理が内蔵されているという理由からでしょうか。あれ?でも、それだったらデータ構造はTextBoxがもっているものを使ってやらないとIME窓を出す機能も利用できなさそうな気がしますね。(^^;うーん…難しい。
じゃんぬねっと
ぬし
会議室デビュー日: 2004/12/22
投稿数: 7811
お住まい・勤務地: 愛知県名古屋市
投稿日時: 2005-12-12 08:53
引用:

冬星さんの書き込み (2005-12-12 01:50) より:

すみません、おっしゃっていることの意図をつかみたいと真剣に思っているのですが、本質的な部分でさっぱりわかりません。(汗)クラスとは、扱うべきデータ構造を適切な形でもち、それに対する必要な処理機能を備えているモジュールのようなものだと考えているのですが、そうすると今回のように TextBox 自身の文字表示機能もデータ構造も一切使わず、自前で表示し自前でデータを取り扱うにも関わらず、TextBox から派生させることの自然さ(メリット??)とは、何なのでしょうか?


入力する機能が用意されているのに自前で実装する不自然さの方を問題視しています。

引用:

あ…もしかして、TextBoxならIME窓を開く処理が内蔵されているという理由からでしょうか。あれ?でも、それだったらデータ構造はTextBoxがもっているものを使ってやらないとIME窓を出す機能も利用できなさそうな気がしますね。(^^;うーん…難しい。


継承したコントロールを触られたことはありませんか?
ユーザーコントロールと継承コントロールの区別は付いていますよね?

# 私も完全に読み取れてないような気がしてきましたが... (^^;)

_________________
C# と VB.NET の入門サイト
じゃんぬねっと日誌
冬星
会議室デビュー日: 2005/12/11
投稿数: 6
投稿日時: 2005-12-12 16:28
引用:

じゃんぬねっとさんの書き込み (2005-12-12 08:53) より:
入力する機能が用意されているのに自前で実装する不自然さの方を問題視しています。


なるほど…TextBoxは全角入力が普通にできますよね。それでは…と試しに、TextBox 派生クラス myTextBox を作り Form に貼り付けてみました。これを使って、私が前の post で書いたような複雑な仕様を満たす表示を行うためには:

1. TextBox 自身の文字表示方法では望む表示が実現できないので、独自に描画する必要がある。
2. TextBox 自身の Undo/Redo では使い物にならないので、独自に caret位置と入力文字 を保存する Undo バッファをもち、それを使って無限回数の Undo/Redo を実現する必要がある。
3. 1. や 2. のためには、派生元の TextBox に対して入力された文字を、TextBox が TextBox のデータ構造に保存する直前で奪い、派生クラス独自のデータ構造管理部分に渡してやる必要がある。また、無駄なメモリの消費を抑えるためにも、TextBox 自身が入力文字をメモリ保存する必要は全くないので、その意味からも、全角文字を入力したいという当初の目的からも「 TextBox に全角文字入力が発生して、その文字を TextBox が挿入する直前で、入力文字を奪う必要がある。」と考えましたが、こういう考えで、じゃんぬねっとさんのおっしゃっている方法と合致しているでしょうか?(かなり違ってしまっている予感が……。もし違っているとしたら、私の知らない方法をおっしゃっている可能性大です。私の方がスキル不足なのは間違いないです。)

もし大方の方針は合っているよということでしたら、あとは実現方法を探すということになるのですが…とりあえず TextBox には OwnerDraw プロパティはありませんでした。また、全角文字が受け付けられた後の入力文字を受け取る(文字入力イベントをフックする)ようなイベントハンドラも見つけられませんでした。(OnKeyPress/OnKeyDown などしかないようでした。後ろに書きますが OnKeyPress などで全角入力がそのまま対応できるのかどうか…という部分でも疑問が残っています。)ここまででとりあえずのお手上げ状態になりました。(汗

引用:

継承したコントロールを触られたことはありませんか?
ユーザーコントロールと継承コントロールの区別は付いていますよね?


「継承コントロール」の意味が、単純に親クラスの派生クラスを作って(e.g. class foo : TextBox { } のように…。)使うということでしたら今回もやっています。違う意味でしたら、もしかしたら未経験かも知れません。
なんとなく、違う意味でおっしゃってる気がします。なんだろう…派生クラスを作って使うという意味以外に「継承する」という言葉が使われることがあるんだろうか…もしかして、ある部分でものすごく無知なのかも知れません。(^^;

えっと…念のため。私がしたことがあるのは ListView をカスタマイズして subitem に画像を表示する ListView をつくったり、右クリックでタブを切り替えられる TabControl をつくったりするために、親になるクラス ListView/TabControl から派生したクラスを作って、そのクラスでイベントプロシージャをフックして(この場合でしたら TreeView の DrawItem,DrawSubItem とか、TabControl のマウスクリックイベントプロシージャ)元々の ListView や TabControl には無い機能を実装したりするケースです。

ところで、前の post で書いたのですが「 .Net1.1+#develop1.1 だと、何もしないでも UserControl+Panel の構成で全角文字が入力できてしまった」方の件ですが、これについてなぜかわかる方はおられませんでしょうか。(多分最初にじゃんぬねっとさんが「内容によってはIME制御しなくても実現できると思います。」とおっしゃったのがそのあたりではと思うんですが。

OnKeyPress/OnKeyDown で文字を受け取って描画するやり方で、特にIME制御などしなくてもIME窓が開き全角文字が受け取れるのが「正常」であり、「.Net2.0+#develop2.0 の動作が変」なのでは?と推測したのですが、正しいのかどうかわからず気になってます。

ちなみに #develop2.0 自身のIDEのエディタも、現在全角文字が一切入力できなくなっています。(私が参考にした実装なので、当然といえば当然なのですが、なぜ #develop1.1 だと同じコードで平気で全角入力できるのかがわからないのです。)

もし、この推測があっているのでしたら、#dev2.0での何らかの障害???がなおりさえすれば、IME制御なぞしなくとも現在の方法で「全角入力がしたい」という当初の質問は満たされるのですが…。


[ メッセージ編集済み 編集者: 冬星 編集日時 2005-12-12 16:46 ]

スキルアップ/キャリアアップ(JOB@IT)