1

为了我自己的喜悦,我正在尝试想出一种更好的方法来简化使用 Windows API 菜单的方法。虽然我确实找到了这个,并且实际上可能会按类别区分菜单项的类型,但我有点像这样:


描述

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 函数来获取当前状态,但前提是所有者有效。这允许用户创建自己的MenuItems 列表并通过它初始化 a Menu。此方法还允许用户完全访问以修改内部项目列表而不会产生任何后果,因为MenuItem该类在保护自己方面做得很好。

但这与我发现的一个好概念背道而驰:为什么包含的对象应该知道包含它们的内容?是否有处理这个问题的设计模式,或者我最好的选择是打破这个“规则”,以便能够让菜单项控制自己(以及其他菜单项),而不是由菜单?

4

1 回答 1

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函数。为了避免这种情况,我们制作了一个可变参数模板来从函数中提取返回类型和参数类型,并返回具有相同签名但具有正确调用约定的 a,然后可以与.WINAPI__stdcallstd::functionstd::bind

另一部分是可以将任意 ID 传递到函数中,并且需要额外的行来传递所需的 winapi 结构的地址。我相信两者都可以以通用方式解决(只要存在从包装器到包装类型的转换运算符),但我还没有完全弄清楚。

于 2012-12-03T22:08:40.643 回答