リソースを使用しないメニュー構造の説明

エントリー


もちろん、私たちはそれぞれ、プログラムの実行中にメニューを動的に直接生成する必要がある状況に直面していました。 これは、さまざまな理由で必要になる場合があります。たとえば、リソースのローカライズが複数存在する場合や、メニューオプションの種類が多すぎる場合などです。

通常の方法


このためのWin32 APIは、CreateMenu()、AppendMenu()などの関数を提供します。 そのような説明に表示されるサブメニューを備えた多かれ少なかれ複雑なメニューは、読めないとはいえ、あまり明確ではありません。 ATLでも役に立たない:
 CMenu menu ; menu.CreateMenu() ; menu.AppendMenu(MF_STRING, ECmdOpen, L"") ; menu.AppendMenu(MF_STRING, ECmdClose, L"") ; menu.AppendMenu(MF_SEPARATOR) ; menu.AppendMenu(MF_STRING, ECmdTwist, L"") ; CMenu scratchMenu ; scratchMenu.CreateMenu() ; scratchMenu.AppendMenu(MF_STRING, ECmdScratchHead, L"") ; scratchMenu.AppendMenu(MF_STRING, ECmdScratchNose, L"") ; menu.AppendMenu(MF_POPUP, scratchMenu, L"") ; menu.AppendMenu(MF_STRING, ECmdLose, L"") ; 

多数の繰り返しコードは目を疲れさせ、暗闇に突入します。

新しい方法


簡潔かつ明確に書きたい:
 popup(L"") [command(L"", ECmdOpen)] [command(L"", ECmdClose)] [separator()] [command(L"", ECmdTwist)] [popup(L"") [command(L"", ECmdScratchHead)] [command(L"", ECmdScratchNose)] ] [command(L"", ECmdLose)] 

