C++でjava風リスナを実装する
前回いまさらwindowsプログラミング - while( c++ );のプログラムにリスナを追加してみます。
今日の目標
- WindowListenerインターフェイスを実装した派生クラスをウィンドウに追加する。
- WindowClosing()でSystem::Exit()を呼び出すと、プログラムが終了する。
class HogeWindowListener : public WindowListener { public: //×ボタンを押したときの処理 void WindowClosing( WindowEvent& e ) { //プログラム終了 System::Exit( 0 ); std::cout << "closing" << std::endl; } }; int main() { std::cout << "start" << std::endl; //デフォルトのプロシージャを使って、ウィンドウクラスの登録。 System::DefRegisterClass(); //ウィンドウの作成 Frame frame; frame.AddWindowListener( WindowListenerPtr( new HogeWindowListener() ) ); frame.Create( 200, 150 ); //w, h System::MessageLoop(); std::cout << "end" << std::endl; return 0; }
Eventクラス
まずはイベントクラスを用意します。とりあえずウィンドウプロシージャの引数をまとめただけです。
class Event { public: HWND hwnd; UINT msg; WPARAM wp; LPARAM lp; public: Event( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp ) : hwnd( hwnd ) , msg( msg ) , wp( wp ) , lp( lp ) {} };
WindowEventクラス、WindowListenerインターフェイス
上記のEventクラスを継承したWindowEventクラスとWindowEventを処理するためのWindowListenerインターフェイスを用意します。
class WindowEvent : public Event { public: WindowEvent( Event const& e ) : Event( e.hwnd, e.msg, e.wp, e.lp ) {} }; class WindowListener { public: virtual ~WindowListener(){} virtual void WindowClosing( WindowEvent& e ) = 0; };
Frameクラス
FrameクラスにWindowListenerリストを持たせます。自動的にdeleteしてもらうためにshared_ptrの実装6 - while( c++ );で作ったshared_ptrを使います。
#include <list> #include "shared_ptr.h" typedef shared_ptr< WindowListener > WindowListenerPtr; typedef std::list< WindowListenerPtr > WindowListenerList; class Frame { private: HWND hwnd; WindowListenerList window_listener; /*...*/ public: void AddWindowListener( WindowListenerPtr const& listener ); };
void Frame::AddWindowListener( WindowListenerPtr const& listener ) { window_listener.push_back( listener ); }
Systemクラス
プログラムを終了させるためのExit()を追加します。
class System { /*...*/ public: static void Exit( int exit_code ); };
void System::Exit( int exit_code ) { //メッセージループを終了させる PostQuitMessage( exit_code ); }
さらに、各Frameに関連付けられたリスナを実行するために、System::WinProc()を改良します。
LRESULT CALLBACK System::WinProc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp ) { //ここでFrameのリスナを実行したい Event e( hwnd, msg, wp, lp ); frame->...( e ); return DefWindowProc( hwnd, msg, wp, lp ); }
さて、ここでFrame*が必要になります。どこから持ってくれば良いのでしょう??
いろいろ解決法はあると思いますが、今回はCreateWindowEx()の引数からFrame*を取得しましょう。
CreateWindowEx()の最後の引数は、WM_CREATEのLPARAMとしてプロシージャに送られます。
bool Frame::Create( int w, int h ) { hwnd = CreateWindowEx ( 0 , "hoge" , "title" , WS_OVERLAPPEDWINDOW , CW_USEDEFAULT , CW_USEDEFAULT , w , h , 0 , 0 , GetModuleHandle( 0 ) , this //←WinProc()に送る ); if( hwnd == 0 )return false; ShowWindow( hwnd, SW_SHOW ); return true; }
LRESULT CALLBACK System::WinProc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp ) { if( msg == WM_CREATE ) { //CreateWindowEx()の最後の引数(Frame*)を受け取る LPCREATESTRUCT cs = reinterpret_cast< LPCREATESTRUCT >( lp ); Frame* frame = reinterpret_cast< Frame* >( cs->lpCreateParams ); if( frame ) { /*...*/ } } /*...*/ }
しかし、このままではWM_CREATE時しかFrame*を使うことが出来ないので、どこかに保持しておく必要があります。ウィンドウが複数ある場合、hwndに対応するFrame*が必要になるのでstd::map< HWND, Frame* >で管理しましょうか。
とりあえず、Systemクラスのstaticメンバにしておきます。
#include <map> class Frame; class System { /*...*/ private: static std::map< HWND, Frame* > frame_map; static void Add( HWND hwnd, Frame* frame ); static void Remove( HWND hwnd ); static Frame* Search( HWND hwnd ); public: static size_t GetFrameCount() { return frame_map.size(); } };
std::map< HWND, Frame* > System::frame_map; LRESULT CALLBACK System::WinProc( HWND hwnd, UINT msg, WPARAM wp, LPARAM lp ) { if( msg == WM_CREATE ) { //CreateWindowEx()の最後の引数(Frame*)を受け取る LPCREATESTRUCT cs = reinterpret_cast< LPCREATESTRUCT >( lp ); Frame* frame = reinterpret_cast< Frame* >( cs->lpCreateParams ); //ウィンドウハンドルをキーとするFrame*マップに追加 if( frame ) System::Add( hwnd, frame ); } //ウィンドウハンドルをキーとしてFrame*を検索 Frame* frame = System::Search( hwnd ); if( frame ) { //Frameクラスが持つオーバーライド可能なプロシージャを呼び出す Event e( hwnd, msg, wp, lp ); frame->Proc( e ); //ウィンドウが破棄された場合、Frame*マップから削除 if( msg == WM_DESTROY ) { System::Remove( hwnd ); } return e.ret; } return DefWindowProc( hwnd, msg, wp, lp ); } void System::Add( HWND hwnd, Frame* frame ) { frame_map.insert( std::make_pair( hwnd, frame ) ); } void System::Remove( HWND hwnd ) { frame_map.erase( hwnd ); } Frame* System::Search( HWND hwnd ) { std::map< HWND, Frame* >::iterator it = frame_map.find( hwnd ); if( it != frame_map.end() ) { return it->second; } return 0; }
後はリスナを呼び出せば目的は達成できますね。
ということで、Frameクラスに個別のプロシージャを追加しましょう。
ちょと長くなりましたが、もう少しです。
class Frame { /*...*/ ~Destroy() { Destroy(); } void Destroy() { DestroyWindow( hwnd ); } public: void Default( Event& e ); virtual void Proc( Event& e ); protected: void ProcessWindowEvent( WindowEvent& e ); };
void Frame::Proc( Event& e ) { if( e.msg == WM_CLOSE /* || その他のWindowEvent */ ) { ProcessWindowEvent( WindowEvent( e ) ); //イベントが消費されていればすぐに終了 if( e.IsConsumed() )return; } //デフォルトのプロシージャを実行 Default( e ); } void Frame::ProcessWindowEvent( WindowEvent& e ) { if( e.msg == WM_CLOSE ) { struct closing { WindowEvent& e; closing( WindowEvent& e ) : e( e ){} void operator()( WindowListenerPtr const& listener ) { e.Consume(); //イベントが消費された listener->WindowClosing( e ); } }; std::for_each ( window_listener.begin() , window_listener.end() , closing( e ) ); } /* その他のWindowEvent */ } void Frame::Default( Event& e ) { e.ret = DefWindowProc( e.hwnd, e.msg, e.wp, e.lp ); }
イベント制御用にメンバを追加しました
class Event { /*...*/ public: Frame* frame; ///< イベントの発生元Frame LRESULT ret; ///< DefWindowProc()の戻り値を保持 bool consumed; ///< イベントが消費されたかどうか };
完成
class HogeWindowListener : public WindowListener { public: //×ボタンを押したときの処理 void WindowClosing( WindowEvent& e ) { //プログラム終了 System::Exit( 0 ); std::cout << "closing" << std::endl; } }; int main() { std::cout << "start" << std::endl; //デフォルトのプロシージャを使って、ウィンドウクラスの登録。 System::DefRegisterClass(); //ウィンドウの作成 Frame frame; frame.AddWindowListener( WindowListenerPtr( new HogeWindowListener() ) ); frame.Create( "title", 200, 150 ); //title, w, h System::MessageLoop(); std::cout << "end" << std::endl; return 0; }
リスナを実装したFrame派生クラスも可能です。
ただし、shared_ptrにthisを渡すことになるので、deleteさせないためのdeleterを渡す必要があります。
class HogeFrame : public Frame //extends , public WindowListener //implements { static void not_delete( WindowListener* ){} public: HogeFrame() : Frame() { AddWindowListener( WindowListenerPtr( this, not_delete ) ); } public: void WindowClosing( WindowEvent& e ) { System::Exit( 0 ); std::cout << "closing" << std::endl; } }; int main() { std::cout << "start" << std::endl; //デフォルトのプロシージャを使って、ウィンドウクラスの登録。 System::DefRegisterClass(); //ウィンドウの作成 HogeFrame frame; frame.Create( "title", 200, 150 ); //title, w, h System::MessageLoop(); std::cout << "end" << std::endl; return 0; }