GUI05-老板,给我菜单!

  • 学习wxMenuBar、wxMenu、wxMenuItem及彼此关系;
  • 学习如何创建菜单、普通菜单项、可选菜单项、单选菜单项、菜单项间的视觉分隔及逻辑分组;
  • 学习如何为菜单项挂接事件响应;
  • 学习两种常用的获取及使用菜单“选中”状态的方法。

 0. 听课,听课

看南老板是怎么搞出一份菜单,可以打勾,可以选择……最主要的是,学习单选和多选菜单获取菜单状态的惯用法:主动获取和被动(事件触发)式获取。

GUI05-玩转wxWidgets主菜单

1. 相关class

一个主菜单需要有一个菜单栏(wxMenuBar),一个菜单栏通常包含多个菜单(wxMenu),每个菜单之下,又可以包含多个菜单项(wxMenuItem)或子菜单(同样是wxMenu,本课不演示)。

三者关系示意如下:

菜单栏、菜单(包括子菜单)、菜单项除了视觉上的包含关系以外,同时也存在包含者是被包含者的 “Owner / 拥有” 关系,即:包含者被释放(delete)者时,将负责释放被包含的组件。也就是说,当 wxMenuBar 要被释放(delete)时,它会负责先释放其下的菜单,而后者又会负责释放其下的子菜单或菜单项。

那,菜单栏由谁负责呢?由它所在的窗口负责——这种父组件“拥有并负责释放”子组件的设计,不仅是wxWidgets 库的基础设计,也是其它多数GUI库的基础设计。

3. 创建新菜单

创建新菜单的常用方法是:通过 new wxMenu 得到一个菜单对象,然后通过 wxMenuBar 的 Append( … ) 方法加入:

wxMenu* optionsMenu = new wxMenu(_T("")); 
mbar->Append(optionsMenu, _("&Options"));

其中 mbar 是向导生成的框架构造函数中,wxMenuBar 的对象。

尽管 wxMenu 构造函数提供一个字符串入参,用以设置该菜单的标题,但通常此时仅设置为空,直到第二步,即调用 wxMenuBar 的 Append() 方法,才通过第二个入参真实设置所添加的菜单的标题。

4. 添加新菜单项

4.1 添加普通菜单项

无需显式 new wxMenuItem,而是通过菜单的 Append 方法添加,该方法三参数版各参数含义如下:

  • 入参1:待添加菜单的唯一ID。(指:在所属窗口类的范围唯一),比如本例中的 idMenuAboutAuthor
  • 入参2:菜单项标题。如果需使用包含汉字的标题,需配合使用 wxT("") 宏或 _T(""),并确保当前源文件使用 utf-8 编码;如考虑支持多国语言,则标题等默认内容应使用英文,并配合 _("")宏;另,标题中前面加 & 前缀的英文字母,将成为热键(通常在用户按下 Alt 时显示下划线)
  • 入参3:菜单项的功能提示。通常显示在状态栏。提示内容如涉及国际化,参看入参2说明。

示例:在 Help 菜单下添加 “About author” 菜单项,并设置 o 字母为快捷键。

 helpMenu->Append(idMenuAboutAuthor, _("About auth&or"), 
                 _("Show info about this application's author"));

4.2 添加 Check 菜单项

添加方法改用 wxMenu 的 AppendCheckItem( … ),三个入参同 Append ( … ) 版本。

示例:在 Options 菜单下添加 “Show &motion info” 可选菜单项:

optionsMenu->AppendCheckItem(idMenuShowMotionInfo, 
           _("Show &motion info"), _T("Show motion info or no"));

4.3 添加 Radio 菜单项

添加方法改用 wxMenu 的 AppendRadioItem( … ),三个入参同 Append ( … ) 版本。

RadioItem,即单选菜单项,通常成组出现,除非中间存在分割线(Seperator)或其它非单选菜单项,否则连续排列的所有单选项,将划归一组,用户每次仅能在一组当中选中一项。

示例:在 Options 菜单下,添加一组颜色相关的 单选菜单项:

optionsMenu->AppendRadioItem(idMenuBlueColor, 
        _("&Blue text"), _("Set text blue"));

    optionsMenu->AppendRadioItem(idMenuRedColor, 
	_("&Red text"), _("Set text red"));

    optionsMenu->AppendRadioItem(idMenuGreenColor, 
	_("&Green text"), _("Set text green"));

4.4 添加菜单项分隔线 (Seperator)

在需要分隔的位置,调用菜单(wxMenu)对象的 AppendSeparator() 方法即可。

示例:

 wxMenu* optionsMenu = new wxMenu(_T(""));
    mbar->Append(optionsMenu, _("&Options"));
    optionsMenu->AppendCheckItem(idMenuShowMotionInfo,
	 _("Show &motion info"), _("Show motion info or no"));

    optionsMenu->AppendSeparator(); // 分隔线1,纯视觉分隔效果

    optionsMenu->AppendRadioItem(idMenuBlueColor, 
	_("&Blue text"), _("Set text blue"));
    optionsMenu->AppendRadioItem(idMenuRedColor, 
	_("&Red text"), _("Set text red"));
    optionsMenu->AppendRadioItem(idMenuGreenColor, 
	_("&Green text"), _("Set text green"));

    optionsMenu->AppendSeparator(); // 分隔线2,同时逻辑分组上下两组单选菜单项

    optionsMenu->AppendRadioItem(idMenuGreenColor + 100, 
	_("Big text"), _("Set big text"));
    optionsMenu->AppendRadioItem(idMenuGreenColor + 101, 
	_("Small text"), _("Set small text"));

