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

Cyc*_*var 6 delphi excel 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;
Run Code Online (Sandbox Code Playgroud)

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

Ken*_*ite 5

如果行数很小(并且您知道将有多少行),则可以使用变体的变体数组更快地(并且一次全部)传输数据,如下所示:

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].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;
Run Code Online (Sandbox Code Playgroud)

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


DNR*_*DNR 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;
Run Code Online (Sandbox Code Playgroud)

这非常简单,当您想要从线程更新视觉元素时,请记住始终使用同步。通常,您还需要注意用户在线程仍在工作时无法再次调用该线程,因为他现在可以使用 GUI。

  • 使用这个来解决发帖者的问题只有一个问题,那就是需要为线程设置 COM(使用“CoInitializeEx”),因为它使用 COM 自动化。您还需要一个新的数据库连接和数据集用于单独的线程,这意味着连接到数据库并在线程的上下文中再次获取正确的行和列。 (10认同)