-1

在不使用 glade 的情况下,在 C++11/Gtkmm3 中以编程方式创建菜单的最快方法是什么?我正在寻找这样简单的东西:

      Gtk::MenuBar* menubar = ezmenubar.create(
        {"MenuBar1",
        "File.New",
        "File.Open",
        "File.Save",
        "File.Separate1:-",
        "File.Coffee:check!",
        "File.Cream:check!",
        "File.Sugar:check",
        "File.Separate2:-",
        "File.Donuts:check",
        "Edit.nested.item1:radio!",
        "Edit.nested.item2:radio",
        "Edit.nested.item3:radio",
        "Edit.Copy",
        "Edit.Cut",
        "Radio.On:radio!",
        "Radio.Off:radio",
        "Radio.Random:radio",
        "Help.About"}
      );
      add(m_box);
      m_box.pack_start(*menubar, 0, 0);
4

2 回答 2

2

你的问题有点误导。如果您想手动构建它,我想向您推荐 gtkmm 主页上的 app_and_win_menus 示例:https ://git.gnome.org/browse/gtkmm-documentation/tree/examples/book/application/app_and_win_menus 。

请在下面找到有关如何在启动期间构建应用程序菜单的摘录。

void ExampleApplication::on_startup(){
    //Call the base class's implementation:
    Gtk::Application::on_startup();

    auto app_menu = Gio::Menu::create();
    app_menu->append("_Something", "app.something");
    app_menu->append("_Quit", "app.quit");
    set_app_menu(app_menu);

    // [...]
}

完整的示例将为您提供如何使用 Gio::Menu 类函数创建菜单的一个很好的概述。请在https://developer.gnome.org/glibmm/stable/classGio_1_1Menu.html找到有关 Gui::Menu 类参考页面的更多信息

于 2017-01-10T21:05:51.583 回答
1

但是,可能有更好的方法可以在不使用 xml 的情况下执行此操作,因为创建 gtkmm3 菜单的官方方法是根据 gktmm3 手册在源代码中嵌入 xml 字符串。这是一个为您完成杂乱的非人类友好的 xml 工作的类。希望 gnome 中的某个人会明白人们讨厌在他们的 C++ 代码中编写 xml 并切换回类似这样的内容:

#include <gtkmm.h>
#include <string>
#include <iostream>
#include <vector>
#include <map>

using namespace std;

class EzMenuBar
{
public:
    EzMenuBar(Gtk::Window* window) : window{window}
    {}

    void create(initializer_list<const char*> ilist)
    {
        string         menu_name = "noname";
        vector<string> slist;
        menubar = nullptr;
        int i = 0;
        for(string e : ilist)
        {
            if (i==0) menu_name = e;
            else slist.push_back(e);
            i++;
        }
        PrivateGetMenuBar(menu_name, slist);
        return;
    }

    void add_click(const string& name, const sigc::slot<void>& slot)
    {
        Gtk::MenuItem* menuitem;
        builder->get_widget(name, menuitem);
        if (!menuitem)
        {
            throw std::runtime_error{string{"Error: widget does not exist:("} + name + string{")\n"}};
        }
        menuitem->signal_activate().connect(slot);
    }

    bool is_checked(const string& name) {
        Gtk::CheckMenuItem* menuitem;
        builder->get_widget(name, menuitem);
        if (!menuitem)
        {
            throw std::runtime_error{string{"Error: widget does not exist:("} + name + string{")\n"}};
        }
        bool active = menuitem->get_active();
        //delete menuitem; //memory leak? or managed by builder object?
        return active;
    }


    Gtk::MenuBar& operator()()
    {
        return *menubar;
    }


private:
    void PrivateGetMenuBar(string menu_name, vector<string>& ilist)
    {
        string MenuXml = BuildMenuXml(menu_name, ilist);

        try
        {
            builder = Gtk::Builder::create_from_string(MenuXml);
        }
        catch (...)
        {
            throw std::runtime_error{string{"Error: Menu XML Format:("} + menu_name + string{")\n"}};
        }

        builder->get_widget(menu_name, menubar);
        if (!menubar)
        {
            throw std::runtime_error{string{"Error: widget does not exist:("} + menu_name + string{")\n"}};
        }
        return;
    }

    string BuildMenuXml(string menu_name, vector<string>& list)
    {
        tree.clear();

        auto top = pair<string, vector<string>>
        {
            menu_name,
            vector<string>{}
        };

        tree.insert(top);

        for (string x : list)
        {
            string leaf_last {menu_name};
            int    leaf_i = 0;
            vector<string> branchlist = StrSplit('.', x);
            for (string& leaf_this : branchlist)
            {
                if (tree.count(leaf_this) == 0)
                {
                    auto newpair = pair<string, vector<string>>
                    {
                        leaf_this,
                        vector<string>{}
                    };
                    tree.insert(newpair);
                    tree[leaf_last].push_back(leaf_this);
                }
                leaf_last = leaf_this;
                leaf_i++;
            } // foreach leaf of treeachy
        } // foreach menuItem in list

        string xml = BuildIt1(menu_name);
        #if 1
        cout << xml << "\n";
        cout << "NOTES: to speed up, remove debug printing near:\n ";
        cout << "     " << __FILE__ << ":" << __LINE__ << "\n";
        cout << "\n";
        #endif
        return xml;
    }

