パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.6.3.6(15)
ユーティリティクラスの追加

KTxtEditプロジェクトにイベントハンドラ管理クラスとメニュー項目管理クラスを追加する。

イベントハンドラ管理クラス(TMyEventHandlerManager)はイベント捕捉するwxWidgetsオブジェクト(wxEvtHandler派生クラス、ほとんどのGUI要素が相当)のイベントハンドラを管理する。TMyEventHandlerManagerインスタンスで登録したイベントハンドラはインスタンス解体時に自動的に解除される。メニュー項目管理クラス(TMyMenuItemManager)はメニュー項目(wxMenuItem)をラベル、ヘルプ文字列、ハンドラ、有効/無効メソッド、アイコンを一括定義してメニュー(wxMenu)に登録する。KTxtEditプロジェクトの将来拡張に備えるリファクタリングとしてこれらのクラスを導入する。

プログラミング手順

  1. インクルード/ソースコードファイルの作成
  2. イベントハンドラ管理クラス(TMyEventHandlerManager)
  3. メニュー項目管理クラス(TMyMenuItemManager)
  4. KTxtEditFrameクラスへの実装
  5. リポジトリ更新とビルドテスト

インクルード/ソースコードファイルの作成

プロジェクトにユーティリティクラスを記述する空のインクルード/ソースコードファイルを追加する。Code::Blocksの[File|New|File]はインクルード/ソースコード作成が別々で面倒なので、[File|New|Class]で適当なクラスを指定して一括作成する。[File|New|Class]で[Create new class]ダイアログを開き以下を入力する。なお[File policy|Folder]はプロジェクトディレクトリであなたの環境は恐らく異なる。

項目
Class definition Class name MyUtility
File policy Add paths to project チェックを外す
Header and implementation file shall be in same folder チェックする
Folder C:\Users\user\MinGW\Sample\KTxtEdit\
Header and implementation file shall always be lower case チェックを外す
Header file Filename MyUtility.h
Add guard block in header file チェックする
Guard block MYUTILITY_H
Implementation file Filename MyUtility.cpp
Header include "MyUtility.h"

[Create]でダイアログを閉じ、[Add to project]ダイアログに[はい]で応え、[Multiple selection]ダイアログのチェックボックス全てにチェックして[OK]を押す。MyUtility.h/MyUtility.cppファイルがMyUtilityクラススケルトンと共に作成される。クラススケルトンは不要なので削除する。今後はプロジェクト共用ユーティリティクラス/関数はこのファイルで定義する。

MyUtility.h

#ifndef MYUTILITY_H
#define MYUTILITY_H
#endif // MYUTILITY_H

MyUtility.cpp

#include "MyUtility.h"

イベントハンドラ管理クラス(TMyEventHandlerManager)

TMyEventHandlerManagerはイベント捕捉するwxWidgetsオブジェクトのイベントハンドラを管理し、そのインスタンスで登録したイベントハンドラはインスタンス解体時に自動的に解除される。

覚え書き
通常のケースでイベントハンドラ解除は不要だが、サイト作成者は解除に努める事を習慣とする。大昔にwxWidgetsでないライブラリでイベントハンドラの解体後に発生するはずのないイベントが発生し大変な目にあった事がある。

TMyEventHandlerManagerクラスはインターフェースにメンバ関数テンプレートを持ち、かつ単純なクラスなので、全てのメンバ関数定義をソースコードファイル(MyUtility.cpp)で行わずインクルードファイル(MyUtility.h)のクラス定義内で行い、使用ライブラリ<vector>をインクルードする。MyUtility.cppは不要だが将来クラスの複雑化で実装を隠蔽する場合に備え空のまま残す。ただし空でも翻訳単位なのでプリコンパイル対象"wx_pch.h"をインクルードする。

以下においてEventTag、Class、EventArgおよびEventHandlerはテンプレート仮引数型である。

メンバ関数 機能 返値 説明 仮引数 説明
TMyEventHandlerManager コンストラクタ - - wxEvtHandler* wxHandler イベント捕捉オブジェクト
Add ハンドラの追加 void - const EventTag& eventType イベントタイプ
void (Class::*method)(EventArg&) ハンドラメソッドメンバ関数ポインタ
EventHandler* handler ハンドラメソッドをコールするオブジェクト
int id ハンドラに関連付ける最初のID値
int lastId ハンドラに関連付ける最後のID値
wxObject* userData オプションデータ

