パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.6.3.6(15)
オプション設定ダイアログの作成

KTxtEditプロジェクトにオプション設定ダイアログを作成する。

wxSmithでオプション設定ダイアログ(OptionDialog)を作成する。そのラッパークラス(TMyOptionDialog)はオプション設定値を保持し、ソースコードの任意の位置から設定値が取得できるようにする。オプション設定ダイアログで設定値を変更すると、直ちに必要なフィードバックを行う。

プログラミング手順

  1. 共有インクルードファイルの作成
  2. wxSmithによるダイアログ(OptionDialog)作成
  3. ダイアログラッパークラス(TMyOptionDialog)の追加
  4. メニュー項目の追加
  5. [Appearance]ページ設定の実装
  6. リポジトリ更新とビルドテスト

共有インクルードファイルの作成

プロジェクト構成クラスのインターフェースが共有する型を定義するインクルードファイルMyInterfaceType.hを作成する。ソースコードファイル(*.cpp)は不用なので[File|New|File]で[New from template]ダイアログ[Files]ページのC/C++ headerで作成して全ターゲットに登録する。オプションダイアログのインターフェースの一つを構成するTMyFileOption構造体を定義するが、これは後に実装する複数文字コードへの対応で使用する。

MyInterfaceType.h

#ifndef MYINTERFACETYPE_H_INCLUDED
#define MYINTERFACETYPE_H_INCLUDED
struct TMyFileOption
{
wxString encoding_;
bool byteOrderMark_;
enum class EOL {CRLF,LF,CR} endOfLine_;
};
#endif // MYINTERFACETYPE_H_INCLUDED

wxSmithによるダイアログ(OptionDialog)作成

[wxSmith|Add wxDialog]の[New wxDialog resouce]ダイアログで[Class Name]テキストボックスにOptionDialogを入力し[OK]を押す。wxSmithエディタが生成されたOptionDialog.wxsスケルトンを読み込み開くので、リンク先のサンプルに従いwxSmithコンポーネントを配置する。このダイアログは[File]、[Appearance]の2ページからなる。[File]ページ設定はファイル入出力機能の拡張なので、その実装までPanelFileのEnabledプロパティのチェックを外しておくのも良いだろう。

覚え書き
PanelFileのHiddenプロパティをチェックして[File]ページを非表示とできれば完璧だが、何故かこのプロパティは機能しない。

OptionDialogのクラス内処理を隠蔽するためpimplイディオム化する。wxWidgetsライブラリのダイアログを利用するためOptionDialog.cppに<wx/fontdlg.h>と<wx/colordlg.h>をインクルードする。またイベントハンドラ管理にTMyEventHandlerManagerクラスを用いるため"MyUtility.h"もインクルードする。いくつかのイベントハンドラを定義してダイアログ内で完結する処理を実装する。

OptionDialog.h

class OptionDialog: public wxDialog
{
...
private:
//(*Handlers(OptionDialog)
//*)
MY_PIMPL(std::unique_ptr,Impl,pimpl_);
DECLARE_EVENT_TABLE()
};

OptionalDialog.cpp