    string BuildIt1(string menu_name)
    {
        string xml;
        if (!tree.count(menu_name))
            return string{""};
        xml += "<interface>\n";
        xml += "    <!-- MENU BAR: " + menu_name + " -->\n";
        xml += "    <object class=\"GtkMenuBar\" id=\"" + menu_name + "\">\n";
        xml += "    <property name=\"visible\">True</property>\n";
        xml += "    <property name=\"can_focus\">False</property>\n";
        for (string leaf : tree[menu_name])
        {
            xml += BuildIt2(string{menu_name + "." + leaf}, leaf, 1);
        }
        xml += "\n";
        xml += "    </object>\n";
        xml += "</interface>\n";
        return xml;
    }

    string BuildIt2(string fullpath, string leaf, int level)
    {

        string xml;
        if (!tree.count(leaf))
        {
            return string{""};
        }

        int count    = tree[leaf].size();
        vector<string> tmp = StrSplit(':', fullpath);
        string fullpath_only = tmp[0];
        string label = StrSplitLast('.', fullpath_only);
        string attrib;
        string action_id = fullpath_only;
        size_t idpos = action_id.find_first_of('.');
        for(int i=idpos+1; i < (int)action_id.size(); i++) {
            if (action_id[i]=='.') action_id[i]='_';
        }

        if (tmp.size() == 2)
        {
            //cout << "TMP:(" << tmp[1] << ")\n";
            attrib = tmp[1];
        }

        // GtkMenuItem
        if (count == 0)
        {

            xml += indent(level) + "\n";
            xml += indent(level) + "<!-- MENU ITEM: " + fullpath + " -->\n";
            if (attrib == "-")
            {
                radio_group = string{};
                xml += indent(level) + "<child><object class=\"GtkSeparatorMenuItem\" id=\"" + action_id + "\">\n";
                xml += indent(level) + "<property name=\"visible\">True</property>\n";
                xml += indent(level) + "<property name=\"can_focus\">False</property>\n";
            }
            else if (attrib == "check" || attrib == "check!")
            {
                radio_group = string{};
                xml += indent(level) + "<child><object class=\"GtkCheckMenuItem\" id=\"" + action_id + "\">\n";
                xml += indent(level) + "<property name=\"visible\">True</property>\n";
                xml += indent(level) + "<property name=\"can_focus\">False</property>\n";
                xml += indent(level) + "<property name=\"label\" translatable=\"yes\">" + label + "</property>\n";
                xml += indent(level) + "<property name=\"use_underline\">True</property>\n";
                if (attrib == "check!")
                {
                    xml += indent(level) + "<property name=\"active\">True</property>\n";
                    // Not using xml signals. How to connect glade signals from c++??
                    xml += indent(level) + "<signal name=\"toggled\" handler=\"" + action_id + "\" swapped=\"no\"/>\n";
                }
            }
            else if (attrib == "radio" || attrib == "radio!")
            {
                bool group_start = false;
                if (radio_group.empty())
                {
                    group_start = true;
                    radio_group = action_id; //fullpath_only;
                }
                xml += indent(level) + "<child><object class=\"GtkRadioMenuItem\" id=\"" + action_id + "\">\n";
                xml += indent(level) + "<property name=\"visible\">True</property>\n";
                xml += indent(level) + "<property name=\"can_focus\">False</property>\n";
                xml += indent(level) + "<property name=\"label\" translatable=\"yes\">" + label + "</property>\n";
                xml += indent(level) + "<property name=\"use_underline\">True</property>\n";
                if (attrib == "radio!")
                {
                    xml += indent(level) + "<property name=\"active\">True</property>\n";
                }
                xml += indent(level) + "<property name=\"draw_as_radio\">True</property>\n";
                xml += indent(level) + "<property name=\"group\">" + radio_group + "</property>\n";
                if (group_start) {
                xml += indent(level) + "<signal name=\"group-changed\" handler=\"" + action_id + "\" swapped=\"no\"/>\n";
                }
            }
            else
            {
                radio_group = string{};
                xml += indent(level) + "<child><object class=\"GtkMenuItem\" id=\"" + action_id + "\">\n";
                xml += indent(level) + "<property name=\"visible\">True</property>\n";
                xml += indent(level) + "<property name=\"can_focus\">False</property>\n";
                xml += indent(level) + "<property name=\"label\" translatable=\"yes\">" + label + "</property>\n";
                xml += indent(level) + "<property name=\"use_underline\">True</property>\n";
                xml += indent(level) + "<signal name=\"activate\" handler=\"" + action_id + "\" swapped=\"no\"/>\n";
            }
        }
        // GtkMenu
        else
        {
            xml += indent(level) + "\n";
            xml += indent(level) + "<!-- SUB-MENU: " + fullpath + " -->\n";
            xml += indent(level) + "<child><object class=\"GtkMenuItem\" id=\"" + action_id + "\">\n";
            xml += indent(level) + "<property name=\"visible\">True</property>\n";
            xml += indent(level) + "<property name=\"can_focus\">False</property>\n";
            xml += indent(level) + "<property name=\"label\" translatable=\"yes\">" + label + "</property>\n";
            xml += indent(level) + "<child type=\"submenu\"><object class=\"GtkMenu\" id=\"" + fullpath_only + ".submenu" + "\">\n";
            xml += indent(level) + "<property name=\"visible\">True</property>\n";
            xml += indent(level) + "<property name=\"can_focus\">False</property>\n";
        }

        level++;
        for (string child : tree[leaf])
        {
            xml += BuildIt2(string{fullpath + string{"."} + child}, child, level);
        }
        level--;
        if (count == 0)
        {
            xml += indent(level) + "</object></child>\n";
        }
        else
        {
            xml += indent(level) + "</object></child>\n";
            xml += indent(level) + "</object></child>\n";
        }
        return xml;
    }