MyUtility.h

#ifndef MYUTILITY_H
#define MYUTILITY_H
#include <vector>
class TMyEventHandlerManager final
{
private:
template<typename EventTag,typename Class,typename EventArg,typename EventHandler>
struct Bind
{
wxEvtHandler* const wxHandler_;
const EventTag eventType_;
void (Class::* const method_)(EventArg&);
EventHandler* const handler_;
const int id_,lastId_;
wxObject* const userData_;
Bind(wxEvtHandler* wxHandler
,const EventTag& eventType,void (Class::* method)(EventArg&),EventHandler* handler
,int id,int lastId,wxObject* userData)
:wxHandler_{wxHandler}
,eventType_{eventType},method_{method},handler_{handler},id_{id},lastId_{lastId},userData_{userData}
{
wxHandler_->Bind(eventType_,method_,handler_,id_,lastId_,userData_);
}
~Bind()
{
wxHandler_->Unbind(eventType_,method_,handler_,id_,lastId_,userData_);
}
};
wxEvtHandler* const wxHandler_;
std::vector<std::shared_ptr<void>> binders_;
public:
TMyEventHandlerManager(wxEvtHandler* wxHandler):wxHandler_{wxHandler},binders_{} {}
TMyEventHandlerManager(const TMyEventHandlerManager&)=delete;
TMyEventHandlerManager& operator=(const TMyEventHandlerManager&)=delete;
~TMyEventHandlerManager()
{
wxHandler_->DeletePendingEvents();
}
template<typename EventTag,typename Class,typename EventArg,typename EventHandler>
void Add(const EventTag& eventType,void (Class::* method)(EventArg&),EventHandler* handler
,int id=wxID_ANY,int lastId=wxID_ANY,wxObject* userData=NULL)
{
binders_.push_back(std::shared_ptr<void>{new Bind<EventTag,Class,EventArg,EventHandler>
{wxHandler_,eventType,method,handler,id,lastId,userData}});
}
};
#endif // MYUTILITY_H

MyUtility.cpp

#include "wx_pch.h"
#include "MyUtility.h"

メニュー項目管理クラス(TMyMenuItemManager)

TMyMenuItemManagerはメニュー項目(wxMenuItem)をラベル、ヘルプ文字列、ハンドラ、有効/無効、アイコンを一括定義してメニュー(wxMenu)に登録する。ハンドラはコンストラクタに参照で渡すTMyEventHandlerManagerインスタンスが管理する。TMyEventHandlerManager同様に全てのメンバ関数をMyUtility.hクラス定義内で定義し、<map>と<functional>をインクルードする。

覚え書き
これは厳密には危険な実装である。TMyMenuItemManagerインスタンスを解体してもTMyEventHandlerManagerインスタンスにポインタとメンバ関数ポインタがハンドラとして残り、該当イベントの発生は解析困難なエラーをもたらす。サンプルコードはたまたま両者をKTxtEditFrame::Implクラスの隣り合うメンバ変数に保持して、前者解体と後者解体の間にイベント発生は無く問題は生じない(恐らく)。これが問題となるならTMyMenuItemManagerは自ら専用のTMyMenuItemManagerインスタンスを構築保持する必要がある。

以下においてClass、EventHandler、TおよびUはテンプレート仮引数型である。CallEnablersは主にアプリケーションアイドル時イベントハンドラからコールしてenableにbool()関数オブジェクトを与えたメニュー項目の有効/無効を設定する。

メンバ関数 機能 返値 説明 仮引数 説明
TMyMenuItemManager コンストラクタ - - TMyEventHandlerManager& evtHandlerManager イベントハンドラ管理
Append メニュー項目の追加 void - wxMenu* menu メニュー項目を追加するメニュー
const wxString& item ラベル
const wxString& helpString ヘルプ文字列
void (Class::*method)(wxCommandEvent&) ハンドラメソッドメンバ関数ポインタ
EventHandler* handler ハンドラメソッドをコールするオブジェクト
T enable 有効/無効、boolあるいはbool()関数オブジェクト
U bmp アイコンビットマップ、nullptrなら無し
CallEnablers 有効/無効の設定 void - - -

MyUtility.h