#include "wx_pch.h"
#include <wx/fontdlg.h>
#include <wx/colordlg.h>
#include "OptionDialog.h"
#include "MyUtility.h"
...
class OptionDialog::Impl
{
private:
OptionDialog* const dlg_;
TMyEventHandlerManager evtHandlerManager_;
bool dirty_;
private:
void ShowTextCtrlFont()
{
auto font=dlg_->TextCtrlAppearance->GetFont();
auto msg=wxString::Format(_("%s (%dpt)"),font.GetFaceName(),font.GetPointSize());
switch (font.GetWeight())
{
case wxFONTWEIGHT_LIGHT:
msg<<" "<<_("Light");
break;
case wxFONTWEIGHT_BOLD:
msg<<" "<<_("Bold");
break;
case wxFONTWEIGHT_NORMAL:
default:
break;
}
switch (font.GetStyle())
{
case wxFONTSTYLE_ITALIC:
msg<<" "<<_("Italic");
break;
case wxFONTSTYLE_NORMAL:
default:
break;
}
dlg_->TextCtrlAppearance->SetLabel(msg);
}
void ChangeTextCtrlColor(wxColour (wxWindow::* getter)() const,bool (wxWindow::* setter)(const wxColour&))
{
auto colorData=wxColourData{};
colorData.SetColour((dlg_->TextCtrlAppearance->*getter)());
wxColourDialog colorDlg{dlg_,&colorData};
if (colorDlg.ShowModal()==wxID_OK)
{
(dlg_->TextCtrlAppearance->*setter)(colorDlg.GetColourData().GetColour());
dlg_->TextCtrlAppearance->Refresh();
dirty_=true;
}
}
public:
Impl(OptionDialog* dlg
,wxButton* buttonAppearanceForegroundColor
,wxButton* buttonAppearanceBackgroundColor
,wxButton* buttonAppearanceFont)
:dlg_{dlg},evtHandlerManager_{dlg_},dirty_{false}
{
evtHandlerManager_.Add(wxEVT_INIT_DIALOG,&Impl::OnInit,this);
evtHandlerManager_.Add(wxEVT_IDLE,&Impl::OnIdle,this);
evtHandlerManager_.Add(wxEVT_SPINCTRL,&Impl::OnChange,this,dlg_->SpinCtrlRecentFilesMax->GetId());
evtHandlerManager_.Add(wxEVT_CHOICE,&Impl::OnChange,this,dlg_->ChoiceDefaultEncoding->GetId());
evtHandlerManager_.Add(wxEVT_CHECKBOX,&Impl::OnChange,this,dlg_->CheckBoxDefaultByteOrderMark->GetId());
evtHandlerManager_.Add(wxEVT_CHOICE,&Impl::OnChange,this,dlg_->ChoiceDefaultEndOfLine->GetId());
evtHandlerManager_.Add(wxEVT_BUTTON,&Impl::OnButtonFG,this,buttonAppearanceForegroundColor->GetId());
evtHandlerManager_.Add(wxEVT_BUTTON,&Impl::OnButtonBG,this,buttonAppearanceBackgroundColor->GetId());
evtHandlerManager_.Add(wxEVT_BUTTON,&Impl::OnButtonFont,this,buttonAppearanceFont->GetId());
}
private:
void OnInit(wxInitDialogEvent& event)
{
ShowTextCtrlFont();
}
void OnIdle(wxIdleEvent& event)
{
dlg_->CheckBoxDefaultByteOrderMark->Enable
(dlg_->ChoiceDefaultEncoding->GetClientData(dlg_->ChoiceDefaultEncoding->GetCurrentSelection()));
dlg_->FindWindowById(wxID_OK)->Enable(dirty_);
event.Skip();
}
void OnChange(wxCommandEvent& event)
{
dirty_=true;
event.Skip();
}
void OnButtonFG(wxCommandEvent& event)
{
ChangeTextCtrlColor(&wxWindow::GetForegroundColour,&wxWindow::SetForegroundColour);
}
void OnButtonBG(wxCommandEvent& event)
{
ChangeTextCtrlColor(&wxWindow::GetBackgroundColour,&wxWindow::SetBackgroundColour);
}
void OnButtonFont(wxCommandEvent& event)
{
auto fontData=wxFontData{};
fontData.EnableEffects(false);
fontData.SetAllowSymbols(false);
fontData.SetInitialFont(dlg_->TextCtrlAppearance->GetFont());
wxFontDialog fontDlg{dlg_,fontData};
if (fontDlg.ShowModal()==wxID_OK)
{
dlg_->TextCtrlAppearance->SetFont(fontDlg.GetFontData().GetChosenFont());
ShowTextCtrlFont();
dirty_=true;
}
}
};
OptionDialog::OptionDialog(wxWindow* parent,wxWindowID id,const wxPoint& pos,const wxSize& size)
{
//(*Initialize(OptionDialog)
...
//*)
pimpl_.reset(new Impl{this
,ButtonAppearanceForegroundColor,ButtonAppearanceBackgroundColor,ButtonAppearanceFont});
}
OptionDialog::~OptionDialog()
{
//(*Destroy(OptionDialog)
//*)
}

