Create object in 'with' statement without using a variable - variables

var
UserName, NickName, UserID: string;
begin
with TStringList.Create do
begin
CommaText := 'ali,veli,4950';
UserName := x[0]; //what is x ? (x is Tstringlist.create)
NickName := x[1];
UserID := x[2];
end;
end;
If I use the below code, it works. But I don't want to declare a variable. Can I use it with any variable?
In the with statement, how can I use it?
var
tsl: TStringList;
begin
tsl := TStringlist.Create;
with tsl do
begin
CommaText := 'ali,veli,4950';
UserName := tsl[0];
NickName := tsl[1];
UserID := tsl[2];
end;
end;

When an object is created directly in a with statement, there is no syntax to refer to that object (unless it provides a member that refers to itself, which is very rare), so you typically must use a variable, as your bottom code does.
Also, both codes are leaking the TStringList object, as you are not calling Free() on it when you are done using it.
That being said, in this particular example, the [] operator is just shorthand for accessing the TStrings.Strings[] default property, which you can access without needing a variable to the created TStringList object, just like you are doing with the TStrings.CommaText property, eg:
var
UserName, NickName, UserID: string;
Begin
with TStringList.Create do
try
CommaText := 'ali,veli,4950';
UserName := Strings[0];
NickName := Strings[1];
UserID := Strings[2];
finally
Free;
end;
end;

Related

Is there any way to put text into SQL using Delphi? [duplicate]

Using Delphi 2010
Can anyone tell me what I am doing wrong here with my code. The comments show the errors that I receive with the particular methods that I tried to pass parameters to my ADOQuery
procedure CreateAdminLogin(const APasswd: string);
var
qry: TADOQuery;
//P1, P2: TParameter;
begin
qry := TADOQuery.Create(nil);
try
qry.Connection := frmDataModule.conMain;
qry.SQL.Text := 'INSERT INTO Users (User_Id, Password) VALUES (:u, :p)';
//Syntax error in INTO statement
qry.Parameters.ParamByName('u').Value:= 'Admin';
qry.Parameters.ParamByName('p').Value:= GetMd5(APasswd);
//invalid variant operation
{qry.Parameters.ParamByName('u').Value.AsString:= 'Admin';
qry.Parameters.ParamByName('p').Value.AsString:= GetMd5(APasswd);}
//invalid variant operation
{P1:= qry.Parameters.ParamByName('u');
P1.Value.asString:= 'Admin';
P2:= qry.Parameters.ParamByName('p');
P2.Value.asString:= GetMd5(APasswd);}
qry.Prepared := True;
qry.ExecSQL;
finally
qry.Free;
end;
end;
NOTE: GetMD5 is declared as follows
function GetMd5(const Value: String): string;
var
hash: MessageDigest_5.IMD5;
fingerprint: string;
begin
hash := MessageDigest_5.GetMd5();
hash.Update(Value);
fingerprint := hash.AsString();
Result := fingerprint;
end;
Thankx
This works fine for me, using the DBDemos.MDB file that shipped with Delphi (C:\Users\Public\Documents\RAD Studio\9.0\Samples\Data\dbdemos.mdb by the default installation)
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('INSERT INTO Country (Name, Capital, Continent, Area, Population)');
ADOQuery1.SQL.Add('VALUES (:Name, :Capital, :Continent, :Area, :Population)');
ADOQuery1.Parameters.ParamByName('Name').Value := 'SomePlace';
ADOQuery1.Parameters.ParamByName('Capital').Value := 'Pitsville';
ADOQuery1.Parameters.ParamByName('Continent').Value := 'Floating';
ADOQuery1.Parameters.ParamByName('Area').Value := 1234;
ADOQuery1.Parameters.ParamByName('Population').Value := 56;
ADOQuery1.ExecSQL;
ADOQuery1.Close;
// Open it to read the data back
ADOQuery1.SQL.Text := 'SELECT * FROM Country WHERE Name = :Name';
ADOQuery1.Parameters.ParamByName('Name').Value := 'SomePlace';
ADOQuery1.Open;
ShowMessage(ADOQuery1.FieldByName('Name').AsString);
For using like extra thing to know:
Datasource SQL like this
select * from Table where Phone like :param
DataModule.findQuery.Parameters.ParamByName('param').Value:= '%%'+yourEdit.Text + '%%';
You should create parameters first:
procedure CreateAdminLogin(const APasswd: string);
var
qry: TADOQuery;
begin
qry := TADOQuery.Create(nil);
try
// this part is missed in your code
with qry.Parameters.AddParameter do
begin
Name := 'u';
DataType := ftString;
end;
with qry.Parameters.AddParameter do
begin
Name := 'p';
DataType := ftString;
end;
qry.Connection := frmDataModule.conMain;
qry.SQL.Text := 'INSERT INTO Users (User_Id, Password) VALUES (:u, :p)';
// Now it will be ok!
qry.Parameters.ParamByName('u').Value:= 'Admin';
qry.Parameters.ParamByName('p').Value:= GetMd5(APasswd);
qry.Prepared := True;
qry.ExecSQL;
finally
qry.Free;
end;
end;

