在新的线程中有操作主线程界面的代码,新的线程挂起程序就死住,用Synchronize后却只有执行完新的线程里面的代码后,主线程的窗体才能移动,有没有其他方法? 在新的线程中有操作主线程界面的代码,新的线程挂起程序就死住,用Synchronize后却只有执行完新的线程里面的代码后,主线程的窗体才能移动,有没有其他方法? 解决方案 » 免费领取超大流量手机卡,每月29元包185G流量+100分钟通话, 中国电信官方发货 要用SendMessage或PostMessage來處理 eg1:在對資料庫的操作時,有時要用一個子執行緒來進行後臺的資料操作。比如說資料備份,轉檔什麼的。在主視窗還能同是進行其它操作。而有時後臺每處理一個資料檔案,要向主視窗發送消息,讓主視窗即時顯示處理進度在視窗上(可視),同時進行日誌處理等。我用的是下面的方法:[1]用到的API函數:RegisterWindowsMessage----------------------函數功能:該函式定義一個新的視窗消息,該消息確保在系統中是唯一的。返回的消息值可在調用函數SendMessage或PostMessage時使用。function RegisterWindowMessage(lpString: PChar): UINT; stdcall;SendNotifyMessage----------------------函數功能:該函數將指定的消息發送到一個視窗。 如果該視窗是由調用執行緒創建的;此函數為該視窗調用視窗程式, 並等待視窗程式處理完消息後再返回。 如果該視窗是由不同的執行緒創建的,此函數將消息傳給該視窗程式, 並立即返回,不等待視窗程式處理完消息。 SendNotifyMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM IParam);BroadcastSystemMessage----------------------函數功能:該函數發送消息給指定的接受者。 接受者可以是一個應用程式、安裝驅動器、網路磁碟、系統級裝置驅動程式 或這些系統元件的組合。[2]過程: type TForm1 = class(TForm) ............... ............... private Msg: Cardinal; protected procedure WndProc(var Message: TMessage); override; public ............... ............... end; var Form1: TForm1; MsgStrList: TStringList; MsgStrLock : TCriticalSection;implementationuses ThreadCommunication_Unit;{$R *.dfm}procedure TForm1.FormCreate(Sender: TObject);begin Msg := RegisterWindowMessage('wm_threadmsg'); MsgStrList := TStringList.Create;end;procedure TForm1.WndProc(var Message: TMessage);begin if Message.Msg = Msg then begin MsgStrLock.Enter; if MsgStrList.Count > 0 then begin Caption := MsgStrList.Strings[0]; MsgStrList.Delete(0); end; MsgStrLock.Leave; ShowMessage('收到消息了'+ inttostr(Message.Msg)); end else begin inherited; end;end;procedure TForm1.Button1Click(Sender: TObject);begin TThreadCommunication.Create(Msg,Memo1);end; ............... ...............initialization MsgStrLock := TCriticalSection.Create;finalization MsgStrLock.Free;end.一個子執行緒類的單元:unit ThreadCommunication_Unit;interfaceuses Classes,StdCtrls;type TThreadCommunicaiton = class(TThread) private FMsg : Cardinal; FMemo: TMemo; protected procedure Execute; override; procedure SendMsg; public constructor Create(aMsg:Cardinal;am:TMemo);virtual; end;implementationuses Messages,Windows, Dialogs,SysUtils, ThreadMsg;{ TThreadCommunicaiton }constructor TThreadCommunicaiton.Create(aMsg: Cardinal; am:TMemo);begin inherited Create(True); FMsg := aMsg; FMemo:= am; FreeOnTerminate :=True; Resume;end;procedure TThreadCommunicaiton.Execute;begin Synchronize(SendMsg);end;procedure TThreadCommunicaiton.SendMsg;var M: TMessage; B: DWord; d: integer;begin { Place thread code here } sleep(50); M.Msg := FMsg; B := BSM_ALLCOMPONENTS; MsgStrLock.Enter; MsgStrList.Add('子執行緒子柄:'+inttostr(ThreadID)+ ' 用BroadcastSystemMessage發送'); d := MsgStrList.Count; MsgStrLock.Leave; BroadcastSystemMessage(BSF_POSTMESSAGE, @B , M.Msg, M.WParam, M.LParam ); FMemo.Lines.Add('子執行緒子柄:'+inttostr(ThreadID)+ ' 用BroadcastSystemMessage發送'+inttostr(d));end;end.我在視窗上放有一Memo控制項,可以顯示一些資訊。同時我定義了一個全域的TStringList的變數,用於存在要從子執行緒傳出的一些值。用BroadcaseSystemMessage發送消息,而消息號由創建子執行緒時傳入。而消息號在FormCreate中用RegisterWindowsMessage定義,並獲得一個消息號。而消息觸發後的事件處理寫在WndProc中。這裡將子執行緒傳出的字串寫入視窗的標題。而TStringList的變數作為臨界區使用, 因為當兩個執行緒訪問全域量時,為防止它們同時執行,需要使用執行緒同步。用TCriticalSection進行操作。Enter,進入臨界區Leave,離開臨界區這樣可以正確的處理從子執行緒發來的消息。如果是用SendNotifyMessage函數發送消息的話。用法如下: M.Msg := FMsg; SendNotifyMessage(HWND_BROADCAST,M.Msg , M.WParam, M.LParam);參數為HWND_BROADCAST,則消息將被發送到系統中所有頂層視窗,包括無效或不可見的非自身擁有的視窗、被覆蓋的視窗和彈出式視窗,但消息不被發送到子窗口。由於是用SendNotifyMessage將消息發送到主視窗,而主視窗所在執行緒與調用執行緒是同一個執行緒,所以要等待視窗程式處理完消息後再返回。才會執行子執行緒中的:FMemo.Lines.Add('子執行緒子柄:'+inttostr(ThreadID)+ ' 用SendNotifyMessage發送'); eg2:Delphi中DLL的消息處理經過一翻摸索, 找到了解決方法. 在DLL的表單上放一個TTimer控制項. Interval儘量小. OnTimer只添加一行代碼: CheckSynchronize; 但接下來的一個問題卻很惱火的: 在DLL的表單上放一個TSpeedButton控制項, Flat屬性設置為True. 運行. 當滑鼠從TSpeedButton上移過時, TSpeedButton怎麼也還原不了. 試著調用它的重畫等功能. 全部沒用. 好幾天的時間一直在思考這個問題. 後來在處理應用程式的消息的時候, 突然想到: DLL雖然有自己Application, 但它並沒有自己的消息迴圈, 而執行緒的Synchronize不能執行, TSpeedButton不能還原都是因為有些消息沒有得到相應的處理而導致的. 也就是說, 只要給DLL加上一個消息迴圈, 上面的這些問題都會全部解決.剛開始的時候想從主程序發送消息給DLL. 可消息截取的結果是: 很多DLL裡產生的消息並沒有發送給主程序. 看來這個方法是行不通的. 只得另尋方法.在看到以下幾行大家很熟悉的代碼後想到. Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run;可不可以給DLL也加上這們的代碼呢?動手實驗, 創建一個DLL, DLL裡包含一個表單DLLForm. 從DLL裡匯出一個函數. 加上上面的代碼. 如下:procedure InitDLL; stdcall;begin Application.Initialize; Application.CreateForm(TDLLForm, DLLForm); Application.Run;end;再到主程序表單的創建事件代碼如下:procedure TForm1.FormCreate(Sender: TObject);begin InitDLL;end;先打開主表單了... :(且 InitDLL; 也不是立即返回, 而是當DLL裡主表單關閉後 把OnCreate的代碼放到一個TTimer控制項裡. Interval為1. OnTimer的代碼如下.procedure TForm1.Timer1Timer(Sender: TObject);begin TTimer(Sender).Enabled := False; InitDLL;end;這下可以了. 但不能讓DLL裡的表單一開始就顯示出來吧. 得. 再改改InitDLL. 如下:procedure InitDLL; stdcall;begin Application.Initialize; Application.ShowMainForm := False; Application.CreateForm(TDLLForm, DLLForm); Application.Run;end;主表單不顯示了, 得加上一個, 看看效果:) 再到DLL里加上一個Form( 命名為 DLLChildForm ), 在表單上放一個TSpeedButton控制項.再給DLL匯出一個函數, 如下:procedure CreateChildForm; stdcall;begin with TDLLChildForm.Create(Application) do begin Show; end;end;再到主表單中添加一個按鈕. 點擊事件代碼如下.procedure TForm1.Button1Click(Sender: TObject);begin CreateChildForm;end;再從DLL裡匯出一個過程, 代碼如下:procedure DestoryDLL; stdcall;var i: Integer;begin for i := Application.ComponentCount - 1 downto 0 do begin if Application.Components[i].ClassNameIs('TDLLChildForm') then begin TDLLChildForm(Application.Components[i]).Release; end; end; if DLLForm = nil then begin Exit; end; DLLForm.Release; DLLForm := nil;end;再給主程序主表單的OnCloseQuery添加代碼如下:procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);begin DestoryDLL;end;雖然DLL裡的表單全關閉了, 可主程序還是退不出啊. 換換方法, 把 DLLForm.Release; 這裡改成Application.Terminate; 試試. 還是不行. 咋回事? 反復調試, 發現雖然Terminate了, 可Run仍在迴圈. 並沒有結束. 再研究Run的代碼. 呵呵. 有了.把Application.Terminate;換成PostMessage(Application.Handle, WM_QUIT, 0, 0);運行, 還是不行. 但Run是迴圈是退出了. 那哪裡還會有問題呢? 該不會是表單沒有釋放吧. 好, 在PostMessage前加上DLLForm.Release;這時, DestoryDLL過程的代碼如下:procedure DestoryDLL; stdcall;var i: Integer;begin for i := Application.ComponentCount - 1 downto 0 do begin if Application.Components[i].ClassNameIs('TDLLChildForm') then begin TDLLChildForm(Application.Components[i]).Release; end; end; if DLLForm = nil then begin Exit; end; DLLForm.Release;// Application.Terminate; PostMessage(Application.Handle, WM_QUIT, 0, 0); DLLForm := nil;end;運行OK. 完美解決...再加上執行緒試試( 這時InitDLL過程要改成如下, 這樣才能真正的處理所有的消息 ) . 真爽. 與想像的一樣.procedure InitDLL(AHandle: Thandle); stdcall;begin Application.Initialize; Application.ShowMainForm := False; Application.CreateForm(TDLLForm, DLLForm); // 保存原來的控制碼 DLLForm.Tag := Application.Handle; // DLL 從屬的控制碼 ( 如果沒有此行, 執行緒的執行不能達到理想效果 ) // 並且這樣才能真正的讓消息迴圈處理它應處理的所有消息 Application.Handle := AHandle; Application.Run; Application.Handle := DLLForm.Tag;end; delphi dbgrid 跳转行 FastReport 如何实现chart的柱形图空心打印 我遇到的delphi的几个问题,请大家帮助 我用DELPHI和SQL做的收费系统,其中用了数据模块,为什么点登陆后会出现这个?? 怎么判断XP里自带的防火墙功能有没启用,如果启用了怎么关了它(声明,我不是在做本马)。 报表里显示图片问题 Indy9传文件 case的使用問題 delphi 关于fastreport的问题 请教delphi中不固定参数的用法。 【CSDN开心辞典】Delphi版牌牌最多的人是谁? WebServices获取股票实时信息
RegisterWindowsMessage
----------------------
函數功能:該函式定義一個新的視窗消息,該消息確保在系統中是唯一的。返回的消息值可在調用函數SendMessage或PostMessage時使用。
function RegisterWindowMessage(lpString: PChar): UINT; stdcall;SendNotifyMessage
----------------------
函數功能:該函數將指定的消息發送到一個視窗。
如果該視窗是由調用執行緒創建的;此函數為該視窗調用視窗程式,
並等待視窗程式處理完消息後再返回。
如果該視窗是由不同的執行緒創建的,此函數將消息傳給該視窗程式,
並立即返回,不等待視窗程式處理完消息。
SendNotifyMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM IParam);BroadcastSystemMessage
----------------------
函數功能:該函數發送消息給指定的接受者。
接受者可以是一個應用程式、安裝驅動器、網路磁碟、系統級裝置驅動程式
或這些系統元件的組合。[2]過程:
type
TForm1 = class(TForm)
...............
...............
private
Msg: Cardinal;
protected
procedure WndProc(var Message: TMessage); override;
public
...............
...............
end; var
Form1: TForm1;
MsgStrList: TStringList;
MsgStrLock : TCriticalSection;
implementation
uses ThreadCommunication_Unit;
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
Msg := RegisterWindowMessage('wm_threadmsg');
MsgStrList := TStringList.Create;
end;
procedure TForm1.WndProc(var Message: TMessage);
begin
if Message.Msg = Msg then begin
MsgStrLock.Enter;
if MsgStrList.Count > 0 then begin
Caption := MsgStrList.Strings[0];
MsgStrList.Delete(0);
end;
MsgStrLock.Leave;
ShowMessage('收到消息了'+ inttostr(Message.Msg));
end
else begin
inherited;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
TThreadCommunication.Create(Msg,Memo1);
end;
...............
...............initialization
MsgStrLock := TCriticalSection.Create;
finalization
MsgStrLock.Free;
end.一個子執行緒類的單元:
unit ThreadCommunication_Unit;
interface
uses
Classes,StdCtrls;
type
TThreadCommunicaiton = class(TThread)
private
FMsg : Cardinal;
FMemo: TMemo;
protected
procedure Execute; override;
procedure SendMsg;
public
constructor Create(aMsg:Cardinal;am:TMemo);virtual;
end;
implementation
uses Messages,Windows, Dialogs,SysUtils, ThreadMsg;{ TThreadCommunicaiton }
constructor TThreadCommunicaiton.Create(aMsg: Cardinal; am:TMemo);
begin
inherited Create(True);
FMsg := aMsg;
FMemo:= am;
FreeOnTerminate :=True;
Resume;
end;
procedure TThreadCommunicaiton.Execute;
begin
Synchronize(SendMsg);
end;procedure TThreadCommunicaiton.SendMsg;
var
M: TMessage;
B: DWord;
d: integer;
begin
{ Place thread code here }
sleep(50);
M.Msg := FMsg;
B := BSM_ALLCOMPONENTS;
MsgStrLock.Enter;
MsgStrList.Add('子執行緒子柄:'+inttostr(ThreadID)+ ' 用BroadcastSystemMessage發送');
d := MsgStrList.Count;
MsgStrLock.Leave;
BroadcastSystemMessage(BSF_POSTMESSAGE, @B , M.Msg, M.WParam, M.LParam );
FMemo.Lines.Add('子執行緒子柄:'+inttostr(ThreadID)+ ' 用BroadcastSystemMessage發送'+inttostr(d));end;
end.我在視窗上放有一Memo控制項,可以顯示一些資訊。
同時我定義了一個全域的TStringList的變數,用於存在要從子執行緒傳出的一些值。用BroadcaseSystemMessage發送消息,而消息號由創建子執行緒時傳入。而消息號在FormCreate中用RegisterWindowsMessage定義,並獲得一個消息號。
而消息觸發後的事件處理寫在WndProc中。
這裡將子執行緒傳出的字串寫入視窗的標題。而TStringList的變數作為臨界區使用, 因為當兩個執行緒訪問全域量時,為防止它們同時執行,需要使用執行緒同步。
用TCriticalSection進行操作。
Enter,進入臨界區
Leave,離開臨界區
這樣可以正確的處理從子執行緒發來的消息。如果是用SendNotifyMessage函數發送消息的話。
用法如下:
M.Msg := FMsg;
SendNotifyMessage(HWND_BROADCAST,M.Msg , M.WParam, M.LParam);參數為HWND_BROADCAST,則消息將被發送到系統中所有頂層視窗,包括無效或不可見的非自身擁有的視窗、被覆蓋的視窗和彈出式視窗,但消息不被發送到子窗口。由於是用SendNotifyMessage將消息發送到主視窗,而主視窗所在執行緒與調用執行緒是同一個執行緒,所以要等待視窗程式處理完消息後再返回。才會執行子執行緒中的:FMemo.Lines.Add('子執行緒子柄:'+inttostr(ThreadID)+ ' 用SendNotifyMessage發送');
經過一翻摸索, 找到了解決方法. 在DLL的表單上放一個TTimer控制項. Interval儘量小. OnTimer只添加一行代碼: CheckSynchronize; 但接下來的一個問題卻很惱火的: 在DLL的表單上放一個TSpeedButton控制項, Flat屬性設置為True. 運行. 當滑鼠從TSpeedButton上移過時, TSpeedButton怎麼也還原不了. 試著調用它的重畫等功能. 全部沒用. 好幾天的時間一直在思考這個問題. 後來在處理應用程式的消息的時候, 突然想到: DLL雖然有自己Application, 但它並沒有自己的消息迴圈, 而執行緒的Synchronize不能執行, TSpeedButton不能還原都是因為有些消息沒有得到相應的處理而導致的. 也就是說, 只要給DLL加上一個消息迴圈, 上面的這些問題都會全部解決.剛開始的時候想從主程序發送消息給DLL. 可消息截取的結果是: 很多DLL裡產生的消息並沒有發送給主程序. 看來這個方法是行不通的. 只得另尋方法.在看到以下幾行大家很熟悉的代碼後想到.
Application.Initialize;
Application.CreateForm(TForm1, Form1);
Application.Run;可不可以給DLL也加上這們的代碼呢?動手實驗, 創建一個DLL, DLL裡包含一個表單DLLForm. 從DLL裡匯出一個函數. 加上上面的代碼. 如下:
procedure InitDLL; stdcall;
begin
Application.Initialize;
Application.CreateForm(TDLLForm, DLLForm);
Application.Run;
end;
再到主程序表單的創建事件代碼如下:
procedure TForm1.FormCreate(Sender: TObject);
begin
InitDLL;
end;
先打開主表單了... :(且 InitDLL; 也不是立即返回, 而是當DLL裡主表單關閉後
把OnCreate的代碼放到一個TTimer控制項裡. Interval為1. OnTimer的代碼如下.
procedure TForm1.Timer1Timer(Sender: TObject);
begin
TTimer(Sender).Enabled := False;
InitDLL;
end;這下可以了. 但不能讓DLL裡的表單一開始就顯示出來吧. 得. 再改改InitDLL. 如下:
procedure InitDLL; stdcall;
begin
Application.Initialize;
Application.ShowMainForm := False;
Application.CreateForm(TDLLForm, DLLForm);
Application.Run;
end;
主表單不顯示了, 得加上一個, 看看效果:)
再到DLL里加上一個Form( 命名為 DLLChildForm ), 在表單上放一個TSpeedButton控制項.
再給DLL匯出一個函數, 如下:
procedure CreateChildForm; stdcall;
begin
with TDLLChildForm.Create(Application) do
begin
Show;
end;
end;
再到主表單中添加一個按鈕. 點擊事件代碼如下.
procedure TForm1.Button1Click(Sender: TObject);
begin
CreateChildForm;
end;
再從DLL裡匯出一個過程, 代碼如下:
procedure DestoryDLL; stdcall;
var
i: Integer;
begin
for i := Application.ComponentCount - 1 downto 0 do
begin
if Application.Components[i].ClassNameIs('TDLLChildForm') then
begin
TDLLChildForm(Application.Components[i]).Release;
end;
end; if DLLForm = nil then
begin
Exit;
end; DLLForm.Release;
DLLForm := nil;
end;再給主程序主表單的OnCloseQuery添加代碼如下:
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
DestoryDLL;
end;
雖然DLL裡的表單全關閉了, 可主程序還是退不出啊. 換換方法, 把 DLLForm.Release; 這裡改成Application.Terminate; 試試. 還是不行. 咋回事?
反復調試, 發現雖然Terminate了, 可Run仍在迴圈. 並沒有結束.
再研究Run的代碼. 呵呵. 有了.
把Application.Terminate;換成PostMessage(Application.Handle, WM_QUIT, 0, 0);
運行, 還是不行. 但Run是迴圈是退出了. 那哪裡還會有問題呢? 該不會是表單沒有釋放吧. 好, 在PostMessage前加上DLLForm.Release;這時, DestoryDLL過程的代碼如下:
procedure DestoryDLL; stdcall;
var
i: Integer;
begin
for i := Application.ComponentCount - 1 downto 0 do
begin
if Application.Components[i].ClassNameIs('TDLLChildForm') then
begin
TDLLChildForm(Application.Components[i]).Release;
end;
end; if DLLForm = nil then
begin
Exit;
end; DLLForm.Release;
// Application.Terminate;
PostMessage(Application.Handle, WM_QUIT, 0, 0);
DLLForm := nil;
end;
運行OK. 完美解決...
再加上執行緒試試( 這時InitDLL過程要改成如下, 這樣才能真正的處理所有的消息 ) . 真爽. 與想像的一樣.
procedure InitDLL(AHandle: Thandle); stdcall;
begin
Application.Initialize;
Application.ShowMainForm := False;
Application.CreateForm(TDLLForm, DLLForm);
// 保存原來的控制碼
DLLForm.Tag := Application.Handle;
// DLL 從屬的控制碼 ( 如果沒有此行, 執行緒的執行不能達到理想效果 )
// 並且這樣才能真正的讓消息迴圈處理它應處理的所有消息
Application.Handle := AHandle;
Application.Run; Application.Handle := DLLForm.Tag;
end;