5. 挂接菜单项点击事件

通过绑定一个方法(成员函数),以实现菜单项被点击(或热键等其它方式激活)后,调用该方法。此类方法原型为:

void OnMenuItem (wxCommandEvent& event); 

重点在于事件类型: wxCommandEvent。该类提供 GetId() 方法以获得当前被触发(比如点击)的菜单项的唯一ID。

挂接(或称绑定)菜单项的点击事件及对应的响应函数,可在窗口的事件表中,通过 EVT_MENU 宏 实现,如:

BEGIN_EVENT_TABLE(Ls11Frame, wxFrame)
    ...

    EVT_MENU(idMenuQuit, Ls11Frame::OnQuit)
    EVT_MENU(idMenuAbout, Ls11Frame::OnAbout)
    EVT_MENU(idMenuAboutAuthor, Ls11Frame::OnAboutAuthor)

    EVT_MENU(idMenuBlueColor, Ls11Frame::OnTextColorSelected)
    EVT_MENU(idMenuRedColor, Ls11Frame::OnTextColorSelected)
    EVT_MENU(idMenuGreenColor, Ls11Frame::OnTextColorSelected)

    ...
END_EVENT_TABLE()

6. 获取选中状态

普通菜单项(wxMenuItem)不存在状态,可选(Check)和单选(Radio)菜单项拥有“是否选中”的状态。

通常,在事件响应函数中,我们并不通过菜单项本身来获得这一状态(感觉有那么一点点不“面向对象:),而是借助菜单项所在的菜单栏,来获取(检索)这一状态。不过,在事件响应函数中,通常我们也没有“菜单栏”对象的变量,怎么办?

方法是:首先,通过 this->GetMenuBar() 先获得当前框架式窗口(即 this 代表的对象)的菜单栏。

注意,仅框架式窗口,即 wxFrame 及其派生类拥有 GetMenuBar() 方法,普通窗口类并不拥有。

然后,通过菜单栏的 IsChecked(菜单项ID) 以查询指定的菜单项的选中状态。

如果对一个普通的菜单项(非Check、非Radio)执行此操作,将永远得到 false。

6.1 可选菜单项状态获取惯用法

以下是在 OnPaint() 函数中主动获取特定 Check 菜单项是否选中,从而决定是否在窗口上显示当前鼠标位置的示例应用:

void Ls11Frame::OnPaint(wxPaintEvent& event)
{
    // 示例:获取 CheckItem 菜单项的选中状态
    bool showInfo = this->GetMenuBar()->IsChecked(idMenuShowMotionInfo);

    wxString txt;

    if (showInfo)
    {
        txt << wxT("鼠标位置:")  << xPos << wxT(" - ") << yPos;
    }
    else
    {
        txt = wxT("你可以选中\"Show motion info\"来显示鼠标位置");
    }

    ...
}

 6.2 单选菜单项状态获取惯用法

作为对比,Radio 菜单项成组出现,因此,如果仍然使用 wxMenuBar 的 IsChecked() 来检索,N 个选项的菜单项,就至少需要写 N - 1 个判断 (最后一个直接使用 else ),比如:

/* 示例,相对“麻烦”的写法 */

   bool isBlue = false, isRed = false, isGreen = false;

   // 是否蓝色?
   isBlue = this->GetMenuBar()->IsChecked(idMenuBlueColor); 

   if(this->GetMenuBar()->IsChecked(idMenuBlueColor))
   {
      isBlue = true;
   }
   else if (this->GetMenuBar()->IsChecked(idMenuRedColor))
   {
      isRed = true;
   }
   else // 都不是,那就是绿色的
   { 
       isGreen = true;
   }

   ...

此时,为每个单选项挂接一个事件响应函数,从而在不同的函数内做出不的处理,是最简单也最常见的方法,不过,有些时候,我们还可以使用另一种惯用法:为同一组的所有单选菜单项,创建并挂接同一个事件响应函数,并于该函数中记录当前选中的是哪一项。

比如:

// 三个颜色菜单项点击时,均调用以下函数:
void Ls11Frame::OnTextColorSelected(wxCommandEvent& event)
{
    this->selectedColorMenuItemId = event.GetId(); // 记录选中的菜单ID
}

有了状态数据之后,可通过 switch / case 等 流程结构 ,做出相应处理;或者,如果需求是通过 “点击不同菜单项”,从而得到不同的数据——比如本例中的不同颜色——则可以借助 数据结构 以得到更优雅的实现。本例中,我们使用的数据结构是字典(也称映射),具体类型是 C++ 标准库的 std::map 。

    // 一个静态数组,以避免每次调用当前函数都需要重新生成
    static std::map<int, wxColor const*> colors =
    {
        { idMenuBlueColor,  wxBLUE },
        { idMenuRedColor, wxRED },
        { idMenuGreenColor, wxGREEN }
    };

    wxPaintDC dc(this); 

    // 菜单ID -> 颜色
    if (auto c = colors[this->selectedColorMenuItemId]; c)
    {
        dc.SetTextForeground(*c);
    }

  • 27
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南郁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值