#pragma twice

KAB-studio > プログラミング > #pragma twice > 393 Version 18.06 イベントハンドラをオーバーライドで

#pragma twice 393 Version 18.06 イベントハンドラをオーバーライドで

前のページへ 表紙・目次へ 次のページへ

 Version 18.06
イベントハンドラをオーバーライドで

前回は、ウィンドウの 32 ビット整数にビットウィンドウプロシージャの
アドレスを入れて、それを通して呼び出すという方法を説明しました
なんかものすごい方法だったね……
で、ちょっと難しかったかもしれないので、図を使ってもう一度説明しま

おー

┌ウィンドウ────────────────┐
│ ※イベント発生!※→→→→→→→→→→→→→→→
│┌32ビット領域────────────┐  │    ↓(1)
││CDialog クラスの変数のアドレス      │  │    ↓イベントが発生
│└──────────────────┘  │    ↓して、ウィンドウ
└─────────────────↑───┘    ↓プロシージャが
                                    ↑            ↓呼び出されます
┌CDialog::DispatchDialogProc() ──↑───┐    ↓
│(2) GetWindowLong() で CDialog クラスの   │    ↓
│    変数のアドレスを取得します            │ ←←
│(3) そのアドレスを使って CDialog クラスの │
│    DialogProc() メンバ関数を呼び出します │
└────────────────↓────┘
                                  ↓
┌CDialog クラスの変数──────↓────┐      
│┌ DialogProc() メンバ関数──────┐  │
││(4)ここでイベント処理をします       │  │
│└──────────────────┘  │
└─────────────────────┘

(1)ウィンドウで〈ボタンが押された〉といったイベントが発生すると、
Windows システムがそのウィンドウに結びつけられたウィンドウプロシージャ
を呼び出します
これはいつものやつね
このウィンドウプロシージャは、ダイアログの場合 DialogBox() 関数の
第4引数なので、 CDialog クラスの DispatchDialogProc() メンバ関数が呼
び出されます
うんうん
次に(2) DispatchDialogProc() メンバ関数がウィンドウプロシージャと
して呼び出されますが、このときイベントが発生したウィンドウの
ウィンドウハンドルが一緒に渡されます
第1引数ね
これを使って、 GetWindowLong() 関数でウィンドウから 32 ビット整数
を取得します。ここには WinMain() 関数で作った CDialog クラスの変数の
アドレスが入っています
一番最初、 WM_INITDIALOG メッセージを受け取った時にセットしたヤツ

(3)このアドレスを CDialog クラスのポインタにキャストして入れてから
DialogProc() メンバ関数を呼び出します。全部引数を渡してね
で、(4) DialogProc() メンバ関数が呼ばれるからここで実際のイベント
処理をする、ってわけね
そういうこと
質問!
はい火美ちゃん
 CDialog クラスのメンバ関数なのに、なんで図では CDialog クラスに入
ってないの?
 static メンバ関数だからね。この図、一番下は CDialog クラスの
〈変数〉でしょ
あ、ホントだ
 static メンバはそのクラスの変数の数に関係なく使えるから別にしまし

もひとつ質問!
はい火美ちゃん
この仕組みを使うメリットって?
ひとつは、ウィンドウが増えた時に対応しやすいこと。普通の方法だと、
複数のウィンドウでひとつのウィンドウプロシージャを共有するため、処理
が煩雑になります
それがこの方法だと大丈夫ってこと?
そう。この方法なら、ウィンドウの数だけ CDialog クラスの変数が用意
されて、それぞれメンバ変数としてデータを持つことができるし、メンバ関数
も別々に持つことだってできるから
んー、そのメンバ関数を別々に持つっていうのがよく分からないんだけど
今の段階だとまだ分からないと思うから、もう少し改良してみようか
改良?
今のプログラムは、ダイアログプロシージャ、つまり 
DialogProc() メンバ関数が CDialog クラスの中にあるでしょ
それじゃいけないの?
これでもいいんだけど、仮想関数を使うことで拡張性が高くなるんです
仮想関数…… virtual 付けてオーバーライドするってこと?
そういうこと。じゃあプログラムを見てみようか。全体的にちょっとずつ
修正するから注意してね。まず CDialog クラスの DialogProc() メンバ関数
を純粋仮想関数にします

