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

KTxtEditプロジェクトにコア実装クラスを追加してメインウィンドウインターフェースから分離する。

コア実装クラス(TMyCoreImpl)はアプリケーションの中核をなすクラスで、テキストコントロール(wxTextCtrl)を意味論(セマンティクス)的に所有しファイル入出力機能と編集機能を実装する。メインウィンドウ(KTxtEditFrame)はwxTextCtrlインスタンスをwxWindowポインタとして所有するものの、TMyCoreImplインターフェースを介して操作して将来的にはより高機能なコントロールに置換することを想定する。

プログラミング手順

  1. インクルード/ソースコードファイルの作成
  2. コア実装クラス(TMyCoreImpl)の実装
  3. メインウィンドウクラス(KTxtEditFrame)の修正
  4. リポジトリ更新とビルドテスト

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

プロジェクトに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_H

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); // Redundant.
}
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)を追加する。