870920 Menu

JUCE类库附带示例的源码分析·2

Hello World

对话框式GUI程序。运行后,主窗口中有一个Path围成的语泡图形,图形中是一个Label,显示“Hello World”,主界面中有一个【退出】按钮。主窗口带有JUCE风格的标题栏(包含最大化、最小化和关闭按钮)。该程序虽然简短,什么功能也没有,但完整的演示了JUCE GUI程序的整体架构、事件处理和编码流程,由三个核心类和一个启动宏构成:

 继承自Component和Button::Listener的主界面类MainComponent
 继承自DocumentWindow的主窗口类,该类创建并显示主界面类的对象
 继承自JUCEApplication的主程序类,该类持有并在初始化函数中创建主窗口类的对象
 启动宏:START_JUCE_APPLICATION (主程序类)

重要知识点:

 组件(GUI界面)的组合设计模式。该程序的操作主界面为内容组件(MainComponent类),内容组件中组合了标签、按钮和path对象(创添显设删这些控件对象,控件本身也是Component组件。创添显设删中的“设”有两个含义,一是该控件对象调用自身的成员函数,对自身进行必要的设置,二是内容组件在resized()函数中对这些控件进行布局定位等设置);而内容组件又组合到窗口框架组件中(DocumentWindow),由窗口框架创添显设删。

 DocumentWindow窗口框架类添加内容组件与普通的内容组件添加控件(子组件)所用的函数不同。普通内容组件添加并显示子组件的成员函数为addAndMakeVisibel(),该函数是Component类中最重要的成员函数之一。而DocumentWindow添加并显示内容组件的成员函数为setContentOwned()。

 由上可知,采用层层嵌套、每层均为一个Component派生类,而不是所有控件全部集中到一个大的组件中,这是GUI界面设计的核心思路之一。

 主程序类继承自JUCEApplication基类,该类持有DocumentWindow类的堆对象,在initialise()初始化函数中创建之即可。由于DocumentWindow也继承自Component类,所有Component对象必须显式调用可视函数,才能出现在屏幕中,因此,尽管主程序类的初始化函数创建了该对象,但它并不能自动显示出来,解决思路是:在DocumentWindow类的构造函数中调用setVisible (true);使自己可视。

 主程序类的类声明和类定义可集中写在cpp源文件中,该文件即为启动单元。文件的最后一条语句即为启动宏。启动宏封装了各平台下所对应的C++ main()函数或WinMain()函数。

 操作界面中显示文本通过Label对象来完成,可设置字体颜色和大小。该对象也可设置为单击或双击后可编辑。界面中显示文本,还可通过TextEditor对象来完成。

 按钮能够响应鼠标点击事件是Button对象的天然属性,处理按钮点击事件的思路与流程:内容组件需继承自抽象基类Button::Listener,实现其纯虚函数buttonClicked()。在该函数中判断处理。同时,内容组件类的构造函数中,Button对象需绑定Listener捕获器,所绑定的捕获器为内容组件。语句为:
button->addListener (this);

内容组件类的析构函数中,销毁按钮对象之前,先移除它所绑定的捕获器(该语句可选):
button->removeListener (this);

 上述是JUCE类库事件处理的最基本思路与流程,即两个类之间的消息关联通过函数回调与捕获器的思路来实现,而不是直接耦合。A类具有发出消息的能力(天然具有或手工编码实现该功能,该类有一个嵌套的抽象基类,为消息处理类,同时有一个ListenerList数据容器对象,该对象用于存储本类对象所绑定的所有Listener),B类具有处理消息的能力(通过继承消息处理类来实现。消息处理类恰好是消息发出类的嵌套类),B类能够判断处理A类的消息,通过A类对象addListener(B类对象)这一关键语句来实现。

 内容组件中的path对象,通常在内容组件的resized()函数中完成形状的描述,在内容组件的paint()函数中完成绘制。resized()函数中形状描述语句:
// 轨迹对象清除当前已有的描述
path.clear();
// 开始描述,参数为起点x,y坐标,float值
path.startNewSubPath (136.0f, 80.0f);
// 二阶曲线。参数:弧度控制点x,y坐标,结束点x,y坐标
path.quadraticTo (176.0f, 24.0f, 328.0f, 32.0f);
// 画直线,参数为结束点x,y坐标
path.lineTo (184.0f, 216.0f);
// 封闭形状的最先起点和当前终点
path.closeSubPath();

paint()函数中的绘制语句:
// 设置Graphics对象的绘制颜色,该颜色用来填充path轨迹
g.setColour (Colours::white);
// 填充轨迹
g.fillPath (path);
// 设置描边颜色
g.setColour (Colours::grey);
// 轨迹描边,1参为要描边的轨迹对象,2参为边线的宽度
g.strokePath (path, PathStrokeType (5.5f));

 由上可知,组件的背景在其paint()函数中进行绘制或填充,可“画出”文本、线条、形状、轨迹等各种GUI元素。而该组件的子组件(控件)的布局定位,则在resized()函数中完成。当然,如果子组件的位置是定死的,也可在内容组件类的构造函数中设置。

 启动宏START_JUCE_APPLICATION (AppClass)的展开代码如下所示(以Windows平台为例):
static juce::JUCEApplicationBase* juce_CreateApplication()
{
return new AppClass();
}

extern “C”
int WinMain (void*, void*, const char*, int)
{
JUCEApplication::createInstance = &juce_CreateApplication;
return JUCEApplication::main();
}

最后一条语句的main()函数中将调用JUCEApplication::initialise()初始化函数。而后进入消息循环,直到JUCE系统的消息管理器收到了退出循环的消息。这些细节并不需要深究,直接使用即可。