開始使用QML編程(4)

我們完成一個簡單的文本編輯器的UI的生成工作。UI完成了,我們可以繼續往前走,使用正常的Qt和C++來實現程序邏輯了。QML是一個很好的原型設計工具,將UI設計程序邏輯分割開來。

 

menubar3

Extending QML using Qt C++

現在我們有了文本編輯器的佈局,可以在C++中實現功能了。使用帶C++的QML使我們可以用Qt創建程序邏輯。我們可以使用Qt的declarative類在C++程序中創建一個QML上下文,使用圖形場景來顯示QML元素。或者也可以將我們的C++代碼導出到qmlviewer工具可以讀的plugin中。對於我們的程序,我們將在C++中實現load和save功能,並導出爲一個plugin。這樣,我們只需要直接加載這個QML文件,而不需要運行一個可執行程序。

Exposing C++ Classes to QML

我們會使用Qt和C++來實現文件的加載和保存。註冊了C++類和函數,它們就可以在QML中使用。這個類也需要編譯成一個Qt的plugin,QML文件還需要知道這個plugin的位置

 

對我們的程序,我們需要創建下面的項:

  1. Directory 類會處理目錄相關的操作。
  2. File 類是一個QOjbect,模擬一個目錄下的文件列表。
  3. plugin類將這個類註冊到QML上下文。
  4. 編譯這個plugin用到的Qt 項目文件.
  5. 一個告訴qmlviewer工具何處可以加載這個plugin的qmldir 文件.
Building a Qt Plugin

爲生產一個plugin,我們需要在Qt項目文件中做如下設置。第一,必需的源文件,頭文件和Qt模塊。所有的C++代碼和項目文件都在filedialog 目錄下。

 

  1. <PRE class=javascript name="code"><PRE class=javascript name="code"><PRE class=html name="code">In filedialog.pro:  
  2.   
  3.      TEMPLATE = lib  
  4.      CONFIG += qt plugin  
  5.      QT += declarative  
  6.   
  7.      DESTDIR +=  ../plugins  
  8.      OBJECTS_DIR = tmp  
  9.      MOC_DIR = tmp  
  10.   
  11.      TARGET = FileDialog  
  12.   
  13.      HEADERS +=     directory.h \  
  14.              file.h \  
  15.              dialogPlugin.h  
  16.   
  17.      SOURCES +=    directory.cpp \  
  18.              file.cpp \  
  19.              dialogPlugin.cpp  
  20.   
  21. </PRE><BR></PRE>  
  22. <BR>  
  23. <PRE></PRE>  
  24. <BR>  
  25. <PRE></PRE>  
  26. <PRE></PRE>  
  27. <PRE></PRE>  
  28. <PRE></PRE>  
  29. <PRE></PRE>  
  30. </PRE> 

















尤其是我們要編譯帶declarative模塊的Qt,並且配置成一個plugin,我們就需要一個lib模板。我們將這個編譯過的plugin放到它的父親的plugin目錄下。

Registering a Class into QML
  1. In dialogPlugin.h:  
  2.   
  3.     #include <QtDeclarative/QDeclarativeExtensionPlugin>   
  4.   
  5.     class DialogPlugin : public QDeclarativeExtensionPlugin  
  6.     {  
  7.         Q_OBJECT  
  8.   
  9.         public:  
  10.         void registerTypes(const char *uri);  
  11.   
  12.     };  
     

我們的plugin程序中,DialogPluginQDeclarativeExtensionPlugin的子類。我們需要實現繼承來的registerTypes()函數。dialogPlugin.cpp 文件看起來這樣:

  1. DialogPlugin.cpp:  
  2.   
  3.      #include "dialogPlugin.h"   
  4.      #include "directory.h"   
  5.      #include "file.h"   
  6.      #include <QtDeclarative/qdeclarative.h>   
  7.   
  8.      void DialogPlugin::registerTypes(const char *uri){  
  9.   
  10.          qmlRegisterType<Directory>(uri, 1, 0, "Directory");  
  11.          qmlRegisterType<File>(uri, 1, 0,"File");  
  12.      }  
  13.   
  14.      Q_EXPORT_PLUGIN2(FileDialog, DialogPlugin);  
     

registerTypes() 函數將我們的File和Directory類註冊到QML中。這個函數需要以下參數:模板用到的類名,主版本號,小版本號和類名。我們使用Q_EXPORT_PLUGIN2導出plugin。注意在dialogPlugin.h 文件裏,我們把Q_OBJECT宏放在了類的頂部。同樣到,我們需要運行qmake來生成必需的meta-object代碼。

Creating QML Properties in a C++ class

我們可以使用C++和Qt的Meta-Object系統來創建QML元素和屬性。我們能用slots和signals實現屬性,只要讓Qt知道它們。這樣這些屬性就能在QML中使用了。

 

對我們的編輯器,我們要加載和保存文件。典型地,這些特性都包含在文件對話框中。幸運的是,我們可以使用QDir,QFile和QTextStream來實現目錄的讀取和輸入/輸出流。

 

  1. class Directory : public QObject{  
  2.   
  3.          Q_OBJECT  
  4.   
  5.          Q_PROPERTY(int filesCount READ filesCount CONSTANT)  
  6.          Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)  
  7.          Q_PROPERTY(QString fileContent READ fileContent WRITE setFileContent NOTIFY fileContentChanged)  
  8.          Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )  
  9.   
  10.          ...  
