C++/CLI の薄い本(第2巻)

本ページは2013年に「Visual Studio 2008 express」を試用した際に書いたものを
「Visual Studio 2015 express」用に書き直したものです。
一部、VS2008とVS2015の差異による動作の違いがある可能性が御座います。

Copyright © 2013-2015 GATARO All Rights Reserved. 筆者のメインサイト

第1巻の復習

SAMPLE04の微修正

以下のソースは前回最後に紹介したSAMPLE04を若干手直ししたものです(赤字は主な変更部分、詳細は後述)。
今回はこのソースをベースとして、色々な機能を追加してみたいと思います。もちろん1つのファイルだけをコピペするだけで動くという原則は保っています。

//SAMPLE05
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") 
#using <System.dll>
#using <System.Windows.Forms.dll>
#using <System.Drawing.dll>
using namespace System::Windows::Forms ;
ref class MyText : public System::Windows::Forms::RichTextBox{};
ref class MyForm : public System::Windows::Forms::Form{
    public : MyForm(){InitializeComponent();}
    private: MyText^ mytext ;
    private: void InitializeComponent();
};
void MyForm::InitializeComponent(){
    Size = System::Drawing::Size(200,300);
    mytext = (gcnew MyText());
    mytext->Dock = DockStyle::Fill;
    mytext->BackColor = System::Drawing::Color::FromArgb(255,255,180); 
    Controls->Add(mytext); 
}
void main(){
    Application::Run(gcnew MyForm());
}

表示属性の設定処理を専用関数 InitializeComponent() に移動

フォームのコンストラクタの中で直接行なっていたウィンドウの属性(大きさや形など)の設定を、InitializeComponent()と言う名前の関数を作ってその中にまとめました。これはVC++のフォームデザイン機能を用いた時に自動生成される関数と同じ名前です。同じ関数名にする必然性は全く無いのですが、自動生成されたソースとの対応を容易にする為にあえて同じ名前にしました。

アクセス修飾子の厳密化

アクセス修飾子とはprivateprotectedpublicの3つを示します。この使い分けはクラスを用いたプログラミングをする上でとても大事な事ですが、使い分けをせずに全てpublicとしてもプログラム自体は正しく動くという短所も持ち合わせています。以後本書ではprivateにできる所は全てprivateとし他の部分はpublicとする事とします。

