Change font color on a DevExpress TcxGrid row depending on the value of a field in the displayed record - devexpress

I use the event OnCustomDrawCell of TcxGridDBDataController to change the font row color of a DevExpress TcxGrid to red if a certain Field of the displayed record (e.g 'Debit' has value 1)
if Sender.DataController.GetValue(AViewInfo.GridRecord.RecordIndex, 15) = 1 then
begin
ACanvas.Font.Color := clRed;
end;
The above code works if field 'Debit' has recordIndex 15. But if I change the order of fields it stops working (because recordindex is not 15 anymore).
Instead of Recordindex I would like to use the fieldname 'Debit' in order to check the value.
I would be grateful of someone could help change the above code so that it works regardless of the position of field 'Debit'.
Thank you

It's ok I found it out : Here's the code doing this
{
procedure TfrmAirReservs.grdReservsListDBTableView1CustomDrawCell(
Sender: TcxCustomGridTableView; ACanvas: TcxCanvas;
AViewInfo: TcxGridTableDataCellViewInfo; var ADone: Boolean);
var Debit: TcxGridDBColumn;
begin
Debit := TcxGridDBTableView(Sender).GetColumnByFieldName('Debit');
if AViewInfo.GridRecord.Values[Debit.Index] = 1 then
begin
ACanvas.Font.Color := clRed;
end;
end;
}

Related

Is there a way to INSERT Null value as a parameter using FireDAC?

I want to leave some fields empty (i.e. Null) when I insert values into table. I don't see why would I want to have a DB full of empty strings in fields.
I use Delphi 10, FireDAC and local SQLite DB.
Edit: Provided code is just an example. In my application values are provided by user input and functions, any many of them are optional. If value is empty, I would like to keep it at Null or default value. Creating multiple variants of ExecSQL and nesting If statements isn't an option too - there are too many optional fields (18, to be exact).
Test table:
CREATE TABLE "Clients" (
"Name" TEXT,
"Notes" TEXT
);
This is how I tried it:
var someName,someNote: string;
begin
{...}
someName:='Vasya';
someNote:='';
FDConnection1.ExecSQL('INSERT OR REPLACE INTO Clients(Name,Notes) VALUES (:nameval,:notesval)',
[someName, IfThen(someNote.isEmpty, Null, somenote)]);
This raises an exception:
could not convert variant of type (Null) into type (OleStr)
I've tried to overload it and specify [ftString,ftString] and it didn't help.
Currently I have to do it like this and I hate this messy code:
FDConnection1.ExecSQL('INSERT OR REPLACE INTO Clients(Name,Notes) VALUES ('+
IfThen(someName.isEmpty,'NULL','"'+Sanitize(someName)+'"')+','+
IfThen(someNote.isEmpty,'NULL','"'+Sanitize(someNote)+'"')+');');
Any recommendations?
Edit2: Currently I see an option of creating new row with "INSERT OR REPLACE" and then use multiple UPDATEs in a row for each non-empty value. But this looks direly ineffective. Like this:
FDConnection1.ExecSQL('INSERT OR REPLACE INTO Clients(Name) VALUES (:nameval)',[SomeName]);
id := FDConnection1.ExecSQLScalar('SELECT FROM Clients VALUES id WHERE Name=:nameval',[SomeName]);
if not SomeString.isEmpty then
FDConnection1.ExecSQL('UPDATE Clients SET Notes=:noteval WHERE id=:idval)',[SomeNote,id]);
According to Embarcadero documentation ( here ):
To set the parameter value to Null, specify the parameter data type,
then call the Clear method:
with FDQuery1.ParamByName('name') do begin
DataType := ftString;
Clear;
end;
FDQuery1.ExecSQL;
So, you have to use FDQuery to insert Null values, I suppose. Something like this:
//Assign FDConnection1 to FDQuery1's Connection property
FDQuery1.SQL.Text := 'INSERT OR REPLACE INTO Clients(Name,Notes) VALUES (:nameval,:notesval)';
with FDQuery1.ParamByName('nameval') do
begin
DataType := ftString;
Value := someName;
end;
with FDQuery1.ParamByName('notesval') do
begin
DataType := ftString;
if someNote.IsEmpty then
Clear;
else
Value := someNote;
end;
if not FDConnection1.Connected then
FDConnection.Open;
FDQuery1.ExecSql;
It's not very good idea to execute query as String without parameters because this code is vulnerable to SQL injections.
Some sources tells that it's not enough and you should do something like this:
with FDQuery1.ParamByName('name') do begin
DataType := ftString;
AsString := '';
Clear;
end;
FDQuery1.ExecSQL;
but I can't confirm it. You can try it if main example won't work.

