パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.6.3.6(15)
レジストリ機能の実装

KTxtEditプロジェクトにレジストリ機能を実装する。

レジストリ機能を追加するため、レジストリクラス(TMyRegKey)とレジストリ対象値管理クラス(TMyRegValues)を作成する。前者はwxWidgetsライブラリのwxRegKeyクラスのラッパーでその機能を拡張する。後者はレジストリ保存の対象とするデータを管理する。アプリケーション終了時にメインウィンドウ位置、オプション設定ダイアログ選択ページ、オプション設定値をレジストリに保存して次回起動時に復元する。

プログラミング手順

  1. レジストリクラス(TMyRegKey)の作成
  2. レジストリ対象値管理クラス(TMyRegValues)の作成
  3. メインウィンドウ位置の保存
  4. オプション設定ダイアログ選択ページの保存
  5. オプション設定値の保存
  6. リポジトリ更新とビルドテスト

レジストリクラス(TMyRegKey)の作成

wxRegKeyクラスの対象データ型は文字列(wxString)、整数(long)、メモリバッファ(wxMemoryBuffer)に限定される。TMyRegKeyはwxRegKeyインターフェースを単純化しながら対象データ型を拡張する。オーバーロードされたSet/Getメンバ関数がデータを保存/取得する。そのテンプレート版がwxString、long、wxMemoryBuffer以外を扱い、DoSet/DoGetメンバ関数テンプレートで実装する。テンプレート版でSet/GetとDoSet/DoGetを分離するのはインターフェースをクリーンとするためで、こだわらなければSet/Getに直接実装して良い。DoSet/DoGetは整数型、列挙型、ブール型をlongにキャストして保存/取得し、浮動小数点型や単純な構造体をwxMemoryBufferに収納して保存/取得する。DoSet/DoGetは特殊化で任意型に拡張でき、サンプルではwxColourとwxFontを加える。

これまで同様TMyRegKey.h/TMyRegKey.cppにTMyRegKeyクラススケルトンを作成して修正する。TMyRegKey.hには<wx/msw/registry.h>をインクルードする。pimplイディオム総称プログラミングを組み合わせてかなり解りにくい。以下においてTはwxString/long/wxMemoryBufferオーバーロードあるいはテンプレート仮引数型である。

メンバ関数 機能 返値 説明 仮引数 説明
TMyRegKey コンストラクタ - - wxRegKey::StdKey システム定義キー
const wxString& 親キー名(システム定義キーから相対)
Set 対象データをレジストリに保存 bool 保存成功 const wxString& データキー名(親キーから相対)
const T& 保存データ値を保持する定数参照
Get 対象データをレジストリから取得 bool 取得成功 const wxString& データキー名(データ保存キーから相対)
T& 取得データ値を代入する参照変数

TMyRegKey.h

