delegate4

前回delegate3-C#風windowsアプリ - while( c++ );の続き。
日があいてしまったので、別エントリに書きます。

class.h

  • アプリケーションで使用しているnamespace、クラス、列挙型の一覧を記述しておきます。
  • shared_ptrで管理するクラスはdef_managed_classマクロで定義します。
#pragma once

#include <xtl/shared_ptr.h>		//自作boost風shared_ptr
#include <xtl/bind.h>			//自作boost風bind
#include <xtl/delegate.h>		//自作C#風delegate

//shared_ptrで管理されるクラスの定義
#define def_managed_class( name ) class name; typedef xtl::shared_ptr< name > name##Ptr;

//
//	アプリケーションのクラス一覧
//

class System;

def_managed_class( Component );
    def_managed_class( Control );
        def_managed_class( Form );
        def_managed_class( Button );

class Event;
    class WindowEvent;
    class ActionEvent;


typedef xtl::delegate< void ( Component*, WindowEvent& ) > WindowEventHandler;
typedef xtl::delegate< void ( Component*, ActionEvent& ) > ActionEventHandler;

#undef def_class

C#ではもっと深いクラス階層になっていますが、例によって必要なものだけ実装して行きます。

Component

class Component
{
public:
    Component(){}
    virtual ~Component(){}
};

Control

  • Form、Buttonなどのコントロール基底クラスです。
  • 面倒なのでpublicメンバを使っています。property的な何かがほしいですね。
  • クリック時のイベントハンドラを保持しています。
Control.h
#pragma once

#include <string>
#include "Component.h"

class Control
	: public Component
{
public:
    HWND hwnd;
    std::string text;
    int x, y;
    int w, h;
    ActionEventHandler click;
    bool created;

public:
    Control();
    virtual ~Control(){}

public:
    //各コントロールの具体的な実装
    virtual void Create( Control* /*parent*/ ){}
public:
    //WindowEventを処理する
    virtual void ProcessActionEvent( ActionEvent& /*e*/ ){}
};
Control.cpp
#include <pch.h>	//プリコンパイル済みヘッダ

#include "Control.h"

Control::Control()
    : hwnd( 0 )
    , text( "" )
    , x( CW_USEDEFAULT )
    , y( CW_USEDEFAULT )
    , w( CW_USEDEFAULT )
    , h( CW_USEDEFAULT )
    , created( false )
{
}

Form

Form.h
#include "Control.h"
#include <list>

class Form :
    public Control
{
    friend class System;
    typedef std::list< ControlPtr > control_list;

protected:
    control_list controls;			//Form上に配置するコントロールリスト

public:
    WindowEventHandler load;		//Form生成時イベント
    WindowEventHandler closing;		//Form破棄時イベント

public:
    Form();
    virtual ~Form();

public:
    //Formの破棄。別スレッドから呼び出すことは出来ない
    void Destory();
    //Formの表示。Formを作るだけでは画面に表示されない
    void Show();
    //control_listにコントロールの実体を追加する
    void AddControl( ControlPtr c );
private:
    //control_listに登録されたコントロールを実際に生成する
    void CreateControls();

private:
    //デフォルトのウィンドウプロシージャ
    void Default( Event& e );
    //各Formのプロシージャ
    virtual void Proc( Event& e );

private:
    //WindowEventを処理する
    virtual void ProcessWindowEvent( WindowEvent& e );
    //ActionEventを処理する
    virtual void ProcessActionEvent( ActionEvent& e );

private:
    //Formの生成
    void Create( Control* parent );
};
Form.cpp
  • 正直、Eventの扱いがイケてない気がする。。。
#include <pch.h>

#include "Form.h"
#include "WindowEvent.h"
#include "ActionEvent.h"

Form::Form()
    : Control()
{
}


Form::~Form(void)
{
    Destory();
}

void Form::Destory()
{
    DestroyWindow( hwnd );
}

void Form::Show()
{
    ShowWindow( hwnd, SW_SHOW );
}