したがって、私たちに書き込むことで、C ++での演算子のオーバーロードの存在を許可します。 「角括弧」演算子をオーバーロードして、オブジェクトに対して何らかのアクションを実行し、オブジェクト自体へのリンクを返します。
 struct node { node & operator[](...) { // ... return *this ; } } ; 

これにより、オブジェクトに対してこの演算子を必要な回数呼び出すことができます。
 node n ; n[123][456][789] ; 

メニューは、4つのタイプのノードを持つツリーの形式で表すことができます。
 enum node_type { EEmpty, ESeparator, ECommand, EPopup } ; 

EPopupノードは、任意のタイプの任意の数の子を持つことができ、残りのノードは子を持つことができません。
それらが同じツリーにある場合、共通の祖先が不可欠であることは明らかです。 append()メソッドを使用すると、ノードに子を追加できますappend_to()メソッドは、ツリーを走査するときに実際のメニューを作成するために必要です。
 struct node_impl_base ; typedef std::auto_ptr<node_impl_base> node_impl_ptr ; struct node_impl_base { virtual ~node_impl_base() {} virtual node_type type() const = 0 ; virtual void append(node & n) = 0 ; virtual void append_to(HMENU aMenu) const = 0 ; } ; typedef std::auto_ptr<node_impl_base> node_impl_ptr ; 

繰り返さないために、小さな補助構造も役立ちます。
 template <node_type Type> struct node_impl: public node_impl_base { static const node_type KType = Type ; node_type type() const { return KType; } void append(node & n) { _ASSERT(!"not allowed"); } }; 

_ASSERT 、子を持つことができないノードで誤ってappend()メソッドを呼び出さないために必要です。
次に、ノードを実装します。
 struct empty_node: node_impl<EEmpty>, boost::noncopyable { void append_to(HMENU aMenu) const { CMenuHandle(aMenu).AppendMenu(MF_STRING); } }; struct separator_node: node_impl<ESeparator>, boost::noncopyable { void append_to(HMENU aMenu) const { CMenuHandle(aMenu).AppendMenu(MF_SEPARATOR); } }; struct command_node: node_impl<ECommand>, boost::noncopyable { command_node(PCTSTR text, int id): text_(text), id_(id) {} void append_to(HMENU aMenu) const { CMenuHandle(aMenu).AppendMenu(MF_STRING, id_, text_); } private: CString text_ ; int id_ ; } ; struct popup_node: node_impl<EPopup>, boost::noncopyable { popup_node(PCTSTR text): text_(text) {} void append(node & n) { children_.push_back(new node(n)); } void append_to(HMENU aMenu) const { CMenuHandle menu ; menu.CreatePopupMenu() ; BOOST_FOREACH(const node & n, children_) { n.append_to(menu) ; } CMenuHandle(aMenu).AppendMenu(MF_STRING, menu, text_) ; } private: boost::ptr_vector<node> children_ ; CString text_ ; } ; 

ツリー自体は、同じnodeタイプのオブジェクトで構成されnode 。 イディオム「pimpl」が使用されます。つまり、ノードには、ノードの特定の実装へのポインターが含まれます。 コピーと割り当てのセマンティクスに注意してください。
 struct node { friend node empty() ; friend node separator() ; friend node command(PCTSTR text, int id) ; friend node popup(PCTSTR text) ; node(): impl_(new empty_node()) {} node(node & other): impl_(other.impl_) {} // move node & operator=(node & other) { impl_ = other.impl_; return *this; } // move node & operator[](node & n) { impl_->append(n); return *this; } node_type type() const { return impl_->type(); } void append_to(HMENU aMenu) const { impl_->append_to(aMenu); } private: node(node_impl_ptr impl): impl_(impl) {} // take ownership node_impl_ptr impl_ ; } ; 

あるnode別のnode割り当てると、実装が転送されます(いわゆる移動セマンティクス)。 右側のオブジェクトはダミーになります。 コピーコンストラクターも機能します。 (同様に、 std::auto_ptr機能します)。
マルチレベル構造を一時オブジェクトとして記述するためにすべてが考案されたため、ムーブセマンティクスはここでコピー操作の多くを節約します。
ところで、 node_impl_ptrstd::auto_ptrであるため、 node(node & other)およびoperator=(node & other)明示的に定義できなかったため、コンパイラーはそれらを生成します。

ここで、ノードを作成するための関数を定義するだけです。 empty()除き、プライベートコンストラクターを使用するため、 friendとして宣言されます。
 node empty() { return node() ; } node separator() { return node(node_impl_ptr(new separator_node())) ; } node command(PCTSTR text, int id) { return node(node_impl_ptr(new command_node(text, id))) ; } node popup(PCTSTR text) { return node(node_impl_ptr(new popup_node(text))) ; } 

できた! 作業コードでは、これは次のように使用されます。
 struct menubar { menubar(node key1, node key2) ; // ... }; SetMenuBar( menubar( command(L"Ok", IDOK), popup(L"") [command(L"", ECmdOpen)] [command(L"", ECmdClose)] [separator()] [command(L"", ECmdTwist)] [popup(L"") [command(L"", ECmdScratchHead)] [command(L"", ECmdScratchNose)] ] [command(L"", ECmdLose)] ) ) ; 

ノードmove-semanticsのおかげで、ここでは構造全体のコピーは行われず、代わりにSetMenuBar()直接渡されます。
Windows Mobileのアプリケーションには2つのソフトキーがあるため、 menubarは2つのツリーで構成されます。 SetMenuBar()の実装はこの記事の範囲を超えており、すでに多くのテキストがすでに判明しています:)

おわりに


主な目標は、C ++言語の構文のみを使用して、マルチレベルの異種構造を明確に記述する方法を示すことでした。 小さな拡張機能を使用すると、このメソッドは、より豊富なメニュー機能を備えたデスクトップアプリケーションに適用できます。

Source: https://habr.com/ru/post/J88844/


All Articles