- android - 多次调用 OnPrimaryClipChangedListener
- android - 无法更新 RecyclerView 中的 TextView 字段
- android.database.CursorIndexOutOfBoundsException : Index 0 requested, 光标大小为 0
- android - 使用 AppCompat 时,我们是否需要明确指定其 UI 组件(Spinner、EditText)颜色
为了让我自己高兴,我正在尝试想出一个好方法来更轻松地使用 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
类在保护自身方面做得很好。
但这违背了我发现的一个好概念:为什么包含的对象应该知道包含它们的内容?是否有处理这个问题的设计模式,或者我最好的选择是打破这个“规则”是为了能够让菜单项控制自己(以及其他菜单项),而不是被菜单控制?
最佳答案
实际上,我想出了一个我非常喜欢的答案。它结合了让菜单项自行更改的能力,同时仍对其他项目保持一定的保护。
首先,MenuItem
存储了一个改变自身的函数:
std::function<BOOL(UINT, LPMENUITEMINFO)> changeRealItem;
此函数基于 Windows API SetMenuItemInfo
,但缺少几个参数。那是因为我们要在 Menu
类中绑定(bind)它:
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
。问题是您不能在使用 WINAPI
(__stdcall
) 调用约定的函数上调用 std::bind
。为了规避这一点,制作了一个可变参数模板来从函数中提取返回类型和参数类型,并返回一个具有相同签名但具有正确调用约定的 std::function
,这可以然后与 std::bind
一起使用。
另一点是可以将任意 ID 传递到函数中,并且需要额外的行来传递所需的 winapi 结构的地址。我相信两者都可以通过通用方式解决(只要存在从包装器到包装类型的转换运算符),但我还没有完全弄明白。
关于c++ - 如何安全地将包含的对象(菜单项)与需要容器(菜单)的 API 同步?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13666467/
我是一名优秀的程序员,十分优秀!