#ifndef TMYREGKEY_H
#define TMYREGKEY_H
#include <wx/msw/registry.h>
class TMyRegKey final
{
private:
MY_PIMPL(std::unique_ptr,Impl,pimpl_);
private:
enum class Target {STRING,LONG,MEMORYBUFFER,DEFAULT};
template<typename T> struct Selector
{
static constexpr Target value=
(false?Target::STRING
:std::is_integral<T>::value||std::is_enum<T>::value||std::is_same<std::remove_cv_t<T>,bool>::value?Target::LONG
:std::is_trivial<T>::value?Target::MEMORYBUFFER
:Target::DEFAULT);
};
template<typename T> std::enable_if_t<Selector<T>::value==Target::STRING,bool>
DoSet(const wxString& name,const T& val);
template<typename T> std::enable_if_t<Selector<T>::value==Target::STRING,bool>
DoGet(const wxString& name,T& val) const;
template<typename T> std::enable_if_t<Selector<T>::value==Target::LONG,bool>
DoSet(const wxString& name,const T& val)
{
return Set(name,static_cast<long>(val));
}
template<typename T> std::enable_if_t<Selector<T>::value==Target::LONG,bool>
DoGet(const wxString& name,T& val) const
{
auto lVal=long{};
if (Get(name,lVal)) {val=static_cast<T>(lVal);return true;}
else {return false;}
}
template<typename T> std::enable_if_t<Selector<T>::value==Target::MEMORYBUFFER,bool>
DoSet(const wxString& name,const T& val)
{
auto buf=wxMemoryBuffer{sizeof(T)};
*static_cast<T*>(buf.GetData())=val;
buf.SetDataLen(sizeof(T));
return Set(name,buf);
}
template<typename T> std::enable_if_t<Selector<T>::value==Target::MEMORYBUFFER,bool>
DoGet(const wxString& name,T& val) const
{
auto buf=wxMemoryBuffer{};
if (Get(name,buf)&&buf.GetDataLen()==sizeof(T))
{val=*static_cast<const T*>(buf.GetData());return true;}
else {return false;}
}
template<typename T> std::enable_if_t<Selector<T>::value==Target::DEFAULT,bool>
DoSet(const wxString& name,const T& val)
{
static_assert(sizeof(T)==0,"Member function template specialization is required.");
return false;
}
template<typename T> std::enable_if_t<Selector<T>::value==Target::DEFAULT,bool>
DoGet(const wxString& name,T& val) const;
public:
TMyRegKey(wxRegKey::StdKey keyParent,const wxString& strKey);
~TMyRegKey();
TMyRegKey(const TMyRegKey&)=delete;
TMyRegKey& operator=(const TMyRegKey&)=delete;
TMyRegKey(TMyRegKey&&);
TMyRegKey& operator=(TMyRegKey&&);
bool Set(const wxString& name,const wxString& val);
bool Get(const wxString& name,wxString& val) const;
bool Set(const wxString& name,const long& val);
bool Get(const wxString& name,long& val) const;
bool Set(const wxString& name,const wxMemoryBuffer& val);
bool Get(const wxString& name,wxMemoryBuffer& val) const;
template<typename T> bool Set(const wxString& name,const T& val) {return DoSet(name,val);}
template<typename T> bool Get(const wxString& name,T& val) const {return DoGet(name,val);}
};
template<> bool TMyRegKey::DoSet(const wxString& name,const wxColour& val);
template<> bool TMyRegKey::DoGet(const wxString& name,wxColour& val) const;
template<> bool TMyRegKey::DoSet(const wxString& name,const wxFont& val);
template<> bool TMyRegKey::DoGet(const wxString& name,wxFont& val) const;
#endif // TMYREGKEY_H

TMyRegKey.cpp

#include "wx_pch.h"
#include "TMyRegKey.h"
class TMyRegKey::Impl
{
private:
wxRegKey regKey_;
public:
Impl(wxRegKey::StdKey keyParent,const wxString& strKey)
:regKey_{keyParent,strKey} {regKey_.Create();}
~Impl() {regKey_.Close();}
template<typename T> bool Set(const wxString& name,const T& val)
{return regKey_.SetValue(name,val);}
bool Get(const wxString& name,long& val) const
{return regKey_.HasValue(name)&&regKey_.QueryValue(name,&val);}
template<typename T> bool Get(const wxString& name,T& val) const
{return regKey_.HasValue(name)&&regKey_.QueryValue(name,val);}
};
TMyRegKey::TMyRegKey(wxRegKey::StdKey keyParent,const wxString& strKey):pimpl_{new Impl{keyParent,strKey}} {}
TMyRegKey::~TMyRegKey() {}
TMyRegKey::TMyRegKey(TMyRegKey&& rhs):pimpl_{std::move(rhs.pimpl_)} {}
TMyRegKey& TMyRegKey::operator=(TMyRegKey&& rhs) {pimpl_=std::move(rhs.pimpl_);return *this;}
bool TMyRegKey::Set(const wxString& name,const wxString& val) {return pimpl_->Set(name,val);}
bool TMyRegKey::Get(const wxString& name,wxString& val) const {return pimpl_->Get(name,val);}
bool TMyRegKey::Set(const wxString& name,const long& val) {return pimpl_->Set(name,val);}
bool TMyRegKey::Get(const wxString& name,long& val) const {return pimpl_->Get(name,val);}
bool TMyRegKey::Set(const wxString& name,const wxMemoryBuffer& val) {return pimpl_->Set(name,val);}
bool TMyRegKey::Get(const wxString& name,wxMemoryBuffer& val) const {return pimpl_->Get(name,val);}
template<> bool TMyRegKey::DoSet(const wxString& name,const wxColour& val)
{
return Set(name,static_cast<long>(val.GetRGB()));
}
template<> bool TMyRegKey::DoGet(const wxString& name,wxColour& val) const
{
auto lVal=long{0};
if (Get(name,lVal)) {val.SetRGB(static_cast<wxUint32>(lVal));return true;}
else {return false;}
}
template<> bool TMyRegKey::DoSet(const wxString& name,const wxFont& val)
{
return Set(name,val.GetNativeFontInfoDesc());
}
template<> bool TMyRegKey::DoGet(const wxString& name,wxFont& val) const
{
auto sVal=wxString{};
if (Get(name,sVal)) {val.SetNativeFontInfo(sVal);return true;}
else {return false;}
}
覚え書き
wxRegEx::QueryValueオーバーロードメンバ関数の第二仮引数がwxStringとwxMemoryBufferでは参照だがlongはポインタで一貫しない。