void Form::CreateControls()
{
    control_list::iterator it = controls.begin();
    while( it != controls.end() )
    {
        if( !( *it )->created )
        {
            ( *it )->Create( this );
        }
        ++ it;
    }
}

void Form::AddControl( ControlPtr c )
{
    controls.push_back( c );
}

void Form::Default( Event& e )
{
    e.ret = DefWindowProc( e.hwnd, e.msg, e.wp, e.lp );
}

void Form::Proc( Event& e )
{
    if( e.msg == WM_CREATE
        || e.msg == WM_CLOSE
        /* || その他のWindowEvent */
        )
    {
        WindowEvent we( e );
        ProcessWindowEvent( we );
        e = we;
    }else
    if( e.msg == WM_COMMAND )
    {
        ActionEvent ae( e );
        ProcessActionEvent( ae );
        e = ae;
    }

}

void Form::ProcessWindowEvent( WindowEvent& e )
{
    if( e.msg == WM_CREATE )
    {
        if( !load.empty() )
        {
            e.Consume();
            load( this, e );
        }
    }else
    if( e.msg == WM_CLOSE )
    {
        if( !closing.empty() )
        {
            e.Consume();
            closing( this, e );
        }
    }

    /* その他のWindowEvent */
}

void Form::ProcessActionEvent( ActionEvent& e )
{
    if( e.msg == WM_COMMAND )
    {
        control_list::iterator it = controls.begin();
        while( it != controls.end() )
        {
            ( *it )->ProcessActionEvent( e );
            ++ it;
        }
    }
}

void Form::Create( Control* parent )
{
    //hwndはプロシージャ内で設定される
    /*hwnd = */CreateWindowEx
        ( 0
        , "hoge"
        , text.c_str()
        , WS_OVERLAPPEDWINDOW
        , x
        , y
        , w
        , h
        , parent ? parent->hwnd : 0
        , 0
        , GetModuleHandle( 0 )
        , this				//	プロシージャに渡す情報
        );
    if( hwnd )created = true;
}

Button

  • 普通のボタンです。
Button.h
#include "Control.h"

class Button
    : public Control
{
public:
    Button()
        : Control()
    {}

public:
    //ActionEventを処理する
    void ProcessActionEvent( ActionEvent& e );

public:
    //Buttonの生成
    void Create( Control* parent );
};
Button.cpp
  • ProcessActionEventでclickを呼び出します。
  • IDは使わず、ボタンのウィンドウハンドルで識別しています。
#include <pch.h>

#include "Button.h"
#include "ActionEvent.h"

void Button::ProcessActionEvent( ActionEvent& e )
{
    if( e.GetCtrl() == hwnd )
    {
        if( !click.empty() )
        {
            e.Consume();
            click( this, e );
        }
    }
}

void Button::Create( Control* parent )
{
    hwnd = CreateWindowEx
        ( 0
        , "BUTTON"
        , text.c_str()
        , WS_CHILD | BS_PUSHBUTTON | WS_VISIBLE
        , x
        , y
        , w
        , h
        , parent->hwnd
        , 0                       //とりあえずIDは常に0
        , GetModuleHandle( 0 )
        , 0
        );
    if( hwnd )created = true;
}

System

  • プロシージャ、メッセージループなどのwindows特有の部分はpImplイディオムにしてみました。特に意味はないです。
  • ウィンドウクラスの登録をもう少しうまく隠蔽したいのですが。。。
class System
{
public:
    static System& instance();

private:
    System();
    System( System const& );
    System& operator=( System const& );

public:
    void Release();
    void Run( FormPtr Form );
    void Exit( int exit_code );

public:
    bool DefRegisterClass();
    bool RegisterClass( std::string const& class_name, WNDPROC proc );

private:
    class Impl;
    typedef xtl::shared_ptr< Impl > pImpl;
    pImpl impl;
};

#define system System::instance()
System.cpp
#include <map>
#include "System.h"
#include "../Form/Form.h"
#include "../Form/Event.h"

/**
 *
 */
