線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:1847
推到 Plurk!
推到 Facebook!

拖曳時程式會停住的問題

答題得分者是:syntax
brian_chen
一般會員


發表:14
回覆:20
積分:6
註冊:2007-06-26

發送簡訊給我
#1 引用回覆 回覆 發表時間:2008-01-25 19:09:27 IP:59.120.xxx.xxx 訂閱
[code cpp]
void __fastcall TForm1::progressBarShow(int iPercent)
{
AnsiString sStr;
if (iPercent > 100)
{
iPercent = 100;
}
sStr = iPercent;
sStr = " %";
lbWriteProgress->Caption = sStr;
iCountBar = iPercent;
imglistBar->GetBitmap(iCountBar / 5, FWMain->imgProgressBar->Picture->Bitmap);
imgProgressBar->Repaint();
Application->ProcessMessages();
}
[/code]

上面的程式很簡單,就是進度列的顯示
只是我的progressbar是用20張圖片兜起來的

每5%就換一張圖 (如下) 0%

可是,只要在主form上按下滑鼠左鍵要拖曳視窗,進度列就會停住
我有另外寫一個小程式,就只有用 label

[code cpp]
for (int i = 0; i < 500000; i )
{
Label1->Caption = i;
Application->ProcressMessage();
}

[/code]

也是當我按住視窗要拖曳時, i 值就會停住不動

後來我改用 thread 的方式

[code cpp]
void __fastcall Thread1::Execute()
{
FreeOnTerminate=true;
while (!Terminated)
{
//Synchronize(test);
test();

}
}

void __fastcall Thread1::test(void)
{
for (int i=0;i<100000;i )
{
Form1->progressBarShow(i/1000);
}
}

[/code]

我試了兩個
一個有加Synchronize 一個沒加

當我用 Synchronize(test) 時,發現圖片及進度顯示正常,但是滑鼠壓住視窗要拖曳時,還是會停住
後來我改用直接呼叫的方式

test( );
發現圖片進度列無法顯示,但數字有在跑。
而且用
滑鼠壓住視窗要拖曳時,數字的 % 數有繼續執行 (不過會頓一下)
但在圖片方面就會完全沒辦法顯示進度的差異,甚至有時就會變成白白一片,只剩數字在那邊跑。

我在版上找了很久,似乎好像沒有提到類似的問題
亦或是我找的關鍵字不對

不知道是否有人也曾經遇到過此類的問題,可以幫我解答一下,謝謝。
編輯記錄
brian_chen 重新編輯於 2008-01-25 19:10:47, 註解 無‧
syntax
尊榮會員


發表:26
回覆:1139
積分:1258
註冊:2002-04-23

發送簡訊給我
#2 引用回覆 回覆 發表時間:2008-01-26 22:55:14 IP:61.64.xxx.xxx 訂閱
將 Application->ProcessMessages 加在主視窗的滑鼠移動相關位置
或是加入 xxx.Update, 或 xxx.Repaint 也可以
===================引 用 brian_chen 文 章===================
[code cpp]
void __fastcall TForm1::progressBarShow(int iPercent)
{
AnsiString sStr;
if (iPercent > 100)
{
iPercent = 100;
}
sStr = iPercent;
sStr = " %";
lbWriteProgress->Caption = sStr;
iCountBar = iPercent;
imglistBar->GetBitmap(iCountBar / 5, FWMain->imgProgressBar->Picture->Bitmap);
imgProgressBar->Repaint();
Application->ProcessMessages();
}
[/code]

上面的程式很簡單,就是進度列的顯示
只是我的progressbar是用20張圖片兜起來的

每5%就換一張圖 (如下) 0%

可是,只要在主form上按下滑鼠左鍵要拖曳視窗,進度列就會停住
我有另外寫一個小程式,就只有用 label

[code cpp]
for (int i = 0; i < 500000; i )
{
Label1->Caption = i;
Application->ProcressMessage();
}

[/code]

也是當我按住視窗要拖曳時, i 值就會停住不動

後來我改用 thread 的方式

[code cpp]
void __fastcall Thread1::Execute()
{
FreeOnTerminate=true;
while (!Terminated)
{
//Synchronize(test);
test();

}
}

void __fastcall Thread1::test(void)
{
for (int i=0;i<100000;i )
{
Form1->progressBarShow(i/1000);
}
}

[/code]

我試了兩個
一個有加Synchronize 一個沒加

