2009年11月25日

[QT] 背景圖片

在QT中指定widget 背景圖片的一個方法是使用 stylesheet,如下


QString imageFile = QApplication::applicationDirPath() + "/background/background.bmp";
this->setStyleSheet(QString("background-image: url(%1);").arg(imageFile));


而當背景圖比 widget 長寬還要小時, 會自動"並排顯示"(tile), 如果背景圖是由重覆性的小圖像組成這倒無所謂, 但如果不是的話, 就會造成困擾, 例如背景是一張東京鐵塔的圖, 而當視窗放到最大時, 就會看到2個東京鐵塔以上....



因此我們應該使用的css樣式背景屬性不是 background-imag, 而是 border-image


QString imageFile = QApplication::applicationDirPath() + "/background/b.bmp";
ui.frame->setStyleSheet(QString(" border-image: url(%1) 3 10 3 10; ").arg(imageFile));



3 10 3 10的意思請參考底下這段文字
A border image is an image that is composed of nine parts (top left, top center, top right, center left, center, center right, bottom left, bottom center, and bottom right). When a border of a certain size is required, the corner parts are used as is, and the top, right, bottom, and left parts are stretched or repeated to produce a border with the desired size.


但這樣又有一個問題, 當這個stylesheet指定到一個 UI container上時, 這個屬性也會apply到children上...(如以下視窗中的PushButtn widget..)




這時要用指定obj-name的方式, #frame中的frame即是指定widget的obj-name, 格式如下

#obj-name {...css property...}


QString imageFile = QApplication::applicationDirPath() + "/background/b.bmp";
ui.frame->setStyleSheet(QString("#frame{ border-image: url(%1) 3 10 3 10;border-width: 4px;} ").arg(imageFile));



效果如下, 不管視窗縮小或放大, 都只會有一隻兔子..XD



而底下的link中的範例為


QPushButton {
color: grey;
border-image: url(/home/kamlie/code/button.png) 3 10 3 10;
border-top: 3px transparent;
border-bottom: 3px transparent;
border-right: 10px transparent;
border-left: 10px transparent;
}


最後感謝我家兔子"老皮"的配合...

reference:http://doc.trolltech.com/4.5/stylesheet-reference.html#border-image

http://doc.trolltech.com/4.5/stylesheet-customizing.html#the-box-model

http://pepper.troll.no/s60prereleases/doc/stylesheet-examples.html

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....

[QT]用 QTableView 解決資料過多, 顯示速度太慢的問題

在QT中, 有一個很方便的 class 可以使用來呈現 Table 表格資料: QTableWidget,但是如果在資料很多的情況, 如有幾萬筆的資料時, 使用QTableWidget 會造成 CPU 負載過高,整個應用程式好像 crash 一樣..

如果只有幾千筆的資料, 可以在 insert data前後使用底下的方式, 讓QTableWidget 先暫停 redraw


ui.tableWidget->setUpdatesEnabled(false);
ui.tableWidget->setRowCount( .... );

...
QTableWidgetItem *item = new QTableWidgetItem;
item->setText(ret.value(sql_header[i]));
item->setFlags(Qt::ItemIsEnabled);
ui.tableWidget->setItem(row,col++,item);
...

ui.tableWidget->setUpdatesEnabled(true);



但是當資料超過1萬筆之後, 只能使用 QTableView 來解決此問題, 使用 QTableView 的方式可以看QT Help裏的 Model/View programming 章節, 底下簡單列出繼承 QTableView 之後, 最基本必須要覆載的函式, 讓 QTableView 能呈現資料

1.準備 model
1.1繼承 QAbstractTableModel
1.2覆載

int rowCount(const QModelIndex & parent = QModelIndex() ) const;
int columnCount(const QModelIndex & parent = QModelIndex() ) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole ) const;
QVariant headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const ;


2.將此 model 給 QTableView (QLogData為我們繼承自 QAbstractTableModel 的class)

QLogData *sqlDataModel = new QLogData;
ui.tableView->setModel(sqlDataModel);


2.1每當 model 有變化時, QTableView 自然會知道, 因為 QTableView的資料來源就是 Model
2.2在 setModel() 前後再加上 setUpdatesEnabled()會更快

3.可以想像成我們是一次塞資料給 QTableWidget, 而 QTableView 則只會去抓現在元件呈現的區域所需的資料, 如使用者拉捲軸時, QTableView 便會去跟 Model 要目前所需的 row/column 的資料
3.1 另外resizeColumnsToContents() 這函式你也許會用到

[QT]Signal/Slots auto-connect 自動連結

在QT中,
如果要讓signals/slots之間產生連結, 必須透過connect()這個函式,
除此之外還可以在Qt Designer 裏以拖拉的方式設定,
如果是自訂的signals/slots, 只要隨後在class定義/實作中補上即可























另外針對一些很短的function body, 也可以偷懶一點用auto connect

從以下link
http://doc.trolltech.com/4.5/qmetaobject.html#connectSlotsByName
可以知道底下的命名慣例, 會是該signal的slots


void on_objectName_signal(parameter)