DWScript: Issue updating to current development version - dwscript

This weekend, I updated my code base from DWScript SVN. I used Preview 2.7 and now I'm using up-to-date trunk version.
I recompile my application and now the OnAfterInitUnitTable is no more triggered. Actually TdwsUnit.InitUnitTable is not called at all.
BTW: TDWSunit is created at runtime by code and then two classes are exposed using ExposeRTTI. In need to expose one instance of each class.
What are - now - the prerequisites to have OnAfterInitUnitTable triggered?
Any help appreciated.
EDIT: Sample code to reproduce:
program ExposeTest;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils, Classes, TypInfo,
dwsRTTIExposer, dwsExprs, dwsComp;
type
TScriptApplication = class(TPersistent)
end;
TTestClass = class(TThread)
private
FScript : IdwsProgram;
FDelphiWebScript : TDelphiWebScript;
FUnit : TdwsUnit;
FScriptApplication : TScriptApplication;
FSuccess : Boolean;
procedure ExposeInstancesAfterInitTable(Sender: TObject);
public
constructor Create;
destructor Destroy; override;
procedure Execute; override;
end;
var
Test : TTestClass;
{ TTestClass }
constructor TTestClass.Create;
begin
inherited Create(TRUE);
FScriptApplication := TScriptApplication.Create;
FDelphiWebScript := TDelphiWebScript.Create(nil);
FUnit := TdwsUnit.Create(nil);
FUnit.UnitName := 'Test';
FUnit.Script := FDelphiWebScript;
FUnit.ExposeRTTI(TypeInfo(TScriptApplication), [eoNoFreeOnCleanup]);
FUnit.OnAfterInitUnitTable := ExposeInstancesAfterInitTable;
end;
destructor TTestClass.Destroy;
begin
FreeAndNil(FScriptApplication);
FreeAndNil(FUnit);
FreeAndNil(FDelphiWebScript);
inherited;
end;
procedure TTestClass.Execute;
begin
WriteLn('Test 1');
FSuccess := FALSE;
FScript := FDelphiWebScript.Compile('Unit Test; var I: Integer; I := 0;');
if FSuccess then
WriteLn(' Success')
else
WriteLn(' Failure');
WriteLn('Test 2');
FSuccess := FALSE;
FScript := FDelphiWebScript.Compile('var I: Integer; I := 0;');
if FSuccess then
WriteLn(' Success')
else
WriteLn(' Failure');
WriteLn('Test Done');
end;
procedure TTestClass.ExposeInstancesAfterInitTable(Sender: TObject);
begin
FUnit.ExposeInstanceToUnit('Application', 'TScriptApplication', FScriptApplication);
WriteLn('OnAfterInitUnitTable called');
FSuccess := TRUE;
end;
begin
Test := TTestClass.Create;
Test.Start;
Sleep(1000);
WriteLn('Hit enter to quit');
ReadLn;
Test.Free;
end.
EDIt2: Other version to show the new issue using suggestion by Eric Grange in answer 1 below;
program ExposeTest;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils, Classes, TypInfo,
dwsRTTIExposer, dwsFunctions, dwsExprs, dwsComp;
type
TScriptApplication = class(TPersistent)
published
procedure Demo;
end;
TTestClass = class(TThread)
private
FScript : IdwsProgram;
FDelphiWebScript : TDelphiWebScript;
FUnit : TdwsUnit;
FScriptApplication : TScriptApplication;
FSuccess : Boolean;
procedure ExposeInstancesAfterInitTable(Sender: TObject);
function NeedUnitHandler(const UnitName : UnicodeString;
var UnitSource : UnicodeString): IdwsUnit;
public
constructor Create;
destructor Destroy; override;
procedure Execute; override;
end;
var
Test : TTestClass;
{ TTestClass }
constructor TTestClass.Create;
begin
inherited Create(TRUE);
FScriptApplication := TScriptApplication.Create;
FDelphiWebScript := TDelphiWebScript.Create(nil);
FDelphiWebScript.OnNeedUnit := NeedUnitHandler;
FUnit := TdwsUnit.Create(nil);
FUnit.UnitName := 'Test';
FUnit.Script := FDelphiWebScript;
FUnit.ExposeRTTI(TypeInfo(TScriptApplication), [eoNoFreeOnCleanup]);
FUnit.OnAfterInitUnitTable := ExposeInstancesAfterInitTable;
end;
destructor TTestClass.Destroy;
begin
FreeAndNil(FScriptApplication);
FreeAndNil(FUnit);
FreeAndNil(FDelphiWebScript);
inherited;
end;
procedure TTestClass.Execute;
begin
WriteLn('Test 1');
FSuccess := FALSE;
FScript := FDelphiWebScript.Compile('Unit Test; var I: Integer; I := 0;');
WriteLn(FScript.Msgs.AsInfo);
if FSuccess then
WriteLn(' Success')
else
WriteLn(' Failure');
WriteLn('Test 2');
FSuccess := FALSE;
FScript := FDelphiWebScript.Compile('uses Other;');
WriteLn(FScript.Msgs.AsInfo);
if FSuccess then
WriteLn(' Success')
else
WriteLn(' Failure');
WriteLn('Test Done');
end;
procedure TTestClass.ExposeInstancesAfterInitTable(Sender: TObject);
begin
FUnit.ExposeInstanceToUnit('Application', 'TScriptApplication', FScriptApplication);
WriteLn('OnAfterInitUnitTable called');
FSuccess := TRUE;
end;
function TTestClass.NeedUnitHandler(
const UnitName : UnicodeString;
var UnitSource : UnicodeString): IdwsUnit;
begin
Result := nil;
if SameText(UnitName, 'Other') then
UnitSource := 'unit Other;' + #13#10 +
'procedure Func;' + #13#10 +
'begin' + #13#10 +
' Application.Demo;' + #13#10 +
'end;' + #13#10
else
UnitSource := '';
end;
{ TScriptApplication }
procedure TScriptApplication.Demo;
begin
end;
begin
Test := TTestClass.Create;
Test.Start;
Sleep(1000);
WriteLn('Hit enter to quit');
ReadLn;
Test.Free;
end.

