Index out of range (-1) when I click on any item in the TListBox - database

The TListBox called lboMtrlList is populated with records from the database. The data displays properly when I run the application. When I click on any item in the list, shows the error:
Index out of range (-1)
despite the list not being empty.
Here's the code for populating the lboMtrlList:
procedure TfrmMakeQuote.FormCreate(Sender: TObject);
begin
con := TFDConnection.Create(nil);
query := TFDQuery.Create(con);
con.LoginPrompt := false;
con.Open('DriverID=SQLite;Database=C:\Users\katiee\Documents\Embarcadero\Studio\Projects\ProgramDatabase;');
query.Connection := con;
performQuery;
query.SQL.Text :=
'SELECT [Material Description] FROM MtrlDatabase ORDER BY MtrlID';
try
query.Open;
lboMtrlList.Items.Clear;
while not query.EOF do
begin
lboMtrlList.Items.Add(query.Fields[0].AsString);
query.Next;
end;
finally
query.Close;
end;
//ledtDesc.Height := 81;
//ledtNotes.Height := 51;
end;
I want to be able to double click on an item in the lboMtrlList and move it to another TListBox called lboSelectedMtrl. Here's the code:
procedure TfrmMakeQuote.lboMtrlListDblClick(Sender: TObject);
begin
lboMtrlList.Items.Add(lboSelectedMtrl.Items.Strings[lboSelectedMtrl.ItemIndex]);
end;

I want to be able to double click on an item in the lboMtrlList and move it to another TListBox called lboSelectedMtrl.
Your code is doing the opposite of that. It is trying to move an item from lboSelectedMtrl to lboMtrlList. You are getting the bounds error because there is no item selected in lboSelectedMtrl (lboSelectedMtrl.ItemIndex is -1).
Swap the ListBox variables, and add some error checking:
procedure TfrmMakeQuote.lboMtrlListDblClick(Sender: TObject);
var
Idx: Integer;
begin
Idx := lboMtrlList.ItemIndex;
if Idx <> -1 then
lboSelectedMtrl.Items.Add(lboMtrlList.Items.Strings[Idx]);
end;

Related

Inno Setup : Automatically check a checkbox after a specified time in seconds

