📜 ⬆️ ⬇️

Description of the menu structure without using resources

Introduction


Each of us, of course, faced with a situation where the menu needs to be generated dynamically, directly during the execution of the program. This may be required for various reasons, for example, due to the presence of several resource localizations or due to a too wide variety of menu options.

Normal way


The Win32 API for this offers the functions CreateMenu (), AppendMenu () and the like. A little bit complicated menu with submenus nested in this description is not very clear, if not to say unreadable. Even ATL does not help:
 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"") ; 

A large number of repetitive code tires the gaze and plunges into depression.

New way


I want to write briefly and clearly:
 popup(L"") [command(L"", ECmdOpen)] [command(L"", ECmdClose)] [separator()] [command(L"", ECmdTwist)] [popup(L"") [command(L"", ECmdScratchHead)] [command(L"", ECmdScratchNose)] ] [command(L"", ECmdLose)] 

So write to us will allow the presence of operator overloading in the C ++ language. We will overload the "square brackets" operator to do something with the object and return a reference to the object itself:
 struct node { node & operator[](...) { // ... return *this ; } } ; 

This will allow us to call this operator to the object as many times as necessary:
 node n ; n[123][456][789] ; 

The menu can be represented in the form of a tree, which has four types of nodes:
 enum node_type { EEmpty, ESeparator, ECommand, EPopup } ; 

An EPopup node can have as many children of any type as possible; the other nodes cannot have children.
It is clear that if they are to be in the same tree, then they cannot do without a common ancestor. The append() method allows you to add children to the node, the append_to() method is needed to create a real menu when traversing our tree.
 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 ; 

Also useful is a small auxiliary structure, so as not to repeat:
 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 needed in order not to accidentally call the append() method for a node that cannot have children.
Now we implement our nodes:
 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_ ; } ; 

The tree itself will consist of objects of the same node type. The idiom "pimpl" is used, that is, node contains a pointer to a specific implementation of the node. Pay attention to the semantics of copying and assignment:
 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_ ; } ; 

When assigning one node another, the implementation is transferred (the so-called move semantics). An object from the right side becomes a dummy. The copy constructor also works. ( std::auto_ptr works in the same way).
Since everything was started in order to describe a multi-level structure as a temporary object, the move-semantics saves a lot of copy operations here.
By the way, since node_impl_ptr is std::auto_ptr , you could not explicitly define node(node & other) and operator=(node & other) , the compiler would generate them themselves.

Now we just have to define the functions for creating nodes. With the exception of empty() , they use a private constructor and are therefore declared as 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))) ; } 

Done! In working code, it is used like this:
 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)] ) ) ; 

Thanks to the node's move-semantics, the entire structure is not copied here; instead, it is directly passed to SetMenuBar() .
menubar consists of two trees, because the Windows Mobile application has two soft keys. The implementation of SetMenuBar() is beyond the scope of this article, and so a lot of text has already happened :)
')

Conclusion


The main goal was to show how it is possible to visually describe multi-level heterogeneous structures using only the syntax of the C ++ language. With small extensions, this method can be applied to desktop applications with richer menu functionality.

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


All Articles