編譯環境與開發流程
開發QT有兩種IDE可以使用,一種是使用 VS + Qt 的外掛,另一種就是使用QtCreator工具。前一種是微軟的工具,用的都比較多容易上手,缺點是訊號槽的支援不太好,需要手寫,不能自動生成,另外可能有中文編碼的問題。後一種是Qt的官方IDE,智慧提示與除錯功能不如VS強大,但是是跨平臺的IDE,其QtDesigner設計UI介面操作比較方便,並且由於是QT官方的IDE,對編碼等支援都比較好,裡面整合了Qt的幫助文件。不得不說Qt的幫助文件做的是非常好的,整合進QtCreator環境之後更加方便。
我開發的時候使用的是QtCreator開發,目前除了除錯功能比VS差以外,其他的用的比較順手,QtCreator是跨平臺的,ubuntu上也是可以使用,開啟之後介面如下:
下面將對QtCreator的介面各個功能進行大致的介紹:
我們建立一個示例專案,選擇“檔案”—“新建檔案或專案”—“應用程式”—“QT Widgets Application”選擇之後都選擇預設設定,根據提示,就得到了一個專案,我們的UI是一個基於QMainWindow的類,預設提供選單欄,狀態列。如果不需要這些,可以建立一個基於QWidget的UI類,專案如圖所示:
QT專案的構成及原理
將專案切換到編輯模式,如下:
這個專案中一共有4個檔案,入口檔案main.cpp、mainwindow.ui檔案、mainwindow.h和mainwindow.cpp後臺原始檔,在main函式中直接呼叫MainWindow類的show()方法顯示主介面,那麼我們切換到UI的設計檢視(雙擊專案中的mainwindow.ui檔案),在主介面上新增兩個控制元件:
我們看一下MainWindow.cpp的程式碼裡面應該如何操作介面上的控制元件:
我們使用的是ui->txtName->text();
這樣的語句,也就是說並不是像在C#中一樣在後臺程式碼中直接可以通過類似this->txtName->text()
的語句去訪問介面上的控制元件物件,而MainWindow
類中有一個成員變數是ui,其型別是Ui::MainWindow
,通過這個ui成員去訪問介面上的元素,那麼這些介面控制元件是如何初始化的呢? 我們需要檢視ui成員變數的型別Ui::MainWindow
的實現,注意Ui::Mainwindow
類與MainWindow
類是不同的兩個類,Ui::MainWindow
類是在名稱空間Ui下的類,而MainWindow
是沒有名稱空間的,我們在mainwindow.h中可以看到:
MainWindow中的私有成員變數ui實際上是Ui::MainWindow
型別的指標,那麼Ui::MainWindow
是如何定義的呢? 用滑鼠點進去就看到了:
從這裡就可以看出為什麼我們的MainWindow類的建構函式中一進來就呼叫ui->setupUi(this)
去初始化介面了
QT中的佈局
QT中有四種佈局方式,分別是:Vertical垂直佈局、Horizontal水平佈局、Grid佈局、Form佈局,效果如下:
其實Grid佈局感覺跟HTML中的Table差不多,Form佈局好像也是表格的效果,至於這兩種佈局的差異在哪裡我也不是很清楚,專案中基本沒有用過這兩種佈局方式,一般而言所有的效果都可以通過水平佈局和垂直佈局巢狀實現。結合水平佈局和垂直佈局,以及他們之間的相互巢狀,再結合使用自動伸縮調節的佔位控制元件HorizontalSpacer和VerticalSpacer就可以實現非常複雜的佈局效果。
一般使用佈局有兩種方式,第一種即拖放這些佈局控制元件到UI介面上,然後將希望佈局的子控制元件拖放到這些佈局控制元件中,但是這種方式個人認為不夠靈活,特別是在控制元件之間希望巢狀的時候,工具箱中的佈局控制元件如下:
另外一種使用方式,QT的容器控制元件(那些能夠放子控制元件的控制元件)都可以為其指定一種佈局方式,當為一個容器控制元件指定佈局方式之後,該容器控制元件就會以這種佈局方式來約束其所有子控制元件,直接在Qt設計器的容器控制元件中右鍵就可以設定:
我們在一個QFrame控制元件中放入兩個子控制元件,一個文字框一個按鈕,之後在QFrame的空白處右鍵單擊,在其右鍵選單“佈局”的子選單中就可以指定該控制元件的佈局模式了。實際上在程式碼上的原理是一樣的,我們在QtCreator生成的ui_mainwindow.h中可以看到關於frame以及子控制元件和其佈局設定的程式碼:
可以看到是這麼樣的關係,QFrame的子控制元件QPushButton以及QLineEdit(文字框)在構造的時候指定的父物件就是frame,而佈局物件QHBoxLayout指定的父控制元件物件也是frame,也就是說除了我們在介面上看到的按鈕,文字框是frame的子控制元件以外,我們通過右鍵生成的佈局物件(QtCreator自動生成的,其物件id也是自動生成的),也是frame的子控制元件,QHBoxLayout通過addWidget函式將frame的所有直接子控制元件新增到佈局中進行佈局。而我們在工具箱中拖動佈局控制元件到頂級視窗UI介面之後,實際上QtCreator自動生成了一個QWidget作為該佈局控制元件的容器,並且自動生成的這個QWidget的父控制元件就是頂級的MainWindow視窗。也就是說我們每往UI介面上拖放一個佈局控制元件,那麼QtCreator會為該佈局控制元件自動生成一個QWidget作為該佈局控制元件的容器(也就是父控制元件),並且該自動生成的QWidget的父控制元件就是佈局控制元件被拖動到的位置所在的直接容器。例如:
當選定一個佈局控制元件(如果該佈局控制元件是從工具箱拖放到UI上的,則其在UI設計器上是可以看到的),或者是選擇一個容器控制元件的時候(如果該容器控制元件已經通過右鍵的方式指定了佈局方式)。這兩種情況下在QtCreator的屬性欄上就可以看到佈局的相關屬性:
如果是從工具箱中拖放的佈局控制元件,那麼其屬性中的Margin預設都是0 ,如果是通過右鍵為容器控制元件指定的佈局,那麼該佈局的Margin預設是9,所以這種方式下可以看到如果此時相容器控制元件中新增子控制元件,那麼子控制元件與容器控制元件之間是有間隙的,除非將這裡的屬性手工改為0,layoutSpacing引數對於這兩種方式產生的佈局預設值都是6,表示該佈局中的子控制元件之間的間隔是6
QT中的通用控制元件
QT中最常用的控制元件QPushButton(按鈕)、QLineEdit(文字框)、QRadioButton(單選框)、QCheckBox(核取方塊)、QFrame(一般用作容器控制元件,配合佈局)、QProgressBar(進度條控制元件)這些控制元件的使用方法都非常簡單,查一下幫助文件就可以搞定,下面的章節中,我們會講解另外的一些控制元件的常用但是卻不是很容易找到的功能。
QVariant 型別
再講解其他控制元件之前,我們需要先了解Qt中的QVariant型別,為什麼呢,因為需要為控制元件繫結資料,就離不開對QVariant型別的瞭解,下面章節中我們要說到的一些控制元件,在繫結資料的時候就會使用QVariant型別。他除了可以包裹Qt中常見的QString,int等型別之外,還可以包裹自定義的類物件。該型別提供了一系列的建構函式以及轉換函式來攜帶常見型別的資料,和轉換到常見型別資料的方法:
QVariant(int val) QVariant(uint val) QVariant(qlonglong val) QVariant(qulonglong val) QVariant(bool val) QVariant(double val) QVariant(float val) QVariant(const char * val) QVariant(const QString & val) QVariant(const QDate & val) QVariant(const QTime & val) QVariant(const QDateTime & val) bool toBool() const QByteArray toByteArray() const QChar toChar() const QDate toDate() const QDateTime toDateTime() const double toDouble(bool * ok = 0) const float toFloat(bool * ok = 0) const int toInt(bool * ok = 0) const QJsonArray toJsonArray() const qlonglong toLongLong(bool * ok = 0) const QString toString() const QTime toTime() const uint toUInt(bool * ok = 0) const qulonglong toULongLong(bool * ok = 0) const
這只是其中的一部分,其實還包括了一些畫圖相關的型別的封裝,例如QPoint,QRect等,當然Qt提供的是使用頻率很高的常見的型別,有時候我們需要繫結自己定義的類物件,例如實體類:
//設定 MyClass myclass; QVariant courseModelVariant=QVariant::fromValue(myclass); //獲取 myclass = courseModelVariant.value<MyClass>();
這樣我們就可以使用QVariant攜帶任意資料型別了
QComboBox控制元件
下拉選單框控制元件最常見的功能需求就是為該控制元件新增下拉專案,並且為每個下拉專案新增對應的自定義隱藏資料,例如在下拉選單中每一項上面顯示的文字描述是給使用者看的,然而在程式中,我們可能需要該專案對應的隱藏資料,例如ID甚至是自定義的物件。
QComboBox
類使用QComboBox::addItem(const QString &atext, const QVariant &auserData)
成員函式為下拉選單新增專案,第一個引數text表示顯示在下拉項中的文字,而第二個引數我們可以利用來為該項繫結自定義的資料,其型別為QVariant型別。我們可以通過QVariant型別方便的為該下拉項關聯任意自定義的資料型別。
在獲取資料的時候,通過QComboBox:: currentData(int role = Qt::UserRole)
函式獲取當前選中下拉項關聯的QVariant型別的資料,也可以通過QComboBox:: itemData(int index, int role = Qt::UserRole)
獲取指定下拉項的關聯資料。通過currentText()、itemText(int index)可以獲取下拉項上顯示的文字。
QTableWidget控制元件
QTableWidget是Qt中的表格顯示控制元件,與C#中的Grid、GridView類似,主要是用來繫結資料。在UI設計介面中選中該控制元件之後可以在屬性欄對控制元件的屬性進行設定,最常用的屬性有如下:
focusPolicy 焦點策略,如果設定為NoFocus可以去掉單擊時候現實的單元格的虛線框
contextMenuPolicy 可以設定右鍵選單
frameShape 設定外邊框,一般設定為NoFrame去掉邊框
editTriggers觸發單元格的編輯狀態,值NoEditTriggers表示不觸發編輯狀態
selectionMode選擇模式,值ExtendedSelection表示多選
selectionBehavior選擇行為,值SelectRows按行選擇
showGrid是否顯示網格線
rowCount行數
columnCount列數
horizontalHeaderVisible是否顯示水平表頭
verticalHeaderVIsible是否顯示垂直表頭
verticalScrollBarPolicy設定垂直滾動條策略
horizontalScrollBarPolicy設定水平滾動條策略
另外的一些比較實用的功能程式碼:
在單元格中新增控制元件:
QComboBox *comBox = new QComboBox(); comBox->addItem("F"); comBox->addItem("M"); ui->qtablewidget->setCellWidget(0,3,comBox);//這裡不是setItem而是setCellWidget
為單元格新增checkBox:
QTableWidgetItem *item = new QTableWidgetItem(); //設定item的check狀態的時候,item會自動變成QCheckBox的樣子, //不必通過setCellWidget專門插入QCheckBox控制元件 //通過item->checkState()可以獲取該item是否勾選 item->setCheckState(Qt::Unchecked); ui->tableWidgetCourseList->setItem(rowIndex, columnIndex, item);
單元格中顯示字串:
QTableWidgetItem *item = new QTableWidgetItem(QString("xx")); ui->tableWidgetCourseList->setItem(rowIndex, columnIndex, item);
設定單元格關聯的自定義資料:
QTableWidgetItem *item = new QTableWidgetItem(QString("")); QVariant courseModelVariant=QVariant::fromValue(MyClass("xx")); item->setData(USER_DEFINE_ROLE,courseModelVariant); this->ui->tableWidgetCourseList->setItem(rowIndex, columnIndex, item);
獲取單元格關聯的自定義資料:
QTableWidgetItem * item = this->ui->tableWidgetCourseList->item(row,col); Myclass model = item->data(USER_DEFINE_ROLE).value<MyClass>();
設定單元格中的文字對齊方式:
ui->tableWidgetCourseList->item(rowIndex, columnIndex)->setTextAlignment(Qt::AlignCenter);
通過x,y座標獲取所在的item物件:
QModelIndex index = ui->tableWidgetCourseList->indexAt(QPoint(x,y)); int row = index.row(); int col = index.column(); QTableWidgetItem * item = ui->tableWidgetCourseList->item(row,col);
設定表頭的列寬:
ui->tableWidgetCourseList->horizontalHeader()->resizeSection(colIndex,20);//寬20
設定列寬自適應:
ui->tableWidgetCourseList->horizontalHeader()->setSectionResizeMode(colIndex,QHeaderView::Stretch);
初始化表頭文字:
QStringList headerText; headerText.append("列1"); headerText.append("列2"); headerText.append("列3"); ui->tableWidgetCourseList->setHorizontalHeaderLabels(headerText);
為表頭新增核取方塊按鈕:
在表頭上新增核取方塊不能通過在表頭單元格中新增QCheckBox的方式實現,必須進行重繪,下面的程式碼是我們自定義的表頭類
myqheaderview.h的內容:
//該類實現自定義的表頭,主要是為了在表頭中加入CheckBox控制元件 class MyQHeaderView : public QHeaderView { Q_OBJECT public: explicit MyQHeaderView(Qt::Orientation orientation, QWidget *parent = 0); void setChecked(bool checked); signals: void headCheckBoxToggled(bool checked); protected: void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const; void mousePressEvent(QMouseEvent *event); private: QRect checkBoxRect(const QRect &sourceRect) const; bool m_isOn; };
myqheadview.cpp的內容:
MyQHeaderView::MyQHeaderView(Qt::Orientation orientation, QWidget *parent) : QHeaderView(orientation, parent) , m_isOn(false) { // set clickable by default setChecked(false); } void MyQHeaderView::setChecked(bool checked) { if (isEnabled() && m_isOn != checked) { m_isOn = checked; updateSection(0); emit headCheckBoxToggled(m_isOn); } } void MyQHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const { painter->save(); QHeaderView::paintSection(painter, rect, logicalIndex); painter->restore(); if (logicalIndex == 0) { QStyleOptionButton option; if (isEnabled()) option.state |= QStyle::State_Enabled; option.rect = checkBoxRect(rect); if (m_isOn) option.state |= QStyle::State_On; else option.state |= QStyle::State_Off; style()->drawControl(QStyle::CE_CheckBox, &option, painter); } } void MyQHeaderView::mousePressEvent(QMouseEvent *event) { if (isEnabled() && logicalIndexAt(event->pos()) == 0) { m_isOn = !m_isOn; updateSection(0); emit headCheckBoxToggled(m_isOn); } else QHeaderView::mousePressEvent(event); } QRect MyQHeaderView::checkBoxRect(const QRect &sourceRect) const { QStyleOptionButton checkBoxStyleOption; QRect checkBoxRect = style()->subElementRect(QStyle::SE_CheckBoxIndicator, &checkBoxStyleOption); QPoint checkBoxPoint(sourceRect.x()+5, sourceRect.y() + sourceRect.height() / 2 - checkBoxRect.height() / 2); return QRect(checkBoxPoint, checkBoxRect.size()); }
使用自定義表頭:
MyQHeaderView*myHeader=new MyQHeaderView(Qt::Horizontal, ui->tableWidgetCourseList); ui->tableWidgetCourseList->setHorizontalHeader(myHeader);
為QTableWidget新增一行資料實際上是根據行數和列數,迴圈QTableWidget的所有單元格,對每個單元格item設定資料來實現的。
QTabWidget控制元件
該控制元件類就是一個選項卡控制元件,有多個tab頁,下面是一些實用的方法:
切換到tab:
ui->tabWidgetExportEdit->setCurrentIndex(tabIndex);
移除選項卡:
ui->tabWidgetExportEdit->removeTab(tabIndex);
關於選項卡控制元件的操作不多,重要的是怎麼美化控制元件的顯示,QSS將會作為單獨的一篇文章來講解如何美化Qt中的各種控制元件。
QWebview控制元件
該控制元件是用於在Qt中顯示網頁的控制元件,一般而言會將contextMenuPolicy屬性設定為NoContextMenu隱藏系統為其提供的預設右鍵選單
<1>. 載入網頁:
ui->webViewCut->load(QUrl("http://www.baidu.com")); //如果是本地網頁,必須使用file:///的字首作為網頁地址 ui->webViewCut->load(QUrl("file:///c:/test.html "));
<2>. Qt程式碼中呼叫QWebview載入的網頁中的js函式:
//先作如下設定 ui->webViewCut->page()->setForwardUnsupportedContent(true); ui->webViewCut->page()->settings()->setAttribute(QWebSettings::JavascriptEnabled, true); ui->webViewCut->page()->settings()->setAttribute(QWebSettings::PluginsEnabled, true); ui->webViewCut->page()->settings()->setAttribute(QWebSettings::JavaEnabled, true); ui->webViewCut->page()->settings()->setAttribute(QWebSettings::AutoLoadImages, true); //然後在QWebview的loadFinished槽函式中呼叫js,該槽函式表示網頁已經載入完畢 QString js = QString("alert(\'hello Qt!\')"); ui->webViewCut->page()->mainFrame()->evaluateJavaScript(js);
<3>. 在QWebview載入的html的js程式碼中呼叫Qt的函式:
預設情況下在QwebViewCut中的網頁裡面的js不能直接呼叫Qt中的相關功能,這涉及到安全性問題。要滿足js中呼叫Qt的功能必須滿足下面的條件:
在Qt中暴露一個物件給js,然後js就可以在網頁中直接使用這個物件以及該物件的[特定]函式,要求是被暴露Qt物件必須繼承自QObject類,並且在js中呼叫這個暴露的物件的成員函式的定義是有要求的,該物件的滿足下面的要求的成員函式都可以直接被js呼叫:
1.必須是該物件的公共函式,並且在函式宣告前面新增Q_INVOKABLE修飾,例如:
public : Q_INVOKABLE int TestQt();
2.如果該函式被宣告成一個public slot 也可以不新增Q_INVOKABLE修飾:
public slots: void TestQt();
個人認為第一種方法更好,因為可以設定返回值,而Qt的槽函式是沒有返回值的,都是返回void,只需要呼叫this->ui->webViewCut->page()->mainFrame()->addToJavaScriptWindowObject("QtObj", this);
就可以將一個Qt物件,也就是這裡傳遞的this代表的物件,當然也可以直接傳遞其他物件指標,暴露給網頁中的javascript,網頁中的javascript在呼叫的時候可以直接使用 QtObj 去引用我們的Qt物件,以及通過QtObj去直接呼叫符合條件的Qt物件的成員函式。
那麼this->ui->webViewCut->page()->mainFrame()->addToJavaScriptWindowObject("QtObj", this);
程式碼在什麼時候執行呢? 推薦是在QWebFrame的訊號javaScriptWindowObjectCleared
發出的時候執行,所以我們可以在當前UI介面類的建構函式中新增下面的程式碼:
connect(ui->webViewCut->page()->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(populateJavaScriptWindowObject()));
然後在處理javaScriptWindowObjectCleared()
訊號的槽函式中實現上述暴露功能:
void MainWindow::populateJavaScriptWindowObject() { ui->webViewCut->page()->mainFrame()->addToJavaScriptWindowObject("QtObj", this); }
根據Qt文件上對該訊號的描述javaScriptWindowObjectCleared()
這個訊號會在我們呼叫QwebViewCut::load()
載入新的url之前就觸發,我們在這個時候去處理這個訊號,將我們需要暴露的Qt物件暴露給即將載入的網頁
<4>. 將Qt的屬性暴露出去供js呼叫,使用如下方法:
Q_PROPERTY(int Qtvalue READ testValue WRITE setTestValue)
將上面的語句加入到類的宣告中,在private塊下面就可以,最後不需要以分號結尾,例如:
private: Q_PROPERTY(int Qtvalue READ testValue WRITE setTestValue)
這一行的作用是將屬性 Qtvalue 註冊到Qt的元物件系統中,在js中可以通過名字Qtvalue來訪問該屬性,但在js中訪問該屬性的時候假設Qt暴露給js的物件為QtObj,那麼在js中可以這樣訪問該屬性:
QtObj.Qtvalue = 10; //設定該屬性的時候會呼叫void setTestValue(int) alert(QtObj.Qtvalue) //獲取該屬性的時候會呼叫 int testValue()
Q_PROPERTY(int Qtvalue READ testValue WRITE setTestValue)的結構如下:
Q_PROPERTY( 型別 屬性名 READ 返回屬性值的函式 WRITE 設定屬性值的函式 ) int Qtvalue int testValue() void setTestValue(int)
也就是說在js中我們可以直接使用Qtvalue,當獲取Qtvalue的值的時候會自動呼叫暴露物件的 int testValue() 函式 ,Qt規定其返回值必須與Q_PROPERTY語句中指定的型別相同,並且必須沒有引數。當我們為Qtvalue設定值的時候會呼叫暴露物件的void setTestValue(int)
函式,該函式必須有一個int型別的引數(型別也必須與前面Q_PROPERTY語句中指定的型別相同),並且不能有返回值。
經過實驗int testValue()
與void setTestValue(int)
函式的宣告在private區域也可以,好像無所謂。其實這兩個函式的名字是可以隨意定的,對js暴露的屬性名是Qtvalue,當訪問Qtvalue屬性的時候,會自動呼叫Q_PROPERTY宣告中READ後面指定的函式去獲取值,並且呼叫WRITE後面指定的函式去設定值,而不在乎這兩個函式的名字。
另外這兩個函式獲取的值或者設定的值從哪裡得來呢,我們可以在Qt物件中定義一個私有變數來儲存這個值,而這個私有變數的名字是無所謂的,甚至如果需要的話,我們也不必儲存這個值,直接在函式testValue裡面返回一個常量值,也就是說是否應該定義一個私有變數來儲存Qtvalue相關聯的屬性值,這個也不是必須的。
更多Qt QWidget與js的互動可以在Qt文件中搜尋 The Qt WebKit Bridge
關鍵字,其實Q_PROPERTY並不是專用於暴露屬性給js的,Q_PROPERTY是Qt元物件系統的一部分。
<5>. 如果在QWebview載入的網頁中有Flex應用程式,並且Qt中呼叫該QWebview載入的網頁中的js函式中需要呼叫flex程式暴露給js的介面,那麼還需要作如下設定:
在"%appdata%\Macromedia\Flash Player\#Security\FlashPlayerTrust\"
路徑下新建xxx.cfg檔案,將當前flex應用程式所在位置(也就是swf檔案所在的目錄)填寫到該檔案中即可,該xxx.cfg的名字是無所謂的,隨便什麼名字,在xxx.cfg檔案中指定的目錄路徑中的swf檔案的執行是被信任的。xxx.cfg檔案中可以指定多個目錄,每行一個。實際上%appdata%\Macromedia\Flash Player\#Security\FlashPlayerTrust\
路徑下也可以有多個檔名不同的cfg檔案。xxx.cfg檔案中指定的目錄實際上可以直接指定為根目錄,例如swf檔案的路徑是F:/xxx/yyy/zzz/test.swf
,那麼我們新建的xxx.cfg中的內容的第一行可以直接指定為F:/即可。
其實FlexBuilder在建立專案的時候,其生成的swf所在的目錄都被新增到了%appdata%\Macromedia\Flash Player\#Security\FlashPlayerTrust\
下面的flashbuilder.cfg中了,所以使用FlexBuilder除錯專案的時候,執行的swf都是被信任的。
使用QSS
QSS是Qt中的樣式表,用來定義Qt中控制元件的外觀,實際上QSS的語法與屬性大量參考了CSS,如果你有web的CSS開發經驗,幾乎沒有任何障礙就可以掌握QSS,QSS中的選擇器基本上與CSS中的相同,但是QSS只有幾種常用的選擇器型別。
QSS中選擇器的型別:
<1>. 型別選擇器,例如:QPushButton{} 設定所有型別是QPushButton或者繼承自QPushButton的控制元件的樣式。
<2>. 屬性選擇器,例如:QPushButton[flat="false"]{} 設定所有flat屬性是false的QPushButton控制元件的樣式。
<3>. 類選擇器,例如:.QPushButton{} 設定所有QPushButton的樣式,但是不會設定繼承自QPushButton型別的控制元件的樣式,QSS中的類選擇器與CSS中的含義不同,QSS中的類選擇器點號後面指定的類的名稱,而CSS中的類選擇器中的點號後面指定的是HTML標籤中的class屬性的名稱。
<4>. ID選擇器,例如:#okButton{} 設定所有物件名(object name)為okButton的控制元件的樣式。
<5>. 後代選擇器,例如:QDialog QPushButton{} 設定所有QDialog中的QPushButton子控制元件的樣式,只要是QDialog的子控制元件都會應用該樣式,包括直接或非直接的子控制元件。
<5>. 直接子選擇器:例如 QDialog > QPushButton{} 設定所有是QDialog直接子控制元件的QPushButton的樣式。
<6>. QSS支援選擇器分組,支援選擇器組合,例如:QPushButton#okButton{} 設定所有ID為okButton的QPushButton控制元件的樣式。#okButton,#cancelButton{} 設定id為okButton、cancelButton的控制元件的樣式。
那麼如何在Qt中使用這些QSS設定控制元件的外觀呢,一般在程式碼中通過呼叫控制元件物件的setStyleSheet(QString)成員函式進行設定,引數即是QSS字串。例如:
ui->btnTest->setStyleSheet("border:1px solid red;");//設定按鈕的邊框
另外我們可以將所有的QSS放到檔案中,例如main.qss,然後將該檔案新增到Qt的資原始檔中,在主UI介面中載入該main.qss檔案,並呼叫主UI介面類的成員函式設定其下控制元件的樣式:
QFile file(":/qss/main.qss"); file.open(QFile::ReadOnly); QTextStream filetext(&file); QString stylesheet = filetext.readAll(); this->setStyleSheet(stylesheet); file.close();
要注意的是main.qss中設定的樣式應該是針對當前UI介面上的控制元件的,也就是這裡呼叫的this->setStyleSheet(stylesheet);
中的this就是當前UI介面的類的例項。
有關QSS的細節很多,而且每個控制元件的美化技巧不同,同時QSS中還提供了偽類,子控制元件樣式等功能,限於篇幅,本節只做一個大致的介紹,後面將會單獨一篇文章詳細講解QSS的細節,以及如何美化Qt中的各種常見控制元件。
編碼問題
之前在寫Qt程式的時候,如果在原檔案中的字串直接寫中文,例如有些地方需要彈出錯誤或者警告的對話方塊提示,那麼提示內容就是中文資訊,我發現有部分字元會出現亂碼,並且有時候編譯的時候會報錯:error C2001: 常量中有換行符
這一般是編碼問題。我是這麼解決的,在包含中文(即使是註釋中有中文有時候也報錯)的原始檔的開頭加入 #pragma execution_character_set("utf-8")
這一行指定檔案的編碼,同時使用UE編輯器開啟該檔案,另存為UTF-8的編碼,在QtCreator中重新開啟即可。遇到跟我同樣問題的人也可以試一下這個辦法。
QT的記憶體管理
這一小節說題目命名為QT的記憶體管理,題目有點過大,其實我在寫Qt程式的時候,包括Qt的例子程式,中經常出現類似如下的程式碼:
void MainWidget::on_btnClick() { QLabel * lblMessage = new QLabel(“hello”,this); lblMessage->show(); }
似乎Qt中new出來的控制元件型別都只負責new不用delete的,感到很奇怪,後來經過查資料發現很多人有同樣的疑問,有人給出原因是因為Qt中的所有的控制元件類是繼承自QObject類,如果在new的時候指定了父親(在建構函式的引數中有parent這個引數),那麼它的清理是在其父親被delete的時候被delete的。Qt不建議程式設計師在程式碼中手工delete一個QObject,如果一定要這麼做,需要使用QObject的deleteLater()函式,否則可能出現Qt正在一級一級的從一個父親類開始清理下面的所有子物件的時候,程式中手工呼叫delete也去清理其中的子物件,那麼這個時候就可能出現問題,所以建議使用deleteLater()函式,它會讓所有事件都傳送完成之後再清理該片記憶體。
QT的訊號槽
在大多數Qt的程式設計中,我們通過Qt訊號槽機制來對滑鼠或鍵盤在介面上的操作進行響應處理,例如滑鼠點選按鈕的處理。Qt中的控制元件能夠發出什麼訊號,在什麼情況下發射訊號,這在Qt的文件中有說明,每個不同的控制元件能夠發射的訊號種類和觸發時機也是不同的。
如何為控制元件發射的訊號指定對應的處理槽函式呢,我們有兩種方式,第一種是在UI設計介面上操作:
在按鈕控制元件上點選右鍵,選擇“轉到槽”選單之後彈出如下的對話方塊:
可以看到按鈕控制元件會發射很多訊號,只要選擇一個訊號,點選OK之後就會生成對應的槽函式對按鈕發出的該訊號進行處理
void MainWindow::on_btnTest_clicked() { }
選擇clicked()訊號之後生成的處理該訊號的槽函式,除了通過UI介面自動生成槽函式的方式以外,我們還可以在程式碼中自己手寫槽函式,並通過QObject::connect()函式將特定物件的訊號與另外一個物件的槽函式進行連線,當該物件的訊號發射之後,會被關聯的物件的槽函式處理。例如我們可以用下面的一行程式碼完成上面的功能:
connect(ui->btnTest,SIGNAL(clicked()),this,SLOT(on_btnTest_clicked()));
使用程式碼的好處是,很多控制元件的訊號在上面的對話方塊中並沒有顯示出來,也就是說上面的對話方塊中其實只列出了該控制元件物件的一部分訊號,另外如果我們的物件是在程式中通過程式碼動態構建的,那麼我們也就需要在程式碼中為該控制元件的訊號指定處理的槽函式了。上面的connect程式碼是我們直接在UI介面類的建構函式中寫的(當然在任何地方都可以,並不一定要在建構函式中),由於UI介面類也是繼承自QObject所以自然也繼承了connect函式,通過connect函式我們可以將一個物件的訊號與另一個物件的槽函式進行連線,當個該物件的訊號發射的時候(訊號的發射時機有可能在程式碼中呼叫物件的某個成員函式觸發,也有可能在程式的UI介面上操作滑鼠,鍵盤等觸發)。
另外訊號與槽在通過connect函式連線的時候,其引數型別必須完全一致,否則是沒有效果的。實際上訊號槽的原理,是依賴於Qt的元物件系統,Qt的一系列的構建工具為程式設計師做了很多自動化的工作,自動生成了一些程式碼,所以使得我們看起來只需要用connect函式進行關聯之後,在訊號發射的時候(通過emit發射訊號),槽函式會被自動呼叫。在我們的Qt的專案的debug目錄下,我們往往會看到很多以moc_為字首的cpp檔案,開啟這些檔案我們就可以看到該檔案中的qt_meta_data_為字首的靜態陣列裡面描述了訊號槽的關聯資訊,而在qt_static_metacall函式的實現中,我們可以大致看到通過一系列的case分支,對應的槽函式被呼叫。如果要詳細研究Qt的訊號槽的實現原理,可以研究QObject類的原始碼,以及Qt的元物件系統。
槽函式被slots修飾,當然它可以是普通的成員函式。訊號被signals修飾。一個訊號可以關聯多個槽函式,當訊號被髮射的時候,這些槽函式依次被執行,但是執行的順序是未知的,一個槽函式可以被多個訊號關聯。一個訊號也可以關聯另外一個訊號,當該訊號被髮射的時候,與它關聯的訊號也被髮射。通過disconnect函式可以取消訊號與槽函式之間的關聯關係。在槽函式中直接呼叫sender()就可以獲得觸發該槽函式的訊號源物件,該函式是QObject的成員函式,返回的也是一個QObject型別的指標。
另外訊號槽可以在不同的執行緒之間使用,但是使用的時候需要注意呼叫connect時候指定連線的方式,不同的執行緒之間Qt可以通過訊息佇列來實現訊號與槽函式的關聯,我經常在UI執行緒中關聯另外一個工作執行緒的訊號到UI介面類中的成員函式,以便在工作執行緒中通過傳送訊號的方式來呼叫UI主執行緒中的UI介面類的成員函式,來達到更新UI介面的效果。Qt中不能在工作執行緒中直接對UI介面控制元件進行操作。有關訊號的連線方式可以參考這篇文章:對訊號與事件的認識(http://blog.chinaunix.net/uid-25147458-id-3706122.html)
QT中繪圖
我們可以在Qt中繪圖,在Qt的控制元件上繪圖,一般是需要重寫該控制元件的重繪事件的,例如:
void MovieImageWidget::paintEvent(QPaintEvent*p) { QPainter painter(this); if(this->currentImagePath!="") { QImage image(this->currentImagePath); QRect rect(0,0,this->width(),this->height()); painter.drawImage(rect,image); } }
在重繪事件中,我們先建立一個基於控制元件的QPainter物件,然後在重繪事件函式中,我們就可以利用該painter物件的一系列的繪製函式進行繪圖操作了,繪製的圖形會在該Painter關聯的控制元件上顯示,其原點座標是從該控制元件的左上角開始的。在需要的時候我們可以手工呼叫控制元件的update()函式,這樣會直接觸發重繪事件進行重繪。
QPainter類提供的一系列的draw函式可以幫助我們繪製各種各樣的圖形,這裡就不再舉例說明,可以自行查閱Qt的幫助文件。
QT的執行緒
Qt的執行緒使用起來非常簡單,我們首先要建立一個自定義的類(例如MyThread),繼承自QThread,並實現其run方法即可。在使用執行緒的時候直接得到MyThread的例項,呼叫其start()函式即可啟動執行緒,執行緒啟動之後會自動呼叫其實現的run方法,該方法就是執行緒的執行函式,我們的執行緒任務就寫在這裡,當run退出之後執行緒基本就結束了,QThread有一個started和finished訊號,我們可以為這兩個訊號指定槽函式,線上程啟動和結束的時候執行一段程式碼進行資源的初始化和資源的釋放操作。
QT中使用第三方的dll
通過QtCreator的嚮導可以非常方便的在Qt程式中使用第三方的dll,具體步驟如下:
在專案上點選右鍵,選擇“新增庫”選單
選擇外部庫
指定對應的lib檔案,以及標頭檔案的包含路徑,設定平臺為windows,選擇庫的連線型別然後點選下一步
最後點選完成既可,可以看到實際上在Qt的個工程檔案中,也就是pro檔案中新增了如下的程式碼:
win32: LIBS += -L$$PWD/E://trans/ -lTransAPI INCLUDEPATH += $$PWD/E:/trans/include DEPENDPATH += $$PWD/E:/trans/include win32:!win32-g++: PRE_TARGETDEPS += $$PWD/E:/trans/TransAPI.lib else:win32-g++: PRE_TARGETDEPS += $$PWD/E:/trans/libTransAPI.a
在需要使用的地方,包含標頭檔案之後就可以就可以直接呼叫庫裡面的函式了,使用方式與VC中沒有區別。
QT中為控制元件新增右鍵選單的方法
在Qt中QWidget控制元件以及其子類都可以新增右鍵選單,Qt中所有介面上顯示的控制元件基本都繼承自QWidget控制元件,所以基本上Qt中的控制元件都可以新增右鍵選單,下面舉例說明為按鈕新增右鍵選單的方法:
<1>. 在UI設計介面中選中按鈕,在屬性欄中設定其屬性contextMenuPolicy
的值為CustomContextMenu
(如果控制元件是在程式碼中生成,可以通過控制元件物件的成員函式setContextMenuPolicy()
在程式碼中設定)
<2>. 在UI設計介面的按鈕上單擊右鍵,轉到槽,在彈出的對話方塊中選擇customContextMenuRequested(const QPoint&)
,單擊確定,為按鈕的該訊號指定槽函式,在程式碼中可以通過connect手工關聯。
<3>. 在該槽函式中生成選單程式碼如下:
void MainWindow::on_menu_click(bool checked) { //通過sender()得到訊號的傳送物件,也就是哪個選單項被單擊 } void MainWindow::on_btnTest_customContextMenuRequested(const QPoint &pos) { QMenu *cmenu = new QMenu(ui->btnTest); QAction *action1 = cmenu->addAction("Menu 1"); QAction *action2 = cmenu->addAction("Menu 2"); QAction *action3 = cmenu->addAction("Menu 3"); connect(action1, SIGNAL(triggered(bool)), this, SLOT(on_menu_click(bool))); connect(action2, SIGNAL(triggered(bool)), this, SLOT(on_menu_click(bool))); connect(action3, SIGNAL(triggered(bool)), this, SLOT(on_menu_click(bool))); cmenu->exec(QCursor::pos()); }
當然這裡僅僅是demo程式碼,每次點選右鍵的時候,我們都要重新new出選單來,這樣肯定會耗費資源,這些選單建立的程式碼可以放在一個全域性的函式中,只需要建立一次,但是cmenu->exec(QCursor::pos());
這條語句是顯示選單用的,執行之後選單才能顯示出來,所以每次槽函式被執行的時候都需要呼叫一次來撥出選單。
最終顯示效果如下:
除了上面的方法之外,還可以通過重寫contextMenuEvent()
事件來實現右鍵選單,這裡就不細說了,可以自行百度。
結束語
本篇總結性的講解了Qt的諸多方面的知識點,有些地方限於篇幅,可能需要單獨另起一篇文章進行講解,有的是我自己也並沒有完全弄透徹怕誤人子弟。
由於公司需要開發一個視窗程式,要求不需要安裝附帶的框架,所以.NET就被排除在外了,因為公司之前有同事使用WPF開發過其他的程式,介面也比較漂亮,但是工程部的同事在外面部署的時候由於安裝框架的原因經常出現各種系統問題。至於MFC太古老,學習週期長,所以也被排除,另外兩個一個是Flex AIR,一個是Qt,權衡之下還是選擇了Qt,經過一個月的邊學邊做,效果還可以。其實Qt還是比較好學的,基本上熟練掌握了QSS的話,也可以實現非常好的介面效果,而且還是跨平臺的,特別是在嵌入式系統中,如果需要顯示介面的話,會是一個非常好的選擇。
希望這篇文章對大家有所幫助,由於篇幅比較長,雖然我已經檢查過,如果發現文字錯誤,還希望園友不吝指正,我會及時改正。