i have a DBGrid and it is linked to client dataset when i assign a SQLQuery at run time
the DBGrid automatically assigns no of column. What i need is when DBGrid automatically assign columns i need to set one of those columns to assign a picklist.
can anyone help me?
the following procedure calls in the forms on show event. the form contains DataSource, ClientDataSet, SQLViewQuery (TSQLQuery), DatasetProvider and DBGridDetails (TDBGrid).
procedure TViewDetailsForm.ViewPendingAndReturnCheques;
var I : Integer;
slPickList:TStringList;
begin
slPickList := TStringList.Create;
slPickList.Add('Pending');
slPickList.Add('Returned');
slPickList.Add('Passed');
SQL := 'SELECT a.CHEQUE_NO, a.BANK, a.CHEQUE_DATE, a.AMOUNT,a.STATUS FROM CHEQUES a';
//refreshisng the DBGrid
SQLViewQuery.SQL.Clear;
SQLViewQuery.SQL.Add(SQL);
ClientDataSet.Active := false;
ClientDataSet.Active := true;
DBGridDetails.Columns[0].Width := _Block;
DBGridDetails.Columns[1].Width := _Block;
DBGridDetails.Columns[2].Width := _Block;
DBGridDetails.Columns[3].Width := _Block;
DBGridDetails.Columns[4].Width := _Block;
for I := 0 to DBGridDetails.Columns.Count - 1 do
begin
if DBGridDetails.Columns[I].FieldName = 'STATUS' then
begin
DBGridDetails.Columns[i].ButtonStyle := cbsAuto;
DBGridDetails.Columns[I].PickList := slPickList;
end;
end;
Show;
end;
Here's a sample app I just created in Delphi 2007 that demonstrates how to accomplish this. Here's all I did to set it up:
Click File->New-VCL Forms Application from the IDE's main menu.
Drop a TClientDataSet, a TDataSource, and a TDBGrid on the form.
Click on the form, and then use the Object Inspector to create a new OnCreate event handler. Add the following code:
procedure TForm1.FormCreate(Sender: TObject);
var
SL: TStringList;
begin
with ClientDataSet1 do
begin
FieldDefs.Clear;
FieldDefs.Add('OrderNo', ftInteger);
FieldDefs.Add('Status', ftString, 10);
CreateDataSet;
end;
ClientDataSet1.Active := True;
// Connect a datasource to the CDS
DataSource1.DataSet := ClientDataSet1;
// Connect the grid to that datasource to create the columns.
DBGrid1.DataSource := DataSource1;
// Create the picklist for the second column (Status)
SL := TStringList.Create;
try
SL.Add('Pending');
SL.Add('Returned');
SL.Add('Passed');
DBGrid1.Columns[1].ButtonStyle := cbsAuto;
DBGrid1.Columns[1].PickList := SL;
finally
SL.Free;
end;
end;
Run the application, click in the Status column in the grid, and you'll see the three choices added to the PickList above.
You can assign values to the dbgrid column picklist during the run time.
Below is the code:
procedure Tfrm1.FormShow(Sender: TObject);
var
slPickList:TStringList;
I: Integer;
begin
slPickList := TStringList.Create;
slPickList.Add('Pending');
slPickList.Add('Returned');
slPickList.Add('Passed');
for I := 0 to 2 do
begin
dbgViewAxiomClaims.Columns1.PickList.add(slPickList[i]);//assigning
end;
end;
Below is the result:
Related
I am currently doing a school project, I am making a Credit Card machine. I need the 'Enter Button' to
run different code when it is clicked. The first click must get the card number from an edit ps... (I clear the edit once the card number has been retrieved), and the second click must get the pin from the same edit.
How would I do this?
procedure TfrmMainMenu.btbtnEnterClick(Sender: TObject);
var
sCvv,sPin:string;
begin
iCount2:=0;
sCardNumber:=lbledtCardInfo.Text;
if (Length(sCardNumber)<>16) AND (iCount2=0) then
begin
ShowMessage('Card number has to 16 digits,please try again!!');
end
else
begin
Inc(iCount2);
lbledtCardInfo.clear;
lbledtCardInfo.EditLabel.Caption:='Enter Pin' ;
btbtnEnter.Enabled:=false;
end; //if
if iCount2=2 then
begin
btbtnEnter.Enabled:=true;
sPin:=lbledtCardInfo.Text;
ShowMessage(sPin);//returns a blank
end;
You could try to do everything in a single event handler. There are several different ways to handle that. However, a different solution would be to use separate event handlers for each task, and then each task can assign a new handler for the next click to perform, eg:
procedure TfrmMainMenu.FormCreate(Sender: TObject);
begin
// you can set this at design-time if desired...
btbtnEnter.OnClick := GetCCNumber;
end;
procedure TfrmMainMenu.GetCCNumber(Sender: TObject);
begin
sCardNumber := lbledtCardInfo.Text;
if Length(sCardNumber) <> 16 then
begin
ShowMessage('Card number has to 16 digits,please try again!!');
Exit;
end;
lbledtCardInfo.Clear;
lbledtCardInfo.EditLabel.Caption := 'Enter Pin' ;
btbtnEnter.OnClick := GetCCPin;
end;
procedure TfrmMainMenu.GetCCPin(Sender: TObject);
var
sPin: string;
begin
sPin := lbledtCardInfo.Text;
if Length(sPin) <> 4 then
begin
ShowMessage('Card Pin has to 4 digits,please try again!!');
Exit;
end;
ShowMessage(sPin);
...
lbledtCardInfo.Clear;
lbledtCardInfo.EditLabel.Caption := 'Enter Number' ;
btbtnEnter.OnClick := GetCCNumber;
end;
A variation of this would be to create multiple buttons that overlap each other in the UI, and then you can toggle their Visible property back and forth as needed, eg:
procedure TfrmMainMenu.FormCreate(Sender: TObject);
begin
// you can set this at design-time if desired...
btbtnCCPinEnter.Visible := False;
btbtnCCNumEnter.Visible := True;
end;
procedure TfrmMainMenu.btbtnCCNumEnterClick(Sender: TObject);
begin
sCardNumber := lbledtCardInfo.Text;
if Length(sCardNumber) <> 16 then
begin
ShowMessage('Card number has to 16 digits,please try again!!');
Exit;
end;
lbledtCardInfo.Clear;
lbledtCardInfo.EditLabel.Caption := 'Enter Pin' ;
btbtnCCNumEnter.Visible := False;
btbtnCCPinEnter.Visible := True;
end;
procedure TfrmMainMenu.btbtnCCPinEnterClick(Sender: TObject);
var
sPin: string;
begin
sPin := lbledtCardInfo.Text;
if Length(sPin) <> 4 then
begin
ShowMessage('Card Pin has to 4 digits,please try again!!');
Exit;
end;
ShowMessage(sPin);
...
lbledtCardInfo.Clear;
lbledtCardInfo.EditLabel.Caption := 'Enter Number' ;
btbtnCCPinEnter.Visible := False;
btbtnCCNumEnter.Visible := True;
end;
Notice that you test iCount2 = 0 immediately after setting iCount2 := 0. Thus, that test will always be True. Furthermore, the later test iCount2 = 2 will always be False because the value starts at 0 and you only have one Inc in between.
Instead try the following.
Add two string fields FCardNumber and FPin to your form class:
private
FCardNumber: string;
FPin: string;
Also create an enumerated type TEntryStage = (esCardNumber, esPin) and add a field of this type. This will make your code look like this:
private
type
TEntryStage = (esCardNumber, esPin);
var
FCardNumber: string;
FPin: string;
FEntryStage: TEntryStage;
In Delphi, class fields (class member variables) are always initialized, so FEntryStage will be esCardNumber (=TEntryStage(0)) when the form is newly created.
Add a TLabeledEdit (I see you use those) and a TButton; name them eInput and btnNext, respectively. Let the labeled edit's caption be Card number: and the caption of the button be Next.
Now add the following OnClick handler to the button:
procedure TForm1.btnNextClick(Sender: TObject);
begin
case FEntryStage of
esCardNumber:
begin
// Save card number
FCardNumber := eInput.Text;
// Prepare for the next stage
eInput.Clear;
eInput.EditLabel.Caption := 'Pin:';
FEntryStage := esPin;
end;
esPin:
begin
// Save pin
FPin := eInput.Text;
// Just do something with the data
ShowMessageFmt('Card number: %s'#13#10'Pin: %s', [FCardNumber, FPin]);
end;
end;
end;
You might notice that you cannot trigger the Next button using Enter, which is very annoying. To fix this, do
procedure TForm1.eInputEnter(Sender: TObject);
begin
btnNext.Default := True;
end;
procedure TForm1.eInputExit(Sender: TObject);
begin
btnNext.Default := False;
end;
Much better!
On my system , the main menu is fully dynamic, is builded from data in the database.
I have a specific situation where some menu items need to be assembled before being displayed.
Let's assume that my menu has the following main items:
Files - Customer - Reports - About
When I click in Reports menu item must assemble the items before they are displayed.
I did an analysis of the code in TMainMenu, TMenu and TMenuItem class. Unfortunately I have not found a simple solution to the problem.
Exist a way to create these items before being displayed?
There is a trick you can use. You need to add one dummy TMenuItem under Reports and set its Visible property to False. Then add OnClick event to Reports item and do your populating logic there. Before you add new items you have to delete existing ones, but you should leave your dummy item intact.
Something like:
procedure TForm1.ReportItemClick(Sender: TObject);
var
Item: TMenuItem;
I: Integer;
begin
// Delete all items but first - dummy - one
for I := ReportItem.Count - 1 downto 1 do
ReportItem.Items[I].Free;
Item := TMenuItem.Create(ReportItem);
Item.Caption := 'abc';
// Item.OnClick := ...
// or
// Item.Action := ...
ReportItem.Add(Item);
Item := TMenuItem.Create(ReportItem);
Item.Caption := '123';
// Item.OnClick := ...
// or
// Item.Action := ...
ReportItem.Add(Item);
end;
Lets Suppose that you have an DS (TDataSet) how load all your data reports. And in the moment that you drop Reports Menu you can call UpdateMenuReport.
type
TMenuArray = array of TMenuItem;
var MyMenu: TMenuArray;
procedure TMain.MyClickPopupMenu(Sender: TObject);
begin
case TMenuItem(TMenuItem(Sender).Parent).Tag of
// do Something
end;
end;
procedure TMain.UpdateMenuReport;
procedure InitMyMenu(var AMenu: TMenuArray);
var i: Integer;
begin
if Length(AMenu)>0 then for i:= 0 to Length(AMenu)-1 do AMenu[i].Free;
SetLength(AMenu, 0);
end;
var j, i: integer;
begin
InitMyMenu(MyMenu);
Reports.Clear;
if DS.Active and (DS.RecordCount > 0) then
begin
SetLength(MyMenu, DS.RecordCount);
for i:= 0 to DS.RecordCount-1 do
begin
MyMenu[i] := TMenuItem.Create(Self);
MyMenu[i].Name := 'Reports_'+ IntToStr(i);
MyMenu[i].Caption := DS.FieldByname('NOM_REPORT').AsString;
MyMenu[i].Tag := DS.FieldByname('ID').AsInteger;
MyMenu[i].OnClick := MyClickPopupMenu;
end;
end;
end;
I have a function to update a cxGrid made with help from answers to Loop through records on a cxgrid and update a field/column
But it is sometimes acting a bit strange. If I open the form with the cxGrid and click the columnheader without doing anything else, the records are updateted OK. But if the 'selectorbar' is moved away from the top, the record marked is not updated.
I am sure it is a property that needs to be changed, but which one.
The variable fSelected is set to False at FormShow and is ther so that the user can unselect records as well.
procedure TfrmContactsSelect.colContactSelectedHeaderClick(Sender: TObject);
var
i: Integer;
Index: Integer;
BookMark : TBookMark;
Contact: variant;
begin
if fMulti = True then
begin
Screen.Cursor := crHourGlass;
fSelected := not fSelected;
BookMark := qryContacts.GetBookmark;
qryContacts.DisableControls;
try
for i := 0 to grdContactsView1.DataController.FilteredRecordCount - 1 do
begin
Index := grdContactsView1.DataController.FilteredRecordIndex[i];
Contact := grdContactsView1.DataController.Values[Index, 4];
if grdContactsView1.DataController.LocateByKey(Contact) then
begin
qryContacts.Edit;
qryContacts.FieldByName('fldcontact_selected').AsBoolean := fSelected;
qryContacts.Post;
end;
end;
finally
qryContacts.EnableControls;
qryContacts.GotoBookmark(BookMark);
qryContacts.FreeBookmark(BookMark);
end;
Screen.Cursor := crDefault;
end;
end;
Delphi XE7, DevExpress 14.2.2, UniDAC 5.5.12 for DB access
Comment:
I have ended up with the following solution based on the answer and input from MartynA
procedure TfrmContactsSelect.colContactSelectedHeaderClick(Sender: TObject);
var
i: Integer;
Index: Integer;
MarkedRecord: variant;
CurrentRecord: variant;
begin
if fMulti = True then
begin
Screen.Cursor := crHourGlass;
fSelected := not fSelected;
Index := grdContactsView1.DataController.FocusedRecordIndex;
MarkedRecord := grdContactsView1.DataController.Values[Index, colContactGuid.ID];
try
for i := 0 to grdContactsView1.DataController.FilteredRecordCount - 1 do
begin
Index := grdContactsView1.DataController.FilteredRecordIndex[i];
CurrentRecord := grdContactsView1.DataController.Values[Index, colContactGuid.ID];
if grdContactsView1.DataController.LocateByKey(CurrentRecord) then
begin
grdContactsView1.DataController.Edit;
grdContactsView1.DataController.SetEditValue(colContactSelected.ID, fSelected, evsText);
grdContactsView1.DataController.Post;
end;
end;
finally
grdContactsView1.DataController.LocateByKey(MarkedRecord);
end;
Screen.Cursor := crDefault;
end;
end;
I can reproduce your problem using the sample project I posted in my answer to your other q.
Try this: Add a TMemo to your form, and inside the 'if grdContactsView1.DataController.LocateByKey(Contact) then' block, write the value of a row-unique datafield and the Selected datafield value to the memo.
Then, what I get when some row other than the top row is selected is that one row is listed twice in the memo, with Selected both false and true, and one of the rows in the filter isn't listed at all, which I think accounts for the behaviour you're seeing. If I then comment out the .Edit .. .Post lines, it correctly lists all the rows in the filter.
So evidently doing the Selected field changes inside a block which iterated the FilteredRecordIndex property of the DBTableView is what's causing the problem.
Personally, I find that it goes a bit against the grain to modify dataset rows in code via a DB-aware control (because you usually end up fighting the DB-awareness of the control), but in this case, it's straightforward to do the processing via the DBTableView of the cxGrid.
procedure TForm1.ProcessFilteredRecords;
var
PrevV,
V : Variant;
i,
Index: Integer;
S : String;
begin
// First, pick up a reference to the current record
// so that we can return to it afterwards
Index := cxGrid1DBTableView1.DataController.FocusedRecordIndex;
PrevV := cxGrid1DBTableView1.DataController.Values[Index, 0];
try
for i := 0 to cxGrid1DBTableView1.DataController.FilteredRecordCount - 1 do begin
Index := cxGrid1DBTableView1.DataController.FilteredRecordIndex[i];
V := cxGrid1DBTableView1.DataController.Values[Index, 0];
if cxGrid1DBTableView1.DataController.LocateByKey(V) then begin
cxGrid1DBTableView1.DataController.Edit;
// 2 is the index of my Selected column in the grid
if cxGrid1DBTableView1.DataController.SetEditValue(2, True, evsText) then
Caption := 'OK'
else
Caption := 'Failed';
cxGrid1DBTableView1.DataController.Post;
end;
end;
finally
if cxGrid1DBTableView1.DataController.LocateByKey(PrevV) then
Caption := 'OK'
else
Caption := 'Failed';
end;
end;
Another way to avoid the problem is to change the Selected states in two steps:
Iterate the FilteredRecordIndex to build a list of rows to change - in your case this would be a list of guids
Then, iterate the list of rows and update their Selected states.
Code:
procedure TForm1.ProcessFilteredRecords;
var
V : Variant;
i,
Index: Integer;
BM : TBookMark;
S : String;
TL : TStringList;
begin
Memo1.Lines.Clear;
TL := TStringList.Create;
try
for i := 0 to cxGrid1DBTableView1.DataController.FilteredRecordCount - 1 do begin
Index := cxGrid1DBTableView1.DataController.FilteredRecordIndex[i];
V := cxGrid1DBTableView1.DataController.Values[Index, 0];
if cxGrid1DBTableView1.DataController.LocateByKey(V) then begin
if CDS1.FieldByName('Selected').AsBoolean then
S := 'True'
else
S := 'False';
S := CDS1.FieldByName('Name').AsString + ' ' + S;
Memo1.Lines.Add(S);
TL.Add(CDS1.FieldByName('Guid').AsString);
end;
end;
try
BM := CDS1.GetBookMark;
CDS1.DisableControls;
for i := 0 to TL.Count - 1 do begin
if CDS1.Locate('guid', TL[i], []) then begin
CDS1.Edit;
CDS1.FieldByName('Selected').AsBoolean := True;
CDS1.Post;
end
end;
finally
CDS1.EnableControls;
CDS1.GotoBookmark(BM);
CDS1.FreeBookmark(BM);
end;
finally
TL.Free;
end;
end;
Like you, I was expecting that changing a property or two of the cxGrid might avoid the problem without any code, but I haven't been able to find anything which does.
I was creating table during execution, however at the creation of the table, (see my code)
procedure CreateTempTable(pDataBaseName,pSessionName:String);
begin
//-------create "TempTable"
TempTable:=TTable.Create(application);
With TempTable Do
begin
//-------set false in "Active"
Active := False;
//-------name of Session
SessionName:=pSessionName;
//-------name of DataBase.
DatabaseName :=pDataBaseName;
Filtered := True;
//-------name of table
TableName := 'TempTabl.DB';
//-------paradox type
TableType := ttParadox;
//-------if the table is already exists
if TempTable.Exists then
//-------delete the table
TempTable.DeleteTable;
//-------create 2 fields "Field1" & "Field2"
with FieldDefs do
begin
Clear;
with AddFieldDef do
begin
DataType := ftFloat;
Name := 'Field1';
end;
with AddFieldDef do
begin
DataType := ftFloat;
Name:='Field2';
end;
end;
//-------Create table
CreateTable; // Here where the exception bursts off
end;
end;
an exception is raised, which is: "Table is open, Table does not exist".
so what is exactly the problem, is it open or doesnot exist?
This is the exception:
(inside With TempTable Do) There is no need to delete the TempTabl.DB explicit. Is overwritten by CreateTable anyway.
To test if the table is not being used by other components in the IDE, you can try to delete the file TempTabl.DB.
CreateTempTable is now a function CreateTempTable(pDataBaseName,pSessionName:String):Boolean;
So you can better handle errors.
Tested with delphi 5 and RAD Studio 2007.
function CreateTempTable(pDataBaseName,pSessionName:String):Boolean;
begin
result:=false;
// assume pDataBaseName=directory, or change it
if FileExists(pDataBaseName+'TempTabl.DB') then begin
if NOT DeleteFile(pDataBaseName+'TempTabl.DB') then begin
showMessage('Table opened by another part of the IDE');
exit;
end;
end;
TempTable:=TTable.Create(application);
With TempTable Do
begin
Active := False;
SessionName:=pSessionName;
DatabaseName :=pDataBaseName;
//Filtered := True;
TableName := 'TempTabl.DB';
TableType := ttParadox;
with FieldDefs do
begin
Clear;
with AddFieldDef do
begin
DataType := ftFloat;
Name := 'Field1';
end;
with AddFieldDef do
begin
DataType := ftFloat;
Name:='Field2';
end;
end;
CreateTable;
result:=true;
end;
end;
My code is the below, it's working correctly but, but after compiling program i see all the fullname and country listed vertically something like :
_________________________________
Fullname1
Country1
Fullname2
Country2
Fullname3
Country3
etc...
SQLQuery1.SQL.Text := 'SELECT * FROM users where user_age="'+age+'"';
SQLQuery1.Open;
rec := SQLQuery1.RecordCount;
SQLQuery1.First; // move to the first record
ListView1.Visible := false;
if rec>0 then
begin
while(not SQLQuery1.EOF)do begin
ListView1.Visible := true;
// do something with the current item
ListView1.AddItem('Full name: '+SQLQuery1['fullname'], Self);
ListView1.AddItem('Country: '+SQLQuery1['cntry'], Self);
// move to the next record
SQLQuery1.Next;
end;
But i want something Like :
First: add the column headers:
var
Col: TListColumn;
begin
Col := ListView1.Columns.Add;
Col.Caption := 'Name';
Col.Alignment := taLeftJustify;
Col.Width := 140;
Col := ListView1.Columns.Add;
Col.Caption := 'Country';
Col.Alignment := taLeftJustify;
Col.Width := 140;
end;
then add the records as follows:
var
Itm: TListItem;
begin
// start of your query loop
Itm := ListView1.Items.Add;
Itm.Caption := SQLQuery1['fullname'];
Itm.SubItems.Add(SQLQuery1['cntry']);
// end of your query loop
end;
Update:
Of course, in order to get the list as in your screenshot, you need to set the ListView's ViewStyle property to vsReport
Your code should look like that:
var
ListItem: TListItem;
...
ListView.Items.BeginUpdate;
try
while(not SQLQuery1.EOF)do begin
ListItem:= ListView.Items.Add;
ListItem.Caption:= 'Full name: '+SQLQuery1['fullname'];
with ListItem.SubItems do begin
Add('Country: '+SQLQuery1['cntry']);
// if you need more columns, add here
end;
SQLQuery1.Next;
end;
finally
ListView.Items.EndUpdate;
end;
You should also set ListView.Style to vsReport to show listview as grid.
I'm not sure how to get the listview to multiline, but I do know you're not using the Query correctly.
As it stands your code has an SQL-injection hole and the implicit reference to 'fieldbyname' inside the loop makes it slow.
var
FullName: TField;
Country: TField;
ListItem: TListItem;
begin
//Use Params or suffer SQL-injections
SQLQuery1.SQL.Text := 'SELECT * FROM users where user_age= :age';
SQLQuery1.ParamByName('age').AsInteger:= age;
SQLQuery1.Open;
if SQLQuery1.RecordCount = 0 then Exit;
//Never use `FieldByName` inside a loop, it's slow.
FullName:= SQLQuery1.FieldByName('fullname');
Country:= SQLQuery1.FieldByName('cntry');
ListView1.Style:= vsReport;
SQLQuery1.First; // move to the first record
SQLQuery1.DisableControls; //Disable UI updating until where done.
try
ListView1.Items.BeginUpdate;
//ListView1.Visible := false;
while (not SQLQuery1.EOF) do begin
//Code borrowed from #Serg
ListItem:= ListView.Items.Add;
ListItem.Caption:= 'Full name: '+Fullname.AsString;
ListItem.SubItems.Add('Country: '+Country.AsString);
SQLQuery1.Next;
end; {while}
finally
SQLQuery1.EnableControls;
ListView1.Items.EndUpdate;
end;
end;
The Delphi documentation contains this example that does exactly what you want.
procedure TForm1.FormCreate(Sender: TObject);
const
Names: array[0..5, 0..1] of string = (
('Rubble', 'Barney'),
('Michael', 'Johnson'),
('Bunny', 'Bugs'),
('Silver', 'HiHo'),
('Simpson', 'Bart'),
('Squirrel', 'Rocky')
);
var
I: Integer;
NewColumn: TListColumn;
ListItem: TListItem;
ListView: TListView;
begin
ListView := TListView.Create(Self);
with ListView do
begin
Parent := Self;
Align := alClient;
ViewStyle := vsReport;
NewColumn := Columns.Add;
NewColumn.Caption := 'Last';
NewColumn := Columns.Add;
NewColumn.Caption := 'First';
for I := Low(Names) to High(Names) do
begin
ListItem := Items.Add;
ListItem.Caption := Names[I][0];
ListItem.SubItems.Add(Names[I][2]);
end;
end;
end;
For all that the Delphi documentation is much maligned, it often has very useful examples like this. The gateway page to the examples is here and the examples are even available on sourceforge so you can check them out using your favourite svn client.
Procedure TForm1.GetUsers;
var
ListItem: TListItem;
begin
try
ListView1.Items.BeginUpdate;
try
ListView1.Clear;
MySQLQuery.SQL.Clear;
MySQLQuery.SQL.Add('select * from users;');
MySQLQuery.Open;
while (not MySQLQuery.EOF) do
begin
ListItem := ListView1.Items.Add;
ListItem.Caption:= VarToSTr(MySQLQuery['username']);
with ListItem.SubItems do
begin
Add(VarToSTr(MySQLQuery['password']));
Add(VarToSTr(MySQLQuery['maxscore']));
end;
MySQLQuery.Next;
end;
MySQLQuery.Close;
finally
ListView1.Items.EndUpdate;
end;
except
on E: Exception do
MessageDlg(PWideChar(E.Message), TMsgDlgType.mtError, [TMsgDlgBtn.mbOK], 0);
end;
end;