#ifndef MYUTILITY_H
#define MYUTILITY_H
#include <vector>
#include <map>
#include <functional>
class TMyEventHandlerManager final
{
...
}
class TMyMenuItemManager final
{
private:
TMyEventHandlerManager& evtHandlerManager_;
std::map<wxMenuItem*,std::function<bool()>> enablers_;
void SetEnable(wxMenuItem* menuItem,bool enable)
{
menuItem->Enable(enable);
}
void SetEnable(wxMenuItem* menuItem,std::function<bool()> enabler)
{
enablers_.insert({menuItem,enabler});
}
void SetBitmap(wxMenuItem* menuItem,const char* const* bmp)
{
if (bmp) {menuItem->SetBitmap(bmp);}
}
void SetBitmap(wxMenuItem* menuItem,const wxBitmap& bmp)
{
menuItem->SetBitmap(bmp);
}
public:
TMyMenuItemManager(TMyEventHandlerManager& evtHandlerManager):evtHandlerManager_{evtHandlerManager},enablers_{} {}
TMyMenuItemManager(const TMyMenuItemManager&)=delete;
TMyMenuItemManager& operator=(const TMyMenuItemManager&)=delete;
~TMyMenuItemManager()=default;
template<typename Class,typename EventHandler,typename T=bool,typename U=const char* const*>
void Append(wxMenu* menu,const wxString& item,const wxString& helpString
,void (Class::* method)(wxCommandEvent&),EventHandler* handler,T enable=true,U bmp=nullptr)
{
auto* menuItem=new wxMenuItem{menu,wxID_ANY,item,helpString,wxITEM_NORMAL};
evtHandlerManager_.Add(wxEVT_MENU,method,handler,menuItem->GetId());
SetEnable(menuItem,enable);
SetBitmap(menuItem,bmp);
menu->Append(menuItem);
}
void CallEnablers()
{
for (const auto& r:enablers_)
{
r.first->Enable(r.second());
}
}
};
#endif // MYUTILITY_H

KTxtEditFrameクラスへの実装

追加したユーティリティクラスをKTxtEditFrameクラスに実装する。"MyUtility.h"をインクルードしてKTxtEditFrameのロジック実装クラス(Impl)を修正する。menuItemSave_などのメニュー項目ポインタを保持するプライベートメンバ変数は全て不要となり削除する。プライベートメンバ変数にTMyEventHandlerManager型evtHandlerManager_とTMyMenuItemManager型menuItemManager_を追加し、コンストラクタ初期化リストに加える。CreateMenuBarメンバ関数をmenuItemManager_で全面的に書き換える。コンストラクタ内のシステムイベント登録をevtHandlerManager_利用に変更する。OnIdleメンバ関数のメニュー項目有効/無効処理もmenuItemManager_に委ねる。今後のメニュー項目追加は全てCreateMenuBar内で完結する。

KTxtEditFrame.cpp