レジストリ対象値管理クラス(TMyRegValues)の作成

TMyRegValuesは任意クラスのメンバ変数としての運用を想定し、クラスのメンバ変数に限定しない何らかのプロパティを対象データとしてレジストリへ保存/取得する。AddValueSetメンバ関数テンプレートでレジストリ保存対象データの定義を追加する。AddValueSetは任意数の対象データを定義できるが、さらに複数回コールしても良い。GetAllで全ての対象データをレジストリから取得し、デストラクタで全ての対象データをレジストリに保存する。GetAll/デストラクタは一時的にTMyRegKeyインスタンスを作成して対象データを取得/保存する。AddValueSetの第一仮引数は任意個数の変数参照を仮引数に持つセッター関数オブジェクトで、変数参照に対象データ値を代入する。第二仮引数は第一仮引数に対応する定数参照を仮引数に持つゲッター関数オブジェクトで、対象データ値を定数参照から取得する。第三仮引数以降は各対象データの型、データキー名、デフォルト値を定義する。第三以降の仮引数の数は必ずセッター/ゲッター関数の変数/定数参照仮引数の数に一致し、型もそれぞれに対応する。

コード量も大きくないのでTMyRegKey.h/TMyRegKey.cppに追加コーディングする。TMyRegKey.hには<functional>、TMyRegKey.cppには<vector>をインクルードする。TMyRegKeyよりさらに解りにくくなってしまった。以下においてTはテンプレート仮引数、T1,T2...は可変長テンプレート仮引数である。

説明
KeyAndDefault<T> 対象データの型、データキー名、デフォルト値を記述するオブジェクト。実体はpair<wxString,T>。
メンバ関数 機能 返値 説明 仮引数 説明
TMyRegValues コンストラクタ - - wxRegKey::StdKey システム定義キー
const wxString& 親キー名(システム定義キーから相対)
AddValueSet 対象データ定義の追加 void - void(T1&,T2&,...) 1個以上の対象データをレジストリに保存する関数オブジェクト
void(const T1&,const T2&,...) 1個以上の対象データをレジストリから取得する関数オブジェクト
const KeyAndDefault<T1>& {データキー名,デフォルト値}
const KeyAndDefault<T2>& {データキー名,デフォルト値}
... ...
GetAll 全ての対象データをレジストリから取得 void - - -

TMyRegKey.h

