VB のたまご

作成日: 2016/02/11, 更新日: 2016/02/11


RichTextBox用のインテリセンスを作ってみた

  •  インテリセンスって便利ですよね。入力しようとしている文字を推測してくれて、さらに代わりに入力補完してくれる。 ・・・これって、介護されているうちに入るのかな?私が腱鞘炎にならないのはインテリセンスのおかげかもしれません。 インテリセンスのおかげで実装時間が縮まりますもんね。スペル分かんなくても何とかなるし、タイポも減るし、良いこと尽くしです^^

  •  ちょっとそこまで・・・みたいな軽いノリで、ちょっと IDE を作りたいなと思いまして、あれこれ調べていました。 と言っても、Visual Studio みたいな立派なものではなく、最小限の機能さえあればいいやというレベルです。 まぁ多分、どこかでつまづいて、またはすぐに飽きて、ぽしゃると思いますが・・・。 で、エディタにインテリセンスは必須かなと思っていまして、Visual Studio のインテリセンスの動きを調べていました。

  •  インテリセンスに絞って見てみると、①入力していると、ListBox 的なやつが表示されるのがインテリセンスで、 ②インテリセンスが表示されても、フォーカスはエディタのままで、③入力中の文字に近い文字を勝手に選択してくれて、 ④タブキーとかで候補値をセットしてくれる、という感じで動いていました。

  •  ①はコントロールそのものだと分からないですが、画面でも良ければ、キャレット位置の座標が分かれば Top, Left プロパティというか Location プロパティに表示位置をセットすればできそうです。 ②は通知ウィンドウを作ってみたの記事にあるプログラムを引用してできそうだし、 ③は、キャレット前の入力文字を計算して、インテリセンス画面にプロパティとかで渡せばいいし、 ④は、キー制御命令( ProcessCmdKey とか ProcessDialogKey とか)でいじっちゃえばできそうです!

  • スポンサーリンク


ダウンロード

機能紹介

  •  あらかじめ登録しておいた候補一覧の中で、入力中の文字列がヒットしそうなら、その対象文字列を選択しつつインテリセンスを表示しています。 インテリセンスは非表示かつ最前面で表示するように制御をかけています。で、インテリセンスが表示中の場合だけ、 RichTextBox 内の上下キー、Tab キー、Escape キーをフックして、動作を無効にしつつ、代わりにインテリセンス画面を操作するように作っています。

  •  入力中の文字列取得は、RichTextBox の機能(取得作法?)に習って取得しています。あとインテリセンスは、表示して隠して表示して・・・という風に、 使いまわししています。一回いっかいインスタンス生成して破棄、インスタンス生成して破棄・・・ということはしていないです。 これはこだわった結果ではなく、たまたまです^^;

  •  見た通りですが、Visual Studio と比較するとしょぼいです。 もっといろいろ気が利く機能を付け足して、名前の通りインテリな頭脳と絶妙な入力補助センスを持った子に仕上げないといけないですが、 それはまた、後でそのうちおいおい多分考えるとしましょう。

  • イメージ
  •  サンプル

  • スポンサーリンク