#include "wx_pch.h"
#include <wx/msgdlg.h>
#include <wx/stdpaths.h>
#include <wx/filename.h>
#include <wx/help.h>
#include "KTxtEditFrame.h"
#include "iconimages/bitmapHelp.xpm"
#include "iconimages/bitmapAbout.xpm"
#include "iconimages/bitmapQuit.xpm"
#include "version_macro.h"
#include "MyUtility.h"
...
class KTxtEditFrame::Impl
{
private:
KTxtEditFrame* const form_;
wxTextCtrl* const textCtrl_;
wxStatusBar* const statusBar_;
wxHelpController helpController_;
wxString fileFullPath_;
TMyEventHandlerManager evtHandlerManager_;
TMyMenuItemManager menuItemManager_;
private:
...
wxMenuBar* CreateMenuBar()
{
auto* menuBar=new wxMenuBar{};
auto* menuFile=new wxMenu{};menuBar->Append(menuFile,_("&File"));
auto* menuEdit=new wxMenu{};menuBar->Append(menuEdit,_("&Edit"));
auto* menuHelp=new wxMenu{};menuBar->Append(menuHelp,_("&Help"));
menuItemManager_.Append(menuFile,_("&New"),_("Create a new file"),&Impl::OnNew,this);
menuItemManager_.Append(menuFile,_("&Open...\tCtrl-O"),_("Open a file"),&Impl::OnOpen,this);
menuItemManager_.Append(menuFile,_("&Save\tCtrl-S"),_("Save the current file"),&Impl::OnSave,this
,[this](){return textCtrl_->IsModified();});
menuItemManager_.Append(menuFile,_("Save &as..."),_("Save the current file with a different name"),&Impl::OnSaveAs,this);
menuFile->AppendSeparator();
menuItemManager_.Append(menuFile,_("&Quit\tAlt-F4"),_("Quit the application"),&Impl::OnQuit,this
,true,bitmapQuit_XPM);
menuItemManager_.Append(menuEdit,_("&Undo\tCtrl-Z"),_("Undo the last editing operation"),&Impl::OnUndo,this
,[this](){return textCtrl_->CanUndo();});
menuItemManager_.Append(menuEdit,_("&Redo\tCtrl-Y"),_("Redo the last editing operation"),&Impl::OnRedo,this
,[this](){return textCtrl_->CanRedo();});
menuEdit->AppendSeparator();
menuItemManager_.Append(menuEdit,_("Cu&t\tCtrl-X"),_("Cut selected text to clipboard"),&Impl::OnCut,this
,[this](){return textCtrl_->CanCut();});
menuItemManager_.Append(menuEdit,_("&Copy\tCtrl-C"),_("Copy selected text to clipboard"),&Impl::OnCopy,this
,[this](){return textCtrl_->CanCopy();});
menuItemManager_.Append(menuEdit,_("&Paste\tCtrl-V"),_("Paste text from clipboard"),&Impl::OnPaste,this
,[this](){return textCtrl_->CanPaste();});
menuItemManager_.Append(menuHelp,_("&Help\tF1"),_("Show help file of this application"),&Impl::OnHelp,this
,true,bitmapHelp_XPM);
menuItemManager_.Append(menuHelp,_("&About"),_("Show info about this application"),&Impl::OnAbout,this
,true,bitmapAbout_XPM);
return menuBar;
}
public:
Impl(KTxtEditFrame* form):form_{form}
,textCtrl_{new wxTextCtrl(form_,wxID_ANY,wxEmptyString
,wxDefaultPosition,wxDefaultSize,wxTE_MULTILINE,wxDefaultValidator,wxTextCtrlNameStr)}
,statusBar_{new wxStatusBar{form_,wxID_ANY,wxSTB_DEFAULT_STYLE,wxStatusBarNameStr}}
,helpController_{},fileFullPath_{}
,evtHandlerManager_{form_},menuItemManager_{evtHandlerManager_}
{
form_->SetMenuBar(CreateMenuBar());
statusBar_->SetFieldsCount(2,std::array<int,2>{-1,50}.data());
statusBar_->SetStatusStyles(2,std::array<int,2>{wxSB_NORMAL,wxSB_NORMAL}.data());
form_->SetStatusBar(statusBar_);
evtHandlerManager_.Add(wxEVT_IDLE,&Impl::OnIdle,this);
evtHandlerManager_.Add(wxEVT_CLOSE_WINDOW,&Impl::OnClose,this);
auto iconBundle=wxIconBundle{};
iconBundle.AddIcon(wxIcon{"aaaa",wxICON_DEFAULT_TYPE,16,16});
iconBundle.AddIcon(wxIcon{"aaaa",wxICON_DEFAULT_TYPE,32,32});
form_->SetIcons(iconBundle);
helpController_.Initialize
(wxFileName{wxStandardPaths::Get().GetDataDir(),MYAPPINFO_NAME ".chm"}
.GetFullPath());
}
private:
...
void OnIdle(wxIdleEvent& event)
{
form_->SetTitle(wxString::Format("%s%s - " MYAPPINFO_NAME
,textCtrl_->IsModified()?"*":""
,fileFullPath_.IsEmpty()?_("(Unnamed)"):fileFullPath_));
auto x=long{};auto y=long{};
textCtrl_->PositionToXY(textCtrl_->GetInsertionPoint(),&x,&y);
statusBar_->SetStatusText(wxString::Format("%d:%d",y+1,x+1),1);
menuItemManager_.CallEnablers();
event.Skip();
}
...
};

リポジトリ更新とビルドテスト

動作確認(4)を参考にGit for Windowsリポジトリを更新する。必要ならビルド実行する。

UMLクラス図

簡略したUMLクラス図でアプリケーションの構成を説明する。

  • KTxtEditFrameロジック実装クラス(KTxtEditFrame::Impl)サブオブジェクトとしてイベントハンドラ管理クラス(TMyEventHandlerManager)とメニュー項目管理クラス(TMyMenuItemManager)を追加する。