Hello I want My Inno Setup Script to automatically check a CheckBox in one of my wizard pages after a specified time (e.g. 5 seconds).
Here's Why:
I created a checkbox which can change WizardForm's ClientWidth and ClientHeight when toggled.
If I don't click on it, the width and height of the WizardForm stays same. That's how it behaves.
The code I written to do that:
var
MinimizerCheckBox: TNewCheckBox;
procedure InitializeWizard();
begin
MinimizerCheckBox := TNewCheckBox.Create(WizardForm);
with MinimizerCheckBox do
begin
Name := 'MinimizerCheckBox';
Parent := WizardForm;
Left := ScaleX(560);
Top := ScaleY(315);
Width := ScaleX(90);
Height := ScaleY(14);
Alignment := taLeftJustify;
Caption := 'Compact Mode';
OnClick := #MinimizerCheckBoxClick;
TabOrder := 3;
end;
end;
procedure MinimizerCheckBoxClick(Sender: TObject);
begin
if MinimixerCheckBox.Checked then
begin
with WizardForm do
begin
WizardForm.ClientWidth:=420;
WizardForm.ClientHeight:=175;
end;
end else begin
with WizardForm do
begin
WizardForm.ClientWidth:=654;
WizardForm.ClientHeight:=407;
end;
end;
end;
I want to check that checkbox automatically after a specified time.
Any example code to do this?
Thanks in advance.
You can schedule a timer to check the checkbox like this:
[Code]
var
MinimizerCheckBox: TCheckBox;
...
function SetTimer(
hWnd: longword; nIDEvent, uElapse: longword; lpTimerFunc: longword): longword;
external 'SetTimer#user32.dll stdcall';
function KillTimer(hWnd: HWND; uIDEvent: UINT): BOOL;
external 'KillTimer#user32.dll stdcall';
var
CheckTimerID: Integer;
procedure StopCheckTimer;
begin
Log('Killing timer');
KillTimer(0, CheckTimerID);
CheckTimerID := 0;
end;
procedure CheckProc(h: LongWord; msg: LongWord; idevent: LongWord; dwTime: LongWord);
begin
Log('Timer elapsed');
StopCheckTimer;
MinimizerCheckBox.Checked := True;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpXXX then { your page id }
begin
Log('Starting 5s timer');
CheckTimerID := SetTimer(0, 0, 5000, CreateCallback(#CheckProc));
end
else
if CheckTimerID <> 0 then
begin
StopCheckTimer;
end;
end;
For CreateCallback function, you need Inno Setup 6. If you are stuck with Inno Setup 5, you can use WrapCallback function from InnoTools InnoCallback library.

How can I display only specified database results based on TListbox items?

I have two forms: frmMakeQuote and frmQuoteTemp
In the frmMakeQuote there are two TListboxes: lboMtrlList and lboSelectedMtrl
lboMtrlList displays the Product Description column from the database.
procedure TfrmMakeQuote.FormCreate(Sender: TObject);
begin
con := TFDConnection.Create(nil);
query := TFDQuery.Create(con);
con.LoginPrompt := false;
con.Open('DriverID=SQLite;Database=C:\Users\kasio\Documents\Embarcadero\' +
'Studio\Projects\ProgramDatabase;');
query.Connection := con;
query.SQL.Text :=
'SELECT [Material Description] FROM MtrlDatabase ORDER BY MtrlID';
try
query.Open;
lboMtrlList.Items.Clear;
while not query.EOF do
begin
lboMtrlList.Items.Add(query.Fields[0].AsString);
query.Next;
end;
finally
query.Close;
end;
end;
When the person double clicks on any 'product' in the lboMtrlList, it's moved to the lboSelectedMtrl. (Basically, it shows the selected 'products'.)
procedure TfrmMakeQuote.lboMtrlListDblClick(Sender: TObject);
begin
lboSelectedMtrl.Items.Add(lboMtrlList.Items.Strings[lboMtrlList.ItemIndex]);
end;
I want to be able to display the Product Description and Price columns from the database, of ONLY the selected 'products' from the lboSelectedMtrl. They should be displayed in the TStringGrid called sgdMaterials on the frmQuoteTemp.
I wrote something like this:
procedure TfrmMakeQuote.performMtrlQuery;
var
i: integer;
begin
for i := 1 to frmMakeQuote.lboSelectedMtrl.ItemIndex do
begin
query.SQL.Text := 'SELECT [Material Description], Price FROM MtrlDatabase ' +
'WHERE [Material Description] = "'
+ frmMakeQuote.lboSelectedMtrl.Items.Strings[1]
+ '" ORDER BY MtrlID';
query.Open;
query.First;
end;
end;
It doesn't show any error, but it doesn't work and displays nothing and I'm aware that it's probably completely wrong.
Your loop inside of performMtrlQuery() is wrong. If nothing is actually selected in lboSelectedMtrl, its ItemIndex will be -1 and the loop will not iterate through any items. Not only that, but even if an item were selected, your loop would not iterate through ALL of the available items. Also, when you are indexing into the Strings[] property, you are using a hard-coded 1 instead of the loop variable i.
As for your TStringGrid, why not use a TDBGrid instead, and tie it to a DataSource that is filtering the database by the desired items? In any case, performMtrlQuery() is not doing anything to populate the grid at all, whether it is to store the search results in the grid directly, or to store the results in a list somewhere that frmQuoteTemp can then read from.
Try this instead:
procedure TfrmMakeQuote.performMtrlQuery;
var
i: integer;
begin
for i := 0 to frmMakeQuote.lboSelectedMtrl.Items.Count-1 do
begin
query.SQL.Text := 'SELECT [Material Description], Price FROM MtrlDatabase' +
' WHERE [Material Description] = "'
+ frmMakeQuote.lboSelectedMtrl.Items.Strings[i]
+ '" ORDER BY MtrlID';
query.Open;
query.First;
// do something with query.Fields[0] and query.Fields[1] ...
query.Close;
end;
end;
That being said, searching your materials by their descriptions is not the most efficient search option. You should search by their IDs instead. I would suggest an alternative approach to accomplish that - use your TListBox controls in virtual mode (Style=lbVirtual) instead, and store your search results in separate TStringList objects. That way you can store both IDs and Descriptions together in memory while displaying the descriptions in the UI and using the IDs in queries.
Try something more like this:
procedure TfrmMakeQuote.FormCreate(Sender: TObject);
begin
allmaterials := TStringList.Create;
selectedmaterials := TStringList.Create;
con := TFDConnection.Create(Self);
con.LoginPrompt := false;
con.Open('DriverID=SQLite;Database=C:\Users\kasio\Documents\Embarcadero\Studio\Projects\ProgramDatabase;');
query := TFDQuery.Create(con);
query.Connection := con;
query.SQL.Text := 'SELECT MtrlID, [Material Description] FROM MtrlDatabase ORDER BY MtrlID';
try
query.Open;
while not query.EOF do
begin
allmaterials.Add(query.Fields[0].AsString + '=' + query.Fields[1].AsString);
query.Next;
end;
finally
query.Close;
end;
lboMtrlList.Count = allmaterials.Count;
end;
procedure TfrmMakeQuote.FormDestroy(Sender: TObject);
begin
allmaterials.Free;
selectedmaterials.Free;
end;
// lboMtrlList OnData event handler
procedure TfrmMakeQuote.lboMtrlListData(Control: TWinControl; Index: Integer; var Data: string);
begin
Data := allmaterials.ValueFromIndex[Index];
end;
// lboSelectedMtrl OnData event handler
procedure TfrmMakeQuote.lboSelectedMtrlData(Control: TWinControl; Index: Integer; var Data: string);
begin
Data := selectedmaterials.ValueFromIndex[Index];
end;
procedure TfrmMakeQuote.lboMtrlListDblClick(Sender: TObject);
var
Idx: Integer;
begin
Idx := lboMtrlList.ItemIndex;
if Idx = -1 then Exit;
if selectedmaterials.IndexOfName(allmaterials.Names[Idx]) <> -1 then Exit;
selectedmaterials.Add(allmaterials.Strings[Idx]);
lboSelectedMtrl.Count := selectedmaterials.Count;
end;
procedure TfrmMakeQuote.performMtrlQuery;
var
i: integer;
begin
for i := 0 to selectedmaterials.Count-1 do
begin
query.SQL.Text := 'SELECT [Material Description], Price FROM MtrlDatabase' +
' WHERE MtrlID = '
+ selectedmaterials.Names[i];
query.Open;
query.First;
// do something with query.Fields[0] and query.Fields[1] ...
query.Close;
end;
end;
Lastly, if you switch to a single TCheckListBox or TListView control instead of 2 TListBox controls, you can take advantage of their ability to have checkboxes on each item, then you don't need to deal with the OnDblClick event anymore, and don't need to show two copies of your materials in your UI. The user can just check the desired items before invoking performMtrlQuery().
I would also suggest using a virtual TListView for the search results instead of a TStringGrid. The UI will look better (TStringGrid is not the best looking UI control), and you can utilize memory more efficiently (TStringGrid can be a memory hog if you have a lot of data to display).

How to change DBLookupComboBox value in Delphi?

I'm trying to change the visible text (value) of a DBLookupComboBox, using :
DBLookupComboBox1.KeyValue:=S;
and
DBLookupComboBox2.KeyValue:=X;
If the KeyValue and S are strings, then everything works fine, and I can set the value.
But when the KeyValue is an integer/Int64 (UID in the DB), and X is an Int64 variable - it doesn't work, and nothing changes.
So for an example, i need to set "Amsterdam" in DBLookupComboBox1, and 1500 in DBLookupComboBox2.
"Amsterdam" from the "City" field of the users, and 1500 as the UID.
What am I doing wrong please?
Thanks
This code is Important for you:
lkcbbArzSource.KeyValue:=2;//c(or another number);
lkcbbArzSource is a dbLookupComboBox object and you can insert only a number in this!why? because is numeric and another side of that there is a field that show a text or String of that numeric in combo.
in below sample code you can see a dbLookupComboBox how to menage and fill with Data-set and add in a grid :
rzSource.Enabled:= True;
lkcbbArzSource.Font.Size:=8;
lkcbbArzSource.Tag := V_Counter;
lkcbbArzSource.Visible:= True;
lkcbbArzSource.Enabled:= True;
lkcbbArzSource.Font.Name:='tahoma';
lkcbbArzSource.ListSource := myDsrSource;
lkcbbArzSource.KeyField := 'Code';
lkcbbArzSource.ListField := 'Des';
lkcbbArzSource.KeyValue:= IntToStr(FieldByName('P_ARZSOURCE').AsInteger);
lkcbbArzSource.OnChange := myBicLookUpChange;
SGrid2.Objects[look_col,lkcbbArzSource.Tag]:= lkcbbArzSource;
SGRID2.Objects[look_col,V_Counter] := nil;
Setting the KeyValue calls the SetKeyValue of the TDBLookupControl which in Delphi 7 appears as:
procedure TDBLookupControl.SetKeyValue(const Value: Variant);
begin
if not VarEquals(FKeyValue, Value) then
begin
FKeyValue := Value;
KeyValueChanged;
end;
end;
procedure TDBLookupComboBox.KeyValueChanged;
begin
if FLookupMode then
begin
FText := FDataField.DisplayText;
FAlignment := FDataField.Alignment;
end else
if ListActive and LocateKey then
begin
FText := FListField.DisplayText;
FAlignment := FListField.Alignment;
end else
begin
FText := '';
FAlignment := taLeftJustify;
end;
Invalidate;
end;
As you can see, your variable x is used as part of a LocateKey.
function TDBLookupControl.LocateKey: Boolean;
var
KeySave: Variant;
begin
Result := False;
try
KeySave := FKeyValue;
if not VarIsNull(FKeyValue) and FListLink.DataSet.Active and
FListLink.DataSet.Locate(FKeyFieldName, FKeyValue, []) then // << ---here
begin
Result := True;
FKeyValue := KeySave;
end;
except
end;
end;
Stepping into these procedures and functions should help you to debug your issue. All are located in the DbCtrls unit..
I have used this and solved my problem.
Use this lines of code at Form1.OnShow :
DBLookupComboBox1.ListSource.DataSet.Locate('City', 'Amsterdam', []);
DBLookupComboBox1.ListSource.DataSet.FieldByName(DBLookupComboBox1.KeyField).Value;
DBLookupComboBox2.ListSource.DataSet.Locate('UID', '1500', []);
DBLookupComboBox2.ListSource.DataSet.FieldByName(DBLookupComboBox2.KeyField).Value;

Delete and refresh a record in DBgrid where u maintain the same position

I have a small database I'm using dbgo, I have a DBgrid displaying my records, I need to know how to delete a record and refresh the database where the index arrow stays in the same position or at least go to the next? but currently my index arrow jump to start form the beginning each time I refresh !
Just keep and reset Recno
var
I:Integer;
.......
I := Ads.Recno;
Ads.Delete;
Ads.Recno := I;
an example implementation for usage with DBNavigator could be
Procedure DeleteAndKeepRecno(Ads: TCustomAdoDataset);
var
rn: Integer;
begin
rn := Ads.RecNo;
Ads.Delete;
Ads.RecNo := rn;
end;
procedure TForm4.DBNavigator1Click(Sender: TObject; Button: TNavigateBtn);
begin
if Button = nbDelete then
begin
DeleteAndKeepRecno (TCustomAdoDataset(TDBNavigator(Sender).DataSource.DataSet));
Abort;
end;
end;

Delphi TeeChart only showing one record from dataset

Using Delphi Steema TeeChart component, if I link a BarSeries to a dataset using the user interface, it shows up fine, but if I do it using code (which I need to), it's only showing one bar, even when I have several records in the database. What am I doing wrong?
Code:
var
i:Integer;
Bar:TBarSeries;
begin
ADataSet.Close;
ADataSet.LoadFromDataSet(mtbl);
ADataSet.Active := true;
ADataSet.First;
ASource.DataSet := ADataSet;
Bar := TBarSeries.Create(AChart);
Bar.Assign(Series2);
Bar.ParentChart := AChart;
Bar.DataSource := ASource;
Bar.XLabelsSource := 'Date';
Bar.YValues.ValueSource := 'Load';
for i := 0 to AChart.SeriesCount - 1 do
begin
AChart.Series[i].CheckDataSource;
end;
ADataSet is a DevExpress MemData (TdxMemData). When I run the program, the X axis is only showing one bar, the first record in the dataset, even though I have 4 records in the dataset.
This code works for me (using an Access database with fields ID and Height, I dropped a TDBChart, TADODataSet, and a TButton on a form):
procedure TForm1.Button1Click(Sender: TObject);
var
Bar : TBarSeries;
begin
ADODataSet1.Close;
ADODataSet1.ConnectionString := 'Provider=Microsoft.Jet.OLEDB.4.0;...';
Bar := TBarSeries.Create(DBChart1);
DBChart1.AddSeries(Bar);
Bar.ParentChart := DBChart1;
Bar.DataSource := ADODataSet1;
Bar.XLabelsSource := 'ID';
Bar.YValues.ValueSource := 'Height';
ADODataSet1.Active := true;
end;
Note that the Datasource should be a TTable, TQuery, or TDataSet (not a TDataSource - go figure!).
Hope this helps.
TChart refreshes the query each time you set
ADataSet.Active := true;
so, move this command to the end of your block (e.g. after you've set up the series properties).

Resources