Tlistview - There is any component like Tlistview but with DB access? - delphi

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.

Related

What has happened to ComboBox.Sorted := True; in Delphi 10.2?

Having recently received a 'Tumbleweed' badge for my last question, I am not sure whether I should be asking any more questions, but here goes.
I am populating a TComboBox with items from a sqlite table and this works fine. In my previous version of Delphi I was able to use ComboBox1.Sorted := True; to sort the items, but this seems to have disappeared in Delphi 10.2. I can sort the items in the table by applying a query and then populate the TComboBox from the sorted table. However, for curiosities sake I would like to find out how one now sorts items in a TComboBox. I have found some references to TComboBox(Sort:Compare) but have not succeeded in getting this to work to as of yet.
Can somebody please shed some light on this - many thanks
In Firemonkey you can populate a TComboBox instance either simply with the Items property of type TStrings or you add TListBoxItem instances with the form designer. But internally always TListBoxItem for the elements is used.
To use the TComboBox.Sort you need to provide an anonymous compare-function.
This is a simple example usage of TComboBox.Sort
cbxItems.Sort(
function (pLeft, pRight: TFMXObject): Integer
var
lLeft, lRight: TListBoxItem;
begin
lLeft := TListBoxItem(pLeft);
lRight := TListBoxItem(pRight);
Result := String.Compare(lLeft.Text, lRight.Text);
end
);

tListBox using AutoComplete navigation from other ListBox

Is there a way to use native tListBox AutoComplete navigation system but based on the items of other ListBox? So when ListBox1 focused when i type some chars items should be selected according to data from ListBox2. Both of them have same amount of items.
Is there a way to use native tListBox AutoComplete navigation system but based on the items of other ListBox?
Yes, but only if the TListBox.Style property is set to either lbVirtual or lbVirtualOwnerDraw. In that case, you must use the TListBox.Count property and TListBox.OnData event to provide strings to the ListBox. The auto-complete functionality will then fire the TListBox.OnDataFind event asking you to locate the typed characters in whatever source you get your strings from. In that event handler, you can search the other TListBox as needed. Just know that the Integer returned by the OnDataFind event handler must be an index relative to the TListBox that the user is typing into, not to the TListBox that you are searching. When the OnDataFind event handler exits, whichever index you return will be selected, unless you return -1 to indicate the characters were not found.
Well i tried to make it native but as far as i get it - it's impossible to combine different item-height functionality (Style=lbOwnerDrawVariable) with DataFind events handling. Ofcourse one can edit TCustomListBox.KeyPress procedure in "VCL\StdCtrls", but i dont like to dig into vcl sources. So i decided to implement AutoComplete feature by myself from a scratch.
First of all i decided to index item-strings for faster search, but there are no need to make full graph (octotree... whatever...) cuz search in my case (and i have about 1k items) anyways performed nearly instantly - so cuz my list is lexicographically sorted i made indexing only for first letters...
//...
const
acceptCharz=[' '..'z'];
//...
type
twoWords=packed record a,b:word; end;
//...
var
alphs:array[' '..'z']of twoWords;// indexes of first letters
fs:tStringList;// list for searching in
fnd:string;// charbuffer for search string
tk:cardinal;// tickCounter for timing
//...
procedure Tform1.button1Click(Sender: TObject);// procedure where list filled with items
var
k,l:integer;
h,q:char;
begin
//... here list-content formed ...
if(fs<>nil)then fs.Free;
fs:=tStringList.Create;
// fltr is tListBox which data should be source of AutoCompletion (ListBox2)
fs.AddStrings(fltr.Items);
for h:=low(alphs)to high(alphs)do alphs[h].a:=$FFFF;// resetting index
h:=#0;
l:=fs.Count-1;
if(l<0)then exit;
for k:=0 to l do begin
s:=AnsiLowerCase(fs.Strings[k]);// for case-insensetivity
fs.Strings[k]:=s;
if(length(s)<0)then continue;
q:=s[1];
if(h<>q)and(q in acceptCharz)then begin
if(k>0)then alphs[h].b:=k-1;
h:=q;
alphs[h].a:=k;// this will work only with sorted data!
end;
end;
if(h<>#0)then alphs[h].b:=l;
end;
//...
// fl is tListBox with custom drawing and OwnerDrawVariable style (ListBox1)
// also fl has same amount of items as fltr
procedure Tform1.flKeyPress(Sender: TObject; var Key: Char);
var
n,i,k,e,l,u,m,a:integer;
s:string;
h:char;
function CharLowerr(h:char):char;// make char LowerCase
begin
Result:=char(LoWord(CharLower(Pointer(h))));
end;
begin
if(getTickCount-tk>=800)then fnd:='';// AutoComplete timeout
tk:=getTickCount;
h:=CharLowerr(key);// for case-insensetivity
if(h in ['a'..'z','0'..'9',' ','-','.'])then fnd:=fnd+h;// u can add all needed chars
if(fnd='')then exit;// if no string to find
h:=fnd[1];// obtain first letter of search-string
a:=alphs[h].a;// get index of first item starting with letter
l:=alphs[h].b;// get index of last item starting with letter
if(a=$FFFF)then exit;// if no such items
e:=length(fnd);// get length of search-string
u:=1;// current length of overlap
m:=1;// max overlap
i:=a;// index to select
if(e>1)then for k:=a to l do begin
s:=fs.Strings[k];
if(length(s)<e)then continue;
for n:=2 to e do begin// compare strings char-by-char
if(s[n]<>fnd[n])then begin// compare failed
u:=n-1;
break;
end else u:=n;
if(u>m)then begin// current overlap is max
m:=u;
i:=k;
end;
end;
if(u=e)or(u<m)then break;// if end of search reached
end;
// select needed index:
fl.ClearSelection;
SendMessage(fl.Handle, LB_SELITEMRANGE, 1, MakeLParam(i, i));
fl.ItemIndex:=i;
inherited;
end;
//...
Yeah this code is kinda ugly, but it works fine, as i said - nearly instantly, i just can see how selection jumps thru items while i type, and i type pretty fast...
So it was code which i wrote yesterday and all this could end here, but today i realized that it was absolutely dumb decision at whole: in my case, as i mentioned above, i have listbox with OwnerDrawVariable style, so i have custom MeasureItem and DrawItem procedures and the best decision in this kind of situation will be to turn AutoComplete property true and to fill ListBox1 with items from ListBox2. And strings needed to display could be shown anyways in DrawItem procedure. Also possible to remove ListBox2 and keep strings for displaying inside tStringList variable. So morale is - dont rush into writing boilerplates and try to think more before acting =))
P.S. but one can use this code for some custom AutoComplete handling...

