Essential Qt 第十一章 事件的運用

             這一章將通過一個完成一個完成的程序,這個程序將會運用到多個事件。這個程序被命名爲TitleWidget.在界面程序中,滾動播放一些文字非常常見,而這個程序的最用將是能滾動播放默認的字符或輸入的文本,完成後大概是這個樣子


其中的文字在從左到右不斷的循環滾動播出。要實現這個程序,至少要用到3個事件,都是到目前尚未接觸到的事件,先看程序頭文件TitleWidget.h
#ifndef TITLEWIDGET_H__
#define TITLEWIDGET_H__
#include<QWidget>
#include<QShowEvent>
#include<QPaintEvent>
#include<QTimerEvent>
const QString DEFAULT_TITLE = "This is A TitleWidget For Test Event.         ";//註釋1
class TitleWidget:public QWidget
{
  private:
    int TextPoint;
    QString TextInfo;
    int TextTimeID;//註釋2
  public:
    TitleWidget(QWidget*parent = 0);
    void setText(const QString& str );
    QString text()const;
    QSize sizeHint()const;//註釋3
  protected:
    void showEvent(QShowEvent* event);
    void paintEvent(QPaintEvent* event);
    void timerEvent(QTimerEvent* event);//註釋4
};
#endif


註釋1: 對於這個程序來說,可以使用接下來的setText()函數來設置他滾動播放的內容,如果沒有調用這個函數設置播放內容,則將會播放固定的文本
註釋2: 變量TextPoint用於標記文本在窗體中的位置,在繪圖中有重要作用,TextInfo爲播放文本的內容,TextTimeID將用於標記一個定時器,這個定時器用於控制文本滾動的速度
註釋3:這裏用setText()函數和text()分別獲得和返回播放文本的內容,而sizeHint()函數則用於根據文本內容來覺得窗體大小
註釋4:這裏將修改三個事件,分別對應顯示事件,繪圖事件和時間事件,這個窗體的主要功能依靠這三個時間實現

          接下來是TitleWidget.cxx文件,在構造函數中將TextPoint和TextTimeID設爲0,這兩個值會在下面的事件函數中使用,而TextInfo則等於默認值DEFAULT_TITLE;
void TitleWidget::setText(const QString& str)
{
  TextInfo = str;
  update();//註釋1
  updateGeometry();
}
QString TitleWidget::text()const
{
  return TextInfo;
}
QSize TitleWidget::sizeHint()const
{
  return fontMetrics().size(0,text());//註釋2
}
註釋1 :setText()函數設置滾動播放的文本,然後調用update()函數來強制刷新一次繪圖事件,效果等同與窗體重新繪製一次,在Qt3以前版本,這樣的操作會造成窗體的閃爍,需要做額外的處理,而從Qt4開始,則不會出現閃爍的情況,另一個函數updateGepmetry()則用於告訴窗體自身的佈局管理器,窗體的形狀已經改變了,佈局管理器也需要做相應的改動。
註釋2 :fontMetrics()用於返回一個QFontMetrics對象,這個對象包含了窗體部件字體相關的信息,這個了類的成員函數size()則返回文本的大小,這個QFontMetrics::size()第一個參數用於比特級的細節控制,對於這個例子來說,直接傳值0即可,第二個參數爲希望獲得大小的文本,而上面已經實現了text()函數來獲得程序滾動播放的文本內容

繪圖事件實現
void TitleWidget::paintEvent(QPaintEvent* event)
{
  QPainter P(this); //註釋1
  int TextWidth = fontMetrics().width(text());//註釋2
  if(TextWidth < 1)
    return;
  int X = - TextPoint;  //註釋3
  while(X < TextWidth)
  {
    P.drawText(X,0,TextWidth,fontMetrics().height(),Qt::AlignLeft|Qt::AlignCenter,text()); //  註釋4
    X += TextWidth;
  }
}
註釋1: QPainter是一個畫筆,他作用是在他的父對象上畫畫,這裏他的父對象是this,所以他的作用就是在窗體上畫東西,要使用這個類需要包含頭文件#include<QPainter>
註釋2: 這裏再次利用fontMetrics()函數的返回值來獲得文本的大小信息,不過這裏是獲得文本的長度
註釋3: 這裏X是一個文本的起始位置,舉例,窗體最左段的座標爲0,最初TextPoint的值爲0,程序設定文本每個一段時間向左移動一個位置,而TextPoint用於標記文本移動的距離,假設文本向左移動了兩次,則TextPoint的值爲2,而文本起始的位置則爲-2(即向左移動了單位2),當然,從-2到0這段文本不會顯示在窗體中
註釋4 :這裏調用了QPainter的成員函數來“畫畫”,這裏畫的是文本,