ダイアログラッパークラス(TMyOptionDialog)の追加

オプション設定値は二つの側面があり、第一にソースコードが任意の位置で取得し、第二にダイアログで変更して直ちにフィードバックを行う。第一の実装として、TMyOptionDialogはシングルトンとしてオプション設定値のゲッターメンバ関数を公開する。第二の実装として、TMyOptionDialogはExecuteメンバ関数でOptionDialogを表示してオプション設定を促す。同時にオブザーバーデザインパターンのサブジェクト(観察される側)としてオブザーバー(観察する側)が継承すべき抽象クラスを供給し、設定変更されたらオブザーバーにフィードバックする。フィードバック関数(Notifyメンバ関数)はサブジェクト内からのみコールするためプライベートとする。

コア実装クラスの追加と同様にTMyOptionDialog.h/TMyOptionDialog.cppにTMyOptionDialogクラススケルトンを作成してこれを修正する。TMyOptionDialog.hに"MyInterfaceType.h"を、TMyOptionDialog.cppに"OptionDialog.h"と<set>をインクルードする。TMyOptionDialog::GetInstanceスタティックメンバ関数はシングルトンを取得する。オブザーバーが継承するTMyOptionDialog::Observer抽象クラスはデストラクタをprotectedとして仮想とせず、そのポインタによるインスタンス解体は行わせない。pimplイディオムでこれらのロジックを実装する。GetRecentFilesMaxとGetDefaultFileOptionは[File]ページ設定のゲッターメンバ関数で今は使用しない。

説明
Observer オブザーバーが継承すべき抽象クラス。オブザーバーはUpdate純粋仮想メンバ関数をオーバーライドする。
メンバ関数 機能 返値 説明 仮引数 説明
GetInstance シングルトン取得するスタティックメンバ関数 TMyOptionDialog& TMyOptionDialogインスタンス - -
Execute ダイアログ表示して変更あればNotifyをコール bool オプション設定変更の有無 wxWindow* 親ウィンドウ
GetRecentFilesMax 表示するファイル履歴の最大数を取得 int ファイル履歴の最大数 - -
GetDefaultFileOption デフォルトファイルオプション(文字コードなど)の取得 TMyFileOption デフォルトファイルオプション - -
GetTextCtrlForegroundColor 文字色の取得 wxColour 文字色 - -
GetTextCtrlBackgroundColor 背景色の取得 wxColour 背景色 - -
GetTextCtrlFont フォントの取得 wxFont フォント - -

TMyOptionDialog.h

#ifndef TMYOPTIONDIALOG_H
#define TMYOPTIONDIALOG_H
#include "MyInterfaceType.h"
class TMyOptionDialog final
{
public:
using EOL=TMyFileOption::EOL;
class Observer
{
public:
virtual void Update(const TMyOptionDialog& subject)=0;
protected:
Observer();
~Observer();
};
private:
MY_PIMPL(std::unique_ptr,Impl,pimpl_);
private:
TMyOptionDialog();
public:
static TMyOptionDialog& GetInstance();
TMyOptionDialog(const TMyOptionDialog&)=delete;
TMyOptionDialog& operator=(const TMyOptionDialog&)=delete;
~TMyOptionDialog();
bool Execute(wxWindow* parent);
int GetRecentFilesMax() const;
TMyFileOption GetDefaultFileOption() const;
wxColour GetTextCtrlForegroundColor() const;
wxColour GetTextCtrlBackgroundColor() const;
wxFont GetTextCtrlFont() const;
};
#endif // TMYOPTIONDIALOG_H

TMyOptionDialog.cpp