When encountering a "unit" as main program, the compiler currently assumes it's just a compilation for IDE purposes, ie. to check for syntax errors, build a symbol map, provide suggestions, etc. and the resulting program isn't fully initialized as a consequence.
So if you want to compile the unit and make an executable program, you can have a main program that'll just be something like:
uses Test;
This will compile a program comprised of your unit, for which executions can be created and where functions can be called though exec.Info, classes can be instantiated, etc.
Edit2: For the second test case, it works if "uses Test;" is added. For full cross-compilability with Delphi, you'll also need interface/implementation sections (when targeting script only, they are not necessary)
unit Other;
interface
uses Test;
procedure Func;
implementation
procedure Func;
begin
Application.Demo;
end;
and if RTTI is generated for the methods with the $RTTI directive, at least with
{$RTTI EXPLICIT METHODS([vcPublished])}
TScriptApplication = class(TPersistent)
published
procedure Demo;
end;
otherwise you get an error about "Demo" not being found.

Related

TADOConnection with SQL Server Thread with Delphi

I am working on a project and I need to think about emergency situations.
The main issue is, how to check if the database is connected (circle object = red or green)?
BeforeConnect, AfterDisconnect, they have no good answer.
Inside type:
Create Connection:
procedure TForm1.Button1Click(Sender: TObject);
var
s : String;
begin
ADOConnectionSQL := TADOConnection.Create(nil);
ADOConnectionSQL.LoginPrompt := false;
with ADOSQL do
begin
s := 'Provider=SQLNCLI11.1;'+
'Persist Security Info=False;'+
'User ID='+Edit1.Text+';'+
'Initial Catalog='+Edit2.Text+';'+
'Data Source='+Edit3.Text+';'+
'Initial File Name="";'+
'Server SPN="";'+
'password="'+Edit4.Text+'"';
ADOConnectionSQL.ConnectionString := s;
end;
ADOConnectionSQL.BeforeConnect := SQLConnected;
ADOConnectionSQL.AfterDisconnect := SQLDisconnected;
end;
Try to connect:
procedure TForm1.Button2Click(Sender: TObject);
var
Thread : TThread;
begin
Thread := TThread.CreateAnonymousThread(
procedure
begin
TThread.Synchronize(TThread.CurrentThread,
procedure
begin
try
ADOConnectionSQL.Connected := True;
ADOConnectionSQL.Open;
except
on E: Exception do
begin
ShowMessage('Exception message = '+E.Message);
end;
end;
ADOQuerySQL := TADOQuery.Create(nil);
end);
end);
Thread.OnTerminate := FinishConnected;
Thread.Start;
end;
Green or Red:
procedure TForm1.SQLConnected(Sender: TObject);
begin
Circle1.Fill.Color := $FF00FF00;
end;
procedure TForm1.SQLDisconnected(Sender: TObject);
begin
Circle1.Fill.Color := $FFFF0000;
end;
FinishConnected:
procedure TForm1.FinishConnected(Sender: TObject);
begin
if TThread(Sender).FatalException <> nil then
begin
// something went wrong
ShowMessage ('Failure to connection');
//Exit;
end;
end;
When the SQL Server is online, I would like to see a green circle. When the connection with server goes downs, the circle should be red.
You are creating and opening the ADO connection in the context of the main UI thread, not in the context of the worker thread. So your worker thread is basically useless. You could have just used TThread.ForceQueue() instead to get the same effect.
ADO uses COM technology internally, so you can't really use it across thread boundaries anyway. If you want to use ADO in a thread, give the thread its own ADO Connection and Query objects. Do all your SQL work in the context of the thread, and synchronize status updates with the main UI thread as needed.
Also, you need to initialize the COM library in the worker thread before it can work with ADO.
Try something more like this instead:
procedure TForm1.Button1Click(Sender: TObject);
var
Thread : TThread;
ConnStr: string;
begin
ConnStr := 'Provider=SQLNCLI11.1;'+
'Persist Security Info=False;'+
'User ID='+Edit1.Text+';'+
'Initial Catalog='+Edit2.Text+';'+
'Data Source='+Edit3.Text+';'+
'Initial File Name="";'+
'Server SPN="";'+
'password="'+Edit4.Text+'"';
Thread := TThread.CreateAnonymousThread(
procedure
var
ADOConnectionSQL: TADOConnection;
ADOQuerySQL: TADOQuery;
begin
CoInitialize(nil);
try
ADOConnectionSQL := TADOConnection.Create(nil);
try
ADOConnectionSQL.LoginPrompt := False;
ADOConnectionSQL.ConnectionString := ConnStr;
ADOConnectionSQL.Open;
TThread.Queue(nil,
procedure
begin
Circle1.Fill.Color := TAlphaColorRec.Green;
end
);
ADOQuerySQL := TADOQuery.Create(nil);
try
ADOQuerySQL.Connection := ADOConnectionSQL;
// use ADOQuerySQL as needed...
finally
ADOQuerySQL.Free;
end;
finally
ADOConnectionSQL.Free;
end;
finally
CoUninitialize;
end;
end);
Thread.OnTerminate := SQLFinished;
Thread.Start;
end;
procedure TForm1.SQLFinished(Sender: TObject);
begin
Circle1.Fill.Color := TAlphaColorRec.Red;
if TThread(Sender).FatalException <> nil then
begin
// something went wrong
ShowMessage('Failure! ' + Exception(TThread(Sender).FatalException).Message);
end;
end;

