PeekMessage是否足以查询消息队列以进行鼠标按钮输入?

时间:2017-07-13 21:07:13

标签: delphi winapi wait windows-messages

我们的应用程序处理测量数据,测量数据必须从测量设备读取并存储在数据库中。

我们提供批量阅读和阅读的选项。一次存储多组测量数据。因为这是一个耗时的过程,我们会显示一个模式对话框,其中包含一个进度条和一个取消操作的按钮。

只有在读取并存储完整的测量数据集后才能取消操作。

阅读& store循环如下:

ItemsToStore := GetSelectedTreeItems();
DlgProgress  := TProgressWithAbort.Create(Screen.ActiveForm);

try
  for i := 0 to Pred(ItemsToStore.Count) do
  begin
    if DlgProgress.Cancel then exit;

    DlgProgress.Description := ItemsToStore[i].Name;
    ReadAndStoreItem(ItemsToStore[i].Id);

    DlgProgress.Position := Succ(i) * 100 div ItemsToStore.Count;
  end;

finally
  DlgProgress.Free;
end;

进度对话框的Position属性的setter调用一个名为CheckMouseButtonInput的过程,该过程当前编码如下:

procedure TProgressWithAbort.CheckMouseButtonInput;
var
  Msg: TMsg;

begin
  // if the left mouse button was pressed while the mouse was at the
  // Cancel button call the application's message loop to process the event
  if PeekMessage(Msg, btnCancel.Handle, WM_LBUTTONUP, WM_LBUTTONUP, PM_NOREMOVE) then
    Application.ProcessMessages;
end;

从应用程序的消息循环中调用以下按钮单击处理程序,它设置可通过属性访问的变量取消:

procedure TProgressWithAbort.btnCancelClick(Sender: TObject);
begin
  FCancel := true;
end;

一切正常。但我想知道CheckMouseButtonInput的上述实现是否会占用过多的CPU时间。在GetQueueStatus之前调用MsgWaitForMultipleObjectsPeekMessage(没有句柄和超时为0)会更好吗?

1 个答案:

答案 0 :(得分:1)

  

因为这是一个耗时的过程,我们会显示一个带有进度条的模态对话框和一个取消操作的按钮。

然后应该将进程移动到单独的工作线程。不要在主UI线程中运行冗长的操作。它应该只处理UI而不是其他任何东西。即使你想要阻止主UI线程直到进程完成,你仍然应该让主线程正常处理消息,不要手动完成。

启动线程,显示对话框,如果按下取消按钮则发出线程终止信号,并在线程退出时关闭对话框。线程可以在需要时向对话框发送UI更新,并检查测量之间的终止状态。不需要CheckMouseButtonInput()逻辑。

例如:

type
  TCancelEvent = procedure of object;

  TProgressWithAbort = class(TForm)
    btnCancel: TButton;
    procedure btnCancelClick(Sender: TObject);
  private
    FCancel: Boolean;
    FOnCancel: TCancelEvent;
  public
    property Cancel: Boolean read FCancel;
    property OnCancel: TCancelEvent read FOnCancel write FOnCancel;
  end;

procedure TProgressWithAbort.btnCancelClick(Sender: TObject);
begin
  FCancel := true;
  if Assigned(FOnCancel) then
    FOnCancel();
end;

procedure TMyForm.LengthyProcess;
var
  ItemsToStore: TListOfWhatever;
  StoreThread: TThread;
  DlgProgress: TProgressWithAbort;
begin
  ItemsToStore := GetSelectedTreeItems();

  DlgProgress  := TProgressWithAbort.Create(Self);
  try
    StoreThread := TThread.CreateAnonymousThread(
      procedure
      var
        i: Integer;
      begin
        try
          for i := 0 to Pred(ItemsToStore.Count) do
          begin
            if TThread.CheckTerminated then Exit;

            TThread.Queue(TThread.CurrentThread,
              procedure
              begin
                DlgProgress.Description := ItemsToStore[i].Name;
              end;
            );

            // make sure this function is thread-safe!
            ReadAndStoreItem(ItemsToStore[i].Id);

            TThread.Queue(TThread.CurrentThread,
              procedure
              begin
                DlgProgress.Position := Succ(i) * 100 div ItemsToStore.Count;
              end
            );
          end;
        finally
          DlgProgress.ModalResult := mrClose;
        end;
      end
    );
    try
      StoreThread.FreeOnTerminate := False;
      StoreThread.Start;    
      try
        DlgProgress.OnCancel := StoreThread.Terminate;
        DlgProgress.ShowModal;
      finally
        StoreThread.Terminate;
        StoreThread.WaitFor;
      end;
    finally
      StoreThread.Free;
    end;
  finally
    DlgProgress.Free;
  end;
end;

可替换地:

var
  ItemsToStore: TListOfWhatever;
  StoreThread: TThread;
  DlgProgress: TProgressWithAbort;
  ...

procedure TMyForm.StartLengthyProcess;
begin
  ItemsToStore := GetSelectedTreeItems();

  StoreThread := TThread.CreateAnonymousThread(
    procedure
    var
      i: Integer;
    begin
      for i := 0 to Pred(ItemsToStore.Count) do
      begin
        if TThread.CheckTerminated then Exit;

        TThread.Queue(TThread.CurrentThread,
          procedure
          begin
            DlgProgress.Description := ItemsToStore[i].Name;
          end;
        );

        // make sure this function is thread-safe!
        ReadAndStoreItem(ItemsToStore[i].Id);

        TThread.Queue(TThread.CurrentThread,
          procedure
          begin
            DlgProgress.Position := Succ(i) * 100 div ItemsToStore.Count;
          end
        );
      end;
    end
  );

  StoreThread.OnTerminate := LengthyProcessFinished;

  DlgProgress := TProgressWithAbort.Create(Self);
  DlgProgress.OnCancel := StoreThread.Terminate;
  DlgProgress.Show;
  // disable the rest of the UI as needed..

  StoreThread.Start;
end;

procedure TMyForm.LengthyProcessFinished(Sender: TObject);
begin
  StoreThread := nil;
  FreeAndNil(DlgProgress);
  // enable the rest of the UI as needed..
end;