如何在执行长时间运行的导出任务时拥有响应式UI(表单)?

时间:2013-11-29 22:13:28

标签: excel delphi dbgrid

美好的一天的人。首先,我不是英语母语,我可能会有一些语法错误。

我需要那些做过某些事情或类似我的应用程序的人的建议,好吧,问题是我正在使用我的delphi形式的TProgressBar,另一个名为“TExcelApplication”的组件和一个TDBGrid。

当我导出DBGrid的内容时,应用程序“冻结”,所以我基本上为用户放置了ProgressBar,以查看进程完成了多少。我已经意识到,当TDBGrid检索并将每行导出到新的Excel工作簿时,您无法移动实际的表单,因此您必须等到该过程完成才能移动该表单。

那么,是否有可能做某事(我考虑过线程,但我不确定他们是否可以提供帮助)所以用户可以根据需要移动窗口吗?

非常感谢你花时间阅读并给我一个建议。我正在使用Delphi XE。

这是我用来导出行的代码:

with ZQDetalles do
    begin
        First;
        while not EOF do
        begin
            i := i + 1;
            workSheet.Cells.Item[i,2] := DBGridDetalles.Fields[0].AsString;
            workSheet.Cells.Item[i,3] := DBGridDetalles.Fields[1].AsString;
            workSheet.Cells.Item[i,4] := DBGridDetalles.Fields[2].AsString;
            workSheet.Cells.Item[i,5] := DBGridDetalles.Fields[3].AsString;
            workSheet.Cells.Item[i,6] := DBGridDetalles.Fields[4].AsString;
            workSheet.Cells.Item[i,7] := DBGridDetalles.Fields[5].AsString;
            workSheet.Cells.Item[i,8] := DBGridDetalles.Fields[6].AsString;
            workSheet.Cells.Item[i,9] := DBGridDetalles.Fields[7].AsString;
            Next;
            barraProgreso.StepIt;
    end;
end;

如果您想查看“导出”按钮的完整代码,请随时看到此链接:http://pastebin.com/FFWAPdey

2 个答案:

答案 0 :(得分:4)

每当您在使用GUI的应用程序中执行需要花费大量时间的事情时,您希望将其放在单独的线程中,以便用户仍然可以操作表单。您可以声明一个简单的线程:

TWorkingThread = class(TThread)
protected
  procedure Execute; override;
  procedure UpdateGui;
  procedure TerminateNotify(Sender: TObject);
end;

procedure TWorkingThread.Execute;
begin
  // do whatever you want to do
  // make sure to use synchronize whenever you want to update gui:
  Synchronize(UpdateGui);
end;

procedure TWorkingThread.UpdateGui;
begin
  // e.g. updating the progress bar
end;

procedure TWorkingThread.TerminateNotify(Sender: TObject);
begin
  // this gets executed when the work is done
  // usually you want to give some kind of feedback to the user
end;

  // ...
  // calling the thread:

procedure TSettingsForm.Button1Click(Sender: TObject);
  var WorkingThread: TWorkingThread;
begin
  WorkingThread := TWorkingThread.Create(true);
  WorkingThread.OnTerminate := TerminateNotify;
  WorkingThread.FreeOnTerminate := true;
  WorkingThread.Start;
end;

这很简单,记得在想要更新线程中的可视元素时始终使用Synchronize。通常,您还需要注意,当用户现在仍然能够使用GUI时,用户无法再次调用该线程。

答案 1 :(得分:4)

如果行数很小(而且你知道你有多少行),你可以使用变体变量数组更快地(并一次性)传输数据,如下所示:

var
  xls, wb, Range: OLEVariant;
  arrData: Variant;
  RowCount, ColCount, i, j: Integer;
  Bookmark: TBookmark;
begin
  // Create variant array where we'll copy our data
  // Note that getting RowCount can be slow on large datasets; if
  // that's the case, it's better to do a separate query first to
  // ask for COUNT(*) of rows matching your WHERE clause, and use
  // that instead; then run the query that returns the actual rows,
  // and use them in the loop itself
  RowCount := DataSet1.RecordCount;
  ColCount := DataSet1.FieldCount;
  arrData := VarArrayCreate([1, RowCount, 1, ColCount], varVariant);

  // Disconnect from visual controls
  DataSet1.DisableControls;
  try
    // Save starting row so we can come back to it after
    Bookmark := DataSet1.GetBookmark;
    try    
      {fill array}
      i := 1;
      while not DataSet1.Eof do
      begin
        for j := 1 to ColCount do
          arrData[i, j] := DataSet1.Fields[j-1, i-1].Value;
        DataSet1.Next;
        Inc(i);
        // If we have a lot of rows, we can allow the UI to
        // refresh every so often (here every 100 rows)
        if (i mod 100) = 0 then
          Application.ProcessMessages;
      end;
    finally
      // Reset record pointer to start, and clean up
      DataSet1.GotoBookmark;
      DataSet1.FreeBookmark;
  finally
    // Reconnect GUI controls
    DataSet1.EnableControls;
  end;

  // Initialize an instance of Excel - if you have one 
  // already, of course the next couple of lines aren't
  // needed
  xls := CreateOLEObject('Excel.Application');

  // Create workbook - again, not needed if you have it.
  // Just use ActiveWorkbook instead
  wb := xls.Workbooks.Add;

  // Retrieve the range where data must be placed. Again, your
  // own WorkSheet and start of range instead of using 1,1 when
  // needed.
  Range := wb.WorkSheets[1].Range[wb.WorkSheets[1].Cells[1, 1],
                                  wb.WorkSheets[1].Cells[RowCount, ColCount]];

  // Copy data from allocated variant array to Excel in single shot
  Range.Value := arrData;

  // Show Excel with our data}
  xls.Visible := True;
end;

循环遍历数据的行和列仍然需要相同的时间,但实际将数据传输到Excel所需的时间大大减少,特别是如果有大量数据的话。