第十七课¶
分神补习了操作符重载和复制构造函数的内容,我们已经耽搁了学习如何组织 Haiku 图形应用程序的内容。目前我们已经测试过了那些炙手可热的代码,启动任何 Haiku 程序,基于事件的编程,以及消息发送。那么这一节我们将要使用所了解的消息机制为程序创建一个菜单。
使用菜单
本节我们将要学习下面几个类:BMenu,BMenuBar,BMenuItem,以及 BView,但是在学习它们之前,我们需要把一个相对简单的应用组织起来,MenuColors,它显示一个具有彩色框的窗口,并且我们可以从菜单中选取相应的颜色。那么下面就开始我们的项目。
- 在 Paladin 中创建一个新的空项目。您可以将其命名为 MenuColors 或其他自己喜欢的名称。
- 在 Project 菜单下,点击 Add New File。
3. 输入文件名称 App.cpp,选中 “Creat a header and a source file” 选框,然后点击 OK,或者按下回车键。App.cpp 和 App.h 就创建完成了。 依照相同的步骤完成 MainWindow.cpp。 4. 我们的文件都已经设置完备,就让我们敲入代码吧。首先,是用于 App.h 和 App.cpp 的代码。对于这些示例代码,最好将它们一行一行的输入而非复制粘贴进去,这样可以使您对它们更为熟悉。
App.h¶
#ifndef APP_H
#define APP_H
#include <Application.h>
class App : public BApplication
{
public :
App(void);
};
#endif
App.cpp¶
#include "App.h"
#include "MainWindow.h"
App::App(void)
: BApplication("application/x-vnd.test-MenuColors")
{
MainWindow *mainwin = new MainWindow();
mainwin->Show();
}
int
main(void)
{
App *app = new App();
app->Run();
delete app;
return 0;
}
这些文件中的代码都是我们曾经学习过的。需要注意的是,我们并没有在 MainWindow 头文件的定义中添加它的类定义,如果您希望现在就编译项目,这将会产生错误。但别担心 — 我们很快就会让一切好起来,下面是 MainWindow.h 的代码:
### MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <Window.h>
class MainWindow : public BWindow
{
public:
MainWindow(void);
void MessageReceived(BMessage *msg);
};
#endif
这也是我们非常熟悉的内容。所有这些新代码都会出现在 MainWindow.cpp,但是让我们从 MainWindow 的示例代码开始,将其一点一点的添加进去。编写代码最好的方式是一次添加一点,进行编译,然后测试您所写的代码以帮助找到错误。
#include "MainWindow.h"
#include <MenuBar.h>
#include <Menu.h>
#include <MenuItem.h>
#include <View.h>
MainWindow::MainWindow(void)
: BWindow(BRect(100,100,500,400), "MenuColors", B_TITLED_WINDOW,
B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE)
{
}
void
MainWindow::MessageReceived(BMessage *msg)
{
switch (msg->what)
{
default:
{
BWindow::MessageReceived(msg);
break;
}
}
}
现在我们已经完成了所有基本的代码,为了保证没有发生输入错误,需要将其编译。如果出现错误,再次检查上述代码并修改错误。编译顺利完成后,运行程序,如果一切正常,将其关闭,然后开始学习下面的内容
添加视图¶
窗口控件都是一些对象,它们都继承自 BView 类。如果您快速浏览一下 /boot/develop/headers/os/interface/View.h ,您会发现它是一个巨大,复杂并且具有很多方法的类。幸运的是,我们现在还不需要了解很多。下面的 BView 类是我们的色彩框。添加这段代码到 MainWindow 构造函数:
BView *view = new BView(BRect(100,100,300,200), "colorview", B_FOLLOW_ALL,
B_WILL_DRAW);
AddChild(view);
view->SetViewColor(0,0,160);
上面的代码非常简单。第一行创建了一个新的 BView,它的左上角为 (100,100),右下角为 (300,200)。其名称为 “colorview”,并且当窗口缩放时进行缩放。B_WILL_DRAW 标志告诉窗口,它将自行绘制自己。若没有这个标志,这个 BView 将只是一个空的白色框。第二行代码将这个视图附加到窗口。最后一行代码将 BView 的颜色设置为深蓝色。编译您的项目,看看这次的结果如何。
在 Haiku 里添加控件到窗口并不是很特殊。多数情况下有一个构造函数,并且需要相类似的信息和可能还有一个消息,但不会需要更多的东西。为窗口添加其他的控件也是同样的方式。一定要密切注意我们所使用的方法,您将会发现这些类具有很多的相似性。
添加菜单¶
除了弹出菜单,Haiku 中的其他菜单通常都保存于某种类型的菜单容器。Haiku API 提供了两种菜单容器:BMenuField 和 BMenuBar。暂时我们不需要 BMenuField,而将注意力放在 BMenuBar。如下所示,修改构造函数的代码:
// 下面将定义菜单栏的高度。Bounds()返回窗口的尺寸。
// 在这里,矩形边框为 (0,0)~(200,100)。
BRect r(Bounds());
r.bottom = 20;
// 在这里 r 唯一重要的部分是其高度。当我们添加项目到菜单栏时,
// 它将会在我们指定的高度根据窗口的宽度进行扩展。
BMenuBar *menuBar = new BMenuBar(r, "menubar");
AddChild(menuBar);
BView *view = new BView(BRect(100,100,300,200), "colorview", B_FOLLOW_ALL,
B_WILL_DRAW);
AddChild(view);
view->SetViewColor(0,0,160);
既然我们的颜色菜单已经具有了一个容器,那么我们就可以创建菜单了。这需要三个组件:菜单,菜单中条目,以及每个菜单条目的消息标识符。首先,我们来添加消息标识符。将下面的代码添加到文件顶部 #include 语句的后面。
enum
{
M_SET_COLOR_RED = 'sred' ,
M_SET_COLOR_GREEN = 'sgrn' ,
M_SET_COLOR_BLUE = 'sblu' ,
M_SET_COLOR_BLACK = 'sblk' ,
};
这段代码中唯一让人疑惑的部分就是单引号中的值。这只是一些怪诞的程序员的搞怪而已。消息常量必须是 32 位整型。这些字符中每个都会转换为8位数,因此 “sred” 刚好是 32 位整型。确切说来,这些值本身并不重要,只要它们不同即可,但是按照惯例,它们都会设为这种四个字符的常量。
这些常量可以放在类源文件或头文件的顶部。您通常会避免在头文件中添加标识符,这样可以避免在添加标识符后强制重编译其他的文件。这个约定也有个例外,那就是多个类都需要使用的消息则要添加在头文件中。
现在消息标识符已经做了定义,那么我们就开始创建和构制菜单。代码如下:
MainWindow::MainWindow(void)
: BWindow(BRect(100,100,500,400), "MenuColors", B_TITLED_WINDOW,
B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE)
{
BRect r(Bounds(());
r.bottom = 20;
BMenuBar *menuBar = new BMenuBar(r, "menubar");
AddChild(menuBar);
// 下面是用于创建和构制菜单的代码
BMenu *menu = new BMenu("Colors");
menu->AddItem(new BMenuItem("Red", new BMessage(M_SET_COLOR_RED), 'R'));
menu->AddItem(new BMenuItem("Green", new BMessage(M_SET_COLOR_GREEN), 'G'));
menu->AddItem(new BMenuItem("Blue", new BMessage(M_SET_COLOR_BLUE), 'B'));
menu->AddItem(new BMenuItem("Black", new BMessage(M_SET_COLOR_BLACK), 'K'));
// 菜单栏添加菜单和菜单添加条目的方法相同。事实上,
// 菜单栏基本上也是一个菜单,只是它的条目水平排列,
// 而菜单则竖直排列。
menuBar->AddItem(menu);
BView *view = new BView(BRect(100,100,300,200), "colorview", B_FOLLOW_ALL,
B_WILL_DRAW);
AddChild(view);
view->SetViewColor(0,0,160);
}
我们已经几近完成了!如果您尝试运行您的项目,点击菜单,您会发现什么都没发生。每样东西都应该照常运行的呀。在点击菜单条目时,它将会发送一个消息到窗口,例如,点击 “Red” 菜单,它将会发送消息 M_SET_COLOR_RED 到窗口。窗口接受这个消息,但是并没有对其进行处理,因此下面我们所需要做的就是编写代码,当窗口接受到菜单条目的消息时对其进行处理。
void
MainWindow::MessageReceived(BMessage *msg)
{
// FindView()是一个BWindow的方法,其用于通过名称
// 搜索相应的BView,然后返回其地址的指针。
BView *view = FindView("colorview");
switch (msg->what)
{
case M_SET_COLOR_RED:
{
// 当窗口收到该消息时,我们将其背景颜色设置为深红色。
view->SetViewColor(160,0,0);
// 调用Invalidate()强制视图重绘自己。
view->Invalidate();
break;
}
case M_SET_COLOR_GREEN:
{
view->SetViewColor(0,160,0);
view->Invalidate();
break;
}
case M_SET_COLOR_BLUE:
{
view->SetViewColor(0,0,160);
view->Invalidate();
break;
}
case M_SET_COLOR_BLACK:
{
view->SetViewColor(0,0,0);
view->Invalidate();
break;
}
default:
{
// 通常,默认用于创建我们无需关心的
// BWindow版本的方法处理消息。
BWindow::MessageReceived(msg);
break;
}
}
}
我们终于完成了!运行您的项目,然后当您点击菜单条目时我们的视图框将会修改器颜色。我们并没有完全努力去构制一个菜单。
深入理解¶
如果您愿意更加深入的完善代码,下面是一些您可以尝试完成的内容。
- 尝试修改 BView 构造函数中的 B_FOLLOW_ALL 重设尺寸模式为其他标识。将其修改为下面的内容,检查会有何不同:
- B_FOLLOW_LEFT | B_FOLLOW_TOP
- B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP
- B_FOLLOW_RIGHT | B_FOLLOW_TOP
- B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM
- B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM
- B_FOLLOW_RIGHT | B_FOLLOW_TOP_BOTTOM
尝试在菜单中添加更多颜色。
在菜单中添加 Quit 条目,使其向窗口发送 B_QUIT_REQUESTED 消息。
BWindow¶
- BWindow(BRect frame, const char *title, window_type type, uint32 flags, uint32 workspace = B_CURRENT_WORKSPACE) — 创建一个新窗口。然而还有其他的类型,现在需要记住的窗口类型是 B_TITLED_WINDOW 和 B_DOCUMENT_WINDOW。
- void AddChild(BView *child) — 将一个 BView(或BView子类)附属到一个窗口。
- BView *FindView(const char *name) — 返回一个指向名称为 name 的 BView 的指针,若不存在则为 NULL。
- BRect Bounds(void) — 返回窗口的客户区域尺寸,例如窗口矩形框内部的白色区域。
- void Show(void) — 显示窗口。
BView¶
- BView(BRect frame, const char *name, int32 resizeMode, int32 flags) — 创建一个新的视图,查阅 BeBook 中有关 BView 章节中所有可用的自定义尺寸模式。无需担心 flags 参数,目前只需要记住 B_WILL_DRAW 即可。
- void SetViewColor(uint8 red, uint8 green, unit8 blue) — 设置视图的背景颜色。
- void Invalidate(void) — 强制 BView 重绘自己。
- void AddChild(BView *child) — 将一个 BView(或其子类)附属到视图。