41章 コントロールのサブクラス化


ダウンロード

 今回は、コントロールのサブクラス化を学びます。つまり、13章の続きです。

 コントロールのサブクラス化と言うのは、既存のコントロールの機能を拡張したり変更したりする物です。今回は、ボタンコントロールの機能の拡張を行うことで、サブクラス化の手法を学びます。

 ボタンコントロールは、マウスの左ボタンを押すことで、親ウインドウに、通知メッセージを送ります。今回は、この通知メッセージに応答してメモ帳(notepad.exe)を起動しています。

 マウスの右ボタンに応答して簡単なヘルプを起動さす付加機能を持ったボタンを作りたい場合、2つの方法があります。1つは、プログラマーが、既存のボタンとまったく同様の機能と付加機能を実装する方法です。これは、大変な作業になります。2つめが、今回学ぶサブクラス化という方法で、付加機能だけ実装する方法です。つまり、差分プログラミングです。

 この差分プログラミングという考え方は、オブジェクト指向プログラミングをネイティブにサポートしているC++などでは、文法レベルで“継承”といわれる方法で解決しています。今回は、オブジェクト指向プログラミングをネイティブにサポートしていないC言語でAPIを用いて行おうと言うものです。


 では、ソースコードを見てみましょう。

リスト41−1
/*  C言語で始めるWindowsプログラミング  */
/*  41章のサンプルプログラム                  */
/*                      Programmed by Y.Kondo   */
/* 注:TABサイズは4で見てください          */

#define     STRICT
#include    <windows.h>
#include    "resource.h"

/*  メインウインドウとなるダイアログボックスのダイアログプロシージャのプロトタイプ宣言  */
BOOL CALLBACK MainWindowProc(HWND,UINT,WPARAM,LPARAM);

/*  サブクラス化を行うためのウインドウプロシージャのプロトタイプ宣言    */
LRESULT CALLBACK    ButtonSubWindowProc(HWND,UINT,WPARAM,LPARAM);   

/*  このファイルでのグローバル変数  */
HINSTANCE   hInst;

/*  アプリケーションエントリーポイント  */
int WINAPI WinMain(HINSTANCE    hInstance,
                   HINSTANCE    hPrevInstance,
                   LPSTR        CmdLine,
                   int          CmdShow)
{
    DialogBox(hInst,MAKEINTRESOURCE(IDD_DIALOG1),NULL,(DLGPROC)MainWindowProc);
    return  0;
}

/*  メインウインドウとなるダイアログボックスのダイアログプロシージャで用いられる関数の
                                                            プロトタイプ宣言    */
static  BOOL    Wm_InitDialogProc(HWND);
static  BOOL    Wm_CommandProc(HWND,WORD,WORD,HWND);
static  BOOL    Wm_CloseProc(HWND);
static  LONG    pWndProc;   /*  ボタンコントロールの本来のウインドウプロシージャのアドレス  */

/*  サブクラス化を行うためのウインドウプロシージャ  */
LRESULT CALLBACK    ButtonSubWindowProc(HWND hwnd,UINT msg,WPARAM wparam,LPARAM lparam)
{
    /*  メッセージを奪う    */
    if(msg==WM_RBUTTONDOWN)
        MessageBox(hwnd,"メモ帳を起動します","ヘルプ",MB_OK|MB_ICONINFORMATION);

    /*  本来のボタンコントロールの処理を行う    */
    return  CallWindowProc((WNDPROC)pWndProc,hwnd,msg,wparam,lparam);
}

/*  メインウインドウとなるダイアログボックスのダイアログプロシージャ    */
BOOL CALLBACK MainWindowProc(HWND hwnd,UINT msg,WPARAM wparam,LPARAM lparam)
{
    switch(msg)
    {
        /*  ダイアログボックス初期化    */
        case    WM_INITDIALOG:
            return  Wm_InitDialogProc(hwnd);
        /*  コントロールに応答          */
        case    WM_COMMAND:
            return  Wm_CommandProc(hwnd,HIWORD(wparam),LOWORD(wparam),(HWND)lparam);
        case    WM_CLOSE:
            return  Wm_CloseProc(hwnd);
    }
    return  FALSE;
}

/*  メインウインドウとなるダイアログボックスの初期化    */
static  BOOL    Wm_InitDialogProc(HWND hwnd)
{
    /*  メモ帳を開けるボタンコントロールのウインドウプロシージャのアドレスを取得する    */
    pWndProc=GetWindowLong(GetDlgItem(hwnd,IDC_NOTEPAD),GWL_WNDPROC);
    /*  メモ帳を開けるボタンコントロールのウインドウプロシージャのアドレスをサブクラス化の為の
        ウインドウプロシージャのアドレスに変更する  */
    SetWindowLong(GetDlgItem(hwnd,IDC_NOTEPAD),GWL_WNDPROC,(LONG)ButtonSubWindowProc);
    return  TRUE;
}