実装

  •  IntellisenseTest_RichTextBox.zip を解凍して、スタートアッププロジェクトを切り替えて確認します。 画面の組み合わせで作ったバージョンと、インテリセンスを RichTextBox 内に組み込んで作った、拡張コントロールを利用するバージョンの2パターンがあります。 ここでは、画面の組み合わせで作ったバージョン( IntellisenseTest_RichTextBox1 プロジェクトの方)を説明します。

  •  ①インテリセンスを使う画面と②インテリセンス画面の2つで実現しています。 ①インテリセンスを使う画面というのは、RichTextBox を貼った画面のことで、Form に RichTextBox を貼っただけです。 ②インテリセンス画面は、Form に ListBox を貼っただけです。 画面の方は、それっぽく見えるように見た目を変更していますが、ListBox は貼った時の初期値のままです。

  • 最初はインテリセンス画面です。

  • インテリセンス画面のプロパティ設定
    プロパティ名
    ControlBox False
    ShowInTaskbar False
    StartPosition Manual


  • 【プロパティ】主に外部とのやり取り系が多いですね。

  • メンバープロパティ
    プロパティ名 説明
    OwnerEditor RichTextBox インテリセンス画面のマウスダブルクリック時に、RichTextBox へ候補値をセットするために使用
    InputValue String 現在入力中の値。RichTextBox へ候補値をセットする際、位置計算に使用。その他確認用として公開。
    ReturnValue String 決定した候補値を管理。こちらも RichTextBox へ候補値をセットする際に使用。
    IsShown Boolean 現在、インテリセンスが表示中かの確認用。IsHidden プロパティとは連携している。
    IsHidden Boolean 現在、インテリセンスが非表示かの確認用。IsShown プロパティとは連携している。


  • 【イベント】インテリセンス自体の処理がメインですね。

  • イベント
    イベント名 処理説明
    ListBox の SelectedIndexChanged 選択項目の変更に合わせて、戻り値を更新
    ListBox の MouseDoubleClick リストボックス内の項目をダブルクリックされた時は、候補値の決定なので、 RichTextBox へ候補値をセットする処理と、インテリセンスを非表示にする処理を記載


  • 【メソッド】主に外部からの操作処理がメインです。

  • メソッド
    メソッド名 処理説明
    InitCandidateItems 初期化処理。インテリセンスに表示する候補一覧を ListBox にセット
    ContainsItem インテリセンスを表示するか否かの判定用。 LisBox 内の候補一覧の中に、入力中の文字に該当するものがあるかどうかチェック
    ChangeSelectedIndex 入力中の文字列を元に、ListBox 内の候補一覧を探していき、前方一致している項目があれば、選択項目を切り替える。 精度向上のため、前方一致( xxx.StartsWith )よりも部分一致( xxx.Contains )の方が良い気がしてきた^^;
    HideForm インテリセンス画面を非表示
    ShowForm インテリセンス画面を表示
    EmulateKeyDown_DownKey 下キー押下のエミュレート。インテリセンス画面の ListBox 内で下キーを押下するのと同等
    EmulateKeyDown_UpKey 上キー押下のエミュレート。インテリセンス画面の ListBox 内で上キーを押下するのと同等
    SetOwnerEditor ListBox の MouseDoubleClick イベント内で使用。RichTextBox 内に、決定した候補値をセット


  • 【特殊】インテリセンスっぽく振る舞うための設定です。こういうのは普段なかなか使うことが無いものばかりだったので、勉強になりました。 明日には忘れそうですが・・・。

  • CreateParams
  • ' 常に最前面に表示させる(TopMostプロパティを使うと、ShowWithoutActivationプロパティが効かない不具合の対策)
    Protected Overrides ReadOnly Property CreateParams As CreateParams
        Get
            'Return MyBase.CreateParams
            Const WS_EX_TOPMOST As Integer = &H8
            Dim p = MyBase.CreateParams
            p.ExStyle = p.ExStyle Or WS_EX_TOPMOST
            Return p
        End Get
    End Property
    

  • ShowWithoutActivation
  • ' 非アクティブのまま、画面表示するように制御
    Protected Overrides ReadOnly Property ShowWithoutActivation As Boolean
        Get
            Return True
        End Get
    End Property
    

  • ProcessDialogKey
  • ' インテリセンスがアクティブの場合、Escape キー押下時に非表示にする
    Protected Overrides Function ProcessDialogKey(keyData As Keys) As Boolean
    
        If keyData = Keys.Escape Then
            Me.HideForm()
            Return True
        Else
            Return MyBase.ProcessDialogKey(keyData)
        End If
    
    End Function
    

  •  実は、CreateParams でもできることが分かる前までは、Windows API で対処していました。 こちらでもうまく動作してくれるのでこのままでも良かったのですが、宣言と呼び出しの2つ書かないといけないので、CreateParams に変えました。

  • SetWindowPos
  • ' 画面ロードイベントで使用
    ' 本当は、TopMost = True で、非アクティブかつ最前面に表示したいが、うまくいかない
    ' そうなるように、Windows API を設定
    Private ReadOnly HWND_TOPMOST As IntPtr = New IntPtr(-1)
    Private Const SWP_NOSIZE As Integer = &H1
    Private Const SWP_NOMOVE As Integer = &H2
    Private Const SWP_NOACTIVATE As Integer = &H10
    Private Const SWP_SHOWWINDOW As Integer = &H40
    Private Const SWP_NOSENDCHANGING As Integer = &H400
    Private Const TOPMOST_FLAGS As Integer = SWP_NOACTIVATE Or SWP_NOMOVE Or SWP_NOSENDCHANGING Or SWP_NOSIZE Or SWP_SHOWWINDOW
    
    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function SetWindowPos(hWnd As IntPtr, hWndInsertAfter As IntPtr, x As Integer, y As Integer, cx As Integer, cy As Integer, uFlags As Integer) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function' インテリセンス画面ロードイベント
    Private Sub IntellisenseForm_Load(sender As Object, e As EventArgs) Handles Me.Load
    
        ' 表示時、最前面で表示されるように設定
        '(TopMost = True & ShowWithoutActivation の組み合わせが、うまく効かない現象の対応)
        SetWindowPos(Me.Handle, HWND_TOPMOST, 0, 0, 0, 0, TOPMOST_FLAGS)
    
    End Sub
    



  • 次にインテリセンスを使う画面です。

  • 画面のプロパティはデフォルトのまま、 RichTextBox のプロパティは AcceptsTab プロパティを True に変えた以外は、デフォルトのままです。 コードエディタ前提で考えていたのでタブインデントできるようにです。ただ、False のままでもいけるんじゃないかなと思います多分。

  • RichTextBox のプロパティ設定
    プロパティ名
    AcceptsTab True


  • 【フィールド】インテリセンス画面を操作するための変数です。

  • フィールド
    フィールド名 説明
    popup1 IntellisenseForm インテリセンス画面の変数


  • 【イベント】インテリセンスとの連携処理用です。

  • イベント
    イベント名 処理説明
    Form の Load インテリセンスに表示する候補一覧を準備する。 作り方によっては、別にロードでやらなくても、プロパティ渡しやメソッド経由で渡してもOK
    Form の LocationChanged インテリセンス表示中に画面移動されると、インテリセンス画面が置いてけぼりになってしまう。 画面に追従する処理での対応でも良かったが、思いつかなかったため、強制的に非表示にする対応を組み込んだ。
    RichTextBox の KeyUp 入力中に、インテリセンスを表示する。TextChanged イベントでも OK だったような・・・。


  • 【特殊】RichTextBox 内のキー入力を独自に制御しています。

  • ProcessCmdKey
  • ' キー入力をカスタマイズする
    Protected Overrides Function ProcessCmdKey(ByRef msg As Message, keyData As Keys) As Boolean
    
        Const WM_KEYDOWN As Integer = &H100
        Const WM_SYSKEYDOWN As Integer = &H104
    
        ' ポップアップが表示中の場合だけ、コードエディタ内に限り、指定のキー入力を無効にして独自処理に変更する
        ' (Return True することで、独自処理だけして終わらせることができる)
        If Me.popup1.IsShown Then
            If msg.HWnd = Me.RichTextBox1.Handle Then
                If msg.Msg = WM_KEYDOWN OrElse msg.Msg = WM_SYSKEYDOWN Then
    
                    Select Case keyData
    
                        Case Keys.Escape
                            ' インテリセンスを非表示にする
                            Me.popup1.HideForm()
                            Return True
    
                        Case Keys.Down
                            ' エディタ内の下移動を無効にして、インテリセンスのリストボックス内候補値を1つ下に移動させる
                            Me.popup1.EmulateKeyDown_DownKey()
                            Return True
    
                        Case Keys.Up
                            ' エディタ内の上移動を無効にして、インテリセンスのリストボックス内候補値を1つ上に移動させる
                            Me.popup1.EmulateKeyDown_UpKey()
                            Return True
    
                        Case Keys.Tab
                            ' インテリセンス候補の決定
                            Me.popup1.HideForm()
                            Me.popup1.SetOwnerEditor()
                            Return True
    
                        Case Else
                    End Select
    
                End If
            End If
        End If
    
        ' それ以外のキー操作は従来通り
        Return MyBase.ProcessCmdKey(msg, keyData)
    
    End Function
    

    スポンサーリンク