#ifndef TMYREGKEY_H
#define TMYREGKEY_H
#include <wx/msw/registry.h>
#include <functional>
class TMyRegKey final
{
...
};
class TMyRegValues final
{
public:
template<typename T> using KeyAndDefault=std::pair<wxString,T>;
private:
MY_PIMPL(std::unique_ptr,Impl,pimpl_);
private:
class ValueSet final
{
private:
std::function<void(TMyRegKey&)> setter_;
std::function<void(const TMyRegKey&)> getter_;
private:
template<typename T> struct Proxy
{
TMyRegKey& reg_;
const KeyAndDefault<T>& aPair_;
T val_;
Proxy(TMyRegKey& reg,const KeyAndDefault<T>& aPair)
:reg_{reg},aPair_{aPair},val_{OutGetter(reg_,aPair)} {}
~Proxy() {reg_.Set(aPair_.first,val_);}
operator T&() {return val_;}
};
template<typename T> static Proxy<T> OutSetter
(TMyRegKey& reg,const KeyAndDefault<T>& aPair)
{return Proxy<T>{reg,aPair};}
template<typename T> static T OutGetter
(const TMyRegKey& reg,const KeyAndDefault<T>& aPair)
{T val;return reg.Get(aPair.first,val)?val:aPair.second;}
public:
template<typename F1,typename F2,typename... Ts> ValueSet
(F1&& inSetter,F2&& inGetter,const KeyAndDefault<Ts>&... pairs)
:setter_{[inSetter,pairs...](TMyRegKey& reg)
{inSetter(OutSetter(reg,pairs)...);}}
,getter_{[inGetter,pairs...](const TMyRegKey& reg)
{inGetter(OutGetter(reg,pairs)...);}}
{}
void Set(TMyRegKey& reg) const {setter_(reg);}
void Get(const TMyRegKey& reg) const {getter_(reg);}
};
public:
TMyRegValues(wxRegKey::StdKey keyParent,const wxString& strKey);
~TMyRegValues();
TMyRegValues(const TMyRegValues&)=delete;
TMyRegValues& operator=(const TMyRegValues&)=delete;
TMyRegValues(TMyRegValues&&);
TMyRegValues& operator=(TMyRegValues&&);
void AddValueSet(const ValueSet& valSet);
template<typename F1,typename F2,typename... Ts> void AddValueSet
(F1&& inSetter,F2&& inGetter,const KeyAndDefault<Ts>&... pairs)
{
AddValueSet({std::forward<F1>(inSetter),std::forward<F2>(inGetter),pairs...});
}
void GetAll();
};
#endif // TMYREGKEY_H

TMyRegKey.cpp

#include "wx_pch.h"
#include "TMyRegKey.h"
#include <vector>
...
class TMyRegValues::Impl
{
private:
const wxRegKey::StdKey keyParent_;
const wxString strKey_;
std::vector<ValueSet> valSets_;
template<typename MF> void Iterate(MF mf)
{
if (!valSets_.empty())
{
auto reg=TMyRegKey{keyParent_,strKey_};
for (const auto& r:valSets_) {(r.*mf)(reg);}
}
}
public:
Impl(wxRegKey::StdKey keyParent,const wxString& strKey)
:keyParent_{keyParent},strKey_{strKey},valSets_{} {}
~Impl() {Iterate(&ValueSet::Set);}
void AddValueSet(const ValueSet& valSet) {valSets_.push_back(valSet);}
void GetAll() {Iterate(&ValueSet::Get);}
};
TMyRegValues::TMyRegValues(wxRegKey::StdKey keyParent,const wxString& strKey)
:pimpl_{new Impl{keyParent,strKey}} {}
TMyRegValues::~TMyRegValues() {}
TMyRegValues::TMyRegValues(TMyRegValues&& rhs):pimpl_{std::move(rhs.pimpl_)} {}
TMyRegValues& TMyRegValues::operator=(TMyRegValues&& rhs) {pimpl_=std::move(rhs.pimpl_);return *this;}
void TMyRegValues::AddValueSet(const ValueSet& valSet)
{pimpl_->AddValueSet(valSet);}
void TMyRegValues::GetAll() {pimpl_->GetAll();}

メインウィンドウ位置の保存

KTxtEditFrame.cppに"TMyRegKey.h"をインクルードするが、レジストリキー名に"version_macro.h"定義マクロを利用するのでこれもインクルードする。KTxtEditFrame::ImplクラスにTMyRegValues型regValues_メンバ変数を追加し、初期化リストで初期化する。親キー名はversion_macro.hから取得するマクロを利用し、最後にクラス名KTxtEditFrameを加える。コンストラクタ内でAddValueSetでウィンドウ位置とサイズをレジストリ保存対象データに加え、最後にGetAllをコールする。

KTxtEditFrame.cpp