How to get value of TDBLookupComboBox?

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;

How to access data record after ListBox selected with LiveBindings

I have a TSQLDataset, Im using the livebindings to bind it to a listbox. When I click on the listbox item, I want to be able to access the other fields of data from the record, but I cannot figure out how to do it because I cannot get the dataset to the corresponding item.
I know that I could possibly take the ID Field and maybe assign it to Selected.Tag using live bindings but can't figure that out either, but if I could then I could have another a SQLQuery and then just return the result of the query
SELECT * FROM Dataset WHERE ID=(Tag value)
That would work, but I don't know how to get livebindings to set the items tag value when live bindings populates the Listbox.
Does anyone know how to make this work?
It is easier to link Tag property of the ListBox with the ID of the record.
There's a Sensor Info demo application from Embarcadero in XE5 Samples directory...
There you have OnItemClick = lbMainItemClick in TListBox events then you have to define the event handler:
procedure TfrmAboutSensors.lbMainItemClick(const Sender: TCustomListBox; const Item: TListBoxItem);
begin
if Assigned(Item.OnClick) then
Item.OnClick(Item);
end;
And then for every item on the list:
for LItem in LListBox do
begin
//LItem.ItemData.Accessory := TListBoxItemData.TAccessory.aDetail; // my code
//LItem.ItemData.Accessory := TListBoxItemData.TAccessory.aNone; // my code
LItem.OnClick := ListBoxItemClick;
end;
Please give us callback if that helps.

Modify DBGrid cell content before it is displayed

I want to modify the content of a particular cell in dbgrid control when the database is loaded. For example, lets say I don't want any field of database to be displayed in dbgrid if it is equal to "forbidden". Is there any way that I can do that?
Going to your original question:
Use the OnGetText event of the field to provide a different value from what is stored on the database for presentation purposes.
The DisplayText boolean parameter will be True if the value is required to be presented to the user and will be False if the value is required for other purposes.
procedure TForm1.SQLQuery1Field1GetText(Sender: TField;
var Text: string; DisplayText: Boolean);
begin
if (Sender.AsString = 'forbidden') and (DisplayText)
and (PrivilegeLevel(CurrentUser) < 10) then
Text := '********'
else
Text := Sender.AsString;
end;
you can use the DataSetNotifyEvent Afteropen
DBGrid.Datasource.Dataset.Afteropen :=
and you can hide fields with:
if Condition then
DBGrid.columns[x].visible := false
alternative you can check the condition on the OnDrawColumnCell event in order to overrite / delete some content in a specific cell
Using the DataSet events to synchronize UI is not a good practice. You can rely on DataSource events to do that, separating UI Logic from business logic.
As the state of the DataSet will change from dsInactive to dsBrowse, you can rely on the DataSource OnState change to make anything UI-related upon the data is loaded from database.
You can rely on a Auxiliar field to track previous state to avoid the code executing more than needed.
for example (untested code)
procedure TForm1.DataSource1StateChange(Sender: TObject);
begin
if (DataSource1.State = dsBrowse) and (not FUIStateInSync) then
begin
//dataset is open, change UI accordingly
DBGrid1.Columns[0].Visible := SomeCondition();
//this will prevent the code to be executed again
//as state comes to dsBrowse after posting changes, etc.
FUIStateInSync := True;
end
else if (DataSource1.State = dsInactive) then
FUIStateInSync := False; //to let it happen again when opened.
end;
I publish this even when you have an accepted answer, because O.D. suggestion is just what you shall avoid.
Hookup OnAfterOpen event on dataset.
Get the hidden fields and set its Visible property to False and your dbgrid will not display them
Cheers
I would modify the query that provides the data to the grid so as not to include rows (tuples) which have the 'forbidden' string. This seems much easier than trying hard not to display data after it has already been retrieved from the database.
I think that the best way would be not to SELECT the fields WHERE SOME_VALUE="forbidden" FROM the DATABASE_TABLE

Resources