Invalid Pointer Operation on TObjectList.DisposeOf

Hello folks and sorry if this is a duplicate but my specific issue I haven't seen answered anywhere yet.
I have an "Invalid Pointer Operation" when I try to free an ObjectList created at runtime at the following lines:
Prods := TItemProcedimento.Create(DM.FDQ).lerProdutos;
Prods.DisposeOf; // <- Invalid Pointer Operation at 2nd iteration
So, here's my classes:
unit uItemProcedimento;
interface
[...]
type
TItemProcedimento = class
[...]
public
constructor Create(DataSet: TFDQuery);
function lerProdutos: TObjectList<TItemProcedimento>;
[...]
constructor TItemProcedimento.Create(DataSet: TFDQuery);
begin
FDataSet := DataSet;
end;
function TItemProcedimento.lerProdutos: TObjectList<TItemProcedimento>;
begin
Result := TObjectList<TItemProcedimento>.Create;
try
FDataSet.Close;
FDataSet.SQL.Clear;
FDataSet.SQL.Add('SELECT *');
FDataSet.SQL.Add('FROM Produto p');
FDataSet.SQL.Add('JOIN ItensProcedimento IP on p.PRO_ID = IP.PRO_ID');
FDataSet.SQL.Add('ORDER BY p.PRO_Nome');
FDataSet.Open;
while not FDataSet.Eof do
begin
PRO_ID := FDataSet.FieldByName('PRO_ID').AsInteger;
PRO_Rendimento := FDataSet.FieldByName('PRO_Rendimento').AsInteger;
PRO_Nome := FDataSet.FieldByName('PRO_Nome').AsString;
PRO_Tipo := FDataSet.FieldByName('PRO_Tipo').AsInteger;
PRO_Custo := FDataSet.FieldByName('PRO_Custo').AsFloat;
PRO_Potencia := FDataSet.FieldByName('PRO_Potencia').AsFloat;
IPR_Uso := FDataSet.FieldByName('IPR_Uso').AsFloat;
Result.Add(self);
FDataSet.Next;
end;
finally
FDataSet.Close;
end;
The weirdest thing about this is that I have another class with the exact same behaviour and the method works without issues. Yes I am creating the object and immediately destroying it to test if I'm destroying it correctly.
In another class I have another example with the same code, but this time it works without errors
test := TMyClass.Create(DM.FDQ).lerTeste;
test.DisposeOf;
Why? What am I doing wrong? This test code is running before the current code btw, maybe its related?
UPDATE:
By applying the changes that Remy Lebeau suggested I managed to add properly the items to the list and dispose them so this particular part of the code has no leaks. But in another part of the code I have an ObjectList leak that I have no idea on how to fix.
Inside my class I have a property that is a TObjectList property, I have a method that checks if the list is assigned, if not, it creates it and returns it to whoever is calling the list.
[...]
type
TProcedimento = class
private
[...]
FPRC_Produtos: TObjectList<TItemProcedimento>;
public
[...]
function getPRC_Produtos: TObjectList<TItemProcedimento>;
function criaProcedimentos: TObjectList<TProcedimento>;
[...]
function TProcedimento.GetPRC_Produtos: TObjectList<TItemProcedimento>;
begin
if not Assigned(FPRC_Produtos) then
FPRC_Produtos:= TObjectList<TItemProcedimento>.Create;
result := FPRC_Produtos;
end;
function TProcedimento.criaProcedimentos: TObjectList<TProcedimento>;
var
IPR: TItemProcedimento;
Procedimento: TProcedimento;
ds: TFDQuery;
begin
result := TObjectList<TProcedimento>.Create;
ds := TFDQuery.Create(nil);
ds.Connection := FDataSet.Connection;
IPR := TItemProcedimento.Create(ds);
try
FDataSet.Close;
FDataSet.Open('SELECT * FROM Procedimento');
while not FDataSet.Eof do
begin
Procedimento := TProcedimento.Create(FDataSet);
Procedimento.PRC_ID := FDataSet.FieldByName('PRC_ID').AsInteger;
Procedimento.PRC_Nome := FDataSet.FieldByName('PRC_Nome').AsString;
Procedimento.PRC_Duracao := FDataSet.FieldByName('PRC_Duracao')
.AsDateTime;
Procedimento.PRC_Preco := FDataSet.FieldByName('PRC_Preco').AsCurrency;
Procedimento.PRC_Custo := FDataSet.FieldByName('PRC_Custo').AsCurrency;
Procedimento.PRC_Consumo := FDataSet.FieldByName('PRC_Consumo').AsFloat;
Procedimento.FPRC_Produtos := IPR.getItensProcedimento(FPRC_ID);
result.Add(Procedimento);
FDataSet.Next;
end;
finally
FDataSet.Close;
IPR.DisposeOf;
ds.DisposeOf;
end;
end;
I then use this property in a for-in loop to feed a list with the procs in my database
procedure TKBForm1.CarregaProcedimento;
var
Procedimento: TProcedimento;
Procs: TObjectList<TProcedimento>;
[...]
begin
Procs := TProcedimento.Create(DM.FDQ).criaProcedimentos;
try
LV_Procedimento.Items.Clear;
LV_Procedimento.BeginUpdate;
for Procedimento in Procs do
begin
with LV_Procedimento.Items.Add do
[...]
finally
Procs.DisposeOf;
Procedimento.GetPRC_Produtos.DisposeOf;
end;
end;
But there's still leaks happening after this part runs:
73 - 88 bytes: TProcedimento x 1, TItemProcedimento x 2
How do I fix this?

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.