當我用 Synchronize(test) 時,發現圖片及進度顯示正常,但是滑鼠壓住視窗要拖曳時,還是會停住
後來我改用直接呼叫的方式

test( );
發現圖片進度列無法顯示,但數字有在跑。
而且用
滑鼠壓住視窗要拖曳時,數字的 % 數有繼續執行 (不過會頓一下)
但在圖片方面就會完全沒辦法顯示進度的差異,甚至有時就會變成白白一片,只剩數字在那邊跑。

我在版上找了很久,似乎好像沒有提到類似的問題
亦或是我找的關鍵字不對

不知道是否有人也曾經遇到過此類的問題,可以幫我解答一下,謝謝。
brian_chen
一般會員


發表:14
回覆:20
積分:6
註冊:2007-06-26

發送簡訊給我
#3 引用回覆 回覆 發表時間:2008-01-28 09:51:03 IP:59.120.xxx.xxx 訂閱
先謝謝 syntax 大大的指點
我在滑鼠移動的相關位置加入了 Application->ProcessMessages
因為我是用背景圖所以是用圖片的 imgmousedown

[code cpp]
void __fastcall Form1::imgBGMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
{
if( Button == mbLeft )
{
ReleaseCapture();
Perform( WM_SYSCOMMAND, 0xf012, 0 );
Application->ProcessMessages(); //加在此不知道對不對@@
}
}

[/code]

還有另外大大有提到說用 xxx.Repaint
我原本在 progressBarShow 這裡面就有加入了

[code cpp]
void __fastcall Form1::progressBarShow(int iPercent)
{
AnsiString sStr;
if (iPercent > 100)
{
iPercent = 100;
}
sStr = iPercent;
sStr = " %";
lbWriteProgress->Caption = sStr;
iCountBar = iPercent;
imglistBar->GetBitmap(iCountBar / 5, FWMain->imgProgressBar->Picture->Bitmap);
imgProgressBar->Repaint(); // 我在這邊有加入 xxx.Repaint
Application->ProcessMessages();
}

[/code]

兩種方法都試過了
不過還是不行,不知道還有沒有別種方式

謝謝大大的回答

===================引 用 syntax 文 章===================
將 Application->ProcessMessages 加在主視窗的滑鼠移動相關位置
或是加入 xxx.Update, 或 xxx.Repaint 也可以

syntax
尊榮會員


發表:26
回覆:1139
積分:1258
註冊:2002-04-23

發送簡訊給我
#4 引用回覆 回覆 發表時間:2008-01-28 22:43:28 IP:61.64.xxx.xxx 訂閱
抱歉,先前並沒有很仔細的思考你的意思

你的問題,很不好解決,也很好解決
不好解決是在於,你需要同步更新,很好解決,是如果你很瞭解訊息的運作方式

首先,思考一下 Application->ProcressMessage(); 的用意
這一行可以讓視窗在處理你的程式碼時,有機會去處理自身的訊息處理,也就是有時間,重繪窗體或給使用者反應
所以,在回圈中加入,才不會出現視窗沒有反應,可以拖曳,因為,這一行讓程式跳出回圈一下,做一點事,再回去

注意喔~是再回去執行回圈喔

所以問題來的,這樣可以分成,是
1.以執行更新 progressbar 為主,
要讓程式看起來可以拖曳沒有當掉
2.執行其他事情時,順便更新 progressbar,還要可以拖曳

如果是 1.
這就可以使用 Application->ProcressMessage(); 來達到需求

如果是 2.
就比較麻煩,要看你當下進行的作業。與更新 prograssbar 的關係,同時,還要支援拖曳,這就更不容易
因為畢竟,單 CPU 電腦一次只能做一件事,即使「看起來」是多工的

以上兩者都必須注意「重入」的問題,所以 Application->ProcressMessage(); 並不是那麼好用,用時還是要思考

想想,要是跳出來作的事,會再進入相同類似的程式碼,哪很容易造成無盡的循環,很快的 stack 就被耗盡了
再者,你跳出來,要進行拖曳,還是要更新圖像?所以我才會建議,在拖曳處處理完畢後,加入,才來更新

但是,更新 progressbar 的程式碼,就不可以再使用 Application->ProcressMessage(); ,因為這樣不就又給機會拖曳,表示視窗圖像又要進行更新,更新時又要作拖曳,拖曳再更新...沒完沒了喔

所以當你按住滑鼠時,視窗其實是全力在做你的工作,不斷的跳出來處理滑鼠的事件,即使該事件沒有做任何事,沒移動位置,不用重繪,但,就是佔住 CPU 不放