Object within another Object not persisting between units Delphi

Sorry if this question is a duplicate but I couldn't find any solution to my problem anywhere...
The code below shows how I'm assigning values from a listview into an object that is a property of another object:
Main Unit:
procedure TForm1.SBCadClick(Sender: TObject);
var
Procedimento: TProcedimento;
Produto: TItemProcedimento;
item: TListViewItem;
begin
...
Procedimento := TProcedimento.Create;
for item in LVItensProcedimento.Items do
begin
Produto := TItemProcedimento.Create;
Produto.PRO_ID := item.Tag;
Produto.IPR_Uso := TListItemText(item.Objects.FindDrawable('IPR_Uso'))
.Text.ToDouble;
Procedimento.AddPRC_Produtos(Produto);
Produto.DisposeOf;
end;
DM.gravaProcedimento(Procedimento); // from here we go into another unit to use its function, passing an object as a parameter
Before the command DM.gravaProcedimento(Procedimento); the produto is correctly being added to the TObjectList of TProcedimento, I can get its contents correctly with Procedimento.GetPRC_Produtos. But when I debug the next unit shown below, its getting random IDs that means its not being persisted from one unit to the other:
unit DM:
procedure TDM.gravaProcedimento(Procedimento: TProcedimento);
var
produto: TItemProcedimento;
dura: string;
begin
...
produto := TItemProcedimento.Create;
for produto in Procedimento.GetPRC_Produtos do
begin
DM.FDQ.Append;
DM.FDQ.FieldByName('PRO_ID').AsInteger := produto.PRO_ID; // here the value gets a random ID like 45684 instead of the current item ID
DM.FDQ.FieldByName('PRC_ID').AsInteger := Procedimento.PRC_ID;
DM.FDQ.FieldByName('IPR_Uso').AsFloat := produto.IPR_Uso;
DM.FDQ.Post;
end;
produto.DisposeOf;
DM.FDQ.ApplyUpdates;
DM.FDQ.Close;
end;
This is the class definition of my objects:
unit uClasses;
interface
uses
System.SysUtils, System.Types, Generics.Collections;
type
TItemProcedimento = class
private
FPRO_Nome: string;
FPRO_Tipo: integer;
FPRO_Custo: double;
FPRO_ID: integer;
FPRO_Rendimento: integer;
FPRO_Potencia: double;
FIPR_Uso: double;
procedure SetPRO_Custo(const Value: double);
procedure SetPRO_ID(const Value: integer);
procedure SetPRO_Nome(const Value: string);
procedure SetPRO_Rendimento(const Value: integer);
procedure SetPRO_Tipo(const Value: integer);
procedure SetPRO_Potencia(const Value: double);
procedure SetIPR_Uso(const Value: double);
public
constructor Create;
published
property PRO_Rendimento: integer read FPRO_Rendimento
write SetPRO_Rendimento;
property PRO_ID: integer read FPRO_ID write SetPRO_ID;
property PRO_Nome: string read FPRO_Nome write SetPRO_Nome;
property PRO_Tipo: integer read FPRO_Tipo write SetPRO_Tipo;
property PRO_Custo: double read FPRO_Custo write SetPRO_Custo;
property PRO_Potencia: double read FPRO_Potencia write SetPRO_Potencia;
property IPR_Uso: double read FIPR_Uso write SetIPR_Uso;
end;
TProcedimento = class
private
FPRC_Nome: string;
FPRC_Duracao: TDateTime;
FPRC_Preco: double;
FPRC_ID: integer;
FPRC_Consumo: double;
FPRC_Produtos: TObjectList<TItemProcedimento>;
procedure SetPRC_Consumo(const Value: double);
procedure SetPRC_Duracao(const Value: TDateTime);
procedure SetPRC_ID(const Value: integer);
procedure SetPRC_Nome(const Value: string);
procedure SetPRC_Preco(const Value: double);
public
constructor Create;
function GetPRC_Produtos: TObjectList<TItemProcedimento>;
procedure AddPRC_Produtos(const Value: TItemProcedimento);
procedure DelPRC_Produtos(const Value: TItemProcedimento);
procedure CleanPRC_Produtos;
published
property PRC_Preco: double read FPRC_Preco write SetPRC_Preco;
property PRC_Consumo: double read FPRC_Consumo write SetPRC_Consumo;
property PRC_ID: integer read FPRC_ID write SetPRC_ID;
property PRC_Nome: string read FPRC_Nome write SetPRC_Nome;
property PRC_Duracao: TDateTime read FPRC_Duracao write SetPRC_Duracao;
end;
implementation
{ TProcedimento }
procedure TProcedimento.CleanPRC_Produtos;
begin
if not Assigned(FPRC_Produtos) then
FPRC_Produtos := TObjectList<TItemProcedimento>.Create
else
FPRC_Produtos.Clear;
end;
constructor TProcedimento.Create;
begin
SetPRC_Consumo(0);
SetPRC_Duracao(0);
SetPRC_ID(0);
SetPRC_Nome('');
SetPRC_Preco(0);
end;
procedure TProcedimento.DelPRC_Produtos(const Value: TItemProcedimento);
begin
FPRC_Produtos.Delete(FPRC_Produtos.IndexOf(Value));
end;
function TProcedimento.GetPRC_Produtos: TObjectList<TItemProcedimento>;
begin
if Assigned(FPRC_Produtos) then
result := FPRC_Produtos
else
begin
CleanPRC_Produtos;
result := FPRC_Produtos;
end;
end;
procedure TProcedimento.SetPRC_Consumo(const Value: double);
begin
FPRC_Consumo := Value;
end;
procedure TProcedimento.SetPRC_Duracao(const Value: TDateTime);
begin
FPRC_Duracao := Value;
end;
procedure TProcedimento.SetPRC_ID(const Value: integer);
begin
FPRC_ID := Value;
end;
procedure TProcedimento.SetPRC_Nome(const Value: string);
begin
FPRC_Nome := Value;
end;
procedure TProcedimento.SetPRC_Preco(const Value: double);
begin
FPRC_Preco := Value;
end;
procedure TProcedimento.AddPRC_Produtos(const Value: TItemProcedimento);
begin
FPRC_Produtos.Add(Value);
end;
{ TItemProcedimento }
constructor TItemProcedimento.Create;
begin
SetPRO_Custo(0);
SetPRO_ID(0);
SetPRO_Nome('');
SetPRO_Tipo(0);
SetPRO_Rendimento(0);
end;
procedure TItemProcedimento.SetIPR_Uso(const Value: double);
begin
FIPR_Uso := Value;
end;
procedure TItemProcedimento.SetPRO_Custo(const Value: double);
begin
FPRO_Custo := Value;
end;
procedure TItemProcedimento.SetPRO_ID(const Value: integer);
begin
FPRO_ID := Value;
end;
procedure TItemProcedimento.SetPRO_Nome(const Value: string);
begin
FPRO_Nome := Value;
end;
procedure TItemProcedimento.SetPRO_Potencia(const Value: double);
begin
FPRO_Potencia := Value;
end;
procedure TItemProcedimento.SetPRO_Rendimento(const Value: integer);
begin
FPRO_Rendimento := Value;
end;
procedure TItemProcedimento.SetPRO_Tipo(const Value: integer);
begin
FPRO_Tipo := Value;
end;
end.
Any particular reason why this is happening? What am I doing wrong here?
The problem is that you are destroying the TItemProcedimento objects before gravaProcedimento() has a chance to use them.
You are calling Produto.DisposeOf() immediately after Procedimento.AddPRC_Produtos(Produto) exits, and also in gravaProcedimento(), too. DO NOT DO THAT!
AddPRC_Produtos() saves the original Produto object into a TObjectList, which takes ownership of the object (as TObjectList is set to OwnsObjects=True by default). That means the object will be destroyed automatically when it is removed from the list, which includes when the list is cleared or destroyed.
So, you need to get rid of your DisposeOf() calls completely.
Also, you need to get rid of the call to TItemProcedimento.Create in gravaProcedimento(), too. It does not belong there. All you are doing by that is creating a memory leak on non-ARC systems.
It seems you do not have a firm grasp of how Delphi object lifetimes actually work. You DO NOT need to call Create on an object variable before assigning an object instance to it. And you DO NOT need to call DisposeOf() on an object variable when you are doing using the variable, only when you are done using the object itself (which TObjectList will handle for you).
Try this instead:
procedure TForm1.SBCadClick(Sender: TObject);
var
Procedimento: TProcedimento;
Produto: TItemProcedimento;
item: TListViewItem;
begin
...
Procedimento := TProcedimento.Create;
try
for item in LVItensProcedimento.Items do
begin
Produto := TItemProcedimento.Create;
try
Produto.PRO_ID := item.Tag;
Produto.IPR_Uso := TListItemText(item.Objects.FindDrawable('IPR_Uso')).Text.ToDouble;
Procedimento.AddPRC_Produtos(Produto);
// Produto.DisposeOf; // <-- DO NOT DO THIS HERE!!!
except
Produto.DisposeOf; // <-- DO THIS HERE INSTEAD, if AddPRC_Produtos fails!!!
raise;
end;
end;
DM.gravaProcedimento(Procedimento);
finally
Procedimento.DisposeOf; // <-- ADD THIS, if needed!!!
end;
end;
procedure TDM.gravaProcedimento(Procedimento: TProcedimento);
var
produto: TItemProcedimento;
dura: string;
begin
...
// produto := TItemProcedimento.Create; // <- DO NOT DO THIS!!!
for produto in Procedimento.GetPRC_Produtos do
begin
FDQ.Append;
try
FDQ.FieldByName('PRO_ID').AsInteger := produto.PRO_ID;
FDQ.FieldByName('PRC_ID').AsInteger := Procedimento.PRC_ID;
FDQ.FieldByName('IPR_Uso').AsFloat := produto.IPR_Uso;
FDQ.Post;
except
FDQ.Cancel; // <-- ADD THIS!!!
raise;
end;
end;
// produto.DisposeOf; // <-- DO NOT DO THIS!!!
FDQ.ApplyUpdates;
FDQ.Close;
end;
You should not call Produto.DisposeOf in procedure TForm1.SBCadClick.
You are destroying the object you have just added..

