KTxtEditプロジェクトにコア実装クラスを追加してメインウィンドウインターフェースから分離する。
コア実装クラス(TMyCoreImpl)はアプリケーションの中核をなすクラスで、テキストコントロール(wxTextCtrl)を意味論(セマンティクス)的に所有しファイル入出力機能と編集機能を実装する。メインウィンドウ(KTxtEditFrame)はwxTextCtrlインスタンスをwxWindowポインタとして所有するものの、TMyCoreImplインターフェースを介して操作して将来的にはより高機能なコントロールに置換することを想定する。
プログラミング手順
- インクルード/ソースコードファイルの作成
- コア実装クラス(TMyCoreImpl)の実装
- メインウィンドウクラス(KTxtEditFrame)の修正
- リポジトリ更新とビルドテスト
インクルード/ソースコードファイルの作成
プロジェクトにTMyCoreImplを記述するインクルード/ソースコードファイルを追加する。Code::Blocksの[File|New|Class]でクラス名TMyCoreImplを指定して作成する。
項目 | 値 |
Class definition | Class name | TMyCoreImpl |
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 | TMyCoreImpl.h |
Add guard block in header file | チェックする |
Guard block | TMYCOREIMPL_H |
Implementation file | Filename | TMyCoreImpl.cpp |
Header include | "TMyCoreImpl.h" |
[Create]でダイアログを閉じ、[Add to project]ダイアログに[はい]で応え、[Multiple selection]ダイアログのチェックボックス全てにチェックして[OK]を押す。
コア実装クラス(TMyCoreImpl)の実装
TMyCoreImpl.h/TMyCoreImpl.cppのTMyCoreImplクラススケルトンをpimplイディオムで全面的に書き換える。KTxtEditFrameクラスのメニュー項目ハンドラでtextCtrl_に依存する処理をこちらで実装する。Newなどの実装メンバ関数の戻り値をboolとしてエラー表示をKTxtEditFrame側で行う場合に備えたが未使用なのでvoidでも良い。
メンバ関数 | 機能 | 戻り値 | 説明 | 仮引数 | 説明 |
TMyCoreImpl | コンストラクタ | - | - | wxWindow* parent | textCtrl_の親ウィンドウ |
New | ファイルの新規作成 | bool | 常にtrue | - | - |
Open | ファイルを開く | bool | 常にtrue | - | - |
Save | ファイルを保存 | bool | 常にtrue | - | - |
SaveAs | ファイルに名前を付けて保存 | bool | 常にtrue | - | - |
Undo | 直前の編集を取り消す | bool | 常にtrue | - | - |
Redo | 取り消した編集をやり直す | bool | 常にtrue | - | - |
Cut | 選択をクリップボードへ切り取る | bool | 常にtrue | - | - |
Copy | 選択をクリップボードへコピー | bool | 常にtrue | - | - |
Paste | クリップボードを選択へ貼り付け | bool | 常にtrue | - | - |
CanUndo | 取消し可否確認 | bool | 取消し可 | - | - |
CanRedo | やり直し可否確認 | bool | やり直し可 | - | - |
CanCut | 切り取り可否確認 | bool | 切り取り可 | - | - |
CanCopy | コピー可否確認 | bool | コピー可 | - | - |
CanPaste | 貼り付け可否確認 | bool | 貼り付け可 | - | - |
CanClose | 終了可否確認 | bool | 終了可 | - | - |
IsModified | ファイル保存後の編集確認 | bool | 編集有 | - | - |
GetTitleString | タイトル表示文字列の取得 | wxString | タイトル表示文字列 | - | - |
GetStatusString | ステータス表示文字列の取得 | wxString | ステータス表示文字列 | - | - |
TMyCoreImpl.h
#ifndef TMYCOREIMPL_H
#define TMYCOREIMPL_H
class TMyCoreImpl final
{
private:
MY_PIMPL(std::unique_ptr,Impl,pimpl_);
public:
TMyCoreImpl(wxWindow* parent);
TMyCoreImpl(const TMyCoreImpl&)=delete;
TMyCoreImpl& operator=(const TMyCoreImpl&)=delete;
~TMyCoreImpl();
bool New();
bool Open();
bool Save();
bool SaveAs();
bool Undo();
bool Redo();
bool Cut();
bool Copy();
bool Paste();
bool CanUndo() const;
bool CanRedo() const;
bool CanCut() const;
bool CanCopy() const;
bool CanPaste() const;
bool CanClose() const;
bool IsModified() const;
wxString GetTitleString() const;
wxString GetStatusString() const;
wxMenu* CreateRecentFilesMenu();
};
#endif
TMyCoreImpl.cpp
#include "wx_pch.h"
#include "TMyCoreImpl.h"
#include "version_macro.h"
#include "TMyOptionDialog.h"
#include "TMyFileOpenSave.h"
class TMyCoreImpl::Impl:public TMyOptionDialog::Observer
{
private:
wxTextCtrl* const textCtrl_;
TMyFileOpenSave fileOpenSave_;
bool ConfirmIfModified() const
{
return !textCtrl_->IsModified()
||wxMessageBox(_("The current file is modified. Do you want to discard?")
,MYAPPINFO_NAME,wxICON_QUESTION|wxYES_NO|wxNO_DEFAULT|wxCENTER)==wxYES;
}
public:
Impl(wxWindow* parent)
:textCtrl_{new wxTextCtrl(parent,wxID_ANY,wxEmptyString
,wxDefaultPosition,wxDefaultSize,wxTE_MULTILINE,wxDefaultValidator,wxTextCtrlNameStr)}
,fileOpenSave_{textCtrl_}
{
Update(TMyOptionDialog::GetInstance());
}
bool New()
{
if (ConfirmIfModified())
{
textCtrl_->Clear();
fileOpenSave_.UnnameCurrent();
}
return true;
}
bool Open()
{
if (ConfirmIfModified()&&fileOpenSave_.Open())
{
textCtrl_->SetModified(false);
}
return true;
}
bool Save()
{
if (fileOpenSave_.Save())
{
textCtrl_->SetModified(false);
}
return true;;
}
bool SaveAs()
{
if (fileOpenSave_.SaveAs())
{
textCtrl_->SetModified(false);
}
return true;
}
bool Undo()
{
textCtrl_->Undo();
return true;
}
bool Redo()
{
textCtrl_->Redo();
return true;
}
bool Cut()
{
textCtrl_->Cut();
return true;
}
bool Copy()
{
textCtrl_->Copy();
return true;
}
bool Paste()
{
textCtrl_->Paste();
return true;
}
bool CanUndo() const
{
return textCtrl_->CanUndo();
}
bool CanRedo() const
{
return textCtrl_->CanRedo();
}
bool CanCut() const
{
return textCtrl_->CanCut();
}
bool CanCopy() const
{
return textCtrl_->CanCopy();
}
bool CanPaste() const
{
return textCtrl_->CanPaste();
}
bool CanClose() const
{
return ConfirmIfModified();
}
bool IsModified() const
{
return textCtrl_->IsModified();
}
wxString GetTitleString() const
{
auto path=wxString{};
auto encoding=wxString{};
if (fileOpenSave_.CurrentIsUnnamed())
{
path=_("(Unnamed)");
}
else
{
auto fileOption=fileOpenSave_.GetCurrentOption();
path=fileOpenSave_.GetCurrentPath();
encoding<<" - ["<<fileOption.encoding_;
if (fileOption.byteOrderMark_) {encoding<<_("(BOM)");}
encoding<<":";
switch (fileOption.endOfLine_)
{
case TMyFileOption::EOL::CRLF:
encoding<<_("CRLF");break;
case TMyFileOption::EOL::LF:
encoding<<_("LF");break;
case TMyFileOption::EOL::CR:
encoding<<_("CR");break;
}
encoding<<"]";
}
return wxString::Format("%s%s%s - " MYAPPINFO_NAME,textCtrl_->IsModified()?"*":"",path,encoding);
}
wxString GetStatusString() const
{
auto x=long{};auto y=long{};
textCtrl_->PositionToXY(textCtrl_->GetInsertionPoint(),&x,&y);
return wxString::Format("%d:%d",y+1,x+1);
}
void Update(const TMyOptionDialog& subject) override
{
textCtrl_->SetForegroundColour(subject.GetTextCtrlForegroundColor());
textCtrl_->SetBackgroundColour(subject.GetTextCtrlBackgroundColor());
textCtrl_->SetFont(subject.GetTextCtrlFont());
textCtrl_->Refresh();
}
wxMenu* CreateRecentFilesMenu()
{
return fileOpenSave_.CreateRecentFilesMenu();
}
};
TMyCoreImpl::TMyCoreImpl(wxWindow* parent):pimpl_{new Impl{parent}} {}
TMyCoreImpl::~TMyCoreImpl() {}
bool TMyCoreImpl::New() {return pimpl_->New();}
bool TMyCoreImpl::Open() {return pimpl_->Open();}
bool TMyCoreImpl::Save() {return pimpl_->Save();}
bool TMyCoreImpl::SaveAs() {return pimpl_->SaveAs();}
bool TMyCoreImpl::Undo() {return pimpl_->Undo();}
bool TMyCoreImpl::Redo() {return pimpl_->Redo();}
bool TMyCoreImpl::Cut() {return pimpl_->Cut();}
bool TMyCoreImpl::Copy() {return pimpl_->Copy();}
bool TMyCoreImpl::Paste() {return pimpl_->Paste();}
bool TMyCoreImpl::CanUndo() const {return pimpl_->CanUndo();}
bool TMyCoreImpl::CanRedo() const {return pimpl_->CanRedo();}
bool TMyCoreImpl::CanCut() const {return pimpl_->CanCut();}
bool TMyCoreImpl::CanCopy() const {return pimpl_->CanCopy();}
bool TMyCoreImpl::CanPaste() const {return pimpl_->CanPaste();}
bool TMyCoreImpl::CanClose() const {return pimpl_->CanClose();}
bool TMyCoreImpl::IsModified() const {return pimpl_->IsModified();}
wxString TMyCoreImpl::GetTitleString() const {return pimpl_->GetTitleString();}
wxString TMyCoreImpl::GetStatusString() const {return pimpl_->GetStatusString();}
wxMenu* TMyCoreImpl::CreateRecentFilesMenu() {return pimpl_->CreateRecentFilesMenu();}
メインウィンドウクラス(KTxtEditFrame)の修正
KTxtEditFrame.cppに"TMyCoreImpl.h"をインクルードする。KTxtEditFrameロジック実装(Impl)クラスを以下に書き換える。textCtrl_、fileFullPath_メンバ変数はTMyCoreImplロジック実装(Impl)クラスへ移るので削除、代わりにTMyCoreImpl型メンバ変数coreImpl_を追加し、コンストラクタ初期化リストを書き換える。ConfirmIfModifiedプライベートメンバ関数はKTxtEditFrame::Implへ移り削除。CreateMenuBarメンバ関数内のメニュー項目有効/無効を定義するbool()関数オブジェクトでtextCtrl_を参照するものは該当のTMyCoreImplメンバ関数コールに書き換える。KTxtEditFrame::Implのメニュー項目ハンドラでtextCtrl_を参照していたOnNewメンバ関数などは該当のTMyCoreImplメンバ関数コールに書き換える。OnIdleメンバ関数もタイトルバー、ステータスバー表示文字列取得をTMyCoreImplメンバ関数コールに変更する。OnCloseメンバ関数のファイル変更確認もTMyCoreImplメンバ関数コールに変更する。
KTxtEditFrame.cpp
#include "wx_pch.h"
...
#include "MyUtility.h"
#include "TMyCoreImpl.h"
...
class KTxtEditFrame::Impl
{
private:
KTxtEditFrame* const form_;
wxStatusBar* const statusBar_;
wxHelpController helpController_;
TMyCoreImpl coreImpl_;
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 coreImpl_.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 coreImpl_.CanUndo();});
menuItemManager_.Append(menuEdit,_("&Redo\tCtrl-Y"),_("Redo the last editing operation"),&Impl::OnRedo,this
,[this](){return coreImpl_.CanRedo();});
menuEdit->AppendSeparator();
menuItemManager_.Append(menuEdit,_("Cu&t\tCtrl-X"),_("Cut selected text to clipboard"),&Impl::OnCut,this
,[this](){return coreImpl_.CanCut();});
menuItemManager_.Append(menuEdit,_("&Copy\tCtrl-C"),_("Copy selected text to clipboard"),&Impl::OnCopy,this
,[this](){return coreImpl_.CanCopy();});
menuItemManager_.Append(menuEdit,_("&Paste\tCtrl-V"),_("Paste text from clipboard"),&Impl::OnPaste,this
,[this](){return coreImpl_.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}
,statusBar_{new wxStatusBar{form_,wxID_ANY,wxSTB_DEFAULT_STYLE,wxStatusBarNameStr}}
,helpController_{}
,coreImpl_{form_}
,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 OnNew(wxCommandEvent& event)
{
coreImpl_.New();
}
void OnOpen(wxCommandEvent& event)
{
coreImpl_.Open();
}
void OnSave(wxCommandEvent& event)
{
coreImpl_.Save();
}
void OnSaveAs(wxCommandEvent& event)
{
coreImpl_.SaveAs();
}
void OnQuit(wxCommandEvent& event)
{
form_->Close();
}
void OnUndo(wxCommandEvent& event)
{
coreImpl_.Undo();
}
void OnRedo(wxCommandEvent& event)
{
coreImpl_.Redo();
}
void OnCut(wxCommandEvent& event)
{
coreImpl_.Cut();
}
void OnCopy(wxCommandEvent& event)
{
coreImpl_.Copy();
}
void OnPaste(wxCommandEvent& event)
{
coreImpl_.Paste();
}
void OnHelp(wxCommandEvent& event)
{
helpController_.DisplayContents();
}
void OnAbout(wxCommandEvent& event)
{
wxString msg;
msg<<MYAPPINFO_NAME<<"\n"
<<_("Version: ")<<AUTOVERSION_STR_VER_BUILD_COUNT_STATUS<<"\n"
<<_("Date: ")<<AUTOVERSION_STR_DATE_DMY_MONTH_SHORT_NAME;
wxMessageBox(msg, _("Welcome to..."));
}
void OnIdle(wxIdleEvent& event)
{
form_->SetTitle(coreImpl_.GetTitleString());
statusBar_->SetStatusText(coreImpl_.GetStatusString(),1);
menuItemManager_.CallEnablers();
event.Skip();
}
void OnClose(wxCloseEvent& event)
{
if (event.CanVeto()&&!coreImpl_.CanClose())
{
event.Veto();
}
else
{
event.Skip();
}
}
};
...
リポジトリ更新とビルドテスト
動作確認(4)を参考にGit for Windowsリポジトリを更新する。必要ならビルド実行する。
UMLクラス図
簡略したUMLクラス図でアプリケーションの構成を説明する。
- KTxtEditFrameロジック実装クラス(KTxtEditFrame::Impl)サブオブジェクトとしてコア実装クラス(TMyCoreImpl)を追加する。