事件處理(-? Event Processingn)
事件是視窗系統或者Qt 本身在各種不同的情況下產生的。當用戶點擊或者釋放鼠標,鍵盤時,一個鼠標事件或者鍵盤事件就產生了。當窗口第一次顯示時,一個繪制事件會產生告訴新可見的窗口繪制自己。很多事件是為了相應用戶動作產生的,也有一些事件是由 系統獨立產生的。 在用Qt 編程時,我們很少要考慮事件,當一些事件發生時,Qt 控件會發出相應的信號。只有當實現用戶控件或者需要修改現有控件的行為時,我們才需要考慮事件。事件不能和信號混淆。一般來講,在使用控件時需要處理的是信號,在實現一個控件時需要處理事件。例如,我們使用QPushButton 時,我們只要clicked()信號就可以了,而不用管鼠標點擊事件。但是如果我們實現一個像 QPushButton 這樣的類,我們就需要處理鼠標或者鍵盤事件 ,發出clicked()信號。
重寫事件處理函數(Reimplementing Event Handlers)
在 Qt 中,一個事件是 QEvent 的子類的對象。Qt 能夠處理上百種類型的事件,每一類型的事件由一個枚舉值確定。例如,對鼠標點擊事件,QEvent::type()返回的值為 QEvent::MouseButtonPress。
很多情況下,一個 QEvent 對象不能保存有關事件的所有信息,例如,鼠標點擊事件需要保存是左鍵還是右鍵觸發了這個信息,還要知道事件發生時鼠標指針的位置,這些額外的信息儲存在 QEvent 的子類QMouseEvent 中。
Qt 的對象通過QObject::event()得到有關事件的信息。QWidget::event()提供了很多普通類型的信息,實現了很多事件處理函數,例如 mousePressEvent(),keyPressEvent(),paintEvent()等等。
在前面的章節中,我們已經在MainWindow 類,IconEditor 類,Plotter 類中看到了很多事件處理函數,在QEvent 參考文檔中,還列舉了很多類型的事件。
我們還可以定義自己的事件,把事件分派出去。這里,我們討論一下兩種最常用的事件:鍵盤事件和時間事件。
重寫函數 keyPressEvent()和keyReleaseEvent()可以處理鍵盤事件。 Plotter 控件就重寫了 keyPressEvent()函數。通常,我們只需要重寫 keyPressEvent(),需要處理鍵盤釋放事件的只有修改鍵(Ctrl, Shift, Alt),而這些鍵的信息可以通過 QKeyEvent::modifiers()得到。例如,如果我們重寫了控件 CodeEditor 控件的 KeyPressEvent()函數,區分Home 鍵和 Ctrl+Home 鍵:
void CodeEditor::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) { case Qt::Key_Home:
if (event->modifiers() & Qt::ControlModifier) { goToBeginningOfDocument();
} else {
goToBeginningOfLine();
}
break;
case Qt::Key_End:
...
default:
QWidget::keyPressEvent(event);
}
}
Tab 鍵和Backtab(Shift+Tab)鍵很特殊,它們是在控件調用 keyPressEvent()之前,由 QWidget::event()處理的,這兩個鍵的作用是把輸入焦點轉到前一控件或者下一個控件上,在 CodeEditor 中,希望 Tab 鍵的作用是縮進,可以這樣重寫 event():
bool CodeEditor::event(QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); if (keyEvent->key() == Qt::Key_Tab) {
insertAtCurrentPosition('\t'); return true;
}
}
return QWidget::event(event);
}
如果這個事件是一個鍵盤敲擊事件,我們把 QEvent 對象轉換成QKeyEvent,然后確定是那個鍵敲擊了,如果是 Tab 鍵,進行處理后返回 true,通知 Qt 我們已經對事件進行了處理。如果返回 false,Qt 還會把這個事件交給基類控件處理。
響應鍵盤事件的更好的方法是使用 QAction。例如,goToBeginningOfLine()和 goToBeginningOfDocument()是CodeEditor 的兩個公有槽函數, CodeEditor 是MainWindow 的中央控件,下面的代碼實現了鍵盤和槽函數的綁定:
MainWindow::MainWindow()
{
editor = new CodeEditor; setCentralWidget(editor); goToBeginningOfLineAction =
new QAction(tr("Go to Beginning of Line"), this); goToBeginningOfLineAction->setShortcut(tr("Home")); connect(goToBeginningOfLineAction, SIGNAL(activated()),
editor, SLOT(goToBeginningOfLine())); goToBeginningOfDocumentAction =
new QAction(tr("Go to Beginning of Document"), this); goToBeginningOfDocumentAction->setShortcut(tr("Ctrl+Home")); connect(goToBeginningOfDocumentAction, SIGNAL(activated()),
editor, SLOT(goToBeginningOfDocument()));
...
}
這樣可以很容易把一個鍵盤敲擊的命令加入到菜單或者工具條中。如果命令沒有出現在用戶界面中,可用用 QShortcut 對象代替QAction 對象,在QAction內部就是使用這個類實現鍵盤的綁定。
通常情況下,只要窗口中有激活的控件,控件上用 QAction 和 QShortcut 設置的鍵盤綁定都是可用的。綁定的鍵可用 QAction::setShortcutContext()或者 QShortcur::setContext()進行修改。
另一個常用的事件類型是時間事件。其他事件都是由用戶的某種活動引發的,而時間事件則使程序按照一定的時間間隔執行特定的任務。時間事件一般用來使光標閃爍,或者播放動畫,或者只是繪制顯示界面或者控件。
為了介紹時間事件,我們將實現一個 Ticker 控件。這個控件顯示一條標語,每隔 30 毫秒鐘向左移動一個象素。如果控件比標語要寬,標語的文本重復的顯示在控件上,填滿整個控件。
#ifndef TICKER_H #define TICKER_H
#include <QWidget>
class Ticker : public QWidget
{Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText) public:
Ticker(QWidget *parent = 0);
void setText(const QString &newText); QString text() const { return myText; } QSize sizeHint() const;
protected:
void paintEvent(QPaintEvent *event); void timerEvent(QTimerEvent *event); void showEvent(QShowEvent *event); void hideEvent(QHideEvent *event);
private:
QString myText; int offset;
int myTimerId;
};
#endif
在頭文件中,我們實現了 Ticker 的四個事件處理函數,其中三個 timeEvent(), showEvent()和hideEvent()是我們以前沒有見過的。
#include <QtGui> #include "ticker.h"
Ticker::Ticker(QWidget *parent)
: QWidget(parent)
{
offset = 0;
myTimerId = 0;
}
在構造函數中,設置offset 為 0,這個變量是文本要顯示的x 坐標值。時間ID 總是非 0 的,這里設置myTimerId 為 0 說明我們還沒有啟動任何時間
void Ticker::setText(const QString &newText)
{
myText = newText; update(); updateGeometry();
}
函數 setText()設置要顯示的文本。調用 update()引發繪制事件重新顯示文本, updateGeometry()通知布局管理器改變控件的大小。
QSize Ticker::sizeHint() const
{return fontMetrics().size(0, text());
}
函數 sizeHint()返回的是控件在不同文本時完整顯示所需的尺寸。 QWidget::fontMetrics()返回一個 QFontMetrics 對象,得到控件所用的字體的信息。在這里我們需要得到的是文本的大小。(在 QFontMetrics::size()中,第一個參數是一個標識,對字符串來講并不需要,所有賦了 0 值)。
void Ticker::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
int textWidth = fontMetrics().width(text()); if (textWidth < 1)
return;
int x = -offset;
while (x < width()) {
painter.drawText(x, 0, textWidth, height(), Qt::AlignLeft | Qt::AlignVCenter, text());
x += textWidth;
}
}
函數paintEvent()使用QPainter::drawText()繪制文本。調用fontMetrics()得到文本所需要的水平空間,然后多次繪制文本,直至填滿整個控件
void Ticker::showEvent(QShowEvent * /* event */)
{
myTimerId = startTimer(30);
}
showEvent()啟動了一個計時器。調用QObject::startTimer()返回一個ID值,這個 ID 值可以幫助我們識別這個計時器。QObject 能夠支持多個獨立的不同的時間間隔的計時器。調用 startTimer()以后,Qt 大約每 30 毫秒產生一個事件,時間的準確與否取決于不同的操作系統。
我們也可以在 Ticker 的構造函數中調用 startTimer()。但是在控件可見以后再啟動,能夠節省一些資源。
void Ticker::timerEvent(QTimerEvent *event)
{
if (event->timerId() == myTimerId) {
++offset;
if (offset >= fontMetrics().width(text())) offset = 0;
scroll(-1, 0);
} else {
QWidget::timerEvent(event);}
}
函數 timerEvent()由系統以一定間隔進行調用的。把offset 增加 1 來模仿文字的移動,增加到標語的寬度時文字的寬度是重新設置為 0。然后調用scroll()把控件向左滾動一個象素。也可以調用 update(),但是 scroll()更加高效,它對可見的象素進行移動,只是對需要新繪制的地方調用繪制事件(在這個例子中 ,只是一個象素寬的區域)。
如果計時器不是我們需要處理的,則把它傳遞給基類。
void Ticker::hideEvent(QHideEvent * /* event */)
{
killTimer(myTimerId);
}
在 hideEvent()中,調用QObject::killTimer()停止計時器。
時間事件的優先級很低,如果需要多個計時器,那么跟蹤每一個計時器的ID 是很費時的。這種情況下,較好的方法是為每一個計時器創建一個QTimer 對象。在每一個時間間隔內,QTimer 發出一個 timeout()信號。QTimer 還支持一次性計時器(只發出一次 timeout()信號的計時器)。