為什么在頭文件中有的是使用前置聲明,而有的是包含頭文件晕讲?
如下代碼:
#include <QDialog>
class QCheckBox;
class QLabel;
class FindDialog :public QDialog
{
Q_OBJECT
public:
FindDialog(QWidget *parent=0);
private:
QCheckBox *checkBox;
QLabel *label;
}
- 前置聲明(forward declaration)會(huì)告訴C++編譯程序類的存在况芒,而不需要提供類定義中的所有的細(xì)節(jié)(通常放在它自己的頭文件當(dāng)中)憎账。因?yàn)槲覀兊倪@些私有變量都是指針面氓,而且沒(méi)有必要在頭文件當(dāng)中就去訪問(wèn)它們羔味,因而編譯程序就無(wú)需這些類的完整的定義河咽。我們不使用這些類的相關(guān)的頭文件(如
<QCheckBox>
,<QLabel>
),而是使用前置聲明,這可以使得變異的過(guò)程更快一些赋元。 - 在真正的實(shí)現(xiàn)文件.cpp當(dāng)中忘蟹,我們可以包含
#include <QtGui>
,這個(gè)頭文件包含了Qt GUI類的定義。Qt由數(shù)個(gè)模塊組成搁凸,每一個(gè)模塊都有自己的類庫(kù)媚值。最為重要的模塊有QtCore、QtGui护糖、QtNetwork褥芒、QtOpenGL、QtScript嫡良、QtSql锰扶、QtSvg献酗、QtXml。其中少辣,在<QtGui>
頭文件中為構(gòu)成QtCore和QtGui組成部分的所有類進(jìn)行了定義凌摄。在程序中包含這個(gè)頭文件,就能夠使我們省去在每一個(gè)類中分別包含的麻煩漓帅。在頭文件中本可以直接包含<QtGui>
即可锨亏,然而在一個(gè)頭文件中再包含一個(gè)那么大的頭文件實(shí)在不是一種好的編程風(fēng)格,尤其對(duì)于比較大的工程項(xiàng)目更是如此忙干。
為什么在Qt中大多數(shù)時(shí)候都是只見(jiàn)new 不見(jiàn)delete?
- Qt會(huì)在刪除父對(duì)象的時(shí)候自動(dòng)的刪除其所屬的子對(duì)象,包括其所有的子窗口部件和子布局對(duì)象.
信號(hào)和槽機(jī)制
- 信號(hào)和槽機(jī)制是Qt編程的基礎(chǔ).它可以讓應(yīng)用程序的編程人員把這些互不了解的對(duì)象綁定在一起.槽和普通的C++成員函數(shù)幾乎是一樣的----可以是虛函數(shù);可以被重載,可以是公有,保護(hù),私有類型,并且也可以直接被其他的C++成員函數(shù)直接調(diào)用.它們的參數(shù)可以使任意的類型.唯一不同的是:槽還可以和信號(hào)連接在一起.每次發(fā)射和槽連接在一起的信號(hào)時(shí),就會(huì)自動(dòng)的觸發(fā)槽函數(shù).
-
一個(gè)信號(hào)可以連接多個(gè)槽:
connect(slider,SIGNAL(valueChanged(int)),this,SLOT(setValue(int)))
,connect(slider,SIGNAL(valueChanged(int)),this,SLOT(updateStatusBarIndicator(int)))
.在發(fā)射這個(gè)信號(hào)的時(shí)候會(huì)以不確定的順序一個(gè)接一個(gè)地調(diào)用這些槽. - 多個(gè)信號(hào)可以連接同一個(gè)槽函數(shù)
-
一個(gè)信號(hào)也可以和另外的一個(gè)信號(hào)相連接:
connect(slider,SIGNAL(valueChanged(int)),this,SIGNAL(updateRecord(int)));
當(dāng)發(fā)射第一個(gè)信號(hào)的時(shí)候,第二個(gè)信號(hào)也會(huì)被觸發(fā).除此之外,信號(hào)和信號(hào)之間的連接和信號(hào)和槽之間的連接是難以區(qū)分的. -
連接可以被移除:
disconnect(slider,SIGNAL(valueChanged(int)),this,SIGNAL(updateRecord(int)));
這種情況比較少用,因?yàn)楫?dāng)刪除掉對(duì)象時(shí),Qt會(huì)自動(dòng)的移除和這個(gè)對(duì)象相關(guān)的所有的連接. - 對(duì)應(yīng)的信號(hào)和槽必須要有相同順序和類型的參數(shù),但是允許槽的參數(shù)比信號(hào)的參數(shù)少,信號(hào)中多余的參數(shù)會(huì)被簡(jiǎn)單的忽略掉.
- Qt的信號(hào)和槽機(jī)制是在QObject中實(shí)現(xiàn)的,并不僅僅局限于圖形用戶界面編程當(dāng)中,這種機(jī)制可以使用于任何的QObject的子類當(dāng)中.
Qt的元對(duì)象系統(tǒng)
- Qt的主要成就之一就是使用了一種機(jī)制對(duì)C++進(jìn)行了擴(kuò)展,并且使用了這種機(jī)制創(chuàng)建了獨(dú)立的軟件組件.這些組件可以綁定在一起,但是任何組件對(duì)于他所要連接的組件的情況事先都一無(wú)所知.這種機(jī)制稱為元對(duì)象系統(tǒng)(meta-object system),它提供了關(guān)鍵的兩項(xiàng)技術(shù):信號(hào)-槽以及內(nèi)省(introspection).內(nèi)省功能對(duì)于實(shí)現(xiàn)信號(hào)和槽是必須的.并且允許應(yīng)用程序的開(kāi)發(fā)人員在運(yùn)行的時(shí)候獲得有關(guān)QObject子類的元信息(meta-information),包括一個(gè)含有對(duì)象的類名以及他所支持的信號(hào)和槽的列表.這一機(jī)制也支持屬性(廣泛用于Qt的設(shè)計(jì)師中)和文本翻譯(用于國(guó)際化),并且它也為QtScript模塊奠定了基礎(chǔ).
- 標(biāo)準(zhǔn)C++中并沒(méi)有對(duì)Qt的元對(duì)象系統(tǒng)所需要的動(dòng)態(tài)元信息提供支持.Qt通過(guò)一個(gè)獨(dú)立的moc工具解決了這個(gè)問(wèn)題,moc解析Q_OBJECT類的定義并且通過(guò)C++函數(shù)來(lái)提供可供使用的信息.由于moc使用純C++來(lái)實(shí)現(xiàn)其所有的功能,所以Qt的元對(duì)象系統(tǒng)可以在任意的C++編譯器上工作.
這一機(jī)制的工作過(guò)程是:
- Q_OBJECT宏聲明了在每一個(gè)QObject子類中必須要實(shí)現(xiàn)的一些內(nèi)省函數(shù):metaObject()器予、tr(),qt_metacall(),以及其他的一些函數(shù).
- Qt的moc工具生成了用于由Q_OBJECT聲明的所有函數(shù)和所有信號(hào)的實(shí)現(xiàn).
- 像connect()和disconnect()這樣的QObject的成員函數(shù)使用這些內(nèi)省函數(shù)來(lái)完成他們的工作.
由于所有的這些工作都是由qmake,moc和QObject自動(dòng)處理的,所以很少需要再去考慮這些事情.但是如果你對(duì)此充滿好奇,那么也可以閱讀以下有關(guān)QMetaObject類的文檔和由moc生成的C++源代碼文件,可以從中看出這些實(shí)現(xiàn)工作是如何進(jìn)行的.
元對(duì)象工具
- 元對(duì)象編譯器moc(meta object compiler)對(duì)C++文件中的類聲明進(jìn)行分析并產(chǎn)生用于初始化元對(duì)象的C++代碼,元對(duì)象包含全部信號(hào)和槽的名字以及指向這些函數(shù)的指針. moc讀取C++源文件,如果發(fā)現(xiàn)有Q_OBJECT宏聲明的類,他就會(huì)生成另外一個(gè)C++源文件,這個(gè)新生成的源文件中包含有該類的元對(duì)象代碼. 例如,假設(shè)我們有一個(gè)頭文件mysignal.h捐迫,在這個(gè)文件中包含有信號(hào)或槽的聲明乾翔,那么在編譯之前 moc 工具就會(huì)根據(jù)該文件自動(dòng)生成一個(gè)名為mysignal.moc.h的C++源文件并將其提交給編譯器;類似地施戴,對(duì)應(yīng)于mysignal.cpp文件moc 工具將自動(dòng)生成一個(gè)名為mysignal.moc.cpp文件提交給編譯器反浓。
- 元對(duì)象代碼是signal/slot機(jī)制所必須的。用moc產(chǎn)生的C++源文件必須與類實(shí)現(xiàn)一起進(jìn)行編譯和連接赞哗,或者用#include語(yǔ)句將其包含到類的源文件中雷则。moc并不擴(kuò)展#include或者#define宏定義,它只是簡(jiǎn)單的跳過(guò)所遇到的任何預(yù)處理指令。
Qt 進(jìn)程間通信
Qt的通信可分為Qt內(nèi)部通信和外部通信兩大類肪笋。對(duì)于這兩類通信機(jī)制及應(yīng)用場(chǎng)合做如以下分析:
1. Qt內(nèi)部對(duì)象間通信
對(duì)于這種內(nèi)部對(duì)象間的通信月劈,QT主要采用了信號(hào)和槽的機(jī)制。
-
QT的信號(hào)和槽機(jī)制是用來(lái)在對(duì)象間通訊的方法藤乙,當(dāng)一個(gè)特定事件發(fā)生的時(shí)候猜揪,signal會(huì)被 emit 出來(lái),slot 調(diào)用是用來(lái)響應(yīng)相應(yīng)的 signal 的坛梁。簡(jiǎn)單點(diǎn)說(shuō)就是如何在一個(gè)類的一個(gè)函數(shù)中觸發(fā)另一個(gè)類的另一個(gè)函數(shù)調(diào)用,而且還要把相關(guān)的參數(shù)傳遞過(guò)去.好像這和回調(diào)函數(shù)也有點(diǎn)關(guān)系,但是消息機(jī)制可比回調(diào)函數(shù)有用多了,也復(fù)雜多了而姐。例如,實(shí)現(xiàn)單擊按鈕終止應(yīng)用程序運(yùn)行的代碼
connect(button , SIGNAL(clicked()) , qApp , SLOT(quit()) );
實(shí)現(xiàn)過(guò)程就是一個(gè)button被單擊后會(huì)激發(fā)clicked信號(hào)划咐,通過(guò)connect()
函數(shù)的連接qApp會(huì)接收到此信號(hào)并執(zhí)行槽函數(shù)quit()
毅人。在此過(guò)程中,信號(hào)的發(fā)出并不關(guān)心什么樣的對(duì)象來(lái)接收此信號(hào)尖殃,也不關(guān)心是否有對(duì)象來(lái)接收此信號(hào),只要對(duì)象狀態(tài)發(fā)生改變此信號(hào)就會(huì)發(fā)出划煮。此時(shí)槽也并不知曉有什么的信號(hào)與自己相聯(lián)系和是否有信號(hào)與自己聯(lián)系送丰,這樣信號(hào)和槽就真正的實(shí)現(xiàn)了程序代碼的封裝,提高了代碼的可重用性弛秋。 - 關(guān)鍵字signals指出隨后開(kāi)始信號(hào)的聲明器躏,這里signals用的是復(fù)數(shù)形式而非單數(shù)俐载,siganls沒(méi)有public、 private登失、protected等屬性遏佣,這點(diǎn)不同于slots。另外揽浙,signals状婶、slots關(guān)鍵字是QT自己定義的,不是C++中的關(guān)鍵字馅巷。信號(hào)也可采用C++中虛函數(shù)的形式進(jìn)行聲明膛虫,也可以實(shí)現(xiàn)重載.
- 宏定義不能用在signal和slot的參數(shù)中,因?yàn)閙oc工具不擴(kuò)展#define,因此钓猬,在signals和slots中攜帶參數(shù)的宏就不能正確地工作稍刀,如果不帶參數(shù)是可以的。
- 函數(shù)指針不能作為信號(hào)或槽的參數(shù)敞曹。
class someClass : public QObject
{
Q_OBJECT
public slots:
void apply(void (*applyFunction)(QList*, void*), char*); // 不合語(yǔ)法
};
typedef void (*ApplyFunctionType)(QList*, void*);
class someClass : public QObject
{
Q_OBJECT
public slots:
void apply( ApplyFunctionType, char *); // 合法
};
- 信號(hào)與槽也不能攜帶模板類參數(shù)账月。
public slots:
void MyWidget::setLocation (pair location); // 不和法
signals:
void MyObject::moved (pair location); //不和法
typedef pair IntPair;
public slots:
void MyWidget::setLocation (IntPair location); //合法
signals:
void MyObject::moved (IntPair location); //合法
2. Qt 與外部對(duì)象間的通信
QT與外部通信主要是將外部發(fā)來(lái)的消息以事件的方式進(jìn)行接收處理。外部設(shè)備將主要通過(guò)socket與QT應(yīng)用程序進(jìn)行連接澳迫。在此局齿,以輸入設(shè)備與QT應(yīng)用程序的通信為例說(shuō)明QT與外部通信的原理。
- 在QT的應(yīng)用程序開(kāi)始運(yùn)行時(shí)纲刀,主程序?qū)⑼ㄟ^(guò)函數(shù)調(diào)用來(lái)創(chuàng)建并啟動(dòng)qwsServer服務(wù)器项炼,然后通過(guò)socket建立該服務(wù)器與輸入硬件設(shè)備的連接。服務(wù)器啟動(dòng)后將會(huì)打開(kāi)鼠標(biāo)與鍵盤設(shè)備示绊,然后將打開(kāi)的設(shè)備文件描述符fd連接到socket上锭部。等到QT應(yīng)用程序進(jìn)入主事件循環(huán)時(shí),事件處理程序?qū)⑼ㄟ^(guò)Linux系統(tǒng)的select函數(shù)來(lái)檢測(cè)文件描述符fd的狀態(tài)變化情況以實(shí)現(xiàn)對(duì)socket的監(jiān)聽(tīng)面褐。如果文件描述符fd狀態(tài)改變拌禾,說(shuō)明設(shè)備有數(shù)據(jù)輸入。此時(shí)展哭,事件處理程序?qū)?huì)發(fā)出信號(hào)使設(shè)備輸入的數(shù)據(jù)能及時(shí)得到QT應(yīng)用程序的響應(yīng)湃窍。數(shù)據(jù)進(jìn)入服務(wù)器內(nèi)部就會(huì)以事件的形式將數(shù)據(jù)放入事件隊(duì)列里,等待QT客戶應(yīng)用程序接收處理匪傍。處理結(jié)束后再將事件放入請(qǐng)求隊(duì)列里您市,通過(guò)服務(wù)器將事件發(fā)送到相應(yīng)硬件上,完成外部輸入設(shè)備與QT應(yīng)用程序的整個(gè)通信過(guò)程役衡。
3. Qt 與其外部進(jìn)程通信
QT可以通過(guò)QProcess類實(shí)現(xiàn)前端程序?qū)ν獠繎?yīng)用程序的調(diào)用茵休。這個(gè)過(guò)程的實(shí)現(xiàn)首先是將前端運(yùn)行的程序看成是QT的主進(jìn)程,然后再通過(guò)創(chuàng)建主進(jìn)程的子進(jìn)程來(lái)調(diào)用外部的應(yīng)用程序。這樣QProcess的通信機(jī)制就抽象為父子進(jìn)程之間的通信機(jī)制榕莺。QProcess在實(shí)現(xiàn)父子進(jìn)程間的通信過(guò)程中是運(yùn)用Linux系統(tǒng)的無(wú)名管道來(lái)實(shí)現(xiàn)的.
4. Qt 內(nèi)部進(jìn)行間通信
- QCOP協(xié)議
- 信號(hào)-槽(Signal-Slot)機(jī)制
- FIFO機(jī)制
4. 其他通信方式
般操作系統(tǒng)中常用的進(jìn)程間通信機(jī)制也都可以用于QT系統(tǒng)內(nèi)部不同進(jìn)程之間的通信俐芯,如消息隊(duì)列、共享內(nèi)存钉鸯、信號(hào)量吧史、有名管道等機(jī)制。其中信號(hào)量機(jī)制在QT中已經(jīng)重新進(jìn)行了封裝唠雕;有些機(jī)制則可以直接通過(guò)操作系統(tǒng)的系統(tǒng)調(diào)用來(lái)實(shí)現(xiàn)贸营。另外,如果我們只是想通過(guò)管道或socket來(lái)實(shí)現(xiàn)較簡(jiǎn)單的外部通信及塘,也可以重新創(chuàng)建管道或socket來(lái)實(shí)現(xiàn)自己要求的功能莽使。
Qt 的事件處理機(jī)制
- 產(chǎn)生事件: 輸入設(shè)備,鍵盤鼠標(biāo)等笙僚。keyPressEvent芳肌,keyReleaseEvent,mousePressEvent,mouseReleaseEvent事件(他們被封裝成QMouseEvent和QKeyEvent),這些事件來(lái)自底層的操作系統(tǒng)肋层,他們以異步的形式通知Qt事件處理系統(tǒng)亿笤。Qt也會(huì)產(chǎn)生很多事件,如QObject::startTimer()會(huì)觸發(fā)QTimerEvent栋猖。用戶的程序還能夠自定義事件净薛。
- 事件的接收和處理者:QObject類是整個(gè)的Qt對(duì)象模型的核心,事件處理機(jī)制是QObject三大職責(zé)(內(nèi)存管理蒲拉、內(nèi)省(intropection)與事件處理機(jī)制)之一肃拜。任何一個(gè)想要接收并處理事件的對(duì)象必須要繼承自QObject類,可以選擇重載QObject::event()函數(shù)或者是事件的處理權(quán)轉(zhuǎn)交給父類雌团。
- 事件的派送者:對(duì)于non-GUI的Qt程序燃领,由QCoreApplication負(fù)責(zé)將QEvent分發(fā)給QObject的子類-Receiver;對(duì)于GUI程序锦援,則由QApplication負(fù)責(zé)派送猛蔽。
Qt 事件過(guò)濾器
Qt事件模型真正強(qiáng)大的特色是一個(gè)QObject的實(shí)例能夠管理另外一個(gè)QObject實(shí)例的事件.
假設(shè)已有一個(gè)CustomerInfoDialog的小部件,其包含有一系列的QLineEdit,現(xiàn)在,我們想要使用空格來(lái)代替Tab,使得焦點(diǎn)在這些QLineEdit之間進(jìn)行切換.
- 方法一: 子類化QLineEdit,重新實(shí)現(xiàn)keyPressEvent(),并在keyPressEvent()里面調(diào)用focusNextChild().如下:
void MyLineEdit::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Space) {
focusNextChild();
} else {
QLineEdit::keyPressEvent(event);
}
}
但是這樣做的缺點(diǎn)也是很明顯的,若有很多不同的空間(如QComboBox,QEdit,QSpinBox等),我們就必須要子類化這么多控件,這是一個(gè)繁瑣的任務(wù).
- 方法二: 安裝事件過(guò)濾器,讓CustomerInfoDialog去管理他的子部件的按鍵事件.通常在構(gòu)造函數(shù)中安裝事件管理器.如下:
CustomerInfoDialog::CustomerInfoDialog(QWidget *parent)
: QDialog(parent)
{
// ...
firstNameEdit->installEventFilter(this);
lastNameEdit->installEventFilter(this);
cityEdit->installEventFilter(this);
phoneNumberEdit->installEventFilter(this);
}
bool CustomerInfoDialog::eventFilter(QObject *target, QEvent *event)
{
if (target == firstNameEdit || target == lastNameEdit
|| target == cityEdit || target == phoneNumberEdit) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Space) {
focusNextChild();
return true;
}
}
}
return QDialog::eventFilter(target, event);
}
Qt 提供五個(gè)級(jí)別的事件處理和過(guò)濾
- 重新實(shí)現(xiàn)事件.如:mousePressEvent(),keyPressEvent(),paintEvent()...這是最常規(guī)的事件處理方法.
- 重新實(shí)現(xiàn)QObject::event().這一般用在Qt沒(méi)有提供該事件的處理函數(shù)時(shí).也就是我們自己增加新的事件.
- 安裝事件過(guò)濾器
- 在QApplication上安裝事件過(guò)濾器.QApplication上的事件過(guò)濾器將會(huì)捕獲應(yīng)用程序的所有的事件,而且第一個(gè)獲得該事件.也就是說(shuō)事件在發(fā)送給其他的任何一個(gè)event filter之前發(fā)送給QApplication的event filter.
- 重新實(shí)現(xiàn)QApplication的notify()方法.Qt使用notify()來(lái)分發(fā)事件.要想在任何的事件處理器捕獲事件之前捕獲事件,唯一的方法就是重新實(shí)現(xiàn)QApplication的notify()方法.
例如:實(shí)現(xiàn)一個(gè)模擬時(shí)鐘小部件,主要就是使用paintEvent事件.
/********************widget.h********************/
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
protected:
void paintEvent(QPaintEvent *e);
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
/********************widget.cpp*************************/
#include "widget.h"
#include "ui_widget.h"
#include <QtGui>
#include <QTime>
#include <QDebug>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//一秒更新一次,repaint
QTimer *timer = new QTimer(this);
connect(timer,SIGNAL(timeout()),this,SLOT(repaint()));
timer->start(1000);
this->setWindowTitle(tr("Clock"));
this->resize(400,400);
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *e)
{
//時(shí)分秒指針顏色
QColor hourColor(127,0,127);
QColor minuteColor(0,127,127,191);
QColor secColor(Qt::black);
int side = qMin(this->width(), this->height());
QTime time = QTime::currentTime();
//正式開(kāi)始繪畫
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);//無(wú)鋸齒
painter.translate(this->width()/2, this->height()/2);//將坐標(biāo)系原點(diǎn)移動(dòng)到正中心
painter.scale(side/200.0, side/200.0);//
painter.setPen(Qt::NoPen);
painter.setBrush(QBrush(hourColor));//繪制特殊圖形灵寺,用brush
painter.save();//保存當(dāng)前painter狀態(tài)
painter.rotate(30.0 * (time.hour() + time.minute()/60.0));//坐標(biāo)系旋轉(zhuǎn)曼库,根據(jù)真實(shí)時(shí)間確定旋轉(zhuǎn)角度
static const QPoint hourHand[3] = {
QPoint(7,8),
QPoint(-7,8),
QPoint(0,-40)
};//
painter.drawConvexPolygon(hourHand, 3); //畫不規(guī)則多邊形
painter.restore();//恢復(fù)save前原來(lái)的坐標(biāo)系
painter.setPen(hourColor);//畫刻度線,用Pen略板,不用Brush
for(int i=0; i<12; i++){
painter.drawLine(88,0,96,0);//12小時(shí)對(duì)應(yīng)的刻度
painter.rotate(30.0);
}
//畫1-12數(shù)字毁枯,根據(jù)數(shù)學(xué)半徑sin,cos計(jì)算所畫text的位置
int textR = 80; //big ridaus
int textW = 12; //width = height
int textH = 8;
const double pi = 3.1415926;
for(int i=0; i<12; i++){
double angle = 30.0 * i * pi/ 180;
int x = textR * cos(angle) - textW/2;
int y = textR * sin(angle) - textH/2;
//qDebug()<<i<<angle<<x<<y;
painter.drawText(QRect(x, y, textW, textH), Qt::AlignCenter,
QString("%1").arg((i+3)>12?(i+3-12):(i+3)));
}
//畫分叮称,同上
painter.setPen(Qt::NoPen);
painter.setBrush(QBrush(minuteColor));
painter.save();
painter.rotate(6.0 * (time.minute() + time.second()/60.0));
static const QPoint minuteHand[3] = {
QPoint(7,8),
QPoint(-7,8),
QPoint(0,-70)
};
painter.drawConvexPolygon(minuteHand, 3);
painter.restore();
painter.setPen(minuteColor);
for (int j=0; j<60; j++){
if ((j%5) != 0){
painter.drawLine(92, 0, 96, 0);
}
painter.rotate(6.0);
}
//畫秒种玛,同上
painter.setPen(Qt::NoPen);
painter.setBrush(QBrush(secColor));
painter.save();
painter.rotate(time.second() * 6.0);
static const QPoint secHand[3] = {
QPoint(3,4),
QPoint(-3,4),
QPoint(0,-85)
};
painter.drawConvexPolygon(secHand, 3);
painter.restore();
}
Qt 中的窗口刷新事件
- 在窗體刷新事件當(dāng)中胀糜,主要說(shuō)明一下
paintEvent
的使用:void QWidget::paintEvent(QPaintEvent *event)
. Paint 這個(gè)事件只要是窗體事件需要被重繪了就會(huì)被調(diào)用,他是由窗體事件產(chǎn)生的,但是要求程序重畫窗體部件的時(shí)候蒂誉,事件循環(huán)就會(huì)從事件隊(duì)列當(dāng)中選中這個(gè)事件并把它分發(fā)到那個(gè)需要重畫的widget當(dāng)中。并不是所有的paint事件都是由窗口系統(tǒng)產(chǎn)生的距帅,你也可以使用repaint()和update()來(lái)使用它右锨。但是你需要知道的是,因?yàn)閜aintEvent()函數(shù)是protected的碌秸,你無(wú)法直接調(diào)用它绍移。它也繞開(kāi)了任何存在的事件過(guò)濾器。因?yàn)檫@些原因讥电,Qt提供了一個(gè)機(jī)制蹂窖,直接sending事件而不是posting。 -
void QWidget::update()
:用于更新窗體部件恩敌,它規(guī)劃了所要處理的繪制事件瞬测。但是可以被Qt優(yōu)化,有時(shí)update()
執(zhí)行之后不一定會(huì)直接轉(zhuǎn)到paintEvent纠炮。因?yàn)镼t會(huì)把多個(gè)繪制事件自動(dòng)的合并成一個(gè)來(lái)加快繪制的速度,所以推薦使用這個(gè)月趟,而不是repaint()
,幾次調(diào)用update()
的結(jié)果通常僅僅是一次paintEvent()
調(diào)用。利用這一點(diǎn)恢口,在實(shí)現(xiàn)程序的時(shí)候孝宗,我們可以把所有的繪制窗體的那些語(yǔ)句、函數(shù)...都放到paintEvent中耕肩,通過(guò)各種if-else語(yǔ)句進(jìn)行判斷來(lái)繪制因妇,這樣對(duì)速度有很好的優(yōu)化并且可以防止閃爍。 - 繪制事件還有一點(diǎn)需要注意的是:當(dāng)繪制事件發(fā)生時(shí)猿诸,更新的區(qū)域通常被擦除婚被。如果需要在上一次繪制的基礎(chǔ)上進(jìn)行繪制的話,我們的做法是:使用一個(gè)臨時(shí)變量保存著上次繪制之后的圖两芳,然后在這個(gè)圖上進(jìn)行繪制摔寨,最后再直接的顯示一下這個(gè)圖就ok了。這是個(gè)比較笨的方法怖辆,但也簡(jiǎn)單是复。通過(guò)QPaintEvent::erased()可以得知這個(gè)窗口部件是否被擦除。寫完之后記得檢查一下 竖螃,如果在設(shè)置了WRepaintNoErase窗口部件標(biāo)記的時(shí)候是不會(huì)被擦除的淑廊。
Qt 多線程
- 假如一個(gè)類的任何函數(shù)在此類的多個(gè)不同的實(shí)例上,可以被多個(gè)線程同時(shí)調(diào)用,那么這個(gè)類被稱為是可重入的,假如不同的線程作用在同一個(gè)實(shí)例上,而其仍然能夠正常的工作,那么稱之為線程安全的.大多數(shù)的C++類天生就是可重入的,因?yàn)樗鼈兊湫偷闹皇且贸蓡T數(shù)據(jù),任何線程可以在類的一個(gè)實(shí)例上調(diào)用這樣的成員函數(shù),只要沒(méi)有別的線程在同一個(gè)實(shí)例上調(diào)用這個(gè)成員函數(shù).
class Counter{ public: Counter():n(0){}; void increment(){++n}; void decrement(){--n}; private: int n; }
上面的這個(gè)類不是線程安全的,因?yàn)槿绻鄠€(gè)線程都試圖修改數(shù)據(jù)成員n,其結(jié)果是未定義的.這是因?yàn)閏++中的++和--操作符不是原子操作。實(shí)際上特咆,它們會(huì)被擴(kuò)展為三個(gè)機(jī)器指令:
- 把變量值裝入寄存器.
- 增加或減少寄存器中的值
- 把寄存器中的值寫回內(nèi)存
要使其成為線程安全的類,最簡(jiǎn)單的方法是使用QMutex來(lái)保護(hù)數(shù)據(jù)成員:
class Counter{ public: Counter():n(0){}; void increment(){QMutexLocker locker(&mutex);++n}; void decrement(){QMutexLocker locker(&mutex);--n}; int value() const{QMutexLocker locker(&mutex);return n;} private: mutable QMutex mutex; int n; }
QMutexLocker類在構(gòu)造函數(shù)中自動(dòng)的對(duì)mutex進(jìn)行加鎖,在析構(gòu)函數(shù)中自動(dòng)的進(jìn)行解鎖.
- 大多數(shù)的Qt類是可重入的,非線程安全的.有一些類與函數(shù)是線程安全的,它們主要是線程相關(guān)的類.如QMutex,QCoreApplication::postEvent().
Qt 之qSetMessagePattern
- 改變默認(rèn)的消息處理輸出. 允許改變qDebug(), qWaring(), qCritical(), qFatal()的輸出.
支持以下占位符:
占位符 | 描述 |
---|---|
%{appname} | QCoreApplication::applicationName() |
%{category} | 日志類別 |
%{file} | 原文件路徑 |
%{function} | 函數(shù) |
%{line} | 源文件所在行 |
%{message} | 實(shí)際的消息 |
%{pid} | QCoreApplication::applicationPid() |
%{threadid} | 當(dāng)前線程的系統(tǒng)范圍ID(如果它可以獲得) |
%{type} | “debug”季惩、”warning”录粱、”critical”或”fatal” |
%{time process} | “debug”、”warning”画拾、”critical”或”fatal” |
%{time boot} | 消息的時(shí)間啥繁,啟動(dòng)進(jìn)程的秒數(shù) |
%{time [format]} | 消息產(chǎn)生時(shí),系統(tǒng)時(shí)間被格式化通過(guò)把格式傳遞至QDateTime::toString()。如果沒(méi)有指定的格式青抛,使用Qt::ISODate旗闽。 |
%{backtrace [depth=N][separator=”…”]} | 很多平臺(tái)不支持,暫略… |
- 還可以使用條件類型蜜另,
%{if-debug}, %{if-info} %{if-warning}, %{if-critical}
或%{if-fatal}
后面跟著一個(gè)%{endif}
适室。如果類型匹配,%{if-*}
和%{endif}
之間的內(nèi)容會(huì)被打印举瑰。 - 如果類別不是默認(rèn)的一個(gè)捣辆,
%{if-category} ... %{endif}
之間的內(nèi)容將被打印。
例如:
QT_MESSAGE_PATTERN="[%{time yyyyMMdd h:mm:ss.zzz t} %{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}%{if-fatal}F%{endif}] %{file}:%{line} - %{message}"
- 默認(rèn)的模式是:”%{if-category}%{category}: %{endif}%{message}”此迅。也可以在運(yùn)行時(shí)改變模式汽畴,通過(guò)設(shè)置
QT_MESSAGE_PATTERN
環(huán)境變量。如果既調(diào)用了qSetMessagePattern()
又設(shè)置了環(huán)境變量QT_MESSAGE_PATTERN
邮屁,那么整袁,環(huán)境變量?jī)?yōu)先。
示例:
- qSetMessagePattern()
int main(int argc, char **argv)
{
QApplication app(argc, argv);
// 改變?nèi)笔∠⑻幚沓绦虻妮敵? qSetMessagePattern("Message:%{message} File:%{file} Line:%{line} Function:%{function} DateTime:%{time [yyyy-MM-dd hh:mm:ss ddd]}");
// 打印信息
qDebug("This is a debug message.");
qInfo("This is a info message.");
qWarning("This is a warning message.");
qCritical("This is a critical message.");
qFatal("This is a fatal message.");
...
return app.exec();
}
輸出如下:
Message:This is a debug message. File:..\MessagePattern\main.cpp Line:138 Function:main DateTime:[2016-07-06 15:21:40 周三]
Message:This is a info message. File:..\MessagePattern\main.cpp Line:139 Function:main DateTime:[2016-07-06 15:21:40 周三]
Message:This is a warning message. File:..\MessagePattern\main.cpp Line:140 Function:main DateTime:[2016-07-06 15:21:40 周三]
Message:This is a critical message. File:..\MessagePattern\main.cpp Line:141 Function:main DateTime:[2016-07-06 15:21:40 周三]
Message:This is a fatal message. File:..\MessagePattern\main.cpp Line:142 Function:main DateTime:[2016-07-06 15:21:40 周三]
- QT_MESSAGE_PATTERN環(huán)境變量
選擇:項(xiàng)目 -> 構(gòu)建環(huán)境佑吝,添加環(huán)境變量:QT_MESSAGE_PATTERN = [%{type}] %{appname} (%{file}:%{line}) - %{message}
- 此時(shí)遵循環(huán)境變量?jī)?yōu)先的原則.即同時(shí)設(shè)置了qSetMessagePattern()和QT_MESSAGE_PATTERN環(huán)境變量,那么環(huán)境變量生效,qSetMessagePattern()不生效.