C++ GUI Programming with Qt4 Second Edition 之 附录C.1 Qt Jambi入门

Qt Jambi入门

      本节,我们将开发一个简单的Java应用程序并显示如图C.1所示的窗口。除窗口标题之外,Jambi Find对话框与第二章中创建的Find对话框的外观和功能均相同。通过使用相同的例子,可以更容易地看出C++/Qt和Qt Jambi编程中的不同点和相同点。在讲解代码的同时,我们也会介绍出现的C++和Java概念上的区别。

C++ GUI Programming with Qt4 Second Edition 之 附录C.1 Qt Jambi入门_第1张图片

图C.1 Jambi Find对话框

      Jambi Find程序的实现代码在一个单独的文件中,文件名为FindDialog.java。我们将分片介绍文件内容,首先从import声明开始。

import com.trolltech.qt.core.*;

import com.trolltech.qt.gui.*;

      这两个import声明将Qt所有的核心类和GUI类都引入了Java。其他的类集可以通过相同的import声明引入(如import com.trolltech.qt.opengl.*)。

public class FindDialog extends QDialog {

      与C++版本的例子相同,FindDialog类是QDialog的一个子类。在C++中,信号要在头文件里声明,依靠moc工具生成支撑代码。在Qt Jambi中,用Java的内省技术实现信号槽机制。但我们仍需要一些声明信号的方法,这可以使用SignalN类实现:

public Signal2 findNext = new Signal2();

public Signal2 findPrevious = new Signal2();

      共有10个SignalN类——Signal0、Signal1……Signal9。类名中的数字表明了它们接受多少个参数,类型T1……T9指定了参数类型。这里,我们声明了两个信号,每个信号包含两个参数。两个信号的第一个参数是Java String类型,第二个参数是Qt.CaseSensitivity类型(这是一个Java的枚举类型)。在Qt API中的所有QString,在Qt Jambi中均用String替代。

      与其他的SignalN类不同,Signal0不是一个泛型类。要使用Signal0创建一个不含参数的信号,可以像下面这样:

public Signal0 somethingHappened = newSignal0();

      信号创建完成后,我们来看构造函数的实现。函数太长,我们将其分成三部分讲解。

public FindDialog(QWidget parent) {

       super(parent); 

label = new QLabel(tr("Find &what:"));

lineEdit = new QLineEdit();

label.setBuddy(lineEdit); 

caseCheckBox = new QCheckBox(tr("Match&case"));

backwardCheckBox = newQCheckBox(tr("Search &backward")); 

findButton = newQPushButton(tr("&Find"));

findButton.setDefault(true);

findButton.setEnabled(false); 

closeButton = newQPushButton(tr("Close"));

      在Java中创建窗口部件与C++唯一不同的是细微的语法细节。要注意的是,tr()的返回值是String类型,而不是QString。

lineEdit.textChanged.connect(this,"enableFindButton(String)");

findButton.clicked.connect(this,"findClicked()");

closeButton.clicked.connect(this,"reject()");

      Qt Jambi连接信号槽的语法与C++/Qt有点不同,但依然很简短。通常语法如下:

sender.signalName.connect(receiver,"slotName(T1, ..., TN)");

      与C++/Qt不同,我们不需要给信号指定签名。如果信号包含的参数多于它所连接的槽函数,那这些多出来的函数会被忽略。此外,在Qt Jambi中,信号槽机制不止限于在QObject子类中应用,任何继承QSignalEmitter的类都可以发出信号,任何类的任何函数都可以作为槽函数。

QHBoxLayout topLeftLayout = newQHBoxLayout();

topLeftLayout.addWidget(label);

topLeftLayout.addWidget(lineEdit); 

QVBoxLayout leftLayout = new QVBoxLayout();

leftLayout.addLayout(topLeftLayout);

leftLayout.addWidget(caseCheckBox);

leftLayout.addWidget(backwardCheckBox);

QVBoxLayout rightLayout = new QVBoxLayout();

rightLayout.addWidget(findButton);

rightLayout.addWidget(closeButton);

rightLayout.addStretch(); 

QHBoxLayout mainLayout = new QHBoxLayout();

mainLayout.addLayout(leftLayout);

mainLayout.addLayout(rightLayout);

setLayout(mainLayout); 

setWindowTitle(tr("Jambi Find"));

setFixedHeight(sizeHint().height());

}

