1. 程式人生 > >QT執行緒傳送訊息通知介面小例

QT執行緒傳送訊息通知介面小例

初學QT,有很多地方都不懂,靠著Win32開發的樣子寫程式到是出了不少問題,摸索中前進。不管是什麼開發,都有一條基本的原則:不要在UI執行緒中進行耗時操作,這樣會導致介面卡頓;不要在輔助執行緒中操作UI介面,這樣會導致介面重新整理不及時。對於基本的Windows程式,都少不了訊息迴圈和往訊息佇列中傳送訊息的函式(SendMessage PostMessage)。檢視基類標頭檔案,可以看到詳細的介面,一般虛擬函式都是說明我們自己可以去實現並處理的。

QMainWindow的訊息處理函式:virtual bool event(QEvent *event);

  那麼QEvent就是我們要傳送的訊息了,繼續F2定位過去,看到裡面有個訊息ID的列舉,是不是和Win32開發的Windows自定義訊息很像(Windows核心都是C寫的,訊息ID當然都是C原因的巨集定義實現。QT是C++寫的,列舉更加方便有木有)。注意檢視這些ID的值

 User = 1000,                            // first user event id
        MaxUser = 65535                         // last user event id
    };

  也即是說我們自定義訊息的ID都要從User開始 ,以免與QT內部訊息重合,這就和Win32裡的自定義訊息一般都是從WM_USER開始一樣的道理。

  繼續檢視資料,傳送訊息有兩種方式sendEvent 和postEvent 

1、 QCoreApplication :: sendEvent ();   根據Qt Asistant 裡面的講述,這個函式直接將事件訊息直接傳送給接受者進行處理,等到事件處理完畢後才返回;並且使用它所傳遞的訊息事件是在 棧(stack) 上建立的,也就是說它的記憶體空間是有編譯器來自動管理的。

2、 QCoreApplication :: postEvent (); 根據Qt Asistant 裡面的講述, 使用這個函式來傳遞時間訊息時,它將事件訊息傳送到接受者的的訊息佇列裡面,然後立即返回,不需要等到事件處理完畢才返回;並且使用它所傳遞的訊息事件是在 堆(heep) 上建立的,也就是說它的記憶體空間是又程式設計師自己管理的,如用 new 建立的變數!
  QEvent類的資料有限,如果訊息需要加上很多自己的資料,我們可以派生QEvent類,在成員變數裡面另外加上資料。這裡我需要加上一個數值來設定進度條的進度,所以派生了下,提供介面設定和獲取進度值。
#ifndef MYEVENT
#define MYEVENT

class MyEvent
        : public QEvent
{
public:
    MyEvent(QEvent::Type type)
        : QEvent(type)
    {

    }
     ~MyEvent()
    {

    }

    void SetValue(int nValue)
    {
        m_nValue = nValue;
    }

    int GetValue()
    {
        return m_nValue;
    }

private:
    int m_nValue;
};



#endif // MYEVENT

開啟執行緒,不斷髮送訊息設定進度。剛開始使用的是sendEvent,執行緒函式如下:
DWORD MainWindow::Thread1(void *lpParam)
{
    MainWindow* pWnd = (MainWindow*)lpParam;
    MyEvent me(QEvent::Type(QEvent::User+1));
    for ( int i=0; i<=100; ++i )
    {
        me.SetValue(i);
        //QApplication::postEvent(pWnd, new MyEvent(me));
        QEvent evt(me);
        QApplication::sendEvent(pWnd, &evt);
        if ( i == 100 )
            i = 0;
        Sleep(10);
    }
    return 0;
}

但是執行時卻彈出了一個錯誤


  翻一下就是:不能傳送訊息到另一個執行緒所屬的物件!好吧,我以為這個sendEvent和Win32裡面的SendMessage一樣呢,傳送訊息到視窗執行緒的訊息迴圈,等待訊息處理完畢後才返回。實踐證明,不是那樣的。

  最後,全部換成postEvent,並且QEvent物件都是通過new出來的。在訊息迴圈中,我本來是用delete來釋放這些申請的指標的,結果也是導致了崩潰了。檢視呼叫堆疊,可以看到指標已經被QT內部機制析構了。程式碼通過開啟兩個執行緒,往訊息迴圈中傳送兩個訊息通知介面上的兩個進度條不斷改變進度。

bool MainWindow::event(QEvent *event)
{
    switch (event->type()) {
    case QEvent::User+1:
    {
        MyEvent* pEvent = (MyEvent*)event;
        ui->progressBar->setValue(pEvent->GetValue());
        //delete pEvent;
        return 0;
    }
        break;
    case QEvent::User+2:
    {
        MyEvent* pEvent = (MyEvent*)event;
        ui->progressBar_2->setValue(pEvent->GetValue());
        //delete pEvent;
        return 0;
    }
        break;
    default:
        break;
    }
    return QMainWindow::event(event);
}

DWORD MainWindow::Thread1(void *lpParam)
{
    MainWindow* pWnd = (MainWindow*)lpParam;
    MyEvent me(QEvent::Type(QEvent::User+1));
    for ( int i=0; i<=100; ++i )
    {
        me.SetValue(i);
        QApplication::postEvent(pWnd, new MyEvent(me));
        if ( i == 100 )
            i = 0;
        Sleep(10);
    }
    return 0;
}

UINT MainWindow::Thread2(void *lpParam)
{
    MainWindow* pWnd = (MainWindow*)lpParam;
    MyEvent me(QEvent::Type(QEvent::User+2));
    int i = 100;
    while( true )
    {
        me.SetValue(i);
        QApplication::postEvent(pWnd, new MyEvent(me));
        if ( i == 0 )
            i = 100;
        Sleep(10);
        i--;
    }
    return 0;
}
程式執行截圖:



總結下

1、sendEvent只能用在同一個執行緒中;

2、postEvent傳送出去的QEvent物件會自動析構,無需釋放。