870920 Menu

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

 编写类的设置函数(设置某个成员数据的值)并不是简单的直接赋值了事。有时需借鉴赋值运算符重载的典型做法,先判断要设置的值是否与当前值相等,如果相等,则如何处理,特别是设置新值后牵扯到更多关联操作的函数。这种思路和做法具有普遍意义,即:每次编写设置和赋值类函数或语句时都要先停下来想一想,新值是否有可能等于旧值,如果相等,直接赋值是否还有必要,是否会出问题。示例:

void MyClass::setComponentName (const String& newName)
{
if (newName != componentName) // 如果新值与旧值相等,则什么也不做
{
componentName = newName; // 新值与旧值不相等的前提下才进行赋值
changed(); // 设置新值后的有关操作
}
}
 某些函数的参数为const String&,传参时不一定非得传入String对象,完全可以传入const char*。内部会做隐式转换,但这种转换是临时性的拷贝构造,不占用额外的资源。即:某些情况下,如需临时使用String对象,可声明一个const char*对象,而无需声明String对象,这么做的效率更高。通常,声明为指向char常量的常指针(char数组):
// 如果该数据属于类,而并非每个对象均人手一个,则还可声明为static
const char* const stringObject = “something”;
 确定吸附位置的算法(基于既定的吸附值,将给出的位置转换为就近的吸附位置):
// 参数pos为给出的当前位置,snapPix为类的成员变量,代表当前所设置的吸附值(像素数)
int MyClass::snapPosition (int pos) const
{
if (isSnapActive) // 如果当前开启了吸附功能
{
jassert (snapPix > 0);
pos = ((pos + snapPix * 1024 + snapPix / 2) / snapPix – 1024) * snapPix;
}
return pos;
}
 StringArray字符串数组的用途极为广泛,且使用非常方便,一个典型的用例:
// text是一个包含多个单词和句子的大字符串,将其分割、整理到StringArray字符串数组中
StringArray sa; // 建立一个字符串数组对象
// 按逗号进行分割,即将该字符串中每句话分割为一个字符串,
// 分割后的每个字符串作为一个元素添加到字符串数组中
sa.addTokens (text, “,”, String::empty);
sa.trim(); // 将分割后的每个字符串的首尾空格清除掉
sa.removeEmptyStrings(); // 移除空白字符串元素
sa.removeDuplicates (false); // 相同的字符串仅保留一个,不区分大小写
String s (sa[i]); // 获取数组中的第i个字符串
 将当前文档中的数据分层次转换为XmlElement,将已保存的XmlElement数据加载到当前文档,是文档数据操作的核心功能,可参阅model/jucer_JucerDocument.cpp源文件中的328~406行,对应的成员函数为:creatXml()和loadFromXml()。
 继承自FileBasedDocument的文档类,其具体实现与解决思路可参阅JucerDocument类。源码文件见上。
 将字符串转换为XmlElement,通过一个临时的XmlDocument对象来完成,仅需两条语句:
XmlElement* stringToXml (const String& text)
{
if (给出某些判断条件,符合才予转换)
{
XmlDocument doc (text); // 先将字符串转换为Xml文档
return doc.getDocumentElement(); // 根据Xml文档,返回XmlElement对象
}
return nullptr;
}
 字符串中加入换行符,使用”\r\n”,而不是”\n”
 无损缩放组件的实现思路:内容组件对子组件进行仿射变换处理:
// content是当前组件的子组件,zoomFactor是float类型的缩放比例值
content->setTransform (AffineTransform::scale (zoomFactor, zoomFactor));
基于此思路,可写一个放大镜组件类,详情参阅JuceSwing/Gui/MagnifierComponent.h
 放大镜组件通常需置入Viewport中,该Viewport需要自定义,以实现按下Ctrl键转动鼠标滚轮时放大镜组件进行缩放,不按Ctrl键转动鼠标滚轮,则Viewport滚屏。按下空格键拖拽Viewport的内容组件为上下左右移动之,等等。具体实现可参阅:ui\jucer_EditingPanelBase.cpp文件中的ZoomingViewport类。
 内容组件内处理按键事件的函数为keyPressed(),其典型写法为:
bool MyComponent::keyPressed (const KeyPress& key)
{
if (key.isKeyCode (KeyPress::rightKey)) // 按下右光标键
doSomething();
else if (key.isKeyCode (KeyPress::downKey)) // 下光标键
doSomething();
else if (key.isKeyCode (KeyPress::其他键))
// …
else
return false;
return true;
}
 派生自UndoableAction的可撤销操作类通常都是在UndoManager对象perform()时临时创建堆对象,这样可以通过undoManager对象的undo()和redo()实现该操作的撤销与重做。但有些时候,也可直接用UndoableAction对象调用其自身的perform(),执行某个操作,而不进行撤销与重做。示例:
// 创建本次操作的可撤销堆对象
UndoableAction* action = new ThisUndoableAction(参数…);
if (某项条件如果满足,则本次操作可进行撤销重做)
undoManager->perform (action, “该操作的文本描述”);
else
action->perform();
deleteAndZero (action);
 子组件容器类在添加子组件时即可基于XML文本描述来添加,可使用专门的工厂类根据XML文本描述或子组件容器类传过去的有关参数来创建。尽管多了XML这个步骤,但为后面的复制、剪切、粘贴等操作提供了方便和操作概念上的一致性。
 保存子组件自身的属性数据时,可在子组件类中写一个XmlElement* createXml()const函数,也可这么写:bool dataWriteToXml (XmlElement* xml) const; 可返回bool,也可返回Result类型(两者均代表操作是否成功)。
 如果某个子组件的上层组件具有某个方法,该子组件类中的某个成员需要调用该方法,那么可以采用递归查找父组件的思路:
Component* p = getParentComponent();
while (p != nullptr)
{
NeedThisComp* need = dynamic_cast<NeedThisComp*>(p);
if (need != nullptr)
return need->getSomething(); // 找到该上层组件后,返回所需的结果
p = p->getParentComponent(); // p自我赋值为上一层父组件,而后再次循环
}
return nullptr; // 循环完毕,如果没有找到NeedThisComp类,则返回nullptr
 拖拽子组件的边框以改变其大小时,内容组件类需声明并持有ResizableBorderComponent对象,如需拖拽时保持宽高比等特殊要求,则继承ComponentBoundsConstrainer类并重写其虚函数。
 组件类,如要使鼠标进入或退出时触发重绘,则在构造函数的最开始加一句:
setRepaintsOnMouseActivity (true);
 内容组件的paint()函数中绘制子组件四个角的T形边界线:
const int w = getWidth();
const int h = getHeight();
// 创建RectangleList对象r,初始为一个大矩形
RectangleList r (Rectangle<int> (0, 0, w, h));
// r移除一个矩形区域。移除后,初始的大矩形自动分割为多个矩形的集合
r.subtract (Rectangle<int> (1, 1, w – 2, h – 2));
const int size = jmin (w / 3, h / 3, 12);
// 再移除两个矩形区域
r.subtract (Rectangle<int> (size, 0, w – size – size, h));
r.subtract (Rectangle<int> (0, size, w, h – size – size));
g.setColour (Colours::darkgrey); // 设置绘制颜色
for (int i = r.getNumRectangles(); –i >= 0;)
g.fillRect (r.getRectangle (i)); // 填充大矩形中所包含的所有矩形
上述语句的运行结果为:

图 5 86 RectangleList类subtract()的另类用法