      窗口布局代码实际上与原始的C++代码完全一样,用相同的布局类以完全相同的方式实现相同的功能。Qt Jambi中也可以通过Qt Designer以窗体形式创建对话框,然后用juic(Java用户接口编译器)编译,我们将在下节讲解这种方法。

private void findClicked() {

   String text = lineEdit.text();

   Qt.CaseSensitivity cs = caseCheckBox.isChecked()

           ? Qt.CaseSensitivity.CaseSensitive

           : Qt.CaseSensitivity.CaseInsensitive;

    if(backwardCheckBox.isChecked()) {

      findPrevious.emit(text, cs);

    }else {

      findNext.emit(text, cs);

    }

}

      Java关于枚举值的语法比C++稍微复杂点,但很容易理解。调用SignalN对象的emit()函数并传递正确类型的参数即可触发信号。类型检查在程序编译过程中完成。

private void enableFindButton(String text) {

findButton.setEnabled(text.length() != 0);

}

      enableFindButton()函数的功能和使用与C++源码相同。

private QLabel label;

private QLineEdit lineEdit;

private QCheckBox caseCheckBox;

private QCheckBox backwardCheckBox;

private QPushButton findButton;

private QPushButton closeButton;

      为了保持与本书其他部分代码的一致,我们将所有的窗口部件声明为类的私有成员。这仅仅是风格的问题,没什么可以阻止我们在构造函数中声明那些只在构造函数中用到的部件。比如,我们可以在构造函数中声明label和closeButton,因为没有其他地方引用它们。构造函数结束时也无需回收它们占用的资源。这是因为Qt Jambi使用与C++/Qt相同的父子类关系机制,所以一旦label和closeButton被创建出来,FindDialog窗口就获得了它们的所有权,并在内部保留其引用来保证它们的生命周期。QtJambi递归删除子窗口部件,一旦最顶层的窗口被删除,该窗口会反过来删除它所有的子部件和布局,这些子部件和布局又删除它们的子部件和布局,依次类推,直至清除完毕。

使用Java资源系统

      与大多数Java标准类不同,Qt Jambi深度支持Java的资源系统。Java资源由classpath:前缀定义。在Qt Jambi API中任何地方用到的文件名都可以用指定的Java资源代替,比如:

QIcon icon = new QIcon("classpath:/images/icon.png");

if (!icon.isNull()) {

    ...

}

      为找到需要的图标,Qt Jambi会搜索每个目录下的每个image文件夹或搜索由CLASSPATH环境变量指定的.jar文件。一旦找到icon.png图标文件,即结束搜索并使用所找到的文件。

      如果指定的文件不存在,程序也不会报错。在上面的例子中,如果没有找到icon.png,icon.isNull()会返回true。像QImage和QPixmap这种有构造函数的类,其构造函数包含一个文件名参数,我们可以使用isNull()函数检测指定的文件读取是否成功。在QFile中,我们用QFile.error()检查文件是否被读取。