/*  コントロールに応答する関数  */
static  BOOL    Wm_CommandProc(HWND hwnd,WORD wNotifyCode,WORD wID,HWND hwndCtl)
{
    STARTUPINFO         si;
    PROCESS_INFORMATION pi; 
    if(wID==IDC_NOTEPAD)
    {   /*  メモ帳を起動する    */
        ZeroMemory(&si,sizeof(STARTUPINFO));
        si.cb=sizeof(STARTUPINFO);
        si.dwFlags=STARTF_USESHOWWINDOW;
        si.wShowWindow=SW_SHOWNORMAL;
        if(CreateProcess(   NULL,
                        "notepad.exe",
                        NULL,
                        NULL,
                        TRUE,
                        DETACHED_PROCESS|NORMAL_PRIORITY_CLASS,
                        NULL,
                        NULL,
                        &si,
                        &pi)
            )
        {
            CloseHandle(pi.hProcess);
        }
    }
    return  TRUE;
}

/*  終了処理    */
static  BOOL    Wm_CloseProc(HWND hwnd)
{
    EndDialog(hwnd,IDCANCEL);
    return  TRUE;
}

 コントロールのサブクラス化に関係のある個所を太文字にしておきました。

・コントロールのサブクラス化の概略

 コントロールと言うのは、一種のウインドウであることは、以前の章で学びました。つまり、OS定義の子ウインドウです。つまり、コントロールのウインドウプロシージャのソースコードは公開されていません。

 これゆえ、GetWindowLong関数でコントロールのウインドウプロシージャのアドレスを取得し、SetWindowLong関数で自前で作ったウインドウプロシージャのアドレスを変わりにセットし、一度、自前のウインドウプロシージャでメッセージを処理した後、本来のウインドウプロシージャに制御を渡して、本来の動作をさせます。

・コントロールのサブクラス化の詳細

    /*  メモ帳を開けるボタンコントロールのウインドウプロシージャのアドレスを取得する    */
    pWndProc=GetWindowLong(GetDlgItem(hwnd,IDC_NOTEPAD),GWL_WNDPROC);
    /*  メモ帳を開けるボタンコントロールのウインドウプロシージャのアドレスをサブクラス化の為の
        ウインドウプロシージャのアドレスに変更する  */
    SetWindowLong(GetDlgItem(hwnd,IDC_NOTEPAD),GWL_WNDPROC,(LONG)ButtonSubWindowProc);

 この個所で、ボタンコントロールのウインドウプロシージャのすげ替えを行っています。GetWindowLong関数の第2引数にGWL_WNDPROCを指定する事で、第1引数のウインドウハンドルで示されたコントロールの本来のウインドウプロシージャのアドレスを取得しています。ここで得られたアドレスは、サブクラス化の為のウインドウプロシージャで用います。

 また、SetWindowLong関数で、サブクラス化の為のウインドウプロシージャのアドレスを第1引数で示されたコントロールのウインドウプロシージャとしてセットしています。

/*  サブクラス化を行うためのウインドウプロシージャ  */
LRESULT CALLBACK    ButtonSubWindowProc(HWND hwnd,UINT msg,WPARAM wparam,LPARAM lparam)
{
    /*  メッセージを奪う    */
    if(msg==WM_RBUTTONDOWN)
        MessageBox(hwnd,"メモ帳を起動します","ヘルプ",MB_OK|MB_ICONINFORMATION);

    /*  本来のボタンコントロールの処理を行う    */
    return  CallWindowProc((WNDPROC)pWndProc,hwnd,msg,wparam,lparam);
}

 この個所がサブクラス化の為のウインドウプロシージャです。つまり、新たに機能を付け加える為のウインドウプロシージャです。つまり、付加機能だけを実装した差分の個所です。

 普通のウインドウプロシージャと異なるのは、DefWindowProc関数を用いずに、CallWindowProc関数を用いています。DefWindowProc関数と異なるのは、第1引数にGetWindowLong関数で取得した本来のウインドウプロシージャのアドレスを用いていることです。これにより、ボタンコントロールで発生したメッセージは、プログラマー定義のウインドウプロシージャで処理され、この後、本来のウインドウプロシージャで処理される様になるのです。

 今回の場合、マウスの右ボタンが押された事により発生するWM_RBUTTONDOWNメッセージを処理して、ボタンの簡易ヘルプをメッセージボックスとして表示しています。完全に本来のウインドウプロシージャでの処理を殺す場合は、CallWindowProc関数を呼ぶ必要はありません。しかし、本来の処理もさせておいた方が安全なので、今回は、メッセージを処理した後も、CallWindowProc関数を呼んでいます。


 コントロールのサブクラス化は、用意されている便利なコントロールに新たな機能を付け加える強力な方法です。これにより、少しだけ動作の異なるコントロールを1から実装する手間と危険性を回避できます。是非とも、マスターしておきたいテクニックです。

 次回から、再度、GDI入門に戻ります。

 では、お楽しみに。

2003年3月20日


目次
次へ


著作権者:近藤妥