1. 程式人生 > >Delphi7 從子執行緒中傳送訊息到主執行緒觸發事件執行

Delphi7 從子執行緒中傳送訊息到主執行緒觸發事件執行

在對資料庫的操作時,有時要用一個子執行緒來進行後臺的資料操作。比如說資料備份,轉檔什麼的。在主視窗還能同是進行其它操作。而有時後臺每處理一個數據檔案,要向主視窗傳送訊息,讓主視窗實時顯示處理進度在視窗上(可視),同時進行日誌處理等。我用的是下面的方法:

[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;

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傳送');



還可以用
 function PostMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): BOOL; stdcall;
hWnd是視窗控制代碼,你可以將訊息傳送到主視窗。
而SendNotifyMessage是將訊息傳送到所有的頂層視窗。就是說如果你在系統中啟動了兩個例項執行。
一箇中發出的訊息兩個例項都會收到。而PostMessage由於是對控制代碼發訊息。只會在本身這個例項中產生作用。