顯示事件實現
void TitleWidget::showEvent(QShowEvent* event)
{
  TextTimeID = startTimer(30);//註釋1
  QWidget::showEvent(event);//註釋2
}  
註釋1: 這個是窗體的顯示事件函數,前面說過Qt窗體創建是默認是隱藏的,必須調用show()函數來顯示,而調用show()函數就觸發了這個顯示時間,而在這個程序的顯示事件中,需要添加一個功能,這個功能調用基類成員函數startTimer()來生產一個定時器,這個函數參數爲定時器的時間間隔,單位爲毫秒,例子中這個定時器在窗體顯示的時候開始,每隔30毫秒就會觸發一次窗體自身的時間事件,這個函數的返回值用於標記創建的定時器,Qt允許一個窗體有多個定時器,創建的時候將會用一個ID來標記,追蹤他們
註釋2: 由於只是添加功能,所以這裏調用下基類的顯示事件函數

時間事件實現
void TitleWidget::timerEvent(QTimerEvent* event)
{
  if(event->timerId() == TextTimeID)//註釋1
  {
    ++TextPoint;
    if(TextPoint >= fontMetrics().width(text()))//註釋2
      TextPoint = 0;
    scroll(-1,0);//註釋3
  }
  else
    QWidget::timerEvent(event);//註釋4
}
註釋1: 在顯示事件中,窗體在顯示的時候會生成一個定時器,這個定實際每隔30毫秒就會觸發一次時間事件,但操作系統也會觸發事件時間,所以需要判斷是否由顯示事件中的那個定時器觸發的,可以通過事件的timerId()函數返回觸發這次事件的定時器ID來判斷
註釋2: 每隔一段時間(程序中爲30毫秒)TextPoint會記錄文本移動距離,當移動具體超過了文本長度,則重置爲0,即文本向左移動了一個自身的距離,則可以視爲沒有移動
註釋3: 這裏使用了scroll()函數,是的文本向左移動一個像素,注意地一個參數是負代表向左移動,上文中採用update()函數來強制刷新一次繪圖之間,而這裏由於之需要改變文本位置,不需要改動(重新繪製)整個窗體。

然後程序編譯運行後差不多是這個樣子的

               到這裏實現了滾動播放文本的功能,此外還可以在添加一些功能,比如很多人希望有個暫停和繼續滾動的動能,爲了演示這個功能,可以添加一個槽來實現,爲了程序能(想用戶演示)控制空能,可以暫時使用鼠標左鍵點擊來控制,滾動的狀態下點擊就暫停,暫停的狀態下就滾動
              爲了實現這樣的功能,需要在類中添加一個變量bool值來追蹤窗體的滾動狀態,在構造函數中設置TitleState = true;因爲窗體默認是滾動的,然後還需要修改下窗體的鼠標點擊事件,捨得鼠標左鍵點擊的時候會發射一個自定義的信號
