870920 Menu

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

 关于代码文件的组织结构。一到多个类构成一个模块,一到多个模块构成一项具体功能。程序中的某个具体功能,或者某个模块,或者一个集成性的操作界面等等,往往由多个类来完成,但不必每个类都用两个编程文件来完成(一个头文件,一个源文件),这样会产生很多零散的文件,不方便管理。可将有关的类集中在两个文件里,一头一源。甚至某些类可直接在源文件里写(声明和实现在一起,特别是具有友元关系的类群,这样方便管理和今后的复用)。即:按程序功能与模块来组织代码文件,一目了然。如果文件确实太大,必须要分而治之,则建立目录,将相关的文件保存在同一目录下。

 想好了再写。要实现某个功能或想法,也许有很多途径和算法,此时不要急着编码,而是将所有已知的算法和解决方案列出来,仔细衡量和比较之后再动手。有时,换一种思索流程会更好,比如逆向思考。先考虑结果会怎么样,直接在结果上动脑筋,反向逆推到问题的源头或初始阶段,往往会有完全不同或者更简洁的思路。

 自顶向下,逐步细化,分而治之,避免重复。这些做法永远是非常重要的编程思想。

 设计原则。OO设计中的里氏代换(可以接受基类对象的地方必然可以接受子类对象)与依赖倒转(针对共同的基类编程,而不要针对具体的子类编程)等原则非常重要,这是一种思想,而非技术。

 避免编译器自动生成默认的拷构和赋值运算符重载,可在private区中写个原型。或者在类的private区最后使用该宏:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (本类的类名)

 const数据成员不存在被谁修改的问题,可声明在public区,以方便访问。可将其声明为static静态数据成员。比如二进制数据的两个静态const变量(字节流数组与整型大小值)。

 代码要尽量简洁优雅。比如:父级组件添加并显示子组件时,不必写成两行,直接addAndMakeVisible(子组件对象名 = new 子组件类())。大量短小的if…else判断语句均可直接用“? :”来完成,甚至将其作为表达式的一部分。函数返回或语句中需要bool值,直接给出逻辑或关系运算的表达式。等等。

 类中与对象无关的方法,即对象永远无需直接调用的方法,要写在private区中。如果该方法不仅与类的对象没有关联,而且也不读写类的任何非static数据成员和成员函数,则干脆写成的static静态方法,效率更高。

 具体功能交给名称确切的函数来实现,甚至写一到多个函数。而不是一律将执行代码写在相关控件的消息判断与处理方法中。比如,点击某按钮后,实现某个功能,如无太大必要,不在判断按钮点击的方法中写这些代码,而是将相关代码组织成一个函数,该函数的命名尽量清晰直观。如该函数无需对象直接调用,则写到private区中。如允许,还可以写为static函数。而后,该函数被判断按钮点击的buttonClicked()函数所调用。

 自定义的类可使用前缀Swing,这样可以区别是自定义的,还是JUCE类库现有的。积少成多,以便形成自己的专有类库。而不是写一次用一次就完事大吉。这也是C++代码复用的重要思想。

 合理使用友元关系。最好将这些类和函数写在一起(一头一源),这样便于今后的复用和管理。因为友元破坏了类的封装独立,加大了类与类之间的耦合性与依赖性。如果这些类所在的文件分散放置,今后会找不着北。

 不一定所有控件都声明为指针;不一定所有用到的对象都声明为类的数据成员。内容组件中所用到的各个对象,具体声明为哪种类型(栈对象、指针、引用、作用域指针,声明为数据成员还是在某个成员函数中临时使用,等等),要视具体情况、使用的便利性、对资源的占用情况、运行效率、对象的生命期、作用域、在类中的使用范围等因素而定。但是,无论如何声明和使用,都必需杜绝内存泄露和野指针等问题。

 普通数组与指针型数组。包含对象的数组占用空间较大;而元素为指针的数组保存的只是指针变量,可大大节省内存空间的占用。
// 指针型数组。该数组的每一个元素为Component类型的指针对象(内存地址)
Component* vcomps[] = { listBox, verticalDividerBar, nullptr };
// 普通型数组。该数组的每一个元素为Component类型的实际对象(注意指针的*解引用)
Component vcomps[] = { *listBox, *verticalDividerBar, 0 };

 字体数组。定义一个字体数组可保存操作系统中已安装的所有字体,该数组的每一个元素均代表系统中已安装的某一种字体。有了这个数组,就可以很方便的实现文字编辑方面的某些功能。
Array<Font> fonts; // 定义一个字体数组,保存系统中已安装的所有字体
Font::findFonts (fonts);

 根据列表框中所选择的字体,实时刷新文本编辑器中的文字样式,自动应用所选的字体类型。
