2009年11月24日

[BCB]在 TThread 裏存取 UI 元件

[BCB]在 TThread 裏存取 UI 元件

因為接手了一支 BCB 寫的應用程式, 在重構的過程中, 有了這篇文章

///////

使用 BCB 的時候我們都知道, 要在 thread 中存取 UI 的話, 必需要用 Synchronize 這個函式, 原因是因為 VCL 不是 thread-safe的, 使用方式是先實作一個 member function, 裏面再去存取 UI, 如底下是每次新增一個 Thread Object, 在 .cpp 上面便會有的註解.


void __fastcall MyThread::UpdateCaption()
{
Form1->Caption = "Updated in a thread";
}


然而如果在 thread 中會去頻繁存取 UI 的話, 那這種方式會很麻煩, 因為你會在 thread class 中新增 n 個function, 只為了要存取 n 個 UI 物件屬性...

//////

在我接手的程式中, 裏面並沒有用到 thread 或 timer, 不過卻用到一些會 blocking function, 雖然每次 block 的時間不長, 但會很頻繁的寫入及讀取資料, 因此會了不要凍結 UI, 在程式中會發現底下這個 function

 
void Delay(DWORD DT)
{
long tt = GetTickCount();
while (GetTickCount() - tt < DT) {
Application->ProcessMessages();
if ((GetTickCount() - tt) <= 0)
tt = GetTickCount();
}
}


第一眼看起來還不錯吧, 我要 delay 500 millseconds, 只要呼叫 Delay(500)就好了, 因此程式的寫法如下

 
SendData(...)...
Delay(500);
ReadData(...)...
Delay(500);

///////////////////////////////////

for(int i=0;i<128;i++) {
SendData(...)...
Delay(50);
ReadData(...)...
Delay(50);
}
//////////////////////////////////

RESET();
Delay(6000);
//////////////////////////////////


然而這種寫法會造成 CPU load 非常高,甚至100%(因為迴圈的關係), 而且別忘了, Delay 函式在處理 message event 時, 是會中斷整個程式運行的, 所以假設你在送出資料的時候, 有 UI 事件發生時, 程式會中斷, 在處理完 UI 事件之後, 才會離開 Delay 函式。

所以如果使用者在你的視窗上按下滑鼠左鍵, 那你的程式就暫時中止了, 而如果你正在做很重要的事, 那這種寫法就很困擾了, 除了 UI 事件會中斷程式運行之外, 另外造成 CPU load太高也是一個重大問題...

而客戶用了你的程式, 除了整個系統造成非常忙碌之外, 剛好我接手的這支程式又是用來做 Update Firmware的程式, 在更新過程中,尤於是send函式和read函式都有 timeout, 如果 timeout到了, Firmware Update 也跟著掛了..(Flash燒錄到一半)

到最後還是要用 thread 來解決..
然而這支程式有一定的歷史, 我不打算整個改寫, 不過要達到的目標就是把 blocking 的函式全搬到 thread 中, 由 thread 來更新 UI, 還有 thread 也可以呼叫視窗類別的函式..(因為原程式中global變數太多, 造成不好重構)

因此就有了底下這些 function, 其實是很簡單的解決辦法...(轉來轉去)

 
//
// ////////////////////////////call function of Form1
void (__closure *pfun)(void);
void __fastcall CallFunction();
void __fastcall SyncFunction(void (__closure *p)(void));


void __fastcall TOnUpdateAll::SyncFunction(void (__closure *p)(void))
{
pfun = p;
Synchronize(CallFunction);
}

void __fastcall TOnUpdateAll::CallFunction()
{
if (pfun)
this->pfun();
pfun = NULL;
}


///////////////Enable Disable variable of Form1

TControl *pControl;
bool obj_enable;
void __fastcall SyncObject(TControl *obj, bool enable);
void __fastcall UIObjectActionEnable();


void __fastcall TOnUpdateAll::SyncObject(TControl *obj, bool enable)
{
pControl = obj;
obj_enable = enable;
Synchronize(UIObjectActionEnable);
}
void __fastcall TOnUpdateAll::UIObjectActionEnable()
{
if (pControl) {
if (obj_enable)
pControl->Enabled = true;
else
pControl->Enabled = false;
}

}


///Call ShowMessage in Thread (蝦米..沒錯 ShowMessage 也是存取UI)
String msg;
bool popup;
void __fastcall MsgShowWrapper(String str, bool pop = true);
void __fastcall MsgShow();

void __fastcall TOnUpdateAll::MsgShow()
{
Form1->Memo3->Lines->Add(msg);
if (popup)
ShowMessage(msg);
}
void __fastcall TOnUpdateAll::MsgShowWrapper(String str, bool pop )
{
msg = str;
popup = pop;
Synchronize(MsgShow);
}



要呼叫 Form1(視窗類別)的 ShowConnectR() 函式就可以這麼用, ShowConnectR()裏會存取到 UI
 
SyncFunction(Form1->ShowConnectR);


ShowMessage
 
MsgShowWrapper("Hello, World");


將 Form1 中的 UI Disable掉

 
SyncObject(Form1->UpdateBtn, true);



以上這些 function 還可以繼續擴充...希望有簡單的方式可以從 TThread 中去存取 UI, 話說最近在用 QT 就沒有這樣子的困擾, 要存取 UI 就丟個 Signal給那個 UI object....