exceldelphidbgrid

How to have responsive UI (Form) when performing long-running export task?


Good day people. First off, I'm not an native English speaker I might have some grammar mistakes or such.

I need an advice from people who has done something or an application alike mine, well, the thing is that I'm using a TProgressBar in my delphi form, another component called "TExcelApplication" and a TDBGrid.

When I export the DBGrid's content, the application "freezes", so I basically put that ProgressBar for the user to see how much the process is completed. I've realized that when the TDBGrid is retrieving and exporting each row to the new Excel workbook, you can't move the actual form, so you have to wait until the process is completed to move that form.

So, is it possible to do something (I thought about threads but I'm not sure if they could help) so the user could move the window if he wanted?

Thank you so much for taking your time in reading and giving me an advice. I'm using Delphi XE.

Here's the code I use to export the rows:

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;

If you want to see the whole code for the "Export" button, then feel free to see this link: http://pastebin.com/FFWAPdey


Solution

  • Whenever you're doing stuff that takes a significant amount of time in an application with GUI you want to put it in a seperate thread so the user can still operate the form. You can declare a simple thread as such:

    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;
    

    It's pretty straight forward, remember to always use Synchronize when you want to update visual elements from a thread. Usually, you also want to take care that the user can't invoke the thread again while it's still doing work as he's now able to use the GUI.