How to fix Access Violation at address in Delphi

I'm new to delphi and am trying to start OOP. However I get an access violation when using public property to set a private field.
type
User = class;
TData = class
private
CurrUser: User;
Connection: TFDConnection;
Query: TFDQuery;
procedure SetUser(newUser: User);
procedure SetConnection(newConn: TFDConnection);
procedure SetQuery(newQry: TFDQuery);
public
property CUser: User read CurrUser write SetUser;
property Conn: TFDConnection read Connection write SetConnection;
property Qry: TFDQuery read Query write SetQuery;
class procedure Login(uID: integer); static;
class procedure Logout(uID: integer); static;
class procedure ExitApp(); static;
end;
implementation
{$R *.fmx}
procedure TData.SetUser(newUser: User);
begin
CurrUser := newUser;
end;
procedure TData.SetConnection(newConn: TFDConnection);
begin
Connection := newConn;
end;
procedure TData.SetQuery(newQry: TFDQuery);
begin
Query := newQry;
end;
I expect to be able to set the Connection using that property however it gives me the access violation with any code that uses the property write:
TData.Conn.LoginPrompt := False;
TData.Conn.Connected := True;
var
TData: frmData.TData;
LoginForm: TLoginForm;
ErrorCount : integer;
implementation
{$R *.fmx}
procedure TLoginForm.ExitAppButtonClick(Sender: TObject);
begin
TData.ExitApp;
end;
procedure TLoginForm.LoginButtonClick(Sender: TObject);
var
companyPath : string;
nurseID : integer;
begin
if(UsernameInput.Text = '') or (PasswordInput.Text = '') or (PincodeInput.Text = '') then
begin
ShowMessage('Please enter your login details.');
Exit;
end;
try
TData.Conn := TFDConnection.Create(nil);
TData.Conn.Params.DriverID := 'MSAcc';
TData.Conn.Params.Database := 'D:\PulseDB\AlfaPersonnel\Pulse.mdb';
TData.Conn.LoginPrompt := False;
TData.Conn.Connected := True;
if(TData.Conn.Connected <> True) then
begin
ShowMessage('Could not connect, try again');
Exit;
end
else //When Connection
begin
TData.Qry := TFDQuery.Create(TData.Conn);
try
TData.Qry.Connection := TData.Conn;
TData.Qry.SQL.Text := 'SELECT * FROM NurseLogin WHERE Username=:uname AND Password=:pword AND PinCode=:pin;';
TData.Qry.Params.ParamByName('uname').AsString := UsernameInput.Text;
TData.Qry.Params.ParamByName('pword').AsString := PasswordInput.Text;
TData.Qry.Params.ParamByName('pin').AsString := PincodeInput.Text;
TData.Qry.Active := True;
if TData.Qry.RecordCount = 0 then ShowMessage('Details not recognised.')
else if TData.Qry.RecordCount = 1 then
begin
if TData.Qry.FieldByName('IsActive').AsBoolean then //If the user is active
begin
try
//Connect to the path
companyPath := TData.Qry.FieldByName('CompanyName').AsString;
TData.Conn.Params.Database := 'D\PulseDB\' + companyPath + '\Pulse.mdb';
TData.Conn.Connected := True;
ShowMessage('Connected to ' + companyPath);
finally
end;
end;
end;
finally
end;
end;
finally
end;
end;
You don't ever create an instance of your TData class. At some point you need to write:
TData := frmData.TData.Create;
Which is how you instantiate an instance. And you need to destroy it when you are finished also. Like this:
TData.Free;
That you did not instantiate an instance is the explanation for your access violation.
Some other issues:
Use the T prefix for types. Your variable should be named Data rather than TData.
Don't use global variables if at all possible.

