Description of the menu structure without the use of resources

    Introduction


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

    The usual way


    The Win32 API for this offers the functions CreateMenu (), AppendMenu () and the like. Slightly complex menu with submenus in this description does not look very clear, if not 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 repeating code tires our eyes and plunges us into gloom.

    New way


    I would like 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)]
    

    Thus, the presence of operator overloading in the C ++ language allows us to write. We overload the "brackets" that he was doing an action with the object and returns a reference to the object itself:
    struct node
    {
    	node & operator[](...)
    	{
    		// ...
    		return *this ;
    	}
    } ;
    

    It will allow us plenty of time to cause the object this statement:
    node n ;
    n[123][456][789] ;
    

    you can submit a menu in a tree that has four types of nodes:
    enum node_type { EEmpty, ESeparator, ECommand, EPopup } ;
    

    the node EPopupthere can be any number of children of any type, the remaining nodes cannot have children.
    It is clear that if they are to be in the same tree, then a common ancestor is indispensable. The method append()will allow you to add children to the node, the method is append_to()needed to create a real menu when traversing our tree.
    struct node_impl_base ;
    typedef std::auto_ptr 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_ptr ;
    

    A small auxiliary structure is also useful, so as not to be repeated: it is necessary in order not to accidentally call a method for a node that cannot have children. Now we implement our nodes: The tree itself will consist of objects of the same 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: When assigning one to another, the implementation is transferred (the so-called move semantics). The object on the right becomes a dummy. The copy constructor also works. (It works similarly ).
    template 
    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"); }
    };
    

    _ASSERTappend()

    struct empty_node: node_impl, boost::noncopyable
    {
    	void append_to(HMENU aMenu) const { CMenuHandle(aMenu).AppendMenu(MF_STRING); }
    };
    struct separator_node: node_impl, boost::noncopyable
    {
    	void append_to(HMENU aMenu) const { CMenuHandle(aMenu).AppendMenu(MF_SEPARATOR); }
    };
    struct command_node: node_impl, 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, 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 children_ ;
    	CString text_ ;
    } ;
    

    node
    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_ ;
    } ;
    

    nodestd::auto_ptr
    Since everything was conceived in order to describe a multilevel structure as a temporary object, move semantics save a lot of copying operations here.
    By the way, since node_impl_ptrit is std::auto_ptr, it was possible not to define it explicitly, node(node & other)and the operator=(node & other)compiler would generate them by itself.

    Now we only need to define the functions for creating nodes. Except 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, this is used something 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 move semantics of the node, the entire structure is not copied here, instead, it is directly passed to SetMenuBar().
    menubarconsists of two trees, because the application for Windows Mobile has two soft keys. ImplementationSetMenuBar() is beyond the scope of this article, and so it has already turned out a lot of text :)

    Conclusion


    The main goal was to show how you can clearly describe multilevel heterogeneous structures using only the C ++ syntax. With small extensions, this method can also be applied to desktop applications with richer menu functionality.

    Also popular now: