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.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<String,Qt.CaseSensitivity> findNext = new Signal2<String,Qt.CaseSensitivity>();

public Signal2<String,Qt.CaseSensitivity> findPrevious = new Signal2<String, Qt.CaseSensitivity>();

      共有10個SignalN類——Signal0、Signal1<T1>……Signal9<T1……T9>。類名中的數字表明瞭它們接受多少個參數,類型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]


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章