#include "wx_pch.h"
...
#include "MyUtility.h"
#include "TMyCoreImpl.h"
#include "TMyOptionDialog.h"
#include "TMyRegKey.h"
#include "version_macro.h"
...
class KTxtEditFrame::Impl
{
private:
...
TMyRegValues regValues_;
...
public:
Impl(KTxtEditFrame* form):form_{form}
,statusBar_{new wxStatusBar{form_,wxID_ANY,wxSTB_DEFAULT_STYLE,wxStatusBarNameStr}}
,helpController_{}
,coreImpl_{form_}
,evtHandlerManager_{form_},menuItemManager_{evtHandlerManager_}
,regValues_{wxRegKey::HKCU,"Software" "\\" MYAPPINFO_PUBLISHER "\\" MYAPPINFO_NAME "\\" "KTxtEditFrame"}
{
...
regValues_.AddValueSet
([this](WINDOWPLACEMENT& val)
{
val.length=sizeof(WINDOWPLACEMENT);
::GetWindowPlacement(form_->GetHWND(),&val);
}
,[this](const WINDOWPLACEMENT& val)
{
::SetWindowPlacement(form_->GetHWND(),&val);
}
,TMyRegValues::KeyAndDefault<WINDOWPLACEMENT>
{"WindowPlacement"
,{sizeof(WINDOWPLACEMENT),0,SW_SHOWNORMAL,{0,0},{0,0},{100,100,600,600}}});
regValues_.GetAll();
}
private:
...
};
...

オプション設定ダイアログ選択ページの保存

OptionDialog.cppに"TMyRegKey.h"と"version_macro.h"をインクルードする。OptionDialog::ImplクラスにTMyRegValues型regValues_メンバ変数を追加し、初期化リストで初期化する。コンストラクタ内でAddValueSetで選択ページをレジストリ保存対象データに加え、最後にGetAllをコールする。

OptionDialog.cpp

#include "wx_pch.h"
...
#include "MyUtility.h"
#include "TMyRegKey.h"
#include "version_macro.h"
...
class OptionDialog::Impl
{
private:
...
TMyRegValues regValues_;
...
public:
Impl(OptionDialog* dlg
,wxButton* buttonAppearanceForegroundColor
,wxButton* buttonAppearanceBackgroundColor
,wxButton* buttonAppearanceFont)
:dlg_{dlg},evtHandlerManager_{dlg_},dirty_{false}
,regValues_{wxRegKey::HKCU,"Software" "\\" MYAPPINFO_PUBLISHER "\\" MYAPPINFO_NAME "\\" "OptionDialog"}
{
...
regValues_.AddValueSet
([this](long& val)
{
val=dlg_->Notebook->GetSelection();
}
,[this](const long& val)
{
dlg_->Notebook->SetSelection(val);
}
,TMyRegValues::KeyAndDefault<long>{"Selection",0});
regValues_.GetAll();
}
private:
...
};
...

オプション設定値の保存

TMyOptionDialog.cppに"TMyRegKey.h"と"version_macro.h"をインクルードする。TMyFileOption型をレジストリに保存するためのTMyRegKey::DoSet/DoGet特殊化を先に定義する。TMyOptionDialog::ImplクラスにTMyRegValues型regValues_メンバ変数を追加し、初期化リストで初期化する。コンストラクタ内でAddValueSetでオプション設定値をレジストリ保存対象データに加える。最初のコールで2個(long型、TMyFileOption型)の[File]ページプロパティを一括追加し、2回目のコールで3個(wxColour型、wxColour型、wxFont型)の[Appearance]プロパティを一括追加する。1回のコールで追加する対象データの数やAddValueSetのコール回数に制約は無い。最後にGetAllをコールする。

TMyOptionDialog.cpp

#include "wx_pch.h"
#include "TMyOptionDialog.h"
#include "OptionDialog.h"
#include "TMyRegKey.h"
#include "version_macro.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:
...
TMyRegValues regValues_;
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();
}
...

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

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

UMLクラス図

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

  • レジストリクラス(TMyRegKey)とレジストリ対象値管理クラス(TMyRegValues)を追加する。
  • レジストリ保存対象プロパティを持つクラス(KTxtEditFrame::Impl、TMyOptionDialog::Impl、OptionDialog::Impl)はそれぞれTMyRegValuesをサブオブジェクトに所有する。