// Dialog.h

// Dialog クラス。
class CDialog
{
private:
    // 最初にセットするための、自クラスのポインタ。
    static CDialog *m_pcDialog;

public:
    // デストラクタ。
    virtual ~CDialog();

    // ダイアログを作成します。
    int DoModal( HINSTANCE p_hInstance, int p_iDialogId );

    // ダイアログプロシージャ(形式上)。
    static BOOL CALLBACK DispatchDialogProc
    ( HWND p_hDlgWnd
    , UINT p_uiMessage
    , WPARAM p_wParam
    , LPARAM p_lParam
    );

    // ダイアログプロシージャ(純粋仮想関数)。
    virtual BOOL DialogProc
    ( HWND p_hDlgWnd
    , UINT p_uiMessage
    , WPARAM p_wParam
    , LPARAM p_lParam
    ) = 0;
};

 DialogProc() メンバ関数に virtual と【 = 0】が付いた
 Version 17.10 ( No.365 ) で説明したように、 virtual を付けること
でメンバ関数が仮想関数になります
これでオーバーライドできるようになるんだね
続いて Version 17.16 ( No.371 ) で説明したように、仮想関数の宣言
の後ろに【 = 0】を付けることで、そのメンバ関数は純粋仮想関数になりま

ってことは、このメンバ関数の本体はなくなるの?
そう、だから Dialog.cpp ソースファイルはこうなります

// Dialog.cpp
#include <Windows.h>
#include <stdio.h>
#include "resource.h"
#include "Dialog.h"

// 最初にセットするための、自クラスのポインタ。
CDialog *CDialog::m_pcDialog = NULL;

// デストラクタです。
CDialog::~CDialog()
{
    // 何もしません。
}

// ダイアログを作成します。
int CDialog::DoModal( HINSTANCE p_hInstance, int p_iDialogId )
{
    // this ポインタをグローバル変数に取っておきます。
    // あとでダイアログプロシージャで渡します。
    m_pcDialog = this;

    // ダイアログを作成します。
    int iRet
        = DialogBox
            ( p_hInstance
            , MAKEINTRESOURCE( p_iDialogId )
            , NULL
            , &DispatchDialogProc
            );

    return iRet;
}

// ダイアログプロシージャ(形式上)。
BOOL CALLBACK CDialog::DispatchDialogProc
    ( HWND p_hDlgWnd
    , UINT p_uiMessage
    , WPARAM p_wParam
    , LPARAM p_lParam
    )
{
    if( p_uiMessage == WM_INITDIALOG )
    {
        // 直前に DoModal() が呼ばれてる場合。
        // this ポインタをダイアログのユーザー領域に入れます。
        SetWindowLong( p_hDlgWnd, GWL_USERDATA, (LONG)m_pcDialog );
        m_pcDialog = NULL;
    }

    // ダイアログの 32 ビット整数に格納されている 
    // this ポインタを取りだします。
    CDialog *pcDialog 
        = (CDialog *)GetWindowLong( p_hDlgWnd, GWL_USERDATA );
    if( pcDialog == NULL )
    {
        // NULL の時は何もしません。
        return 0;
    }

    // メンバ関数のダイアログプロシージャを呼び出します。
    return 
        pcDialog->DialogProc
            ( p_hDlgWnd
            , p_uiMessage
            , p_wParam
            , p_lParam 
            );
}

// DialogProc() メンバ関数は純粋仮想関数にしたのでは削除しました。

 DialogProc() メンバ関数がなくなってる……あ、あとデストラクタがあ
るね
 Version 17.30 ( No.385 ) で説明したように、継承を使う場合は
デストラクタを仮想関数にする必要があるからね。残りは次回に!

/*
    Preview Next Story!
*/
最近なんだかプログラム長いこと多いね
 API だけだとどうしてもそうなるね
あー、 MFC だとイベントハンドラだけしか見なくていいもんね
その辺は共通ライブラリのメリットかな
メリットなの……かなぁ
というわけで次回
< Version 18.07 ウィンドウプロシージャを純粋仮想関数にする、の続き >
につづく!
……実際、もう残り回数が少ないのにコードでスペース取られるのは……
うわぶっちゃけた
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。