2006年10月09日

IME 現在入力中

絶賛放置中の当サイトですが、流石にここまで放っておくのは些か気まずいので場繋ぎ的に更新。時間はどうとでもするんですが、どうにも心の琴線に触れるネタが無いんですよねぇ……。

さて、今回の更新は某掲示板にヒントを得て、IME を使用しての入力中かどうか取得するクラスです。入力中かどうかの状態が変わった時にはイベントを発生させます。とは言ってもごく簡単というか手抜きで、WM_IME_STARTCOMPOSITION と WM_IME_ENDCOMPOSITION を監視しているだけですので、インスタンス生成時に入力中だった場合は一旦入力が終了するまで正しい値を返しません。

今回のソースの技術的な解説を少々。

WM_IME_STARTCOMPOSITION / WM_IME_ENDCOMPOSITION メッセージは、実際に入力中のコントロールに対して送信されます。つまり、普通にやろうとすると、使用する全ての入力用コントロールについて派生コントロールを用意するか NativeWindow を用意するかしてメッセージを捕まえなくてはなりません。そしてそれは現実的ではないでしょう。今回はその問題を解決するため、メッセージを受信する大元で捕まえるようにしています。それが IMessageFilter インターフェイスです。

IMessageFilter を実装したクラスを Application.AddMessageFilter メソッドで登録する事により、アプリケーションに(多分厳密にはアプリケーションコンテキストのスレッドメッセージキューに)送信されたメッセージに対して、アプリケーションが処理する前(各コントロールへ送る前)に独自処理を施す事が可能になります。

ちなみに、IMessageFilter はモーダル/モードレスダイアログなどについても有効ですが、別スレッドで作成されたコントロールについては扱えません。まあ、普通はそもそもそんなコトしないでしょうが。

今回のソースはカスタムイベントを使えない VB7.0/7.1(VB2002/2003)は対応していません。まあ Component.Events が使えないだけですので、その周辺をカスタムイベントじゃなくて普通のイベントで処理するようにすれば移植は難しい事ではないでしょう。

// C#
using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace HongliangSoft.Utilities.IO {
    [DefaultEvent("ConversionChanged")]
    public class ImeStatus : Component, IMessageFilter {
        public ImeStatus() {
            Application.AddMessageFilter(this);
        }
        public ImeStatus(IContainer container) : this() {
            if (container != null)
                container.Add(this);
        }
        private static readonly object EventConversionChanged
            = new object();
        private bool isConversion = false;
        [Browsable(false)]
        public bool IsConversion {
            get { return this.isConversion; }
        }
        public event EventHandler ConversionChanged {
            add    {
                this.Events.AddHandler(EventConversionChanged, value);
            }
            remove {
                this.Events.RemoveHandler(EventConversionChanged, value);
            }
        }
        protected override void Dispose(bool disposing) {
            if (disposing) {
                Application.RemoveMessageFilter(this);
            }
            base.Dispose(disposing);
        }
        public bool PreFilterMessage(ref Message m) {
            const int WmStartComposition = 0x10D;
            const int WmEndComposition = 0x10E;
            switch (m.Msg) {
              case WmStartComposition :
                this.isConversion = true;
                OnConversionChanged(EventArgs.Empty);
                break;
              case WmEndComposition :
                this.isConversion = false;
                OnConversionChanged(EventArgs.Empty);
                break;
            }
            return false;
        }
        protected virtual void OnConversionChanged(EventArgs e) {
            EventHandler handler
                = this.Events[EventConversionChanged] as EventHandler;
            if (handler != null)
                handler(this, e);
        }
    }
}
' VB8.0
Imports System
Imports System.ComponentModel
Imports System.Windows.Forms

Namespace HongliangSoft.Utilities.IO
   <DefaultEvent("ConversionChanged")> _
   Public Class ImeStatus
      Inherits Component
      Implements IMessageFilter

      Public Sub New()
         Application.AddMessageFilter(Me)
      End Sub

      Public Sub New(ByVal container As IContainer)
         If container IsNot Nothing Then
            container.Add(Me)
         End If
      End Sub
      Private ReadOnly Shared EventConversionChanged As New Object()
      Private m_isConversion As Boolean = False

      <Browsable(False)> _
      Public ReadOnly Property IsConversion() As Boolean
         Get
            Return Me.m_isConversion
         End Get
      End Property
      Public Custom Event ConversionChanged As EventHandler
         AddHandler(ByVal value As EventHandler)
            Me.Events.AddHandler(EventConversionChanged, value)
         End AddHandler
         RemoveHandler(ByVal value As EventHandler)
            Me.Events.RemoveHandler(EventConversionChanged, value)
         End RemoveHandler
         RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)
            Dim handler As EventHandler = _
               TryCast(Me.Events(EventConversionChanged), _
                       EventHandler)
            If handler IsNot Nothing Then
               handler(sender, e)
            End If
         End RaiseEvent
      End Event

      Protected Overrides Sub Dispose(ByVal disposing As Boolean)
         If disposing Then
            Application.RemoveMessageFilter(Me)
         End If
         MyBase.Dispose(disposing)
      End Sub

      Public Function PreFilterMessage(ByRef m As Message) As Boolean _
                            Implements IMessageFilter.PreFilterMessage
         Const WmStartComposition As Integer = &H10D
         Const WmEndComposition As Integer = &H10E
         Select Case m.Msg
            Case WmStartComposition
               Me.m_isConversion = True
               Me.OnConversionChanged(EventArgs.Empty)
            Case WmEndComposition
               Me.m_isConversion = False
               Me.OnConversionChanged(EventArgs.Empty)
         End Select
         Return False
      End Function

      Protected Overridable Sub OnConversionChanged(ByVal e As EventArgs)
            RaiseEvent ConversionChanged(Me, e)
      End Sub
   End Class
End Namespace
posted by Hongliang at 02:41| Comment(0) | TrackBack(0) | .NET | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.seesaa.jp/tb/25115139

この記事へのトラックバック

ここ(hongliang.seesaa.net)で公開しているものについて、利用は自由に行って頂いて構いません。改変、再頒布もお好きになさって下さい。利用に対しこちらが何かを要求することはありません。

ただし、公開するものを使用、または参考したことによって何らかの損害等が生じた場合でも、私はいかなる責任も負いません。

あ、こんなのに使ったってコメントを頂ければ嬉しいです。

×

この広告は1年以上新しい記事の投稿がないブログに表示されております。