[[BORLAND C++]]
INDEX
Section.18 Edit Control Master#1
実はCWCC絡みなのだが、エディットコントロールは、実に多機能な、Windowクラスなので、これを色々いじると、色んな事が分かってきたりする。だから、ここで、エディットコントロールを解剖してみようかな?と思ってみた…ここに書くのは、解剖って程では無いが、そのうち時間が有れば、本格的に解剖して調べるつもり…。12.エディットで書いた事と、13.キーイベントの追記に書いたサブクラス化(WindowクラスのWndProcを別のものに置き換えて使う事を、サブクラス化するというらしい…)も踏まえて、エディットコントロールで、どんな事が出来るかを試してみたい。

  • READ ONLYにする
    エディットコントロールは、ユーザが編集出来るからエディットコントロールなのだが、本格的なアプリを造ろうと画策していると、エディットコントロールを、ログを表示するコンソール的に使用したい場面に出くわして、必ずこの問題に当たるのだ。ユーザが勝手に編集操作をすると、次に書き込むポインタが変化してしまうし、現在エディットに表示しているデータ量も分からなくなる。書き込みを禁止したい/ユーザが編集出来なくしたいのである。では、READONLYにするにはどうしたら良いのだろうか?
    簡単な方法は以下の通りである。

    • スタティックを使う。
      STATICクラスを使用すれば、ユーザは書き込みを行う事は出来ない。

    • 作成時に、ES_READONLY属性を付加する。
      CreateWindow("EDIT"…の属性に、ES_READONLYを追加すれば、見かけ上、スタティックと同様の動作を行う。(STATICになると言っても良い)


    両者共、デフォルトがグレー(灰色)になってしまう難点は有るが、背景を白くする方法は有る(具体的には、親Windowのウインドウプロシージャにて、WM_CTLCOLORSTATICなるメッセージイベントをハンドルし、白色のブラシハンドルを返せば良い。→return (LRESULT)GetStockObject(WHITE_BRUSH) ;)ので、その方法を取れば良いのであるが…。この方法では、ログ用のコンソールとして使うには、致命的な問題が発生するのである。文字を表示/取得する方法として、SetWindowText/GetWindowTextは使えるのだが、WM_CHARのイベントが無視されてしまうのである。このイベントは、エディットコントロールに対して、1文字単位(バイト単位では無い)で文字を追加して表示させるイベントであるので、このイベントが無視されてしまうと、随時、表示メッセージを追加して使うログウインドウとしては不便になってしまうのである。ここは割り切って、メモリバッファに書き込んだ内容を、毎回SetWindowTextにてセットし、表示を更新する方法も有るので、この方法を使うという手も有るが、自分の心情的には、『納得行かない』なのである。そこで,

    • サブクラス
      EDITクラスのウインドウプロシージャを、ユーザ定義のものに置き換える。
      →WM_CHARを生かしたまま、読み出し専用にする事が出来る。

    という方法が有効になるのだ。エディットだろうが、ボタンだろうが、Windowである以上、必ずウインドウプロシージャを持っているから、これを別のものに置き換えて使うことが可能なのである。これの方法は、別の項で書いたので割愛するが、要は、そのプロシージャに届いたメッセージをユーザが自由にコントロール出来るのである。では、具体的に、何をどうすれば良いのか?

    • エディットに、フォーカスが当たらない様にする。

    これだけなのである。フォーカスが当たらなければ、ユーザの操作によって、編集が出来なくなる訳だから、事実上、読み出し専用になるのだ。
    例えば、メインのフレームと、エディットコントロールしか無いアプリを考えてみよう。アプリ自体にフォーカスが有る場合、エディットコントロールにフォーカスさえ当たらなければ、メインのフレーム側に、WM_CHAR等のイベントが、全て入る様になるのだ。従って、WM_CHARのイベントについては、メインフレームにしか発生しなくなるので、全てコントロール出来てしまう訳だ。他の方法を色々試したが、フォーカスがエディットコントロールに当たると、どうしても、カーソル位置を変更出来てしまうので、読み出し専用には出来なかった。さて、どうすればフォーカスが当たらなくなるのか?自分の試した限りでは、エディットコントロール側で、マウスのボタンに関係するイベントを殺してしまう方法が、一番有効だった。即ち、WM_LBUTTONDOWN,WM_RBUTTONDOWN,WM_MBUTTONDOWNのイベントが挙がったら、0を返して、イベントを終了させてしまう。というやり方である。念のため、WM_KEYDOWN,WM_KEYUP辺りも潰しておけば、まずフォーカスは当たらないだろう。まだ、他に抜け道が有るかも知れないが、抜け道が有れば、それを潰せば良いのである。これで一応の対策にはなるのだ。因みに、この場合でも、エディットコントロールに付いているスクロールバーは、マウスによるコントロールが可能だった。これは多分、別のクラスなんだな。

  • 入力文字の制限
    数字だけの入力が許可されたエディットとか、16進入力とか、というエディットコントロールの使い方を考えてみよう。これにも幾つかの方法が考えられる。

    • 上記方法にて、読み出し専用にし、メイン側で受けたWM_CHARの許容文字のみを、SendMessage()にて、エディットに送って表示させる。若しくは、メイン側で受けたWM_CHARはそのままエディットに転送し、エディット側で、許容文字のみを表示させる。
    • エディット側を読み出し専用にせず、エディット側でWM_CHARのイベントの許容文字のみを有効にする。…有効にする為には、CallWindowProc()にて、元のWindowプロシージャにメッセージを送れば良いのだ。→この方法が一般的か?

  • 改行で、入力確定と見なす
    以前、ボタンにデフォルトの属性を付けて、改行[リターン]キーを押す事によって、ボタンが押され、入力確定となる方法を紹介したが、WM_CHARイベントをハンドルすれば、0x0dが挙がった時点で、確定させる事が出来る。これは、勿論,リードオンリーにして行っても良いし、リードオンリーにしなくても可である。

  • セットされている文字数を取得する。
    ログ用に使う場合等は、上述の方法で読み出し専用にして、使うので、イベントの発生をカウントしていて、セットされている文字数をコントロールしても良いのだが、セットされた文字数を返す関数を使用した方が確実なのだ。文字数を得る関数は、
    int GetWindowTextLength(HWND) ;
    である。

  • サンプル
    一応、サンプルを用意した。色々と試してみて、動作を確認してほしい。今回は、完全にエディットを読み出し禁止にしてメイン側からコントロールする方法を採ってみた。記号や漢字などを入力した時の動作が、ちょっとおかしいところも有るが、サンプルだから…。こんな風にコントロールすればOKって事だけ参考に!

    サンプル020のソース

  • ドロップアウト…これが本命!
    エディットの研究をやろうと思った本当の理由は、ドロップアウト,行数制限なのである。エディットコントロールも、無限に文字列をストック出来る訳ではないので、いつかはオーバーフローしてしまうのだ。これを防がないと、ログウインドウとしては不便なのである。元々、不安を感じていては、上手く行くものも行かなくなってしまうのだ。
    と言う訳で、色々調べてみたら、エディットコントロール専用のメッセージイベントは、EM_LINELENGTH,EM_SETSELの様に、頭にEM_が付いた形で宣言されていた。ウエブを検索したら、『エディットコントロールの指定した行を消す』という、使えそうなモジュールが有ったので、そのままパクってみた。(DeleteLine(int))色々試してみたが、エディットコントロールは、行数を、改行コードの数だけで数えているのでは無く、画面表示の幅で強制改行されているものも改行と数えている様なので、改行を入れずに垂れ流した場合でも使えそうだ。ただ、このモジュール,実行するとポインタが、先頭行に移ってしまうので、これに、自家製のDropOut()と言う関数を組み合わせて、ドロップアウト処理を造ってみた。その操作を流れを追って説明する。
    1. 現在の行数を、EM_GETLINECOUNTメッセージにて取得する。この行数が、制限以内ならば、以下の処理は行わない。
    2. DeleteLine(0)にて1行目を削除する。

      DeleteLine()の動作は、以下の通り,
      1. 行の先頭ポインタ位置を、EM_LINEINDEXにて取得する。
      2. 次の行の先頭ポインタ位置を、EM_LINEINDEXにて取得する。
      3. 上記の両ポインタにて囲まれた文字列(ここでは、必然的に、指定した行全体となる)を、EM_SETSELにて選択する。
      4. 選択された文字列を、EM_REPLACESELにて、NULLに書き換える。
      以上を最初の行(0)に対して行うと、最初の行が消える。ただ、この時点で、ポインタが最初に移ってしまうのと、再描画のタイミングで、画面上も先頭が表示されてしまうので、以降の操作で、元の位置に戻す。

    3. 一行目を削除した、現在のエディットの最終ポインタを、GetWindowTextLengthによって求める。(この関数は、現在エディットにセットされている文字列の長さを返すので、即ち、最終ポインタ位置を示す事になる)
    4. 最終位置のポイントを、EM_SETSELにて選択する。(ポインタ位置を最終ポインタ位置に移す為の苦肉の策である。もっと良い方法無いかな?)
    5. 以上で、ポインタ位置は戻るが、画面の表示は先頭のままなので、WM_CHARにて、スペースを書き込んで、再度WM_CHARにて、BS(バックスペース)を書き込んで消しておく。

    若干、強引ではあるが、以上の要領でドロップアウト処理は遂行できる。サンプルの[機能]→[複合動作]で、一連の動きを確認してほしい。また、ポインタが行ったり来たりするので、操作中に再描画のイベントが入ると、画面がちらつく場合も有るが、気になる方は、この操作中の画面更新を止めておいた方が良いかも知れない。(自分のマシンでは今のところ問題無いが…)

    サンプル021のソース

    今回は、この位で勘弁してやろう(^^);
2002/05/18
HomeSweetHome2
Ozzy's Software