How to Sort Sections on TMemIniFile

I am using TMemIniFile to store configuration and I need to sort the sections in alpha order.
For that I have created a descendant of TMemIniFile
TRWStudioMemIniFile = class(TMemIniFile)
public
procedure UpdateFile; override;
procedure GetSortedStrings(List: TStrings);
end;
{ TRWStudioMemIniFile }
procedure TRWStudioMemIniFile.GetSortedStrings(List: TStrings);
var
I, J: Integer;
Strings: TStrings;
begin
List.BeginUpdate;
try
Sections.Sort;
for I := 0 to Sections.Count - 1 do
begin
List.Add('[' + Sections[I] + ']');
Strings := TStrings(Sections.Objects[I]);
for J := 0 to Strings.Count - 1 do List.Add(Strings[J]);
List.Add('');
end;
finally
List.EndUpdate;
end;
end;
procedure TRWStudioMemIniFile.UpdateFile;
var
List: TStringList;
begin
List := TStringList.Create;
try
GetSortedStrings(List);
List.SaveToFile(FileName, Encoding);
finally
List.Free;
end;
end;
but it needs to have access to the Sections (actually FSections: TStringList, that is a private member of TMemIniFile)
I have created a Helper class to expose that member thru a property. However this behavior is not supported anymore in Delphi 10.1
I started copy/paste the TMemIniFile to my unit and after and endless process I am ending up making a copy of the entire System.IniFile, just to access the FSections.
My question is how to access that FSections member without need to duplicate everything from that unit just to gain visibility
OR is there another way that I can Sort the Sections before saving? (I am just calling the TStringList.Sort from FSections)
Rather than relying on type-casting and "cracking open" the private member, you can instead get the sections into your own TStringList using the inherited ReadSections() method, sort that list as needed, and then use the inherited ReadSectionValues() method to read the strings for each section:
var
sections: TStringList;
values: TStringList;
begin
sections := TStringList.Create;
try
ReadSections(sections);
sections.Sort;
values := TStringList.Create;
try
List.BeginUpdate;
try
for I := 0 to sections.Count - 1 do
begin
List.Add('[' + sections[I] + ']');
values.Clear; // Just in case
ReadSectionValues(sections[i], values);
for J := 0 to values.Count - 1 do
List.Add(values[J]);
List.Add('');
end;
finally
List.EndUpdate;
end;
finally
values.Free;
end;
finally
sections.Free;
end;
end;

