为了我自己的喜悦,我试图想出一种更好的方法来使用Windows API菜单。虽然我确实找到this one,并且可能实际上按类别区分菜单项的类型,但我还是会这样:
描述
class MenuItem {...};
class MenuBar { //for the main menu of a window (file, help, etc)
std::list<MenuItem> items; //and whatever else
};
class PopupMenu { //for dropdown (and possibly context) menus
std::list<MenuItem> items; //and whatever else
};
MenuItem
类具有check()
,disable()
,setText()
等功能,以及setType()
等更常见的功能(字符串,位图,分隔符)和setState
(启用,检查,默认)。
在我看来,Menu
类本身会提供appendStringItem()
,appendItem
,insertBitmapItem()
等功能,并提供对项目本身的访问权限。我也在讨论是否有一个Menu
基类,虽然很感兴趣,但这不是问题的主题。
通缉用法
如果这只是C ++,我就不会有问题。但是,我的问题出现在将我的菜单项与Windows使用的菜单项同步,因为我班级中的更改不会自动更改真实的菜单项。为了更改菜单项,必须为其提供菜单,以及该菜单中的位置或项目的ID。这是有道理的,但从使用角度来看,为什么我不能这样做:
MenuBar menuBar; //pretend this is filled
menuBar.itemAt(2).setText("New text");
问题
嗯,问题是我希望这会改变实际菜单上的菜单项,但不会。我需要一些方法来了解拥有该项目的菜单,因为每个项目都有内部存储的ID。
我可以在MenuBar
中的相应插入函数中执行此操作:
bool MenuBar::someInsertionFunction(unsigned index, MenuItem newItem) {
newItem.setOwner(*this); //owner as a raw HMENU type, with *this converting
items.emplace_back(index, newItem); //index checked, of course
}
完成后,我必须检查MenuItem
中的每个setter以确保所有者是有效的,如果是,则使用API函数更新项目。同样,在getter中,我将调用API函数来获取当前状态,但前提是所有者有效。这允许用户创建自己的MenuItem
列表并通过该列表初始化Menu
。此方法还允许用户完全访问以修改内部项列表而不会产生任何后果,因为MenuItem
类可以很好地保护自己。
但这与我发现的一个好概念相反:为什么包含的对象应该知道包含它们的内容?是否存在处理此问题的设计模式,或者我最好打破此问题“规则”是为了能够让菜单项控制自己(以及其他菜单项),而不是由菜单控制?
答案 0 :(得分:1)
我想出了一个我非常喜欢的答案,实际上。它结合了能够让菜单项自行更改,同时仍然保留其他项目的一些保护。
首先,MenuItem
存储一个改变自身的函数:
std::function<BOOL(UINT, LPMENUITEMINFO)> changeRealItem;
此功能基于Windows API SetMenuItemInfo
,但缺少一些参数。那是因为我们要在Menu
类中绑定它:
Menu() {
//one menu item as an example
item.changeRealItem = std::bind(
NonWinapiFunction(SetMenuItemInfo), //explained below
*this, //with *this converting to a HMENU
std::placeholders::_1, //first argument is the ID
FALSE, //previous is an ID, not a position
std::placeholders::_2 //second argument is pointer to info
);
}
现在,在MenuItemClass
中,我基本上可以做到这一点:
MENUITEMINFO info = *this; //passing *this as an HMENU causes address problems
changeRealItem(id(), &info);
作为概念证明,我做了一个MessageBox
示例:
#include <functional>
#include <windows.h>
template<typename Ret, typename... Args>
std::function<Ret(Args...)> NonWinapiFunction(Ret(*WINAPI func)(Args...)) {
return std::function<Ret(Args...)>(func);
}
struct MenuItem {
MenuItem(std::function<int(const char *)> func) : message(func){}
void changeSomething(const char *text) const {
message(text);
}
private:
std::function<int(const char *)> message;
};
struct Menu {
Menu() : item(std::bind(NonWinapiFunction(MessageBox), nullptr, std::placeholders::_1, "Title", MB_OK)){}
MenuItem &getItem() {
return item;
}
private:
MenuItem item;
};
int main() {
Menu menu;
menu.getItem().changeSomething("I can't change other menu items!");
}
最后一位是NonWinapiFunction
。问题是您无法使用std::bind
(WINAPI
)调用约定在函数上调用__stdcall
。为了避免这种情况,使用可变参数模板从函数中提取返回类型和参数类型,并返回具有相同签名的std::function
,但使用正确的调用约定,然后可以将其与{{一起使用1}}。
另一个问题是可以将任意ID传递给函数,并且需要传递winapi结构地址所需的额外行。我相信两者都可以通用的方式解决(只要存在从包装器到包装类型的转换运算符),但我还没有完全理解它。