870920 Menu

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

JUCE Demo

完整的GUI桌面程序,包括主菜单和快捷键,演示了JUCE类库中几乎所有的可视化控件类和大部分内核类与功能类的一般性用法。该程序涉及到大量的GUI编程中最常见、最重要的解决方案和实用技巧,是学习、使用JUCE类库的最权威教材(尽管不够全面,特别是:音频与MIDI部分的演示严重不足)。

项目结构:
主程序类->主窗口类->基础组件类->各个演示页面类

重要知识点和研读体会(有些知识点的总结参见前面的章节中相关类的介绍与示例):

 创建各个演示页面的函数为全局函数,函数声明可视为集中写在一个头文件中,该头文件被主窗口类的cpp文件所include。这些全局函数的定义分散在各个演示页面类的类实现文件中。

 程序主体类中所声明的主窗口对象是栈对象。

 本项目主窗口类所容纳并显示的基础组件只是一个空组件,该组件中,通过addAndMakeVisible()添加并显示不同的演示页面。每个演示页面由该类中所声明的ScopedPointer<Component>作用域指针所指代,切换所显示的页面时(这些页面均是Component自定义组件),作用域指针直接获得该页面堆对象的赋值。由于作用域指针的特性,指向新对象后,自动销毁之前所指的堆对象,因此,看不到显式的销毁演示页面的语句。

 由于本项目中只有主窗口类使用了命令管理器,因此,命令管理器对象声明为主窗口类的public数据成员(栈对象),而非其他JUCE项目所经常使用的声明为全局指针。

 基础组件同时继承了Component组件类、ApplicationCommandTarget命令目标类和菜单栏模型类(MenubarModel),而非其他JUCE项目的通用做法(主窗口类同时充当命令目标和主菜单)。

 组件类的getTopLevelComponent()可返回包含本组件及本组件所有上层父组件的顶级组件。

 整个程序的所有组件均使用OpenGl的语法:
// 此语句可写在主窗口类或基础组件类的构造函数或渲染切换函数中。
// openGLContext是OpenGlContext类的栈对象
openGLContext.attachTo (*getTopLevelComponent());
// 使用OpenGlContext对象的类的析构函数中:
openGLContext.detach();

 屏蔽警告:可使用预处理指令#pragma后加warning和警告代码屏蔽或启用某个编译警告:
#ifdef _MSC_VER // 仅适用于MSVC编译器
// 代码中有未引用的函数形参时,屏蔽编译器警告
#pragma warning (disable: 4100)
// 基类的构造参数为子类对象,子类初始化基类时实参为this,编译器将给出4355警告
#pragma warning (disable: 4355)
#endif
#pragma是预处理指令,比较复杂。作用是设定编译器的状态或指示编译器完成一些特定的动作。#pragma编译指示是机器或操作系统专有的,且每个编译器均不同。其格式一般为:
#pragma Para // Para为参数

 设置本程序的进程优先级。自定义的DocumentWindow窗口框架类的构造函数中添加:
// 设置本程序的进程优先级
Process::setPriority (Process::HighPriority);

 文本宽度:如果某个字符串使用字体对象font来显示,则文本的宽度为:
const int textWidth = font.getStringWidth (字符串对象);

 适当使用全局函数,全局函数的声明位于头文件,具体定义在相关的源文件中。所谓的相关文件是指全局函数中使用的类、对象和其它函数,要在使用之前可视,这些东西最好和全局函数的定义在一个文件中。Juce Demo中,打开不同的演示界面,就是通过全局函数来实现的。

 多用作用域指针:ScopedPointer<类名> 作用域指针名;

 设置命令快捷键可使用ApplicationCommandManager和ApplicationCommandTarget这两个类共同完成。也可个别设置某些控件的快捷键。

 用断点获悉执行的流程。IDE中,在关键处下断点,而后跟踪执行,可以方便地获悉代码的执行流程。

 全屏显示不同于窗口框架类构造函数中的最大化setFullScreen(true)。使用前提:至少已有一个桌面组件。
// 第二个参数为是否显示菜单和标题栏。但即使为false,主菜单和标题栏依然存在,不知何故?
Desktop::getInstance().setKioskModeComponent (getTopLevelComponent(),
false);
// 退出全屏
Desktop::getInstance().setKioskModeComponent (nullptr);

 计算某个普通数组的元素个数:numElementsInArray(数组名),此函数为JUCE类库全局函数,因此直接调用而不必加作用域前缀。注意,该数组必须是使用C++常规方式声明和创建的,比如:
float bouncingPointX [] = { //… };
// 循环法给数组的所有元素赋值,赋的值是随机产生的
for (i = 0; i < numElementsInArray (bouncingPointX); ++i)
{
bouncingPointX[i] = (float) Random::getSystemRandom().nextInt (200);
bouncingPointY[i] = (float) Random::getSystemRandom().nextInt (200);
bouncingPointDX[i] = (Random::getSystemRandom().nextFloat() – 0.5f) * 6.0f;
bouncingPointDY[i] = (Random::getSystemRandom().nextFloat() – 0.5f) * 6.0f;
}

 文字对齐:Justification::centred(居中)。示例:
// 字型排列类对象glyphs添加要绘制的文本
glyphs.addFittedText (Font (30.0f), L”文本”, -150, -60,
300, 120, Justification::centred, 2, 1.0f);

 设置图像的渲染质量。画刷调用setImageResamplingQuality()方法。示例中根据按钮的开关状态来设置:
g.setImageResamplingQuality (ToggleButton->getToggleState() ?
Graphics::highResamplingQuality :
Graphics::lowResamplingQuality);

 时间差和执行某个过程的平均时间:以下代码可通过计时器的timerCallback()方法重复执行。
// 1970年1月1日至当前的总计时间(毫秒数),赋值给一个double变量
double startTime = Time::getMillisecondCounterHiRes();
// … 执行过程 …
// 再定义一个double变量,保存至当前的总计时间(毫秒数)
double endTime = Time::getMillisecondCounterHiRes();
// 单次执行的耗费时间
double timeTaken = endTime – startTime;
// 本次平均时间 = (时间差值 – 上次平均时间) * 0.1 + 上次平均时间
double averageTime += (timeTaken – averageTime) * 0.1;