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()、 、 などの関数と、 (string、bitmap、separator) および(enabled、checked、defaulted)setText()などのより一般的な関数があります。setType()setState

クラス自体は、私のMenuビジョンではappendStringItem()appendIteminsertBitmapItem()、 などの機能を提供し、アイテム自体へのアクセスを提供します。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チェックして所有者が有効であることを確認し、有効である場合は API 関数を使用してアイテムを更新する必要があります。同様に、ゲッターでは、API 関数を呼び出して現在の状態を取得しますが、所有者が有効な場合に限ります。これにより、ユーザーは独自の のリストを作成し、それを介してMenuItema を初期化できます。このメソッドは、クラスが自身を適切に保護Menuするため、ユーザーが何の影響もなく内部アイテム リストを変更するためのフル アクセスを許可します。MenuItem

しかし、これは私が良いと思っている概念に反しています。この問題に対処するデザインパターンはありますか、それとも、メニュー項目によって制御されるのではなく、それ自体を制御できるようにするために、この「ルール」を破るのが私の最善の策です。メニュー?

4

1 に答える 1

1

実際、私はかなり気に入っている答えを思いつきました。それは、他のアイテムをある程度保護しながら、メニューアイテム自体を変更できることを兼ね備えています。

まず、MenuItem自分自身を変更する関数を格納します。

std::function<BOOL(UINT, LPMENUITEMINFO)> changeRealItem;

この関数は Windows APISetMenuItemInfoに基づいていますが、いくつかのパラメーターが欠落しています。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

もう 1 つの要素は、任意の ID を関数に渡すことができるという事実と、必要な winapi 構造のアドレスを渡すために必要な追加の行です。どちらも一般的な方法で解決できると思いますが (ラッパーからラップされた型への変換演算子が存在する限り)、まだすべてを把握していません。

于 2012-12-03T22:08:40.643 に答える