Pascal 'this' reference

I am starting to explore Pascal with an Object Oriented approach. I was wondering if there is a this or self reference to the current object? I have searched around for documentation but I have not come found an answer.
Edit
Through trial and error I found that you can use self. Now my question is can I achieve this (snippet below) without the compiler throwing a 'duplicate identifier error'?
constructor Employee.create(name:String; salary:Real);
begin
self.name := name;
self.salary := salary;
end;
I have a program below which creates 2 employees and displays their information
{$mode objfpc} // directive to be used for defining classes
{$m+} // directive to be used for using constructor
program EmployeeTest;
type
Employee = class
private
name:String;
salary:Real;
public
constructor create(name:String; salary:Real);
procedure setName(name:String);
function getName():String;
procedure setSalary(salary:Real);
function getSalary():Real;
procedure displayEmployee;
end;
var empl1,empl2:Employee;
constructor Employee.create(name:String; salary:Real);
begin
setName(name);
setSalary(salary);
end;
procedure Employee.setName(name:String);
begin
self.name := name;
end;
procedure Employee.setSalary(salary:Real);
begin
self.salary := salary;
end;
function Employee.getName():String;
begin
getName := self.name;
end;
function Employee.getSalary():Real;
begin
getSalary := self.salary;
end;
procedure Employee.displayEmployee;
begin
writeln('Name: ',getName,', Salary: $',getSalary:0:2);
end;
begin
empl1 := Employee.create('Bob', 75000);
empl2 := Employee.create('Joe', 50000);
empl1.displayEmployee();
empl2.displayEmployee();
readln; {pause}
end.
Didn't think that I would be answering my question.
Found the answer here.
You must use {$mode delphi} instead of {$mode objfpc} if you want to duplicate variable names inside nested functions.
Don't use the same name for a parameter and a property. Use the prefix A for a parameter.
Don't implement getter and setter methods. Use in Pascal the property keyword.
Use the prefix F for private fields.
Use the prefix T for a self defined type.
Here is an example for your class:
type
TEmployee = class(TObject)
private
FName: String;
FSalary:Real;
public
constructor Create(AName:String; ASalary:Real);
property Name: String read FName write FName;
property Salary: Real read FSalary write FSalary;
procedure DisplayEmployee;
end;
constructor TEmployee.Create(AName:String; ASalary:Real);
begin
inherited Create;
FName := AName;
FSalary := ASalary;
end;
procedure TEmployee.DisplayEmployee;
begin
WriteLn('Name: ', FName, ', Salary: $', FSalary:0:2);
end;