サンプルの紹介が目的ですから、イベント関数の再定義を除きprotectedは使う機会がないと思います。またC++/CLI(あるいはC#)特有のアクセス修飾子であるinternal(同一コンパイル単位内のみ有効)もサンプルプログラム自体が「単一ソースでビルド可能」のため、意味を持たないので使用しません。

ウィンドウのサイズ設定の簡略化とテキスト表示を省略

SAMPLE04ではフォームのサイズをWidthHeightというプロパティ(メンバ変数)に代入しましたが、今回はSizeというプロパティに代入します。どちらの方法でも効果は同じですが、この方が1行少なくなりますしサイズの調整をしていることが明確になります

また、フォームの上部のタイトル(myFormクラスのTextへの文字列代入)と、テキストボックスの初期表示(myTextクラスのTextへの文字列代入)は削除しました。これはサンプルソースには題意と無関係な記述はしないというポリシーに従うためです。

マウスイベントの奪取と独自表示

イベント関数は継承先が優先

Windowsアプリではキーボード入力やマウス入力は全てイベント関数が処理します。イベント関数は様々な物あります。例えばRichTextBox内でマウスボタンが押下された場合は、RichTextBoxクラスのOnMouseDown()が呼ばれ、マウスで指定した位置にキャレット(縦棒型をしたカーソル)を移動する処理を行います。

C++/CLIではC++同様、継承先のクラスで同じ関数を定義すると、継承元の関数ではなく継承先の関数が呼び出されます。この仕組みを利用すると簡単にイベント処理を独自の物に変更できます。以下のサンプルではリッチテキストボックス上でマウスをクリックしてもキャレットは移動せず、代りに「Click!」という文字列が勝手に加わります。

// SAMPLE06
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") 
#using <System.dll>
#using <System.Windows.Forms.dll>
#using <System.Drawing.dll>
using namespace System::Windows::Forms ;
ref class MyText : public System::Windows::Forms::RichTextBox{
     protected: virtual void OnMouseDown(MouseEventArgs^ event) override {Text+=" Click! ";}
};
ref class MyForm : public System::Windows::Forms::Form{
    public : MyForm(){InitializeComponent();}
    private: MyText^ mytext ;
    private: void InitializeComponent();
};
void MyForm::InitializeComponent(void){
    Size = System::Drawing::Size(200,300);
    mytext = (gcnew MyText());
    mytext->Dock = DockStyle::Fill;
    mytext->BackColor = System::Drawing::Color::FromArgb(255,255,180); 
    Controls->Add(mytext); 
}
void main(){
    Application::Run(gcnew MyForm());
}

C++/CLIで作るウィンドウ 左が実行画面でソース中の赤字部分変更箇所です。
イベント関数を再定義する場合は、将来の再々定義に備えprotected宣言とします。
またマネージ型(C++/CLI)特有の規則としてvirtualoverrideの付加も必須です。


テキストボックスにグラフィックスを表示する

RichTextBoxは文字列の処理に特化したウィンドウ(コントロール)であり、それ自体が完成されたテキストエディタです。ですから、丸や四角等のグラフィックを表示したい場合はRichTextBox以外のウィンドウを使うのが賢明です。

以下のサンプルは、このような賢明な考え方をあえて無視して、マウスクリックするとそこに赤い四角形を表示するサンプルです。RichTextBoxは全体行数が表示可能行数より大きくなった場合は自動的にスクロール機能が動きます。しかしグラフィック表示を前提としたスクロール機能ではないので赤い四角形はテキストと一緒にスクロールしますが、一度枠外にスクロールした赤い四角は枠内に戻っても消滅してしまいます。

// SAMOLE07
#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"") 
#using <System.dll>
#using <System.Windows.Forms.dll>
#using <System.Drawing.dll>

using namespace System::Windows::Forms ;
ref class MyText : public System::Windows::Forms::RichTextBox{
    protected: virtual void OnMouseDown(MouseEventArgs^ e) override {
        Text+=" Click! ";
 /**/   System::Drawing::Graphics^ g = CreateGraphics();
 /**/   g->FillRectangle(System::Drawing::Brushes::Red,e->X,e->Y,10,10); 
    }
};

ref class MyForm : public System::Windows::Forms::Form{
    public : MyForm(){InitializeComponent();}
    private: MyText^ mytext ;
    private: void InitializeComponent();
};
void MyForm::InitializeComponent(void){
    Size = System::Drawing::Size(200,300);
    mytext = (gcnew MyText());
    mytext->Dock = DockStyle::Fill;
    mytext->BackColor = System::Drawing::Color::FromArgb(255,255,180); 
    Controls->Add(mytext); 
}
void main(){
    Application::Run(gcnew MyForm());
}

左が実行画面でソース左端に/**/がある行が変更箇所です。
クリックされた座標はOnMouseDownの引数から取り出すことができます。
グラフィック表示は、対象となるウィンドウに対応するSystem::Drawing::Graphics型の変数(上記サンプルでは変数名はg)が取得できれば上記の様に簡単に図形を表示できます。しかしRichTextBoxはグラフィック表示を前提としたウィンドウではないので、クラスのメンバにはSystem::Drawing::Graphics型の変数は含まれておりません。このため、エレガントな方法ではありませんが、CreateGraphics()を用いて変数gを作り出しています。


オリジナルのイベントも活かす方法

当然の事ですが継承を利用してイベント処理を差し替えてしまうと、オリジナルのイベント関数は呼び出されません。
オリジナルの処理を行ないたい場合は下記(赤字)の様に、オリジナルのOmNouseDownであることを明記してこれを呼びます。これにより、オリジナルの機能であるマウスクリックした位置にキャレットを移動する処理も行われます。

但し今回のサンプルの場合、継承先でTextの内容を勝手に書き換えているため、継承元はTextの現状を把握できずキャレットを移動できません。下記のサンプルではTextを書き換える行をコメントアウトしています。興味があるならこのコメントがある場合と無い場合の処理の違いを実際に試してみてください。

//SAMPLE07を一部抜粋の上、改変
protected: virtual void OnMouseDown(MouseEventArgs^ e) override {
    //Text+=" Click! "; // 子(継承先)勝手にテキストを書き換えると親(継承元)はキャレット位置の管理ができなくなる。
    System::Drawing::Graphics^ g = CreateGraphics();
    g->FillRectangle(System::Drawing::Brushes::Red,e->X,e->Y,10,10);
    RichTextBox::OnMouseDown(e);
}

とりあえず2巻はここまで

第3巻はWindowsアプリの真髄である描画管理を紹介する予定ですが、いつ書き始めて、いつ書き終わるのかは全然わかりません。
何か御質問などあればTwitterの方でお声掛け下さい(お返事はお約束できませんが…)