因為 Application->ProcressMessage(); 在事情沒有處理完畢前,不會回來

所以我們整理一下
1. Edit1.Update; 可以讓程式重繪,更新資訊給使用者,但也僅止於此,不會給使用者有機會互動
2. Application.ProcessMessages; 可以讓使用者互動機會,處理訊息,但不會幫你重繪視窗,除非處理的訊息是「重繪視窗」

再加上, Delphi 幫你包裝後的機制,有些東西都寫死囉,所以
你要做到
1.可以處理你的東西
2.同時可以拖曳
3.並讓視窗更新

你要,自己處理訊息,看你要在哪裡切入都可以,我的看法是直接在 WinProc 內處理 (override WinProc)
當接受到滑鼠訊息時,除處理該訊息,還要加上「重繪視窗」的動作 (當然要加入一個旗標,判斷一下是否需要更新 progressbar),在同一個訊息回圈內完成

然後在你要作的工作程式內加上 Application.ProcessMessages; 就可以解決

當然切入點不只一個,也可以再晚一點的階段,使用攔截訊息的方式,但是這樣要寫的副程式,就不只一個,因為滑鼠訊息不只一種,每一個都要攔的話,用每個訊息都會用到的 WinProc 是比較適合

不過這樣又衍生一個問題,WinProc 不可以亂攔,也不適合在裡面做一堆事,不然效率會大減,而要是你在其內的程式碼會引起重新呼叫到 WinProc ,那恭喜你,程式就當掉囉

所以 Thread 是無法解決你的問題的,因為就算用 Thread,處理滑鼠時處理滑鼠,處理重繪裡「時處理重繪」,你需要是「一起處理」,以免其一摧毀另一個作的事

===================引 用 brian_chen 文 章===================
先謝謝 syntax 大大的指點
我在滑鼠移動的相關位置加入了 Application->ProcessMessages
因為我是用背景圖所以是用圖片的 imgmousedown

[code cpp]
void __fastcall Form1::imgBGMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
{
if( Button == mbLeft )
{
ReleaseCapture();
Perform( WM_SYSCOMMAND, 0xf012, 0 );
Application->ProcessMessages(); //加在此不知道對不對@@
}
}

[/code]

還有另外大大有提到說用 xxx.Repaint
我原本在 progressBarShow 這裡面就有加入了

[code cpp]
void __fastcall Form1::progressBarShow(int iPercent)
{
AnsiString sStr;
if (iPercent > 100)
{
iPercent = 100;
}
sStr = iPercent;
sStr = " %";
lbWriteProgress->Caption = sStr;
iCountBar = iPercent;
imglistBar->GetBitmap(iCountBar / 5, FWMain->imgProgressBar->Picture->Bitmap);
imgProgressBar->Repaint(); // 我在這邊有加入 xxx.Repaint
Application->ProcessMessages();
}

[/code]

兩種方法都試過了
不過還是不行,不知道還有沒有別種方式

謝謝大大的回答

===================引 用 syntax 文 章===================
將 Application->ProcessMessages 加在主視窗的滑鼠移動相關位置
或是加入 xxx.Update, 或 xxx.Repaint 也可以

brian_chen
一般會員


發表:14
回覆:20
積分:6
註冊:2007-06-26

發送簡訊給我
#5 引用回覆 回覆 發表時間:2008-01-29 10:48:45 IP:220.228.xxx.xxx 訂閱
哈 不用抱歉啦
syntax 大大願意回答我的問題,我就很感激了

首先謝謝你花那麼多時間仔細思考我的問題及打這麼多字來回應我的文章
讓我獲益良多
我會再仔細想想怎麼去處理這個問題

非常謝謝你

===================引 用 syntax 文 章===================
抱歉,先前並沒有很仔細的思考你的意思

你的問題,很不好解決,也很好解決
不好解決是在於,你需要同步更新,很好解決,是如果你很瞭解訊息的運作方式

首先,思考一下 Application->ProcressMessage(); 的用意
這一行可以讓視窗在處理你的程式碼時,有機會去處理自身的訊息處理,也就是有時間,重繪窗體或給使用者反應
所以,在回圈中加入,才不會出現視窗沒有反應,可以拖曳,因為,這一行讓程式跳出回圈一下,做一點事,再回去

注意喔~是再回去執行回圈喔

所以問題來的,這樣可以分成,是
1.以執行更新 progressbar 為主,
要讓程式看起來可以拖曳沒有當掉
2.執行其他事情時,順便更新 progressbar,還要可以拖曳