class System::Impl
{
private:
    std::map< HWND, Form* > form_map;

public:
    void Add( HWND hwnd, Form* form )
    {
        form->hwnd = hwnd;
        form_map.insert( std::make_pair( hwnd, form ) );
    }

    void Remove( HWND hwnd )
    {
        form_map.erase( hwnd );
    }

    Form* Search( HWND hwnd )
    {
        std::map< HWND, Form* >::iterator it = form_map.find( hwnd );
        if( it != form_map.end() )
        {
            return it->second;
        }
        return 0;
    }

public:
    static LRESULT CALLBACK WinProc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp )
    {
        if( msg == WM_CREATE )
        {
            //CreateWindowEx()の最後の引数(Form*)を受け取る
            LPCREATESTRUCT cs = reinterpret_cast< LPCREATESTRUCT >( lp );
            Form* form = reinterpret_cast< Form* >( cs->lpCreateParams );
            //ウィンドウハンドルをキーとするForm*マップに追加
            if( form )
            {
                system.impl->Add( hwnd, form );
                form->CreateControls();
            }
        }

        //ウィンドウハンドルをキーとしてForm*を検索
        Form* form = system.impl->Search( hwnd );
        if( form )
        {
            //Formクラスが持つオーバーライド可能なプロシージャを呼び出す
            Event e( hwnd, msg, wp, lp, form );
            form->Proc( e );

            //ウィンドウが破棄された場合、Form*マップから削除
            if( msg == WM_DESTROY )
            {
                system.impl->Remove( hwnd );
            }

            //イベントが消費されていればすぐに終了
            if( !e.IsConsumed() )form->Default( e );
            return e.GetResult();
        }

        return DefWindowProc( hwnd, msg, wp, lp );
    }

public:
    bool RegisterClass( std::string const& class_name, WNDPROC proc )
    {
        WNDCLASSEX wc =
        { 
            sizeof( WNDCLASSEX ),
            CS_HREDRAW | CS_VREDRAW,
            proc,
            0, 0,
            GetModuleHandle( 0 ),
            LoadIcon( 0, IDI_APPLICATION ),
            LoadCursor( 0, IDC_ARROW ),
            ( HBRUSH )GetStockObject( BLACK_BRUSH ),
            0,
            class_name.c_str(),
            0
        };
        if( !RegisterClassEx( &wc ) )return false;

        return true;
    }

    void MessageLoop()
    {
        MSG msg;
        while( GetMessage( &msg, 0, 0, 0 ) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
    }

    void Exit( int exit_code )
    {
        PostQuitMessage( exit_code );
    }
};



/**
 *
 */
System& System::instance()
{
    static System inst;
    return inst;
}

/**
 *
 */
System::System()
    : impl( new System::Impl )
{}

void System::Release()
{
    impl = pImpl();
}

/**
 *
 */
bool System::DefRegisterClass()
{
    return RegisterClass( "hoge", &Impl::WinProc );
}

bool System::RegisterClass( std::string const& class_name, WNDPROC proc )
{
    return impl->RegisterClass( class_name, proc );
}

/**
 *
 */
void System::Exit( int exit_code )
{
    impl->Exit( exit_code );
}

/**
 *
 */
void System::Run( FormPtr Form )
{
    Form->Create( 0 );
    Form->Show();
    impl->MessageLoop();
}


int main();
/**
 *
 */
int WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR, int )
{
#ifdef _DEBUG
    //メモリリークの検出
    _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
    //コンソールの割り当て
    AllocConsole();
    FILE* out = 0; freopen_s( &out, "CON", "w", stdout );
    FILE* in = 0; freopen_s( &in, "CON", "r", stdin );
#endif

    //デフォルトのプロシージャを使って、ウィンドウクラスの登録。
    system.DefRegisterClass();

    int ret = main();

    system.Release();

#ifdef _DEBUG
    std::cout << "Enterキーを押してください"; getchar();
    //コンソール解放
    fclose( out );
    fclose( in );
    FreeConsole();
#endif

    return ret;
}

download