#include "wx_pch.h"
#include "TMyOptionDialog.h"
#include "OptionDialog.h"
#include "TMyRegKey.h"
#include "version_macro.h"
#include "TMyEncodings.h"
#include <set>
template<> bool TMyRegKey::DoSet(const wxString& name,const TMyFileOption& val)
{
auto buf=wxMemoryBuffer{};
buf.SetDataLen((val.encoding_.Length()+1)*sizeof(wchar_t)+sizeof(bool)+sizeof(TMyFileOption::EOL));
auto* pos=static_cast<char*>(buf.GetData());
pos=reinterpret_cast<char*>
(std::copy(val.encoding_.begin(),val.encoding_.end(),reinterpret_cast<wchar_t*>(pos)));
*reinterpret_cast<wchar_t*>(pos)=0;pos+=sizeof(wchar_t);
*reinterpret_cast<bool*>(pos)=val.byteOrderMark_;pos+=sizeof(bool);
*reinterpret_cast<TMyFileOption::EOL*>(pos)=val.endOfLine_;pos+=sizeof(TMyFileOption::EOL);
return Set(name,buf);
}
template<> bool TMyRegKey::DoGet(const wxString& name,TMyFileOption& val) const
{
auto buf=wxMemoryBuffer{};
if (Get(name,buf))
{
auto* pos=static_cast<char*>(buf.GetData());
val.encoding_=reinterpret_cast<wchar_t*>(pos);pos+=(val.encoding_.Length()+1)*sizeof(wchar_t);
val.byteOrderMark_=*reinterpret_cast<bool*>(pos);pos+=sizeof(bool);
val.endOfLine_=*reinterpret_cast<TMyFileOption::EOL*>(pos);pos+=sizeof(TMyFileOption::EOL);
return true;
}
return false;
}
class TMyOptionDialog::Impl
{
private:
int recentFilesMax_;
TMyFileOption defaultFileOption_;
wxColour textCtrlForegroundColor_;
wxColour textCtrlBackgroundColor_;
wxFont textCtrlFont_;
std::set<Observer*> observers_;
TMyRegValues regValues_;
private:
void Notify()
{
for (const auto& p:observers_)
{
p->Update(TMyOptionDialog::GetInstance());
}
}
public:
Impl():recentFilesMax_{10}
,defaultFileOption_{"UTF-8",false,EOL::CRLF}
,textCtrlForegroundColor_{wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)}
,textCtrlBackgroundColor_{wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)}
,textCtrlFont_{wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)}
,observers_{}
,regValues_{wxRegKey::HKCU,"Software" "\\" MYAPPINFO_PUBLISHER "\\" MYAPPINFO_NAME "\\" "TMyOptionDialog"}
{
regValues_.AddValueSet
([this](long& recentFilesMax,TMyFileOption& defaultFileOption)
{
recentFilesMax=recentFilesMax_;
defaultFileOption=defaultFileOption_;
}
,[this](const long& recentFilesMax,const TMyFileOption& defaultFileOption)
{
recentFilesMax_=recentFilesMax;
defaultFileOption_=defaultFileOption;
}
,TMyRegValues::KeyAndDefault<long>{"RecentFilesMax",recentFilesMax_}
,TMyRegValues::KeyAndDefault<TMyFileOption>{"DefaultFileOption",defaultFileOption_});
regValues_.AddValueSet
([this](wxColour& textCtrlForegroundColor,wxColour& textCtrlBackgroundColor,wxFont& textCtrlFont)
{
textCtrlForegroundColor=textCtrlForegroundColor_;
textCtrlBackgroundColor=textCtrlBackgroundColor_;
textCtrlFont=textCtrlFont_;
}
,[this](const wxColour& textCtrlForegroundColor,const wxColour& textCtrlBackgroundColor,const wxFont& textCtrlFont)
{
textCtrlForegroundColor_=textCtrlForegroundColor;
textCtrlBackgroundColor_=textCtrlBackgroundColor;
textCtrlFont_=textCtrlFont;
}
,TMyRegValues::KeyAndDefault<wxColour>{"TextCtrlForegroundColor",textCtrlForegroundColor_}
,TMyRegValues::KeyAndDefault<wxColour>{"TextCtrlBackgroundColor",textCtrlBackgroundColor_}
,TMyRegValues::KeyAndDefault<wxFont>{"TextCtrlFont",textCtrlFont_});
regValues_.GetAll();
}
bool Execute(wxWindow* parent)
{
OptionDialog optionDlg{parent};
TMyEncodings::GetInstance().SetItemContainerControl(optionDlg.ChoiceDefaultEncoding);
optionDlg.ChoiceDefaultEndOfLine->Append(_("CRLF"),reinterpret_cast<void*>(EOL::CRLF));
optionDlg.ChoiceDefaultEndOfLine->Append(_("LF"),reinterpret_cast<void*>(EOL::LF));
optionDlg.ChoiceDefaultEndOfLine->Append(_("CR"),reinterpret_cast<void*>(EOL::CR));
optionDlg.SpinCtrlRecentFilesMax->SetValue(recentFilesMax_);
optionDlg.ChoiceDefaultEncoding->SetStringSelection(defaultFileOption_.encoding_);
optionDlg.CheckBoxDefaultByteOrderMark->SetValue(defaultFileOption_.byteOrderMark_);
for (auto n=(unsigned int){0};n<optionDlg.ChoiceDefaultEndOfLine->GetCount();++n)
{
if (reinterpret_cast<void*>(defaultFileOption_.endOfLine_)==optionDlg.ChoiceDefaultEndOfLine->GetClientData(n))
{
optionDlg.ChoiceDefaultEndOfLine->SetSelection(n);
break;
}
}
optionDlg.TextCtrlAppearance->SetFont(textCtrlFont_);
optionDlg.TextCtrlAppearance->SetForegroundColour(textCtrlForegroundColor_);
optionDlg.TextCtrlAppearance->SetBackgroundColour(textCtrlBackgroundColor_);
if (optionDlg.ShowModal()==wxID_OK)
{
recentFilesMax_=optionDlg.SpinCtrlRecentFilesMax->GetValue();
defaultFileOption_.encoding_=optionDlg.ChoiceDefaultEncoding->GetStringSelection();
defaultFileOption_.byteOrderMark_
=optionDlg.CheckBoxDefaultByteOrderMark->IsEnabled()&&optionDlg.CheckBoxDefaultByteOrderMark->IsChecked();
defaultFileOption_.endOfLine_=static_cast<EOL>(reinterpret_cast<intptr_t>
(optionDlg.ChoiceDefaultEndOfLine->GetClientData(optionDlg.ChoiceDefaultEndOfLine->GetSelection())));
textCtrlFont_=optionDlg.TextCtrlAppearance->GetFont();
textCtrlForegroundColor_=optionDlg.TextCtrlAppearance->GetForegroundColour();
textCtrlBackgroundColor_=optionDlg.TextCtrlAppearance->GetBackgroundColour();
Notify();
return true;
}
return false;
}
int GetRecentFilesMax() const
{
return recentFilesMax_;
}
TMyFileOption GetDefaultFileOption() const
{
return defaultFileOption_;
}
wxColour GetTextCtrlForegroundColor() const
{
return textCtrlForegroundColor_;
}
wxColour GetTextCtrlBackgroundColor() const
{
return textCtrlBackgroundColor_;
}
wxFont GetTextCtrlFont() const
{
return textCtrlFont_;
}
void Attach(Observer* observer)
{
observers_.insert(observer);
}
void Dettach(Observer* observer)
{
observers_.erase(observer);
}
};
TMyOptionDialog::TMyOptionDialog():pimpl_{new Impl{}} {}
TMyOptionDialog::~TMyOptionDialog() {}
TMyOptionDialog& TMyOptionDialog::GetInstance()
{
static TMyOptionDialog onlyInstance{};
return onlyInstance;
}
bool TMyOptionDialog::Execute(wxWindow* parent)
{
return pimpl_->Execute(parent);
}
int TMyOptionDialog::GetRecentFilesMax() const
{
return pimpl_->GetRecentFilesMax();
}
TMyFileOption TMyOptionDialog::GetDefaultFileOption() const
{
return pimpl_->GetDefaultFileOption();
}
wxColour TMyOptionDialog::GetTextCtrlBackgroundColor() const
{
return pimpl_->GetTextCtrlBackgroundColor();
}
wxColour TMyOptionDialog::GetTextCtrlForegroundColor() const
{
return pimpl_->GetTextCtrlForegroundColor();
}
wxFont TMyOptionDialog::GetTextCtrlFont() const
{
return pimpl_->GetTextCtrlFont();
}
TMyOptionDialog::Observer::Observer()
{
TMyOptionDialog::GetInstance().pimpl_->Attach(this);
}
TMyOptionDialog::Observer::~Observer()
{
TMyOptionDialog::GetInstance().pimpl_->Dettach(this);
}