void updatePreviewBoxText()
{
// 定义字体对象,初始化为列表框中所选项目的对应物。
// 注意fonts数组的下标索引为列表框当前所选行的ID
Font font (fonts [listBox->getSelectedRow()]);
// 根据推子的值设置字体高度(大小)
font.setHeight ((float) sizeSlider.getValue());
// 根据按钮的状态设置字体是否加粗
font.setBold (boldButton.getToggleState());
// 根据按钮的状态设置字体是否斜体
font.setItalic (italicButton.getToggleState());
// 根据推子的值设置字体的字间距
font.setExtraKerningFactor ((float) kerningSlider.getValue());
// 根据推子的值设置字体水平缩放的比例
font.setHorizontalScale ((float) horizontalScaleSlider.getValue());
// 文本编辑器textBox应用新字体,自动更新所显示的内容
textBox.applyFontToAllText (font);
}

 静态成员函数,没有默认的this指针。类的静态函数无法访问类的数据成员,如需访问,必须显式使用本类的对象来调用。可在静态函数中临时创建本类对象,或者至少需要一个本类的对象作为其参数,用该对象来调用类的其它非静态函数。

 避免编译器对未使用的变量发出警告。如果某个变量只是临时“借用”一下或者仅限于某个条件下使用,为避免编译器警告,可强转为void类型:
(void) tmpValue; // 由于此处未使用此变量,为避免编译器警告,强转为void

 单键式快捷键的简单写法:
// KetPress(1参为具体的键,键码或枚举均可。2参为功能键。3参为该键可显示的字符)
alertWindow.addButton (L”确定”, 1, KeyPress (KeyPress::returnKey, 0, 0));

 与平台紧密相关的代码,要置于条件宏中:
#if JUCE_MAC // 如果是苹果操作系统
// 添加测试Apple Remote的菜单项
m.addItem (140, L”测试Apple Remote…”);
#endif

 随机获取亮色调的颜色(适合于组件背景),本方法可为全局静态函数,或作为某个类的静态函数:
static const Colour getRandomBrightColour()
{
return Colour (Random::getSystemRandom().nextFloat(), 0.1f, 0.97f, 1.0f);
}

 构造函数初始化列表。如果某个类的数据成员(对象)为栈对象且需要构造参数,那么,实例化该成员只能在类的构造函数初始化列表中进行。派生类继承了需要构造参数的基类,与此同理,必须且只能在派生类的构造函数初始化列表中对基类进行构造。示例:
// ChildClass为ParentClass的派生类,ParentClass需要3个构造参数。
// objNeedPara为ChildClass类中声明的栈对象,它需要2个构造参数。
// ChildClass本身也需要1个构造参数。该类的定义如下:
class ChildClass : public ParentClass
{
public:
// 构造函数
ChildClass(int tmp) : ParentClass(2.0f, ParentClass::enumValue, L”字符串”),
objNeedPara(double(tmp) + 0.30), value(tmp) { //… }
private:
ObjNeedPara objNeedPara;
int value;
};

 同时设置某个颜色的透明度和亮度(函数的串联调用):
// 颜色对象colour初始化为某个随机颜色,同时设置该随机颜色的透明度和亮度
Colour colour = Colour(Random::getSystemRandom().nextInt()).
withAlpha(0.5f).withBrightness (0.7f);

 子组件在父组件中实现弹跳效果有3个关键点:一是使用计时器,二是计时器回调函数中设置新的位置及大小,三是使用绝对值函数:
// 设置x,y的值,保证不出边界,遇到边界即弹回的关键语句。size变量为弹跳组件的宽高
x += dx; y += dy;
if (x < 0) dx = abs (dx);
if (x > parentWidth) dx = -abs (dx);
if (y < 0) dy = abs (dy);
if (y > parentHeight) dy = -abs (dy);
// 设置子组件的位置和大小
setBounds (x – 2, y – 2, size + 4, size + 4);
repaint();// 重绘

 使用操作系统本地标题栏的情况下,窗口有时无法位于最前面,此问题与主窗口构造函数中的语句顺序有关。正确的顺序为:

 使用addToDesktop=false创建TopLevelWindow顶级窗口

 如果使用了菜单栏,调用setMenuBar()

 如果打算使之重新调整大小和自我布局(resized),则调用setResizable()

 如果想使用一个constrainer,则调用setConstrainer()

然后是:
setUsingNativeTitleBar (true);
addToDesktop (getDesktopWindowStyleFlags());
setContentComponent (contentComp, true, true);
centreWithSize (getWidth(), getHeight());
setVisible (true);