870920 Menu

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

The Jucer

JUCE类库GUI编程组件设计器,一款真正意义上的实用软件,而且是多文档程序。重要知识点:

 建立一个项目总头文件,该头文件的主要内容类似于:
// 头文件卫士
#include “../JuceLibraryCode/JuceHeader.h” // 库头
#include “utility/StoredSettings.h” // 存储设置
#include “utility/UtilityFunctions.h” // 全局函数头文件
#include “ui/CommandIDs.h” // 本程序所有命令ID和命令类别
// 其他全局性声明和定义…
 使用SelectedItemSet<T>类模板管理和操作可选择项目,比如可选择的组件。该类本身就是一个数据容器,类似于Array和List,同时还继承自ChangeBroadcaster,可异步发送可变消息,对应的成员函数为:changed()。如需捕获并处理该类所发送的可变消息,则使用该类的内容组件需继承自ChangeListener类,SelectedItemSet对象addListener()添加捕获器,而后在内容组件的changeListenerCallback()中处理所捕获到的可变消息。
 鼠标拖拽,实时移动组件时,如果要实现可撤销,可采用这种思路:拖拽的同时,组件实时改变其位置,拖拽完成后,该组件先重新移动到原始位置(这一步不启用undo),而后开启撤销序列,最后再移动到目标位置。
 Component组件对象自身的属性值,比如当前原点x、y坐标等,可保存在每个Component对象均拥有的NamedValueSet对象中。组件类有一个成员函数getProperties()可获取本组件的NamedValueSet对象。示例:
// 设置属性值
component->getProperties().set (“属性名称”, var类型的属性值);
// 下标法获取属性值
const int x = component->getProperties()[“属性名称”];
 组件类paint()函数中,Graphics对象g设置填充或绘制的颜色时,尽量不要使用硬编码(这一点并不绝对,根据实际情况决定是否采用硬编码),而是使用Component类的findColour()函数返回系统或LookAndFeel类中已经预定义的颜色。利用这一点,可实现切换LookAndFeel后,实时更新界面颜色。示例见下一条。
 可使用下标法获取StringArray字符串数组中的某个元素(字符串)。内容组件使用ListBox时,绘制其每一行项目,可利用这一点与paintListBoxItenm()的1参相对应(此函数为继承并实现的基类ListBoxModel类的纯虚函数):
int getNumRows() // 设置ListBox的行数
{
return stringArray.size();
}
// 绘制ListBox的每一行内容
void paintListBoxItem (int row, Graphics& g, int w, int h,
bool rowIsSelected)
{
if (row < 0 || row >= getNumRows())
return;

if (rowIsSelected) // 设置填充颜色时使用findColour()函数,而不是硬编码
g.fillAll (findColour (TextEditor::highlightColourId));
g.setColour (Colours::black);
g.setFont (height * 0.6f);
g.drawText (stringArray[row], 30, 0, w – 32, h,
Justification::centredLeft, true);
// 调用LookAndFeel类的drawTickBox()函数绘制“是否已选”的标记框
getLookAndFeel().drawTickBox (g, *this, 6, 2, 20, 20,
isSelect(stringArray[row]), true, false, false);
}
 由上例可知,Component类可调用getLookAndFeel()函数,获取系统当前的LookAndFeel对象,而后该对象调用其成员函数完成某些界面或控件的绘制。也就是说,LookAndFeel类中的虚函数并不仅仅用来供子类重写或系统内部调用,也可显式调用。
 复杂组件务必要采用“组件套组件”的解决思路,而不要采取一个大组件包含多个小组件的思路。同一层中的组件不超过3个为宜。
 关于开启新的undo序列的用法,Jucer中的做法是:undoManager对象两次调用beginNewTransaction()函数,第一次调用时给出撤销描述,第二次则无参调用,两次调用之间是需要撤销的操作。但是,在个人编程实践中,都是每次操作之前调用一次即可(有参调用)。此问题有待研究。Jucer中的做法示例:
undoManager->beginNewTransaction(“本次可撤销操作的文本描述”);
// do some undoable perform…
undoManager->beginNewTransaction(); // 再次调用beginNewTransaction()
 ApplicationCommandTarget类添加命令ID时,可使用JUCE类库预定义的一些通用命令ID:
void MyClass::getAllCommands (Array <CommandID>& commands)
{
const CommandID ids[] = {
CommandIDs::close, // 自定义的命令ID
CommandIDs::save,
StandardApplicationCommandIDs::cut, // JUCE类库预定义的命令ID
StandardApplicationCommandIDs::copy,
StandardApplicationCommandIDs::paste,
StandardApplicationCommandIDs::del,
StandardApplicationCommandIDs::selectAll,
StandardApplicationCommandIDs::deselectAll };
commands.addArray (ids, numElementsInArray (ids));
}
 苹果机键盘的Command键相当于Windows键盘的Ctrl键,而苹果机键盘上的Ctrl键则没有对应的Windows键。因此,为了使所编写的代码跨平台运行后不导致用户困惑和保证快捷键统一,需要Ctrl键盘的语句代码一律使用ModifierKeys::commandModifier,而不要使用ModifierKeys::ctrlModifier(该键虽然在Windows下代表Ctrl键,但在苹果机下却对应的是Ctrl键,而非Command键)。示例:
// 全局性的重做,注意,该命令可绑定两组快捷键,分别是Ctrl + Y和Ctrl + Shift + Z
case AppCommandIDs::redoAction:
result.setInfo(TRANS(“Redo “), TRANS(“Redo”), editCategory, 0);
result.addDefaultKeypress(‘y’, ModifierKeys::commandModifier);
result.addDefaultKeypress(‘z’,
ModifierKeys::commandModifier | ModifierKeys::shiftModifier);
result.setActive(undoManager->canRedo());
break;
 undo和redo后,如果改变涉及ChangeBroadcaster对象(比如该对象在此期间调用了changed()函数),则命令管理器对象调用undo()或redo()后,还需调用dispatchPendingMessages():
undoManager().undo();
// 使系统立即调度document对象所发出的可变消息
document->dispatchPendingMessages();
 剪切操作的实现思路:先将所选内容基于XML技术转换为文本,文本复制到系统剪贴板中,而后删除所选内容。如果不删除所选内容,则为复制操作。
 某些情况下,当前GUI界面半透明,原有的元素和控件均不可进行操作,实现这种功能的一个技巧是:切换半透明模式之前,先将当前组件的界面保存为Image图像对象,而后将Image图像渲染为新界面的背景。实现此功能的核心函数是Component类的createComponentSnapshot()函数:
// 将组件comp的操作界面保存为Image
const Image MyClass::createComponentSnapshot() const
{
if (comp != nullptr)
return comp->createComponentSnapshot();
return Image();
}
 与读写文档有关的程序(创建型程序)通常需继承自FilebasedDocument类,而FilebasedDocument类继承自ChangeBroadcaster类,继承而来的changed()函数为虚函数,可重写之以实现更多功能。重写时,可首先调用FilebasedDocument类的此函数,而后加上额外的语句(装饰模式)。示例:
// FilebasedDocument的派生类重写changed()函数
void MyFileDocument::changed()
{
FilebaseDocument::changed(); // 调用基类的同名函数
commandManager->commandStatusChanged(); // 命令管理器对象更新命令状态
}