void TitleWidget::mousePressEvent(QMouseEvent* event)
{
  if(event->button() == Qt::LeftButton)
    emit LeftClick();  //註釋1
  QWidget::mousePressEvent(event);
}
註釋1: 這個信號在類中定義,用於和槽連接
然後是控制是否滾動的槽函數的實現
void TitleWidget::StopTitleRun()
{
  if(TitleState == true)
  {
    TitleState = false;
    killTimer(TextTimeID); //註釋1
    TextTimeID = 0;
  }
  else if(TitleState == false)
  {
    TextTimeID = startTimer(30);//註釋2
    TitleState = true;
  }
}
註釋1:TitleState值可以判斷窗體目前是滾動還是停止狀態, 這裏使用killTimer()函數來停止並刪除顯示事件中產生的定時器
註釋2: 這裏重新生產一個定時器,來刷新時間事件,達到重啓滾動的效果
最後在構造函數中將鼠標點擊事件中發射的信號和這個槽連接起來
connect(this,SIGNAL(LeftClick()),this,SLOT(StopTitleRun()));
這樣鼠標左鍵點擊就可以控制窗體滾動/停止,當這個窗體作爲別的窗體的子窗體(比如一個程序的警告信息播出欄),可以刪除這個連接和鼠標點擊時間函數,讓別的程序特定的信號和這個槽鏈接,這樣就能控制這個部件的行爲
             這個類的功能順序可以概括爲
             1.窗體啓動創建後調用show()函數顯示,出發窗體顯示事件,在顯示事件中創建定時器,觸發了時間時間
             2.時間事件函數中每隔30毫秒刷新一次窗體繪圖事件(scroll()函數)
             3.繪圖函數中重新繪製窗體,使用drawText()函數來重新繪製窗體(的文本)。
             4.成員函數setText()作爲接口,包含了強制窗體重繪的代碼,調用該函數也會觸發繪圖事件

             一些細節上的改動:首先關於時間的間隔這個程序設定爲30毫秒,而這個值30是直接寫在代碼中的,這顯然不是一個很好的編程習慣,因爲很多程序可能需要修改滾動速度,所以在文件中定義一個常量來表示時間間隔會比較合適,可以使用
const int INTERVAL = 30;
然後在實現使用INTERVAL而不是30,這樣當有需要修改的時候只要修改下這個值就可以了而不用在逐行查看代碼有沒有遺漏的地方。
             另一個細節就是在這個類的構造函數中使用了
setWindowFlags(Qt::CustomizeWindowHint);
這個函數是QWidget的成員函數,多數情況下用於自定義窗體,就一這個例子,我在演示是最初的演示截圖中窗體並沒有系統窗體自帶的標題,最大化,最小化等部件,setWindowFlags()函數的參數是枚舉Qt::WindowFlags,這個枚舉值提供了很多自定義窗體的功能,其中就包括了這裏演示的刪除本地風格的標題,最大/小化部件等,用和不用這個函數,最後部件外觀差別很大




最後需要注意的setWindowFlags()調用後很多外觀的改動只有窗體是頂層窗體纔會有區別

              到這裏完成了一個滾動窗體的製作,這個窗體提供了一個setText()的接口,用於設置滾動的文本內容,另外這個程序還可以用鼠標點擊來控制播放/停止,這部分很容易根據需要改成窗體的控制接口代碼,但這裏還有點小問題,這個程序是以文本的大小作爲窗體的大小,這樣做是爲了演示這個程序的時候顯得比較。。。好看,窗體剛好是完美契合文本的大小,但如果這個窗體是用於其他程序的子窗體來說就有些不合適了,程序往往需要經常改變滾動的文本內容,事實上就演示的這個窗體而言,拉昇下窗體就會使得滾動的文本出現問題。
              要解決這個問題,最先要做的是刪掉sizeHint()這個函數以及他的定義,這個函數在這個程序中的作用是讓窗體的大小和文本完美契合,而如果一個程序需要使用這個窗體,往往會根據實際情況自定義窗體的大小,所以就這個窗體而言,沒有必要自己定義大小。
              然後就是修改繪圖事件函數裏的代碼。
void TitleWidget::paintEvent(QPaintEvent* event)
{
  QPainter P(this);
  int TextWidth = fontMetrics().width(text());
  int WidgetWidth = width();//註釋1
  if(TextWidth < 1)
    return;
  int X = - TextPoint;
  while(X < WidgetWidth)//註釋2
  {
    P.drawText(X,0,TextWidth,fontMetrics().height(),Qt::AlignLeft|Qt::AlignCenter,text());
    X += TextWidth;
  }
}
註釋1: 這裏獲取窗體的寬度
註釋2: 原先的代碼最大的繪製範圍(寬度)是文本的寬度,導致拉伸窗體後拉申的部分顯示不全或着不顯示,這裏改用窗體的寬度作爲繪製範圍(寬度),使得窗體拉伸後仍然能夠正常顯示

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