如何检测线程中的VK_ESCAPE键

时间:2014-02-08 15:26:23

标签: multithreading delphi

当我运行此线程时,未检测到VK_ESCAPE键,但如果在主线程中执行类似的代码,则检测到密钥。如何检测线程中的按键?

type

  { A TThread descendent for Saving Pictures }
  TFileSavingThread = class(TThread)
  private
    { Private declarations }
    procedure ImageEnProcFinishWork(Sender: TObject);
    procedure ImageEnProcProgress(Sender: TObject; per: integer);
  protected
    { Protected declarations }
    procedure Execute; override;
  public
    { Public declarations }
    constructor Create(CreateSuspended: Boolean);
  end;

Form1.KeyPreview = true;

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
  if Key = VK_ESCAPE then
    ACancel := True;
end;

procedure TFileSavingThread.Execute;
var
  iImageEnIO: TImageEnIO;
  iFolder: string;
  iFilename: string;
  iNode: TTreeNode;
begin

  inherited;

  { Get the Folder }
  if not Terminated then
  begin
    Synchronize(
      procedure
      begin
        iFolder := Form1.Folder1.Text;
      end);
  end;

  iImageEnIO := TImageEnIO.Create(nil);
  try

    iImageEnIO.OnProgress := ImageEnProcProgress;
    iImageEnIO.OnFinishWork := ImageEnProcFinishWork;

     { Save the Pictures }
    if not Terminated then
    begin
      Synchronize(
        procedure
        var
          i: integer;
        begin
          for i := 1 to Form1.TreeView1.Items.Count - 1 do
          begin
            Form1.LabelProgress1.Caption := 'Saving image ' + IntToStr(i) +
              ' of ' + IntToStr(Form1.TreeView1.Items.Count) +
              ' Press ESC to cancel.';
            Form1.LabelProgress1.Update;
            { Get the image from the camera }
            iNode := Form1.TreeView1.Items[i];
            iImageEnIO.WIAParams.ProcessingBitmap := iImageEnIO.IEBitmap;
            iImageEnIO.WIAParams.Transfer(TIEWiaItem(iNode.Data), False);
            { Get the filename }
            iFilename := iNode.Text + '.jpg';
            iImageEnIO.SaveToFile(IncludeTrailingPathDelimiter(iFolder) +
              iFilename);
            if Form1.ACancel then
              Terminate;
          end;
        end);
    end;

  finally
    iImageEnIO.Free;
  end;

end;

修改

我理解为了正确编写这个线程,其中耗时的部分应该在线程本身而不是在Syncronize中。真正的问题是如何将此代码放在Syncronize循环之外的线程中?

 iImageEnIO.WIAParams.ProcessingBitmap := iImageEnIO.IEBitmap;
 iImageEnIO.WIAParams.Transfer(TIEWiaItem(iNode.Data), False);
 { Get the filename }
 iFilename := iNode.Text + '.jpg';
 iImageEnIO.SaveToFile(IncludeTrailingPathDelimiter(iFolder) + iFilename);

也许将图像存储在TBitmap数组中然后访问线程中的循环中的位图以将位图保存到磁盘?

我认为我需要做的是在Syncronize中获取文件夹,文件名和位图,但将位图保存在线程本身中。我只是看不出怎么做,因为有一个循环来获取图像,并且将图像保存到磁盘的调用也需要在循环中?

2 个答案:

答案 0 :(得分:1)

您正在UI线程上运行代码。这就是Synchronize的作用。因此,由于UI线程繁忙,因此无法抽取消息队列。因此,您的排队输入事件不会被处理。

基本上你的线程编码不正确。由于所有工作都同步到UI线程,因此您对线程的使用会给您带来复杂性,而不会带来任何好处。

如果要将长时间运行的任务放在后台线程上,则需要在该线程上执行该工作。您需要在后台线程上完成所有耗时的工作,但只将UI进度更新放到主线程上。

您可以通过在线程上运行循环和处理图像来完成此操作。只有当您想要显示UI时才使用Synchronize


您的ACancel变量命名错误。将该前缀用于参数。这是一个字段,应该是FCancel

那就是说,这个领域毫无意义。您应该删除它并使用内置终止机制。在线程上调用Terminate。在线程Execute方法中,像你一样调用Terminate是没有意义的。当您需要退出时,只需退出该方法即可。而且您知道何时退出,因为Terminated属性为True

答案 1 :(得分:1)

我正在添加第二个答案来解决你的第二个问题。这些应该是两个独立的问题。您有以下情形:

  1. 您需要从GUI控件中读取以获取图像处理的文件名和数据规范。这需要在UI线程上运行,因为它访问GUI中保存的状态。
  2. 您希望使用线程执行保存,因为它非常耗时,并且您不想阻止UI。
  3. 解决这个问题需要做的是解除执行任务所需信息的收集,以及实际执行这些任务。因此,像这样构造代码:

    type
      TTask = record
        FileName: string;
        // other information specifying task
      end;
    
    procedure TMyForm.SaveButtonClick(Sender: TObject);
    var
      i: Integer;
      Tasks: TArray<TTask>;
    begin
      SetLength(Tasks, TreeView1.Items.Count);
      for i := 0 to high(Tasks) do
        Tasks[i] := GetTask(i);//you need to write GetTask
      end;
      FSaveThread := TSaveThread.Create(Tasks);
    end;
    

    您的主题可能如下所示:

    type
      TSaveThread = class(TThread)
      private
        FTasks: TArray<TTask>;
      protected
        procedure Execute; override;
      public
        constructor Create(const Tasks: TArray<TTask>);
      end;
    
    constructor TSaveThread.Create(const Tasks: TArray<TTask>);
    begin 
      inherited Create(False);
      FTasks := Tasks;
    end;
    
    procedure TSaveThread.Execute;
    var
      i: Integer;
    begin
      for i := 0 to high(FTasks) do
      begin
        if Terminated then
          exit;
        ProcessTask(FTasks[i]);//again, you need to write this
        Sychronize(UpdateUI);
      end;
    end;
    

    重点是您收集预先执行操作所需的信息。这涉及访问UI,可以在事件处理程序或从它调用的方法中进行。然后,一旦收集了所有信息,就将其传递给线程,该线程可以集中精力完成工作,而不必担心GUI的进一步访问。是的,你会想要进行进度报告,但这很快捷,而且可以在不阻止用户界面的情况下轻松完成。