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