Using Firedac to run SQL stored procedure

I'm trying to figure out how to run a stored procedure using firedac
unit DataLayer.OilCommanderConnection;
interface
uses
FireDAC.Phys.FB,
Generics.Collections,
Model.Sample,
Model.Batch,
FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Error,
FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def, FireDAC.Stan.Pool,
FireDAC.Stan.Async, FireDAC.Phys, FireDAC.Phys.MySQL, Data.DB,
FireDAC.Comp.Client, FireDAC.Phys.MSSQL,
FireDAC.DApt,
FireDAC.Comp.UI
;
type
TOilCommanderConnection = class
strict private
public
Connection : TFDConnection;
function GetSampleTypesForBatch(Batch : TBatch) : Boolean;
function Connect:Boolean;
constructor Create;
destructor Destroy; override;
end;
implementation
uses
SysUtils
;
function TOilCommanderConnection.Connect:Boolean;
var
OK : Boolean;
begin
OK := true;
Connection := TFDConnection.Create(nil);
try
Connection.Params.LoadFromFile('MSSQL.ini');
finally
Result := OK;
end;
end;
function TOilCommanderConnection.GetSampleTypesForBatch(Batch : TBatch) : Boolean;
var
StoredProc : TFDStoredProc;
begin
Connect;
StoredProc := TFDStoredProc.Create(nil);
try
StoredProc.Connection := Connection;
StoredProc.StoredProcName := 'GetSampleTypesForBatch';
StoredProc.Prepare;
StoredProc.FetchOptions.Items := StoredProc.FetchOptions.Items - [fiMeta];
with StoredProc.Params do
begin
Clear;
with Add do
begin
Name := 'BatchNo';
ParamType := ptInput;
DataType := ftString;
Size := 6;
end;
end;
StoredProc.StoredProcName := 'GetSampleTypesForBatch';
StoredProc.Prepare;
StoredProc.Params[0].Value := Batch.RackNo;
StoredProc.ExecProc;
while not StoredProc.Eof do
begin
//StoredProc.FieldByName('').AsS
StoredProc.Next;
end;
finally
FreeAndNil(StoredProc);
end;
Result := true;
end;
constructor TOilCommanderConnection.Create;
begin
inherited;
Connection := TFDConnection.Create(nil);
end;
destructor TOilCommanderConnection.Destroy;
begin
if Assigned(Connection) then FreeAndNil(Connection);
inherited;
end;
end.
I get an error message a the first occurrence of the line
StoredProc.Prepare;
Here is the message
--------------------------- Debugger Exception Notification
Project RefractiveIndexTests.exe raised exception class Exception with message 'Object factory for class
{3E9B315B-F456-4175-A864-B2573C4A2201} is missing. To register it, you
can drop component [TFDGUIxWaitCursor] into your project'.
I've called the function using
OilCommanderConnection.GetSampleTypesForBatch(batch);
from a test project.
the tutorial I read didn't explain what to do about this situation.
I've tried adding TFDGUIxWaitCursor into my project as the error message suggests but this has not made any difference. I wonder if this problem is related to me keeping the Database connection logic in a separate unit to my Main Form. I would like to be able to separate my user interface from my Data Layer.
Depending on the type of your application, include one of the following units into any one "uses" clause:
FireDAC.VCLUI.Wait - for VCL applications;
FireDAC.FMXUI.Wait - for FireMonkey applications;
FireDAC.ConsoleUI.Wait - for console / non-visual applications.