メニュー項目の追加

KTxtEditFrame.cppに"TMyOptionDialog.h"をインクルードする。KTxtEditFrame::ImplクラスのCreateMenuBarプライベートメンバ関数で[Tools]メニューおよび[Tools|Option]メニュー項目を追加し、そのOnOptionハンドラメンバ関数を定義する。

KTxtEditFrame.cpp

#include "wx_pch.h"
...
#include "MyUtility.h"
#include "TMyCoreImpl.h"
#include "TMyOptionDialog.h"
...
class KTxtEditFrame::Impl
{
private:
...
wxMenuBar* CreateMenuBar()
{
auto* menuBar=new wxMenuBar{};
auto* menuFile=new wxMenu{};menuBar->Append(menuFile,_("&File"));
auto* menuEdit=new wxMenu{};menuBar->Append(menuEdit,_("&Edit"));
auto* menuTools=new wxMenu{};menuBar->Append(menuTools,_("&Tools"));
auto* menuHelp=new wxMenu{};menuBar->Append(menuHelp,_("&Help"));
...
menuItemManager_.Append(menuTools,_("&Option"),_("Open option settings dialog"),&Impl::OnOption,this
,true);
...
}
public:
...
private:
...
void OnOption(wxCommandEvent& event)
{
TMyOptionDialog::GetInstance().Execute(form_);
}
...
};
...