class Directory : public QObject{

         Q_OBJECT

         Q_PROPERTY(int filesCount READ filesCount CONSTANT)
         Q_PROPERTY(QString filename READ filename WRITE setFilename NOTIFY filenameChanged)
         Q_PROPERTY(QString fileContent READ fileContent WRITE setFileContent NOTIFY fileContentChanged)
         Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )

         ...


Directory 類使用Qt的Meta-Object系統來註冊完成文件操作需要的屬性Directory 類導出爲一個plugin,作爲一個Directory元素在QML中使用。每一個使用Q_PROPERTY宏列出來的屬性都是一個QML屬性。

 

Q_PROPERTY聲明一個屬性和他的讀寫函數到Qt的Meta-Object系統。例如,filename 屬性的類型是QString,可以使用filename()函數來讀取,使用seetFilename()函數來寫。另外還有一個與文件名屬性關聯的signal,叫filenameChanged(),當這個屬性改變時,就會被發出去。讀寫操作在頭文件中聲明爲plblic。

 

同樣地,根據它們的用途,我們還聲明瞭其他屬性。filesCount 屬性表示一個目錄下的文件數量。filename屬性設置爲當前選中的文件的名字。fileContent中存儲了要加載/保存的文件內容。

 

 
  1. Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )  
Q_PROPERTY(QDeclarativeListProperty<File> files READ files CONSTANT )


files屬性是目錄中過濾過的文件列表Directory 類實現來過濾掉不合法的文本文件,只有擴展名爲.txt的文件是有效的。深入一步來說,通過在C++中聲明爲QDeclarativeListProperty,QList可以在QML文件中使用。這個模板對象繼承自QObject,因此,File類也必須繼承自QObject。在Directory類中,File對象的列表被保存到一個叫m_fileList的QList中。

 

  1. class File : public QObject{  
  2.   
  3.          Q_OBJECT  
  4.          Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)  
  5.   
  6.          ...  
  7.      };  
class File : public QObject{

         Q_OBJECT
         Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)

         ...
     };

 

這樣的話這些屬性可以在QML中作爲Directory元素的屬性來使用。注意,我們不需要在我們的C++代碼中創建一個id標識符屬性

 

  1. Directory{  
  2.          id: directory  
  3.   
  4.          filesCount  
  5.          filename  
  6.          fileContent  
  7.          files  
  8.   
  9.          files[0].name  
  10.      }
     

QML使用JavaScript的語法和結構,我們可以迭代文件列表獲取它的屬性。我們可以調用files[0].name來獲取第一個文件的名字

 

正常的C++函數也可以在QML中訪問。文件的加載和保存函數是用C++實現的,可以使用Q_INVOKABLE宏來聲明它們。此外,我們也可以將它們聲明爲slot,這些函數也能夠在QML中訪問。

 

  1. In Directory.h:  
  2.   
  3.      Q_INVOKABLE void saveFile();  
  4.      Q_INVOKABLE void loadFile();  
     

當目錄的內容改變時,Directory類也必須通知其他的對象。這個功能是通過signal來實現的。前面提到過,QML的signals有一個對應的handler,即一個on在他們的名字前面。當目錄刷新時,這個叫directoryChanged 的signal被髮出。這個刷新只是重新加載一下目錄中的內容,並更新有效的文件列表。通過附加一個動作到onDirectoryChanged 這個signal handler,QML的items就能被通知到。

 

list屬性需要進一步開發。因爲它使用callback來訪問和修改文件內容。list屬性是QDeclarativeListProperty<File>類型的。當訪問這個列表時,訪問函數需要返回一個QDeclarativeListProperty<File>。模板類型File,需要從QObject派生。進一步來說,爲了創建QDeclarativeListProperty,列表的訪問者和修改者都必須作爲函數指針傳遞進來。這個列表,在我們的實例中是一個QList,也需要是一個File指針的列表。

 

QDeclarativeListProperty的構造函數和Directory的實現爲:

 

  1. QDeclarativeListProperty  ( QObject * object, void * data, AppendFunction append, CountFunction count = 0, AtFunction at = 0, ClearFunction clear = 0 )  
  2.      QDeclarativeListProperty<File>( this, &m_fileList, &appendFiles, &filesSize, &fileAt,  &clearFilesPtr );  
 

構造函數傳遞了一些函數指針,包括:附加列表的函數,計算列表的函數,使用index獲取item的函數和清空list的函數。只有append函數是強制的。注意,這些函數指針必須與AppendFunction,CountFunction, AtFunction或者ClearFunction的定義匹配。

 

  1. void appendFiles(QDeclarativeListProperty<File> * property, File * file)  
  2.     File* fileAt(QDeclarativeListProperty<File> * property, int index)  
  3.     int filesSize(QDeclarativeListProperty<File> * property)  
  4.     void clearFilesPtr(QDeclarativeListProperty<File> *property)  
 