One Step Installer

I'm looking for a way to create a one page installer in Inno Setup, just look at this screenshot:
Can anyone please give me the codes for doing this?
Or totally I would like to merge pages abilities in Inno Setup. For example merge Select directory page with Components page and etc
It's not easy to do by default. But it can be done, the following code
produced a page like this one.
[Setup]
AppName=Test
AppVersion=1.5
DefaultDirName={code:AppDir}
;Disable all of the default wizard pages
DisableDirPage=yes
DisableProgramGroupPage=yes
DisableReadyMemo=yes
DisableReadyPage=yes
DisableStartupPrompt=yes
DisableWelcomePage=yes
;May want this, after install.
DisableFinishedPage=no
[Messages]
ButtonNext=Install
[Files]
Source:"e:\test.txt"; DestDir: "{app}"
Source:"e:\test.txt"; DestDir: "{app}"; DestName: "test1.txt"; Check: Option1;
Source:"e:\test.txt"; DestDir: "{app}"; DestName: "test2.txt"; Check: Option2;
[Code]
var
MainPage : TWizardPage;
edtFolderToInstall : TEdit;
InstallLocation : String;
Opt1, Opt2 : Boolean;
ChkOption1 : TCheckBox;
ChkOption2 : TCheckBox;
function AppDir(Param: String): String;
begin
// Set Default if not set.
if InstallLocation = '' then
InstallLocation := ExpandConstant('{pf}') + 'test';
result := InstallLocation;
end;
function Option1 : Boolean;
begin
result := Opt1;
end;
function Option2 : Boolean;
begin
result := Opt2;
end;
procedure BrowseClick(Sender : TObject);
var
Dir : String;
begin
Dir := edtFolderToInstall.Text;
if BrowseForFolder('Select Folder',Dir,false) then
edtFolderToInstall.Text := Dir;
end;
procedure InitializeWizard();
var
lblFolderToInstall : TLabel;
btnFolderToInstall : TButton;
begin
MainPage := CreateCustomPage(wpWelcome,'Setup - Test App Name','This will install "Test App Name" to your computer');
lblFolderToInstall := TLabel.Create(MainPage);
lblFolderToInstall.Parent := MainPage.Surface;
lblFolderToInstall.Top := 10;
lblFolderToInstall.Left := 10;
lblFolderToInstall.Caption := 'If you would like to select a different folder, Click Browse.'
edtFolderToInstall := TEdit.Create(MainPage);
edtFolderToInstall.Parent := MainPage.Surface;
edtFolderToInstall.Top := 25;
edtFolderToInstall.Left := 10;
edtFolderToInstall.Width := 250;
edtFolderToInstall.Text := WizardDirValue;
btnFolderToInstall := TButton.Create(MainPage);
btnFolderToInstall.Parent := MainPage.Surface;
btnFolderToInstall.top := 25;
btnFolderToInstall.Left := 275;
btnfolderToInstall.Caption := 'Browse...';
btnFolderToInstall.OnClick := #BrowseClick;
ChkOption1 := TCheckBox.Create(MainForm);
ChkOption1.Parent := MainPage.Surface;
ChkOption1.Top := 50;
ChkOption1.Left := 10;
ChkOption1.Caption := 'Option 1';
ChkOption2 := TCheckBox.Create(MainForm);
ChkOption2.Parent := MainPage.Surface;
ChkOption2.Top := 75;
ChkOption2.Left := 10;
ChkOption2.Caption := 'Option 2';
end;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
result := True;
// Next pressed, better make sure selected items are correct.
if CurPageId = MainPage.ID then
begin
InstallLocation := edtFolderToInstall.Text;
Opt1 := ChkOption1.Checked;
Opt2 := ChkOption2.Checked;
end;
end;
To pull this off, I use {code:AppDir} as the default directory. This tells InnoSetup to use the function AppDir to retrieve the installation directory. I then can set it using my custom dialog.
Instead of using [Components] and/or [Tasks] I have to use Check in the [Files] Section.