『 C++/CLI の薄い本(RichTextBox編)』  Copyright © 2015 GATARO All Rights Reserved.    筆者のメインサイト

RichTextBoxのキーイベントオーバーライド

イベントのオーバーライドとは

RichTextBoxはそれ自身がテキストエディタであり、Aのキーを押すとAが表示され、BSキーやDelキーを押すと一文字消えます。これは全てのキー入力(キーイベント)に対するアクションが予め設定されているからです。しかし、場合によっては予め設定されているアクションとは違う事を行いたいことがあります。そこで予め設定されているアクションを書き換えてしまうのが、オーバーライドです。

行単位コンパイラにおけるオーバーライドの実例

下記は「行単位コンパイラ」を作る為にRichTextBoxの改行キーをオーバーライドした実例です。
「行単位コンパイラ」とは1行毎に(Enterキーを押す度に)ソースプログラムを機械語に変換するコンパイラです。コンパイラ自体の詳細説明はここでは割愛しますが、要は勝手にソースプログラムの行数が増えたり減ったりしないように以下の制限を設けているテキストエディタだと御理解下さい。

・Enterキーを押しても行数は変わらない

RichTextBoxはEnterキーを押すと「改行」が挿入されるが、本テキストエディタは「改行」は挿入されず、次の行の先頭にキャレットが移動するのみに変更。但しキャレットが「空白行ではない最終行」にある状態でEnterキーを押した場合は文末に「空白行」が挿入される。

・BSキーとDelキーの制限

RichTextBoxは行の先頭にキャレットがある状態でBSキーを押す、もしくは行の末尾にキャレットがある状態でDelキーを押すとテキスト全体の行数が勝手に1行減る。これを無効化。

・Insキーの役割変更

RichTextBoxにおけるInsキーは「挿入モード」と「上書きモード」の切り替えですが、これを「現在の行の下に空白行を追加」に変更。更にシフトを押しながらのInsキーは「現在の行の上に空白行を追加」とする。

・1行まるごと削除の追加

シフトを押しながらのDelを「1行削除」に割り当て。このキー以外でテキスト全体の行数が減ることはない

実際のソースコード

ヘッダー (関連行のみ)

ref class R16SrcWin : public System::Windows::Forms::RichTextBox {

public:
        R16SrcWin(); 

protected:
      virtual void OnKeyDown (System::Windows::Forms::KeyEventArgs^ e) override ;
}

ソース(OnKeyDown全体)

#define KEY(X) e->KeyCode == System::Windows::Forms::Keys::X
void  R16SrcWin::OnKeyDown(System::Windows::Forms::KeyEventArgs^ e){

        //下記4行は事前に変数に代入しておく必要性は無い。以降の記述を簡潔にする為に存在
        int idx = SelectionStart;                    //現在のキャレット位置
        int lno = GetLineFromCharIndex(idx);         //現在の行番号(先頭行が0)
        int fst = GetFirstCharIndexFromLine(lno);    //現在行の先頭インデクス
        int nxt = GetFirstCharIndexFromLine(lno+1);  //次行の先頭インデクス、無効時は-1
        
        //矢印キーはオリジナル通りの動きを行う
        if (KEY(Up) || KEY(Down) || KEY(Left) || KEY(Right)) return;
        
        //BSキーはキャレットが行頭以外ならオリジナル通りの動きを行う
        if (KEY(Back) && idx!=fst) return;
        
        //シフト無しDelキーはキャレットが行末以外ならオリジナル通りの動きを行う
        if (!e->Shift && KEY(Delete) && idx+1 != nxt) return;

        //シフト有りDelキーはキャレットのある行自体を削除する
        if (e->Shift && KEY(Delete)){
                if(nxt == -1) nxt=TextLength;
                Select(fst,nxt-fst+1); 
        Cut();  //*** コピペ機能を用いる。美しい方法ではないが代替手段が「?」
        }

        //Insキーは、シフト無しなら現在行の後に、有りなら現在行の前へ空行を追加
        if (KEY(Insert)) { 
                //最終行、もしくは最終行が空行で最終の1つ前の行なら、後ろへの挿入はできない
                if (!e->Shift && nxt != -1 && nxt != TextLength) SelectionStart = nxt; 
                else if(e->Shift) SelectionStart = fst; 
                else return;
                System::Windows::Forms::Clipboard::SetText("\r\r");
            Paste();  //*** コピペ機能を用いる。美しい方法ではないが代替手段が「?」
                --SelectionStart; //2つ目の改行は無視されるがキャレットは一つ進むので戻す
        }
        
        //改行キー
        if (KEY(Enter)) {
                
             //#############################################################################
             // 実際はここで1行翻訳する関数を呼び出している。
             // 説明の便宜上、ではデバッグ文(トレース文)に変更している。
                 System::Diagnostics::Trace::WriteLine(Lines->Length,"Enter") ;
             //#############################################################################
                 
             //次の行がなく、空行でなければ文章末尾にキャレットを移動しオリジナルの行挿入操作
             if (nxt==-1 && fst != TextLength) {SelectionStart = TextLength; return;}
             //次の行があればその先頭にキャレットを移動するのみとし、行も挿入は行わない
              if (nxt!=-1) SelectionStart = nxt;
        }
        
        //その他の文字は全て無効とする
    e->Handled = true; //「ハンドリング終了フラグ」を立てることにより継承元は何もしない。
}

(2015年1月5日)