爲了簡化我們的文本對話框,Directory 類過濾掉了一些不合法的文本文件,只保留了擴展名爲.txt的文件。如果一個文件名沒有.txt擴展名,我們的文件對話框裏就看不到這個文件。而且,這樣的實現也保證了保存的文件的文件名也是以.txt爲擴展名的。Directory 使用QTextStream來讀取文件和輸入一個文件的內容到另外一個文件。

 

使用Directory元素,我們可以獲得一個文件列表,知道有多少個文件在程序目錄下,得到文件名字和內容的字符串,只要目錄內容改變時就可以得到通知。

 

對filedialog.pro 運行qmake命令,然後運行make來生成這個plugin並將它轉移到plugins目錄下。

Importing a Plugin in QML

qmlviewer工具導入相同目錄下的文件作爲一個程序。我們也可以穿件一個qmldir文件來包含我們想要導入的QML文件的位置。qmldir也可以存儲plugin和其他資源的位置。

 

  1. qmldir:  
  2.   
  3.      Button ./Button.qml  
  4.      FileDialog ./FileDialog.qml  
  5.      TextArea ./TextArea.qml  
  6.      TextEditor ./TextEditor.qml  
  7.      EditMenu ./EditMenu.qml  
  8.   
  9.      plugin FileDialog plugins  

我們剛纔創建的plugin叫FileDialog,即在項目文件裏TARGET指定的域。編譯成的plugin在plugins目錄下。

Integrating a File Dialog into the File Menu

我們的FileMenu要顯示FileDialog元素,包含一個目錄下的文本文件列表,因此允許用戶通過點擊列表來選擇文件。哦買也需要指定save,load和新的按鈕到各自的動作上。FileMenu包含一個可編輯的文本輸入區,允許用戶使用鍵盤輸入文件名。

 

Directory 在FileMenu.qml中使用,它會同通知FileDialog元素目錄內容刷新了。這個通知是在onDirectoryChanged,這個signal handler裏實現的。

 

  1. FileMenu.qml:  
  2.   
  3.      Directory{  
  4.          id:directory  
  5.          filename: textInput.text  
  6.          onDirectoryChanged: fileDialog.notifyRefresh()  
  7.      }  
 
爲保持我們的程序的簡潔性,文本對話框總是可見的,並且不會顯示名字不是.txt擴展的那些文本文件。

 

 
  1. In FileDialog.qml:  
  2.   
  3.      signal notifyRefresh()  
  4.      onNotifyRefresh: dirView.model = directory.files  
I

FileDialog元素讀取叫files的list屬性,來顯示目錄的內容。這些文件作爲一個GridView元素的model,GridView元素依據delegate在一個網格中顯示數據items。delegate處理model的外觀,而我們的文件對話框簡單的生成一個文本居中的網格。點擊文件名字會有一個矩形高亮文件名。只要notifyReresh signal發出,FileDialog就會被通知到,然後重新載入目錄下的文件。

 

  1. In FileMenu.qml:  
  2.   
  3.      Button{  
  4.          id: newButton  
  5.          label: "New"  
  6.          onButtonClick:{  
  7.              textArea.textContent = ""  
  8.          }  
  9.      }  
  10.      Button{  
  11.          id: loadButton  
  12.          label: "Load"  
  13.          onButtonClick:{  
  14.              directory.filename = textInput.text  
  15.              directory.loadFile()  
  16.              textArea.textContent = directory.fileContent  
  17.          }  
  18.      }  
  19.      Button{  
  20.          id: saveButton  
  21.          label: "Save"  
  22.          onButtonClick:{  
  23.              directory.fileContent = textArea.textContent  
  24.              directory.filename = textInput.text  
  25.              directory.saveFile()  
  26.          }  
  27.      }  
  28.      Button{  
  29.          id: exitButton  
  30.          label: "Exit"  
  31.          onButtonClick:{  
  32.              Qt.quit()  
  33.          }  
  34.      }  
I

我們的FileMenu現在連接到了各自的行爲上。saveButton會傳送TextEdit裏的text到目錄的的屬性,然後從可編輯的文件輸入區拷貝文件名。最終,這個按鈕調用saveFile()函數保存文件。loadButton有一個類似的執行。另外,New行爲會清空TextEdit的內容。

 

更進一步,EditMenu 按鈕連接到了TextEdit的函數去copy,paste和選擇文本編輯器的全部文本。

 

menubar4

Text Editor Completion

menubar5

這個程序可以作爲一個簡單的文本編輯器,能輸入文本,並保存爲一個文件。它也可以加載文件,執行文本操作。

Running the Text Editor

在運行文本編輯器前,我們需要編譯文本對話框這個C++plugin。進入gsQml目錄,然後運行qmake,再使用make或者namke(取決於你的平臺)來編譯。運行qmlviewer,打開texteditor.qml文件來運行文本編輯器。

 

源代碼在examples/tutorials/gettingStarted/gsQml目錄下。

 

轉自 http://www.cnblogs.com/smoozer/archive/2011/03/28/1997987.html

發佈了0 篇原創文章 · 獲贊 0 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章