如果是 1.
這就可以使用 Application->ProcressMessage(); 來達到需求

如果是 2.
就比較麻煩,要看你當下進行的作業。與更新 prograssbar 的關係,同時,還要支援拖曳,這就更不容易
因為畢竟,單 CPU 電腦一次只能做一件事,即使「看起來」是多工的

以上兩者都必須注意「重入」的問題,所以 Application->ProcressMessage(); 並不是那麼好用,用時還是要思考

想想,要是跳出來作的事,會再進入相同類似的程式碼,哪很容易造成無盡的循環,很快的 stack 就被耗盡了
再者,你跳出來,要進行拖曳,還是要更新圖像?所以我才會建議,在拖曳處處理完畢後,加入,才來更新

但是,更新 progressbar 的程式碼,就不可以再使用 Application->ProcressMessage(); ,因為這樣不就又給機會拖曳,表示視窗圖像又要進行更新,更新時又要作拖曳,拖曳再更新...沒完沒了喔

所以當你按住滑鼠時,視窗其實是全力在做你的工作,不斷的跳出來處理滑鼠的事件,即使該事件沒有做任何事,沒移動位置,不用重繪,但,就是佔住 CPU 不放

因為 Application->ProcressMessage(); 在事情沒有處理完畢前,不會回來

所以我們整理一下
1. Edit1.Update; 可以讓程式重繪,更新資訊給使用者,但也僅止於此,不會給使用者有機會互動
2. Application.ProcessMessages; 可以讓使用者互動機會,處理訊息,但不會幫你重繪視窗,除非處理的訊息是「重繪視窗」

再加上, Delphi 幫你包裝後的機制,有些東西都寫死囉,所以
你要做到
1.可以處理你的東西
2.同時可以拖曳
3.並讓視窗更新

你要,自己處理訊息,看你要在哪裡切入都可以,我的看法是直接在 WinProc 內處理 (override WinProc)
當接受到滑鼠訊息時,除處理該訊息,還要加上「重繪視窗」的動作 (當然要加入一個旗標,判斷一下是否需要更新 progressbar),在同一個訊息回圈內完成

然後在你要作的工作程式內加上 Application.ProcessMessages; 就可以解決

當然切入點不只一個,也可以再晚一點的階段,使用攔截訊息的方式,但是這樣要寫的副程式,就不只一個,因為滑鼠訊息不只一種,每一個都要攔的話,用每個訊息都會用到的 WinProc 是比較適合

不過這樣又衍生一個問題,WinProc 不可以亂攔,也不適合在裡面做一堆事,不然效率會大減,而要是你在其內的程式碼會引起重新呼叫到 WinProc ,那恭喜你,程式就當掉囉

所以 Thread 是無法解決你的問題的,因為就算用 Thread,處理滑鼠時處理滑鼠,處理重繪裡「時處理重繪」,你需要是「一起處理」,以免其一摧毀另一個作的事

===================引 用 brian_chen 文 章===================
先謝謝 syntax 大大的指點
我在滑鼠移動的相關位置加入了 Application->ProcessMessages
因為我是用背景圖所以是用圖片的 imgmousedown

[code cpp]
void __fastcall Form1::imgBGMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
{
if( Button == mbLeft )
{
ReleaseCapture();
Perform( WM_SYSCOMMAND, 0xf012, 0 );
Application->ProcessMessages(); //加在此不知道對不對@@
}
}

[/code]

還有另外大大有提到說用 xxx.Repaint
我原本在 progressBarShow 這裡面就有加入了

[code cpp]
void __fastcall Form1::progressBarShow(int iPercent)
{
AnsiString sStr;
if (iPercent > 100)
{
iPercent = 100;
}
sStr = iPercent;
sStr = " %";
lbWriteProgress->Caption = sStr;
iCountBar = iPercent;
imglistBar->GetBitmap(iCountBar / 5, FWMain->imgProgressBar->Picture->Bitmap);
imgProgressBar->Repaint(); // 我在這邊有加入 xxx.Repaint
Application->ProcessMessages();
}

[/code]

兩種方法都試過了
不過還是不行,不知道還有沒有別種方式

謝謝大大的回答

===================引 用 syntax 文 章===================
將 Application->ProcessMessages 加在主視窗的滑鼠移動相關位置
或是加入 xxx.Update, 或 xxx.Repaint 也可以

系統時間:2024-04-25 16:46:37
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!