[Appearance]ページ設定の実装

[File]ページ設定を実装する機能は用意していないので[Appearance]ページ設定のみ実装する。TMyCoreImpl.cppに"TMyOptionDialog.h"をインクルードしてTMyCoreImpl::ImplクラスをTMyOptionDialog::Observer派生に変更しUpdate純粋仮想メンバ関数をオーバーライドする。コンストラクタ内でUpdateをコールする。

TMyCoreImpl.cpp

#include "wx_pch.h"
#include "TMyCoreImpl.h"
#include "version_macro.h"
#include "TMyOptionDialog.h"
class TMyCoreImpl::Impl:public TMyOptionDialog::Observer
{
...
public:
Impl(wxWindow* parent)
:textCtrl_{new wxTextCtrl(parent,wxID_ANY,wxEmptyString
,wxDefaultPosition,wxDefaultSize,wxTE_MULTILINE,wxDefaultValidator,wxTextCtrlNameStr)}
,fileFullPath_{}
{
Update(TMyOptionDialog::GetInstance());
}
...
void Update(const TMyOptionDialog& subject) override
{
textCtrl_->SetForegroundColour(subject.GetTextCtrlForegroundColor());
textCtrl_->SetBackgroundColour(subject.GetTextCtrlBackgroundColor());
textCtrl_->SetFont(subject.GetTextCtrlFont());
textCtrl_->Refresh();
}
};

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

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

UMLクラス図

簡略したUMLクラス図でアプリケーションの構成を説明する。以降において、KTxtEditApp、TMyEventHandlerManager、TMyMenuItemManager、およびwxWidgetsクラスのほとんどは省略する。

  • wxDialog派生クラス(OptionDialog)とそのラッパークラス(TMyOptionDialog)を追加する。TMyOptionDialogはシングルトンとする。
  • TMyCoreImpl::Implは基底クラス(TMyOptionDialog::Observer)を通じてTMyOptionDialogのオブザーバーに登録する。