チュートリアル② ~ウィンドウの描画~

  • 概要

    前節では、コールバックからデバッグ領域に表示するといったものでした。

    これだけの単純な仕組みであっても 自分なりの制作物を展開していくことが出来ることでしょう。

  • 単純なウィンドウ表示

    #include <windows.h>
    
    using namespace System;
    using namespace System::Drawing;
    using namespace System::Windows::Forms;
    
    
    public ref class ACHelpForm : public Form
    {
    public:
      static ACHelpForm^ f; // 自分自身の置き場
    public:
      ACHelpForm()
      {
        SetFormAttr();
    
        // 最初は非表示
        this->Visible = false;
      }
    
    public:
      // 入力補完の候補リストのどれかを選択している時に呼ばれる。
      void Update(HWND hWnd, int iListBoxSelectedIndex, String^ strListBoxSelectedItem, int iItemHeight)
      {
        RECT rect;
        GetWindowRect(hWnd, &rect);
        this->Left = rect.right + 24;
        this->Top = rect.top;
        this->Width = (rect.right - rect.left) * 2;
        this->Height = 160;
        this->Show();
      }
    
    
    protected:
      // 独立したアプリケーショではなく、子窓のように見せるための工夫
      void SetFormAttr()
      {
        //タイトルバーを消す。秀丸の中のウィンドウかのように振る舞うための工夫。
        this->ControlBox = false;
        this->Text = "";
        this->FormBorderStyle = ::FormBorderStyle::None;
        this->BackColor = ::Color::Gray;
      }
    
      // このフォームが表示された際にアクティブにならないようにする
      property bool ShowWithoutActivation
      {
        virtual bool get() override
        {
          return true;
        }
      }
    
      // このフォームがマウスクリックされた際にアクティブにならないようにする。
      virtual void WndProc(Message %m) override
      {
        if (m.Msg == WM_MOUSEACTIVATE)
        {
          m.Result = (IntPtr)MA_NOACTIVATE;
          return;
        }
    
        Form::WndProc(m);
      }
    
    };
    
    
    extern "C" __declspec(dllexport) int OnCreate(HWND hWnd, LPCTSTR szFileName) {
    
      // ウィンドウの作成
      if (ACHelpForm::f == nullptr || ACHelpForm::f->IsDisposed) {
        ACHelpForm::f = gcnew ACHelpForm();
      }
    
      return TRUE;
    }
    
    extern "C" __declspec(dllexport) int OnListBoxSelectedIndexChanged(HWND hWnd, int iListBoxSelectedIndex, LPCTSTR szListBoxSelectedItem, int iItemHeight) {
    
      // そのまま伝達
      ACHelpForm::f->Update(hWnd, iListBoxSelectedIndex, gcnew String(szListBoxSelectedItem), iItemHeight);
    
      return TRUE;
    }
    
    
    extern "C" __declspec(dllexport) int OnDestroy(HWND hWnd) {
    
      // ウィンドウの破棄
      if (ACHelpForm::f) {
        ACHelpForm::f->Close();
      }
      // このOnDestroyの後に、インスタンスが残っているのに任せるのは不安である。明示的に解放
      if (ACHelpForm::f) {
        OutputDebugString(L"明示解放\n");
        delete ACHelpForm::f;
      }
    
      return TRUE;
    }
    
    
  • 再度コンパイル

    再度コンパイルし、「HmAutoCompleteExPlug.dll」を秀丸ディレクトリにコピーしましょう。
    秀丸が立ち上がりっぱなしだと、コピー出来ないので、一旦秀丸を終了させる必要があります。

    再び、.plファイルを読み込んで、何か入力補完を出し、単語を選択してみましょう。
    以下のように、ねずみ色のウィンドウが表示されたでしょうか。

    大切なことはこのウィンドウが、

    • 秀丸より前方に描画されているが、アクティブではない。
    • マウスで選択しても、アクティブにはならない

    ということです。

  • 解説

    全体としては、.NETのフォームアプリケーションの代表的な形そのものです。
    「System.Windows.Forms.Form」から継承しているため、ボタンやラベルや描画など、
    便利なものが全て極めてわかりやすい形で利用可能です。

    とはいえ、おそらくあまり見慣れない記述もあるため、要点だけピックアップしましょう。

    
      // 独立したアプリケーショではなく、子窓のように見せるための工夫
      void SetFormAttr()
      {
        //タイトルバーを消す。秀丸の中のウィンドウかのように振る舞うための工夫。
        this->ControlBox = false;
        this->Text = "";
        this->FormBorderStyle = ::FormBorderStyle::None;
        this->BackColor = ::Color::Gray;
      }
    
      // このフォームが表示された際にアクティブにならないようにする
      property bool ShowWithoutActivation
      {
        virtual bool get() override
        {
          return true;
        }
      }
    
      // このフォームがマウスクリックされた際にアクティブにならないようにする。
      virtual void WndProc(Message %m) override
      {
        if (m.Msg == WM_MOUSEACTIVATE)
        {
          m.Result = (IntPtr)MA_NOACTIVATE;
          return;
        }
    
        Form::WndProc(m);
      }
    
    
    

    これらは、いずれも先ほどの
    「フォーカスを取らない・アクティブにしない」といったことを実装するための工夫です。



      // 入力補完の候補リストのどれかを選択している時に呼ばれる。
      void Update(HWND hWnd, int iListBoxSelectedIndex, String^ strListBoxSelectedItem, int iItemHeight)
      {
        RECT rect;
        GetWindowRect(hWnd, &rect);
        this->Left = rect.right + 24;
        this->Top = rect.top;
        this->Width = (rect.right - rect.left) * 2;
        this->Height = 160;
        this->Show();
      }
      

    この部分は入力補完窓の位置に合わせて、自分自身(Form)が位置や大きさを決めている部分です。
    単語補完ウィンドウが大きい人は、このフォームも大きくなり、小さい人は小さくなるということです。