      与AWT、SWing、SWT不同,Qt Jambi充分利用Java的碎片资源回收功能,如果删除了最顶层窗口的最后一个引用,系统会自动安排对这个窗口碎片资源的回收,而不需要显式的调用dispose()。这种方式非常方便,工作机制与C++/Qt相同。需要重点提醒的是,对于SDI(单文档界面)程序,必须保留所创建的每个顶层窗口的引用,以免它们被回收(在C++/Qt中,SDI程序通常使用Qt::WA_DeleteOnClose属性防止内存泄漏)。

public static void main(String[] args) {

       QApplication.initialize(args);

       FindDialog dialog = new FindDialog(null);

       dialog.show();

       QApplication.exec();

    }

}

      为方便起见,我们所提供的FindDialog带有main()函数,main()函数实例化了一个对话框并将其显示出来。声明行importcom.trolltech.qt.gui.*确保了我们可以使用静态的QApplication对象。Qt Jambi程序开始时,必须调用QApplication.initialize()并将命令行参数传递进去。QApplication对象可以处理它支持的参数,比如-font和-style。

      创建FindDialog时,将null作为父类传递进去表示所创建的对话框是顶层窗口。一旦main()函数结束,对话框即会消失,其资源也会被回收。调用QApplication.exec()开始事件循环,只有用户关闭对话框时,事件循环才会将控制权返还给mian()函数。

      虽然Qt Jambi API与C++/Qt API很相似,但仍有区别。比如,在C++中,QWidget::mapTo()成员函数有如下定义:

QPoint mapTo(QWidget *widget, const QPoint&point) const;

     其中QWidget作为变量指针传递,但QPoint却是常量引用。在Qt Jambi中,等效的函数定义如下:

public final QPoint mapTo(QWidget widget,QPoint point) { ... }

      因为Java中没有指针,所以在函数定义中并不能看出传递给函数的对象是否可以被函数修改。理论上来说,因为两个参数都是引用,mapTo()函数可以修改其中的任一个,但Qt Jambi不允许修改QPoint参数,因为在C++中QPoint是作为常量引用传递的。通常根据上下文信息可以清楚的判断出哪些参数可以改变哪些不能改变。如有疑问,可以查阅参考文档理清这种情况。

      除了在C++中那些作为常量值或常量引用传递的不可变参数外,Qt Jambi还保证了任何非void函数的返回值是一个独立的副本(在C++中函数返回值都是常量值或常量引用),因此改变函数返回值不会有任何限制。

      前面提到过,C++/Qt中任何用到QString的地方,在Qt Jambi中都用Java的String代替。这种对应关系同样适用于QChar类,在Qt Jambi中有两个Java的对应类:char和java.lang.Character。对于Qt的一些容器类也有类似的对应关系:QHash被替换为java.util.HashMap,QList和QVector被替换为java.util.List,QMap被替换为java.util.SortedMap。此外,QThread被替换为java.lang.Thread。

      Qt的模型/视图架构和数据库API使QVariant得到了广泛应用。因为所有的Java对象都继承自java.lang.Object,所以Java中并不需要这种类型,在所有的Qt Jambi的API中,QVariant被替换为java.lang.Object。QVariant提供的其他函数可以在com.trolltech.qt.QVariant中以静态函数的方式使用。

      我们已经结束了对Qt Jambi小程序的介绍,并讨论了很多Qt Jambi和C++/Qt在编程方便的概念上的差别。编译运行Qt Jambi应用程序与其他Java程序无异,但是要设置CLASSPATH环境变量指向Qt Jambi的安装路径。我们必须用Java编译器编译程序才能用Java解释器运行程序。比如:

exportCLASSPATH=$CLASSPATH:$HOME/qtjambi/qtjambi.jar:$PWD

javac FindDialog.java

java FindDialog

      这里,我们使用Bash shell设置CLASSPATH环境变量值;如果使用其他命令行解释器,其语法可能与此不同。CLASSPATH中包含了当前文件夹,这样编译器和解释器就可以自动找到FindDialog类了。在Mac OS X系统中,必须将命令行选项-XstartOnFirstThread分配给Java,以通过Apple的Java虚拟机处理线程相关事务。在Windows系统中,我们这样执行应用程序:

setCLASSPATH=%CLASSPATH%;%JAMBIPATH%\qtjambi.jar;%CD%

javac FindDialog.java

java FindDialog

      QtJambi也可以在IDE中使用。下节将介绍如何用著名的Eclipse IDE编辑、编译、测试Qt Jambi应用程序。


微信公众号:Qt开发社区(期待您的关注,扫下方二维码或搜索“Qt开发社区”或"Qtkfsq")

投 稿 邮 箱 :[email protected]


你可能感兴趣的:(Qt入门)