Search for field name in access table then update relevant fields names with values delphi 7

I have an Access database table named ReceiptTable with the following field names: item name, buying price, selling price, goods total, cash, change. I am using an Adoquery and datasource to connect to the access database. When I want to update records to receiptTable, I use the following code to locate an item name from the database then update all the records with similar item name in the database with the values from edit box field values:
procedure TReceiptForm.BitBtn1Click(Sender: TObject);
begin
with ADOQuery1 do
ADOQuery1.Open;
ADOQuery1.Locate('item name',Edit1.Text,[]) ;
ADOQuery1.edit;
ADOQuery1.FieldValues['goods total']:=edit3.Text;
ADOQuery1.FieldValues['cash']:=edit4.Text;
ADOQuery1.FieldValues['change']:=edit5.Text;
ADOQuery1.Post;
end;
The problem I have is that only one row with the item name is updated but the other rows with similar item name are not updated. What code should I add above so that all the rows which have similar item names are updated with values from edit boxes?
This simple code answers your question:
procedure TReceiptForm.BitBtn1Click(Sender: TObject);
var
itemname, goodstotal, cash, change: string;
begin
// Execute query
try
ADOQuery1.Open;
except
on E: Exception do begin
ShowMessage(E.Message);
Exit;
end{on};
end{try};
// Values
itemname := Edit1.Text;
goodstotal := Edit3.Text;
cash := Edit4.Text;
change := Edit5.Text;
// Find first matching record, then go to the end of resultset.
try
ADOQuery1.DisableControls;
if ADOQuery1.Locate('item name', itemname, []) then begin
while not ADOQuery1.Eof do begin
if ADOQuery1.FieldByName('item name').AsString = itemname then begin
ADOQuery1.Edit;
ADOQuery1.FieldValues['goods total'] := goodstotal;
ADOQuery1.FieldValues['cash'] := cash;
ADOQuery1.FieldValues['change'] := change;
ADOQuery1.Post;
end{if};
ADOQuery1.Next;
end{while};
end{if};
finally
ADOQuery1.EnableControls;
end{try};
end;
This will work, but you can consider using one SQL statement for updating the table, or
if it is possible order your query by 'item name' and use this:
...
// Find first matching record, then update while next record matches too.
if ADOQuery1.Locate('item name', itemname, []) then begin
while (not ADOQuery1.Eof) and
(ADOQuery1.FieldByName('item name').AsString = itemname) do begin
ADOQuery1.Edit;
ADOQuery1.FieldValues['goods total'] := goodstotal;
ADOQuery1.FieldValues['cash'] := cash;
ADOQuery1.FieldValues['change'] := change;
ADOQuery1.Post;
ADOQuery1.Next;
end{while};
end{if};
...

The main thing is to create a database trigger that the price in my room table where the type is double must be greater than 100

CREATE OR REPLACE TRIGGER PriceMust
BEFORE INSERT ON ROOM
REFERENCE NEW AS new
FOR EACH ROW
BEGIN
SELECT price, type
From Room
Where type =: 'Double'
IF price<=: 100
raise_application_error('Price Must Be Over 100');
END IF;
END;
/
OK, I'll give you a cleaned up version:
CREATE OR REPLACE TRIGGER PriceMust
BEFORE INSERT ON ROOM
REFERENCING NEW AS new
FOR EACH ROW
BEGIN
IF :NEW.TYPE = 'Double' and :NEW.price <= 100 THEN
raise_application_error(-20001, 'Price Must Be Over 100');
END IF;
END PRICEMUST;
/
The trigger is on the ROOM table, so you can't select from ROOM inside the trigger without getting a MUTATING TABLE error. Instead, just use the value in NEW.TYPE to determine the room type.
Best of luck.

Return ref_cursor from for loop of cursor object