    string indent(int level)
    {
        string INDENT;
        for(int i=0; i < level; i++) INDENT += "    ";
        return INDENT;
        //return string{level, ' '};
    }

    static vector<string> StrSplit(char delimit, string& line)
    {
        vector<string> split;
        size_t pos_last = -1;
        while(1)
        {
            size_t pos_this = line.find_first_of(delimit, pos_last+1);
            if (pos_this == string::npos)
            {
                split.push_back(line.substr(pos_last+1));
                break;
            }
            split.push_back(line.substr(pos_last+1, pos_this-pos_last-1));
            pos_last = pos_this;
        }
        return split;
    }

    static string StrSplitLast(char delimit, string& line)
    {
        size_t pos = line.find_last_of(delimit);
        if (pos == string::npos)
        {
            return line;
        }
        return line.substr(pos+1);
    }

private:
    Gtk::MenuBar*                  menubar;
    Gtk::Window*                   window;
    string                         radio_group;
    Glib::RefPtr<Gtk::Builder>     builder;
    map<string, vector<string>>    tree;
    string                         MenuXml;
};


class WnMain : public Gtk::Window
{
public:
    void callback() {
      cout << "HELLO\n";
    }

    WnMain()
    {
        ezmenubar.create({
            "MenuBar1",
            "File.New",
            "File.Open",
            "File.Save",
            "File.Separate1:-",
            "File.Coffee:check!",
            "File.Cream:check!",
            "File.Sugar:check",
            "File.Separate2:-",
            "File.Donuts:check",
            "Edit.nested.item1:radio!",
            "Edit.nested.item2:radio",
            "Edit.nested.item3:radio",
            "Edit.Copy",
            "Edit.Cut",
            "Radio.On:radio!",
            "Radio.Off:radio",
            "Radio.Random:radio",
            "Help.About"
        });

        ezmenubar.add_click("MenuBar1.File_New", sigc::mem_fun(*this, &WnMain::callback));

        ezmenubar.add_click("MenuBar1.File_Open",
          [&]() {cout << "FILE.Open\n";}
        );

        ezmenubar.add_click("MenuBar1.Radio_On",
          [&]() {
             // NOTE: signal_active is buggy for RadioMenuItem under windows,
             //   ie. not always triggering...70% of time? i'll leave that one to gnome.org
             //   to cleanup...(1/9/2017)
             if (ezmenubar.is_checked("MenuBar1.Radio_On")) {
                cout << "MenuBar1.Radio_On: checked\n";
             }
             else if (ezmenubar.is_checked("MenuBar1.Radio_Off")) {
                cout << "MenuBar1.Radio_Off: checked\n";
             }
             else if (ezmenubar.is_checked("MenuBar1.Radio_Random")) {
                cout << "MenuBar1.Radio_Random: checked\n";
             }
             else {
                cout << "MenuBar1.Radio: nothing selected\n";
             }
        });

        ezmenubar.add_click("MenuBar1.File_Coffee",
          [&]() {
             if (ezmenubar.is_checked("MenuBar1.File_Coffee")) {
                cout << "File.Coffee: checked\n";
             }
             else {
                cout << "File.Coffee: not-checked\n";
             }
        });

        add(m_box);
        m_box.pack_start(ezmenubar());
        show_all_children();
    }

private:
    Gtk::Box                    m_box {Gtk::ORIENTATION_VERTICAL};
    EzMenuBar                   ezmenubar {this};
    string                      radio_group;
};

int main(int argc, char** argv)
{
    try
    {
        auto app = Gtk::Application::create(argc, argv, "wmoore");
        WnMain wnmain;
        return app->run(wnmain);
    }
    catch (std::runtime_error e)
    {
        cout << "EXCEPTION:" << e.what() << "\n";
    }
}
于 2017-01-09T18:47:15.570 回答