I have tables on my database Tb_barang and Tb_jenis. Tb_jenis the following columns kd_jenis (primary key) and jenis. I use TDBLookupComboBox to show the items from the Tb_jenis table. There are 6 items (Mie, Susu, etc). I want to save item selected as kd_jenis not jenis.
How to I save jenis in table to kd_jenis?
Sample data: Tb_jenis
jenis kd_jenis
Mie J1
Susu J2
Here it the code I've tried.
if (kd.Text='') or (jenis.Text='Pilih') then
ShowMessage('Data Tidak Lengkap, Silakkan Dilengkapi !')
else
begin
with dm.Qbarang do
begin
sql.Clear;
SQL.Add('select * from Tb_barang where kd_barang='+quotedstr(kd.text)+'');
open;
end;
if DM.Qbarang.Recordset.RecordCount > 0 then
ShowMessage('Data Sudah Ada, Silakkan Isi Yang Lain!')
else
begin
try
DM.koneksi.BeginTrans;
with DM.QU do
begin
close;
SQL.Clear;
SQL.Add('insert into Tb_barang values('+QuotedStr(kd.Text)+','
+QuotedStr(jenis.Text)+')');
ExecSQL;
end;
DM.koneksi.CommitTrans;
ShowMessage('Data Berhasil Disimpan');
except
DM.Qbarang.Close;
DM.Qbarang.Open;
FormShow(sender);
end;
end;
end;
As i understand you want to get Key Value of table from DBLookupComboBox.
so in it is 2 important property in DBLookupComboBox ListField and KeyField
if you set them correctly For Example KeyField set as kd_jenis and List Field Set as jenis then you will see Jenis on List and you can access to jenis in DBLookupCombobox.text and also you can access to KeyField (in this case kd_jenis) by DBLookupCombobox.KeyValue
you should fallow this steps.
Put a TADOConnection to your form for connecting to a database.
build connection string for connecting to Database.
add a ADO table to your form.
Set Connection property to AdoConnection1(Default Name Of ADOCOnnection).
On ADOTable open the Table name Property and select One of your database table and then set active property as true.
Add a DataSource to your form and set Dataset Property as ADOTable1(Default Name of ADOTable)
Add DBLookupCombobox to your form and Set ListSource as DataSource1(the default name of datasource)
Open ListField Property and Select witch field do you want to show in Combobox.
Open the keyField Property and Select witch field is Key field.
All of above Steps should done at Design time. for testing your application add to edit box on your form as edit1 and edit2 then write a small code for OnCloseUp Evwnt of DBLookupCombobox like this
Edit1.Text:=DBLookUpCombobox1.KeyValue;
Edit2.Text:=DBLookupCombobox1.Text;
If you have problems with DBLookupComboBox you may use normal combobox and fill it like the example here.
After someone selects something in your DBLookupComboBox...The cursor to the table is moved.
You can access the value directly:
jenis.ListSource.DataSet.FieldByName('kd_jenis').AsString
This assumes that jenis is a TDBLookupComboBox.
That ListSource.DataSet points to Tb_jenis.
As Craig stated...you have a serious bug in your code...you need a RollbackTrans on exception...other wise you will never release the lock...
DM.koneksi.CommitTrans;
ShowMessage('Data Berhasil Disimpan');
except
DM.koneksi.RollbackTrans;
DM.Qbarang.Close;
DM.Qbarang.Open;
FormShow(sender);
end;
I do something like this...if I need to guarantee saving info and rolling back if fails.
procedure TForm8.SaveData;
begin
Assert(not ADOQuery1.Connection.InTransaction, 'Code problem-We should not be in a Transaction');
ADOQuery1.Connection.BeginTrans;
try
ADOQuery1.ExecSQL;
ADOQuery1.Connection.CommitTrans;
finally
if ADOQuery1.Connection.InTransaction then
begin
{If you are here...your in an ErrorState...and didn't Commit your Transaction}
ADOQuery1.Connection.RollbackTrans;
HandleError(ADOQuery1);
end;
end;
end;
Related
I have a TZTable (ZEOSlib) bound to a DBGrid now I need to know which particular TField was changed by the user.
I tried with
if NOT (taPositionenArtNrGH.NewValue = taPositionenArtNrGH.OldValue) then
ShowMessage('ArticleNumber changed');
I placed the code in
BeforePost, OnUpdateRecord, AfterPost
But in the Debugger OldValue is always NewValue. How do I check which field was changed?
You can use UpdateStatus : TUpdateStatus for this. For example:
Set ZTable.CachedUpdates to true;
Create new calculated field named "Status".
To show old value for example of field "FNAME" create new calculate field named "FNameOldValue"
In OnCalcFields event use:
procedure TDM1.ZTable1CalcFields(DataSet: TDataSet);
begin
if ZTable1.UpdateStatus in [usModified] then
begin
ZTable1Status.value := 'Modified';
ZTable1FNameOldValue.value := ZTable1FNAME.OldValue;
end
else
ZTable1Status.value := 'UnModified'
end;
Result :
Edit:
You can detect field level changes like:
if ZTable1.UpdateStatus in [usModified] then
begin
for I := 0 to ZTable1.Fields.Count - 1 do
begin
if ZTable1.Fields[i].OldValue <> ZTable1.Fields[i].NewValue then
-- do something with this field
end;
end;
As per documentation:
The NewValue property is only usable when the data is accessed using
a TClientDataSet component or cached updates is enabled.
If you just want to know what fields have been changed, why not use TField.OnChange event? You could fill a list of field names in this event and clear it in OnAfterPost. But the Modified property would be very useful indeed; it's odd that it haven't been implemented yet.
Basic Scenario:
User clicks 'edit item'
Edit dialog opens
A combobox 'item type' should be populated with items form a table 'item_type'. The combobox should display 'item_type.name', but also know about 'item_type.id'
User edits other item stuff and selects an item type, then clicks ok
My program does some input validation
if everything is ok, get the 'item_type.id' from the selected combo item and save it to the foreign key column of the item table ('item.fk_item_type').
If I understand this component correctly, I should set DataSource to point to the destination table 'item' and DataField to 'item.fk_item_type'. But that would immediately edit my item table before I get a chance the validate all the other input.
It feels like I am missing something here. Somewhere I read that I need to use a classic ComboBox and fill it manually. But then I don't understand how to get to the id of the selected item.
Thank you for any pointers.
Edit:
I am starting to suspect that maybe I am missing a fundamental thing. All these DB* components, do they load values from that database automatically, but I have to call Post() myself? Meaning they do not automatically change values in the database?
If I understand you correctly, you want to use a DBLookupComboBox. You have to supply values for the following properties
datasource - linked to the table which you are editing, presumably 'items'
datafield - the name of the field in the table which you are editing, presumably 'item_type'
listsource - linked to the table which populated the combobox, presumably 'item_types'
list field - the name of the field from 'item_types' which you want to display, presumably 'name'
key field - the name of the field from 'item_types' which will be inserted into the items record, presumably 'item_type'
The table which populated the combobox is never edited.
You can validate the values before posting the new/edited 'items' record.
I can show you how to use a non-data aware combobox if necessary, but it's easier to use the data aware version.
Regarding validation, I use the following code template in edit dialogs.
Function TEditQuestion.Execute (n: longint): boolean;
var
gen: longint;
begin
sdsEditQuestion.params[0].asinteger:= n; // TSQLDataSet
with qEditQuestion do // TClientDataSet
begin
open;
if n = -1 then // new record
begin
caption:= 'New record';
insert;
fieldbyname ('alive').asinteger:= 1;
// initialise necessary fields in the dataset
end
else caption:= 'Editing record ' + inttostr (n);
edit;
if showmodal = mrOK then
begin
// validation code comes here. Set 'result' to true if everything is ok
if result then
begin
if n = -1 then
begin
with qGenID do
begin
open;
gen:= fields[0].asinteger; // get new value from generator
close
end;
FieldByName ('id').asinteger:= gen;
end;
post;
applyupdates (0)
end
else cancel // showmodal = OK, result = false
else // showmodal cancelled
begin
cancel;
result:= false
end;
end // with qEditQuestion
end;
I've been trying to make a creative thing to avoid the dbgrids, and i've found the Tlistview (using the one from alphaskins, tslistview), and seems to be a nice way!
The problem is, I don't want to code the event onclick on every tlistview to position a record/dataset according to the item I selected on the tlistview .. and I'm doing it with the tlistview item's caption.. and there could be records with the same names
Here is one of the codes I want to avoid:
with q_find_process do
begin
close;
sql.Clear;
sql.Add('Select * from t_process where process_name like '+quotedstr(streeview1.Selected.Text)+');
open;
end;
And no, I don't want to put the ID of the Record on the item caption..!
Any ideas?
Does anyone know other way of showing a lot of records without being only text text and more text? I don't know all components on the tool palette, maybe someone could suggest me other one..
I have sometimes used listviews which have been loaded from database tables - only for small amounts of data. I don't understand what you mean by I don't want to code the event onclick on every tlistview to position a record/dataset according to the item I selected on the tlistview, so I'm going to show you how I solved this problem.
Basically, I create a sub-item which holds the primary key of each record. All the user interface code uses two list views, and at the end, the database is updated. There is no interaction with the database between loading and storing (which might be where I avoid your 'onclick' problem). The widths of each fields are set in the Object Inspector; the final subitem's width is 0 (ie not displayed).
Loading the list view:
srclist.items.clear;
with qSrcList do
begin
close;
params[0].asdate:= dt; // use date of deposit
open;
while not eof do
begin
ListItem:= srclist.Items.Add;
ListItem.Caption:= fieldbyname ('kabnum').asstring;
ListItem.SubItems.Add (fieldbyname ('price').asstring);
ListItem.SubItems.Add (fieldbyname ('duedate').asstring);
ListItem.SubItems.Add (fieldbyname ('docket').asstring);
ListItem.SubItems.Add (fieldbyname ('id').asstring);
next
end;
close
end;
Saving data:
with dstlist do
for index:= 1 to items.count do
with qInsert do
begin
dstlist.itemindex:= index - 1;
lvitem:= dstlist.selected;
parambyname ('p1').asinteger:= deposit;
parambyname ('p2').asinteger:= strtoint (lvitem.SubItems[3]);
parambyname ('p3').asfloat:= strtofloat (lvitem.SubItems[0]);
execsql;
end;
I hope that this helps you. The context of this code (not that it matters too much) is in a financial application where the user wishes to populate a bank deposit form with cheques. SrcList holds the cheques which have yet to be deposited (there will only be a few per given date) and DstList holds the cheques which have already been connected to a given deposit form.
information:
I have an order form.
With "keuze" and "aantal" it wright a new line. The Orderline gets an OrderID.
But the user may only see the orderline from his OrderID.
How can i make it work that it only shows, for example the OrderID "47" ?
procedure TfmOrder.btInvoerenClick(Sender: TObject);
begin
dm.atOrder.open;
dm.atOrder.Append;
dm.atOrder ['OrderStatus'] := ('Aangemeld');
dm.atOrder ['klantID'] := fminloggen.userid;
dm.atOrder ['OrderDatum'] := Kalender.date;
dm.atOrder ['Opmerkingen'] := leOpmerkingen.text;
dm.atOrder.post;
cbkeuze.Visible := true;
dbRegel.Visible := true;
leAantal.visible := true;
btOpslaan.Visible:= true;
end;
This is the code for making a new Order
procedure TfmOrder.btOpslaanClick(Sender: TObject);
var orderid:string;
begin
dm.atOrderregel.Open;
dm.atDier.open;
dm.atorderregel.Append;
dm.atOrderregel ['AantalDieren'] := leAantal.text;
dm.atOrderregel ['OrderID'] := dm.atOrder ['OrderID'];
dm.atOrderregel ['Diernaam'] := cbKeuze.Text;
dm.atOrderregel.Post;
leaantal.clear;
cbkeuze.ClearSelection;
end;
And this for a new orderline
thanks in advance
I know got a different error using this code:
begin
dm.atorder.Open;
dm.atorder.filter := 'KlantID = ' + (fminloggen.userid);
dm.atorder.filtered := true;
while not dm.atorder.Eof do
begin
cbOrder.Items.Add (dm.atorder['OrderID']);
dm.atOrder.Next;
end;
dm.atOrder.Close;
end;
It gives an error: The arguments are from the wrong type, or doesn't have right reach or are in conflict with each other.
here is userid declared.
var Gevonden: boolean;
userid : string;
begin
dm.atInlog.open;
Gevonden := false;
while (not Gevonden) and (not dm.atInlog.eof) do
begin
if dm.atInlog['email'] = leUser.Text
then
begin
Gevonden := true ;
inlognaam := dm.atInlog['email'];
userid := dm.atInlog['KlantID'];
end
else
dm.atInlog.Next
end;
this is obviously in another form
You can use the Filter property of the data set:
atOrderregel.Filter := 'OrderID = 47';
atOrderregel.Filtered := True;
You can add the grid's columns property statically in the object inspector, showing only the fields you need. If the columns list is empty (default) it is filled with all available fields.
Just add as many columns as you need and link each column to the corresponding field. You can reorder the columns and set the widths and titles individually. There are still some more properties available which are worth to explore.
Im assuming your grid is bound to a datasource component. This datasource is then linked with a TDataset descendant. There are a couple of ways you could acheive the desired filtering of the dataset to display only orderid 47.
Firstly, you could set the Datasets SQL property to contain a (server side) SQL query such as:
SELECT * from table WHERE OrderID = #OrderID
You would also need to create a parameter in the dataset to pass the (changing) value for the required OrderID. So add a new Parameter to the dataset (#OrderID), and then at runtime you can set this parameter value in code, something like:
DataSet.Parameters['#OrderID'].Value := ParameterValue;
Alternatively, you could also FILTER the dataset (client side) to just show the correct data:
Set your SQL property of the dataset to retrive the entire table, something like:
SELECT * FROM table
And then at runtime you could set the Filter property of the dataset to only get OrderID 47:
Dataset.Filter := 'OrderID = '+InttoStr(ParameterValue);
Depending on your needs one method may suit better (performance/memory) wise.
As Najem has commented, there is also a third method - using a Master-Detail dataset relationship. This method works using two datasets, one is the master of the other. When the master table record is changed, the detail dataset is then filtered using the value defined in the Key or MasterFields property of the M-D relatioship.
If you are connected to some datasource you could always create a SQL Query. Something like:
SELECT * FROM YourDBTable WHERE OrderID=47
I have a TTable, and I am loading CSV files to this TTable. Three fields are there: Id, Hits & Path.
I made some lookup fields to this TTable with another query.
I want to sort the table. I am getting the message "Capability not supported." when I try to call AddIndex('ndxHits','HITS',[]);
Here is my code:
with DM.TblCVResults do
begin
try
Active := False;
TableName := 'C:\CSV\123.txt';
Active := True;
AddIndex('ndxHits','HITS',[]);
AddIndex('ndxCandidate','LkCandidate',[]);
AddIndex('ndxLastCV','LkLastCV',[]);
AddIndex('ndxPostCode','LkPostCode',[]);
IndexDefs.Update;
Active := True;
DM.TblCVResults.IndexName := 'ndxHits';
except
on E: Exception do
MsgError(E.Message);
end;
end
Your previous question mentioned you were using ttASCII as the TableType. ttASCII tables, AFAIK, don't support indexes.
Your best bet is to load the ttASCII TTable content into a TClientDataset` (CDS), which does support indexes. I haven't tested with a ttASCII table as the source, but it should be as simple as:
Add a TDatasetProvider component to your application. Set it's DataSet property to your TTable.
Add a TClientDataSet component to your application. Set it's ProviderName to the DataSetProvider you added above. (I've named it CDS in the steps below.)
Open both the Table and the ClientDataSet (CDS), in that order.
Table1.Active := True;
CDS.Active := True;
Turn off updating of the TTable if you don't need it. (It's much faster.)
CDS.LogChanges := False;
Run the following code to create the indexes:
// Repeat for each additional index
with CDS.IndexDefs.AddIndexDef do
begin
Name := 'ndxHits';
Fields := 'Hits';
Options := [];
end;
Set the ClientDataSet's IndexName property to the index you want active:
CDS.IndexName := 'ndxHits';
Use the ClientDataSet like you would any other dataset. Search it using Locate or FindKey, add to it using Insert or Append, filter it, and so forth.