I am trying to get a ref_cursor to be assigned to a variable inside a for loop then returned at the end of a function. The loop in going through a local cursor if it gets more than 1 result.
I have noted where the error occurs in the code. I am not sure how to create a loop where i can get a ref_cursor for the current point in the loop, assign it to a variable and then return it to the function. Could someone someone help me figure out how to do that? Below is my i-th attempt at logic based of reading around the google searches.
The error is "PLS-00382: expression is of wrong type" and i know that i obviously am not assigning the correct variably type based on this error but the code below with the error is an illustration of what I want to do and what I need help accomplishing.
FUNCTION GET_PARCEL(p_lat in number, p_long in number) return sys_refcursor
IS
v_distance number(10) := 100000000;
v_shortest_dist number(10) := v_distance;
v_centroid SDO_GEOMETRY;
v_rc_ref_cursor sys_refcursor;
v_ref_geom SDO_GEOMETRY := mdsys.SDO_GEOMETRY(2001, 8311, NULL, SDO_ELEM_INFO_ARRAY(1, 1, 1), SDO_ORDINATE_ARRAY(120.3214, -10.7088));
cursor query_cursor is select * from PARCEL_TABLE where code = 20134;
BEGIN
for query_row in query_cursor loop
v_centroid := SDO_GEOM.SDO_CENTROID(query_row.geometry, 0.05);
IF (v_centroid is not null) then
v_distance := SDO_GEOM.SDO_DISTANCE(v_centroid, v_ref_geom, 0.05);
IF v_distance < v_shortest_dist THEN
v_shortest_dist := v_distance;
v_rc_ref_cursor := query_row; -- Error on this line
END IF;
ELSE
DBMS_OUTPUT.PUT_LINE('Centroid is not initialised for some reason.');
END IF;
end loop;
return v_rc_ref_cursor;
END;
As far as I know, you cannot build up a cursor. Cursors are created and maintained by the database with connections to their source data, transaction and session context, and the like.
I would suggest you declare a type, instantiate it, build up the values in the type.
When you are done, create a cursor by selecting * from table (cast (variable as your_type)).
Munge the cursor into a ref_cursor and return that.
Pro tip is to remember you have data in your table and you can use it for logic. Turns out if I use the IDENTIFIER or ROWID of the row/s which i want then i can do a where clause into a ref cursor that looks up by IDENTIFIER or ROWID.
eg.
open v_rc_ref_cursor for select * from PARCEL_TABLE n where n.identifier = query_row.identifier;
super simple stuff :)

SQLite insert on android works only once

I'm with a problem on Delphi, i create a simple app to test the mobile power of RadStudio, I created a simple app that put some data into some inputs and then add it to database when button is clicked. I followed this Embarcadero tutorial as starting point
The problem is that I only get one entry added, then no more entries are added or the list is not refreshed. Below some code:
Table creation:
procedure TTabbedForm.logAfterConnect(Sender: TObject);
begin
log.ExecuteDirect('CREATE TABLE IF NOT EXISTS lista (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,idCons INTEGER,nome TEXT,kms INTEGER,kmsAlarme INTEGER,quantidade INTEGER,quantidadeAlarme INTEGER,data INTEGER,dataAlarme INTEGER,alarmeMsg TEXT)');
end;
Add button code:
procedure TTabbedForm.btnGravarClick(Sender: TObject);
begin
try
SQLQueryInsert.ParamByName('idCons').AsInteger := PopupBoxTipo.ItemIndex;
SQLQueryInsert.ParamByName('nome').AsString := PopupBoxTipo.Text;
SQLQueryInsert.ParamByName('kms').AsInteger := StrToInt(kmsEdit.Text);
SQLQueryInsert.ParamByName('quantidade').AsInteger := StrToInt(qtdEdit.Text);
SQLQueryInsert.ParamByName('data').AsInteger := DateTimeToUnix(dtaEvento.Date);
SQLQueryInsert.ExecSQL();
lista.Refresh;
LinkFillControlToField1.BindList.FillList;
except
on e: Exception do
begin
ShowMessage(e.Message);
end;
end;
end;
If you need some more code snippet, please ask!
Thanks in advance for any reply!
Try this method it works for me
procedure TData.InsertItem(someObject: TSomeObjectClass);
var
qry: TFDQuery;
begin
qry := CreateQry( 'insert into SomeObject(id, description, something)'+
' values (:id, :description, :something);', false);
qry.Params.ParamByName('id').AsInteger := someObject.id;
qry.Params.ParamByName('description').asstring := someObject.description;
qry.Params.ParamByName('something').asstring := someObject.something;
qry.Prepare;
qry.execsql;
qry.Free;
end;
I've put and object in the parameter but you can also put the data you want to insert seperatly

Resources