I am working with FireDac under Delphi 10.1 Berlin.
For displaying data to the user i use data aware controls like TDBEdit.
I use TFDQuery and TDataSource to link them with the controls.
This works but long sql queries that take some time to exectute will freeze the GUI.
I am wondering how to stop the gui from freezing while performing those long running queries.
I was thinking about background threads.
On the wiki i read that FireDac can work with multithreads:
http://docwiki.embarcadero.com/RADStudio/XE6/en/Multithreading_(FireDAC)
However in embarcadero community forums thread Jeff Overcash writes:
One thing I didn't see asked or Dmitry mention is you can not have
TDataSource or LiveBindings against your background threaded queries.
If you are background threading a query that displays the results you
should disconnect the LB or DataSource, open and fetch all the data
then re establish the connection.
Those two will be trying to move the cursor on you or querying the
buffer for display while the buffer is very volatile being moved
around in a different thread.
I am wondering if someone that also uses FireDac and displays the values on a form can help me out here.
The code sample below shows one way to retrive records from an MSSql Server
in a background thread using FireDAC. This omits a few details. For example, in practice, rather than the TQueryThreads Execute opening the query only once and then terminating, you would probably want the thread's Execute to contain a while loop in which it waits on a semaphore after the call to Synchronize and then close/re-open the query to update the main thread as often as you want.
type
TForm1 = class;
TQueryThread = class(TThread)
private
FConnection: TFDConnection;
FQuery: TFDQuery;
FForm: TForm1;
published
constructor Create(AForm : TForm1);
destructor Destroy; override;
procedure Execute; override;
procedure TransferData;
property Query : TFDQuery read FQuery;
property Connection : TFDConnection read FConnection;
property Form : TForm1 read FForm;
end;
TForm1 = class(TForm)
FDConnection1: TFDConnection;
FDQuery1: TFDQuery;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
public
QueryThread : TQueryThread;
end;
[...]
constructor TQueryThread.Create(AForm : TForm1);
begin
inherited Create(True);
FreeOnTerminate := True;
FForm := AForm;
FConnection := TFDConnection.Create(Nil);
FConnection.Params.Assign(Form.FDConnection1.Params);
FConnection.LoginPrompt := False;
FQuery := TFDQuery.Create(Nil);
FQuery.Connection := Connection;
FQuery.SQL.Text := Form.FDQuery1.SQL.Text;
end;
destructor TQueryThread.Destroy;
begin
FQuery.Free;
FConnection.Free;
inherited;
end;
procedure TQueryThread.Execute;
begin
Query.Open;
Synchronize(TransferData);
end;
procedure TQueryThread.TransferData;
begin
Form.FDQuery1.DisableControls;
Form.FDQuery1.Data := Query.Data;
Form.FDQuery1.EnableControls;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
QueryThread.Resume;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
QueryThread := TQueryThread.Create(Self);
end;
MJN's comment about bookmarks tells you how to preserve the current data row position in the gui.
Btw, although I've often done this with TClientDataSets, putting this answer together was the first time I'd tried it with FireDAC. In terms of configuring the components, all I did was to drag the components off the Palette, "wire them together" as you'd expect and then set the FDConnection's Params and the FDQuery's Sql.
Related
An old program of ours uses dBase tables and an .MDX index - other systems use these tables too, so we're stuck with them. We wish to replace BDE with FireDAC in our software. It seems that BDE methods DbiRegenIndex and DbiPackTable (regenerate index and pack table, respectively) are not provided by FireDAC - is there a way to perform these functions using FireDAC?
The code below shows how to index a dBase table using the MS dBase driver. I've
used the Ado components, rather than FireDAC because it is easier to set up all
their properties in code, so you can see what I'm doing. Note that as well as CREATE INDEX
the driver also supports DROP INDEX. See e.g. https://learn.microsoft.com/en-us/sql/odbc/microsoft/create-index-for-paradox
(which is for Paradox, but works for dBase as well)
To set yourself up for this project, you need to set up an ODBC system DSN called DBFTest using
the MS dBase driver.
It should be straightforward to translate this Ado example into FireDAC.
type
TForm1 = class(TForm)
ADOConnection1: TADOConnection;
btnCreateTable: TButton;
ADOQuery1: TADOQuery;
btnOpenTable: TButton;
DataSource1: TDataSource;
DBGrid1: TDBGrid;
DBNavigator1: TDBNavigator;
btnDropTable: TButton;
btnAddIndex: TButton;
procedure FormCreate(Sender: TObject);
procedure btnAddIndexClick(Sender: TObject);
procedure btnCreateTableClick(Sender: TObject);
procedure btnDropTableClick(Sender: TObject);
procedure btnOpenTableClick(Sender: TObject);
public
procedure CreatedBaseTable;
end;
[...]
procedure TForm1.FormCreate(Sender: TObject);
begin
AdoConnection1.ConnectionString := 'Provider=MSDASQL.1;Persist Security Info=False;Data Source=DBFTest';
end;
procedure TForm1.btnAddIndexClick(Sender: TObject);
var
Sql : String;
begin
if AdoQuery1.Active then
AdoQuery1.Close;
Sql := 'create index byID on dBaseTest (ID)';
AdoConnection1.Execute(Sql);
AdoQuery1.Open;
end;
procedure TForm1.btnCreateTableClick(Sender: TObject);
begin
CreatedBaseTable;
end;
procedure TForm1.btnDropTableClick(Sender: TObject);
var
Sql : String;
begin
Sql := 'drop table dBaseTest';
AdoConnection1.Execute(Sql);
end;
procedure TForm1.btnOpenTableClick(Sender: TObject);
begin
AdoQuery1.SQL.Text := 'select * from dBaseTest';
AdoQuery1.Open;
end;
procedure TForm1.CreatedBaseTable;
var
Sql : String;
i : Integer;
begin
Screen.Cursor := crSqlWait;
Update;
try
Sql := 'create table dBaseTest(ID int, AName char(20))';
AdoConnection1.Execute(Sql);
for i := 1 to 100 do begin
Sql := Format('insert into dBaseTest(ID, AName) values(%d, ''%s'')', [i, 'Name' + IntToStr(i)]);
AdoConnection1.Execute(Sql);
end;
finally
Screen.Cursor := crDefault
end;
end;
Obviously, to "regenerate" the indexes this way, you would just drop them if they exist, handling any exceptions if they don't, and then create them again.
I don't know whether the dBase driver supports a "pack table" command, but you could probably do this yourself using an INSERT INTO ... SELECT * FROM ..." to copy the active rows into temporary table, then delete all rows from your working table, then copy them back from the temporary one.
I am in the unpleasant situation. When one child changes its state, it calls its parent to respond to this change. In my case, the parent destroys all its children and recreates them. After that the program returns to the point, where the original caller is already de-referenced.
Although I see now, that it is a bad practice and I have to change whole philosophy, but for curiosity is it possible to stop execution on the end of routine?
This is only simple illustration:
TPerson = class(TPersistent)
private
FOnChange:TNotifyEvent;
FName:string;
published
property OnStatusChange:TNotifyEvent read FOnChange write FOnChange;
property Name:string read FName write FName;
end;
.... form declaration....
implementation
procedure TForm1.FormCreate(Sender: TObject);
begin
FPerson.OnStatusChange:=ProcessStatusChange;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FPerson.Free;
end;
procedure TForm1.ProcessStatusChange(Sender:TObject);
begin
btn1.free;
// and here I would like to call something like STOP
end;
procedure TForm1.btn1Click(Sender: TObject);
begin
if Assigned(FPerson.OnStatusChange) then FPerson.OnStatusChange(Self);
ShowMessage(btn1.Name); //btn1 does not exist anymore
end;
I have a Field named "refFile" which may or may not have a Path description in it.
I need to go through the entire database and check whether Paths that are defined in "refFile" still actually exist.
Using Delphi (Pascal) this takes many, many minutes
bigDB.First;
while not(bigDB.EOF) do
begin
if Trim(bigDB.FieldByName('refFile').AsString) > '' then
begin
if not(FileExists(bigDB.FieldByName('refFile').AsString)) then
begin
bigDB.Edit;
bigDB.FieldByName('refFile').AsString:='';
bigDB.Post;
end;
end;
bigDB.Next;
end;
How do I do that in SQL?
Thank you.
You cannot check the validity of a path in SQLLite but you can filter records with something in the path and reduce the list of lines to check.
You can order the records on this field (if you have an index on it) and check only the paths you didn't checked before.
You also can use threads to do this long operation in background. Simply use TThread.Createanonymousthread(procedure begin end).Start;
You can't check the existence of a file in a plain SQLLite query. You could do that by using an UDF (User defined function) but it would be a little more complex and would requires some skills in other programming languages (Note that in that case your files should be accessible from the server, otherwise it wouldn't work).
If you are looking for a simpler solution, I think you can speed up your program by reducing the number of records resulted by the query and by improving your Delphi code in order to make it a little more efficient.
Select SQL:
Use length and trim functions due to reduce the number of records to be verified by your Delphi code.
select refFile
from myTable
where (refFile is not null) and (length(trim(refFile)) > 0)
Delphi:
Call TDataSet.FieldByName only once.
Try using TDataSet.DisableControls and TDataSet.EnableControls (In this way, some dataset's components are faster, even if the dataset component is not linked to any control).
var
Fld : TField;
begin
BigDB.DisableControls();
try
Fld := BigDB.FieldByName('refFile');
BigDB.First;
while not(BigDB.Eof) do
begin
if not(FileExists(Fld.AsString)) then
begin
BigDB.Edit;
Fld.AsString := '';
BigDB.Post;
end;
BigDB.Next;
end;
finally
BigDB.EnableControls();
end;
Furthermore, you could consider these other optimizations:
If the refFile field contains the same value multiple times, you could sort the query by the refFile field and change the Delphi code in order to verify each filename only once. (You can do that by storing the last value and the result of the FileExists function).
You can run your code asyncronusly by using the TThread class. In this way your application won't freeze and it could be faster.
For example with FireDAC it's extremely easy to create user defined functions. If you're using it, try something like this. It could save some time because the engine doesn't need to fetch the resultset to the client application:
uses
FireDAC.Phys.SQLiteWrapper;
type
TForm1 = class(TForm)
Button1: TButton;
FDQuery1: TFDQuery;
FDConnection1: TFDConnection;
FDGUIxWaitCursor1: TFDGUIxWaitCursor;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
FValidator: TSQLiteFunction;
procedure ValidateFile(AFunc: TSQLiteFunctionData; AInputs: TSQLiteInputs;
AOutput: TSQLiteOutput; var AUserData: TObject);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FDConnection1.Open;
FValidator := TSQLiteFunction.Create((TObject(FDConnection1.CliObj) as TSQLiteDatabase).Lib);
FValidator.Args := 1;
FValidator.Name := 'FileExists';
FValidator.OnCalculate := ValidateFile;
FValidator.InstallAll;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
FDQuery1.SQL.Text :=
'UPDATE MyTable SET FileName = NULL WHERE ' +
'FileName IS NOT NULL AND NOT FileExists(FileName)';
FDQuery1.ExecSQL;
end;
procedure TForm1.ValidateFile(AFunc: TSQLiteFunctionData; AInputs: TSQLiteInputs;
AOutput: TSQLiteOutput; var AUserData: TObject);
begin
AOutput.AsBoolean := FileExists(AInputs[0].AsString);
end;
Or simply drop the TFDSQLiteFunction component, fill out the FunctionName property with name of the function, write OnCalculate event handler similar to the above and enable the component by setting the Active property.
I am a beginner of DELPHI.
I have to develop a application based on thread.
How can I suspend thread in thread execute function, not in UI thread....
Please give me sample
Suspend a thread simply by pausing. For instance you can sleep for a specified amount of time. Or you can wait on a synchronisation object. For instance, you might wait on an event. When another thread signals that event, the paused thread will resume.
Assuming you are working on Windows platform...
There are many ways to suspend a thread. Which one to use depends largely on when/why it should resume, and whether or not the thread has a message queue.
The point about the message queue is especially important if you need to suspend a thread for a long time. Any thread not processing Windows message can hang many operations, DDE communications, message broadcast, etc. The contract being "If your thread has a message queue, it NEEDS to treat them.", and it needs to do so in a timely fashion. In this case, I would suggest calling MsgWaitForMultipleObjects from the thread. The function works even if you are not waiting on any objects, which allows you to both wait on messages and have a timeout. WaitMessage could work too, but it doesn't timeout which has, amongst other implication, that you would need to send a message to the thread after terminating it, otherwise, it might never terminate.
If the thread does not have a message queue, then there are plenty of options. Sleep is a valid one if you want to wait for a specific amount of time. If you want to wait for a specific event to resume the thread, the TEvent class might be what you are looking for.
Hard to give a definitive answer without more details.
This example runs OK. By working with threads, by very careful. This is a very difficult section.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TMyThread=class(tthread)
FEvent:THandle;
private
procedure SetText;
protected
procedure Execute;override;
public
constructor Create(CreateSuspended:Boolean=false);
destructor Destroy; override;
procedure Start;
procedure Stop;
procedure StopAndTerminate;
end;
type
TForm1 = class(TForm)
OutMem: TMemo;
BtnStart: TButton;
BtnStop: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure BtnStartClick(Sender: TObject);
procedure BtnStopClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
mythread:tmythread;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
mythread:=tmythread.Create();
end;
{ TMyThread }
constructor TMyThread.Create(CreateSuspended: Boolean);
begin
Inherited Create(CreateSuspended);
FEvent:=CreateEvent(nil,true,false,pchar(inttostr(handle)));
end;
destructor TMyThread.Destroy;
begin
CloseHandle(FEVENT);
inherited;
end;
procedure TMyThread.Execute;
begin
while not terminated do
begin
WaitForSingleObject(FEvent,INFINITE);
if terminated then exit;
Synchronize(SetText);
{ Methods and properties of objects in visual components (forms, buttons, memo and other)
can only be used in a method called using Synchronize}
end;
end;
procedure TMyThread.SetText;
begin
form1.OutMem.Lines.Append(inttostr(gettickcount));
application.ProcessMessages;
// Application.ProcessMessages method must be call ONLY in MainThread.
// by using Synchronize, SetText procedure will be run in MainThread.
end;
procedure TMyThread.Start;
begin
SetEvent(FEVENT);
end;
procedure TMyThread.Stop;
begin
ResetEvent(FEVENT);
end;
procedure TMyThread.StopAndTerminate;
begin
Terminate;
PulseEvent(FEVENT);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
mythread.StopAndTerminate;
mythread.WaitFor;
mythread.Free;
end;
procedure TForm1.BtnStartClick(Sender: TObject);
begin
mythread.Start;
end;
procedure TForm1.BtnStopClick(Sender: TObject);
begin
mythread.Stop;
end;
end.
I need to detect when a USB Device is removed or inserted from my program.
I have made some research and found this but don't know how to implement/use it.
could someone help me out probably with a function that would return true on USB Insert and false on Removal so that i can call a Timer to check that function every second?
Using Delphi XE7.
Thanks.
Create an instance of TComponentUSB. Assign two event handlers to OnUSBArrival and OnUSBRemove events. Execute appropriate code in event handlers. Note that polling with TTimer is not needed.
Code sketch:
type
TMyForm = class(TForm)
FormCreate(Sender: TObject);
...
private
CUSB: ComponentUSB;
procedure USBArrival(Sender: TObject);
...
end;
procedure TMyForm.FormCreate(Sender: TObject);
begin
CUSB := ComponentUSB.Create(Self);
CUSB.OnUSBArrival = USBArrival;
end;
procedure TMyForm.USBArrival(Sender: TObject);
begin
Caption := 'I''m here now!';
end;