Inno Setup: Combo box closing immediately on drop-down (which starts an application) - combobox

I am getting a list of Application Pools and then populating the combo box with the names of app pools. The problem is that when when the OnDropDown event is called the combo box opens for a fraction of a second and then closes straight away. It does not remain "dropped down". I do see all the app pools in the combo box. Here is my code:
function GetApplicationPoolList() : TArrayOfString;
var
i, nExitCode: Integer;
sFileLines: TArrayOfString;
sTempFileName, sListAppPoolCmd: String;
begin
sListAppPoolCmd := ExpandConstant('{sys}') + '\inetsrv\appcmd list apppool /text:name';
sTempFileName := ExpandConstant('{tmp}') + '\appPoolList.txt';
if not ExecAppCmd(Format('%s > %s',[sListAppPoolCmd, sTempFileName]), nExitCode) then begin
MsgBox('Could not get app pools', mbError, MB_OK);
end else begin
LoadStringsFromFile(sTempFileName, sFileLines);
end
Result := sFileLines;
end;
// ==============================================
procedure OnAppPoolComboBoxDropDown(Sender: TObject);
var
sAppPoolList: TArrayOfString;
i: Integer;
begin
// Clear existing
appPoolComboBox.Items.Clear;
// Populate the combo box with the application pools
sAppPoolList := GetApplicationPoolList;
For i := 0 to GetArrayLength (sAppPoolList) - 1 do
begin
// ComboBox with Application Pool Names
appPoolComboBox.Items.Add(sAppPoolList[i]);
end;
appPoolComboBox.ItemIndex := 0;
end;
function ExecAppCmd(params :String; nExitCode: Integer) :Boolean;
var
execSuccessfully :Boolean;
resultCode :Integer;
begin
execSuccessfully := Exec('cmd.exe', '/c ' + '' + ' ' + params, '', SW_HIDE, ewWaitUntilTerminated, resultCode);
nExitCode := resultCode;
Result := execSuccessfully and (resultCode = 0);
end;
I am not sure what is happening here. Any advice is appreciated.
EDIT: ExecAppCmd seems to be the issue, commenting it out makes the combo box behave normally... Though not sure why

The drop-down closes probably because the combo box loses focus momentarily as the application is starting.
I'd say it's a bad practice to call an application on drop-down, as it takes time and it ruins a user experience. Populate the combo box earlier, like from CurPageChanged.

Related

Drag & Drop Component Suite: Drop files and if path isn't exists get data from file

I use the Drag and Drop Component Suite for Delphi.
I try to create a drag & drop area which accepts files (ie, from Windows Explorer) and data (ie, from Outlook attachments). So, I use the demo (CombatTargetDemo) to learn how it works, and after this I create a wrapper class which creates a TDropComboTarget object:
constructor TDragAndDrop.Create( vpntOwner: TWinControl);
begin
fpntDragAndDrop := TDropComboTarget.Create(vpntOwner);
fpntDragAndDrop.Name := 'DropComboTarget_'+vpntOwner.Name;
fpntDragAndDrop.DragTypes := [dtCopy, dtLink];
fpntDragAndDrop.OnDrop := DropFiles;
fpntDragAndDrop.Target := vpntOwner;
fpntDragAndDrop.Formats := [mfFile, mfData];
end;
procedure TDragAndDrop.DropFiles(Sender: TObject; ShiftState: TShiftState; Point: TPoint; var Effect: Integer);
var
intCnt: Integer;
pntStream: TStream;
strFileName: String;
strDragAndDropFile: String;
begin
try
fstlDroppedFilePaths.Clear;
fstlDroppedFilePaths.Assign(fpntDragAndDrop.Files);
for intCnt := 0 to fpntDragAndDrop.Data.Count-1 do begin
strFileName := fpntDragAndDrop.Data.Names[intCnt];
if (strFileName = '') then begin
strFileName := IntToStr(intCnt)+'_'+FormatDateTime('yyyymmddhhnnss', Now())+'.dat';
end;
strDragAndDropFile := GetDragAndDropSavePath+strFileName;
pntStream := TFileStream.Create(strDragAndDropFile, fmCreate);
try
pntStream.CopyFrom(fpntDragAndDrop.Data[intCnt], fpntDragAndDrop.Data[intCnt].Size);
finally
pntStream.Free;
end;
if FileExists(strDragAndDropFile, false) then begin
fstlDroppedFilePaths.Add(strDragAndDropFile);
end;
end;
except
end;
end;
First of all, the code works.
If I drop a Windows Explorer file on the area:
fpntDragAndDrop.Files.Count is 1 (contains the path+name from file)
fpntDragAndDrop.Data.Count is 1 (contains the file as a stream)
If I drop a file from Outlook on the area:
fpntDragAndDrop.Files.Count is 0 (contains nothing)
fpntDragAndDrop.Data.Count is 1 (contains the file as a stream)
Now my problem:
If I drop very large files from Windows Explorer, the component does the following:
Read the file header and add an item to fpntDragAndDrop.Files
Create a TMemoryStream and try to load the data from the file into the stream
Step 1 is perfect, but on step 2 I get an exception because of insufficient memory.
My solution:
I want that the component does Step 1. If Step 1 gives a result, then the component should skip Step 2. After this, the variables in the DropFiles procedure should have the following values:
If I drop a Windows Explorer file on the area:
fpntDragAndDrop.Files.Count is 1 (contaims the path+name from the file)
fpntDragAndDrop.Data.Count is 0 (No memory stream is loaded)
If I drop a file from Outlook on the area:
fpntDragAndDrop.Files.Count is 0 (comtains nothing)
fpntDragAndDrop.Data.Count is 1 (contains the file as a stream)
Does somebody have an idea? Or maybe the component has a setting for that?
I'm not overly familiar with this suite, but just browsing through its source, I think you can use the OnAcceptFormat event to reject formats you don't want on a per-drop basis.
So, even though you have enabled drops of mfData doesn't mean you have to actually accept a dropped stream (TDataStreamDataFormat) if a file path (TFileDataFormat or TFileMapDataFormat) is available. So, query the fpntDragAndDrop.DataObject to see what formats it actually holds, such as by passing it to the HasValidFormats() method of the various formats in the fpntDragAndDrop.DataFormats property.
For example:
fpntDragAndDrop.OnAcceptFormat := AcceptStreams;
...
procedure TDragAndDrop.AcceptStreams(Sender: TObject;
const DataFormat: TCustomDataFormat; var Accept: boolean);
var
Fmt: TCustomDataFormat;
i: Integer;
begin
if DataFormat is TDataStreamDataFormat then
begin
// FYI, TFileDataFormat should be in DataFormats[0],
// and TFileMapDataFormat should be in DataFormats[5],
// if you want to avoid this loop...
for i := 0 to fpntDragAndDrop.DataFormats.Count-1 do
begin
Fmt := fpntDragAndDrop.DataFormats[i];
if (Fmt <> DataFormat) and ((Fmt is TFileDataFormat) or (Fmt is TFileMapDataFormat)) then
begin
if Fmt.HasValidFormats(fpntDragAndDrop.DataObject) then
begin
Accept := False;
Exit;
end;
end;
end;
end;
Accept := True; // should already be True by default...
end;

Connect database table to StringGrid with LiveBindings via code

I want to use LiveBindings to connect a database table to a StringGrid, but I don't want to use the LiveBindings Designer, I want to do it manually via code. The documentation is in my opinion nearly not existing, which makes it more complicated than it should be.
I created a FMX application with my Delphi 10.3 and this is the code I wrote to do all I need:
procedure TForm_LiveBindings.CornerButton_Click(Sender: TObject);
var
aLinkTableToDataSource: TLinkGridToDataSource;
aConnection: TADOConnection;
aQuery: TADOQuery;
aBindSource: TBindSourceDB;
begin
aConnection:= TADOConnection.Create(self);
aQuery:= TADOQuery.Create(self);
aBindSource:= TBindSourceDB.Create(self);
aLinkTableToDataSource:= TLinkGridToDataSource.Create(self);
// Connection is set up here
aQuery.Connection := aConnection;
aQuery.SQL.Text := 'SELECT * FROM TestTable';
aQuery.Active := True;
aBindSource.DataSource.DataSet := aQuery;
aBindSource.DataSource.AutoEdit := True;
aBindSource.DataSource.Enabled := True;
aLinkTableToDataSource.DataSource := aBindSource;
aLinkTableToDataSource.GridControl := StringGrid1;
end;
The result: my StringGrid shows the table headers, but the rows stay empty. Which means the connection between the table and the string grid is existing, the columns have the correct header, but the content is not shown. So where did I go wrong?
Another question: is the StringGrid a good choice for displaying my database table or are there better solutions?
Thank a lot for your answers!
The example below shows all the code necessary to set up and populate a TStringGrid and a TGrid
using Live Bindings. It uses a TClientDataSet as the dataset so that it is completely self-
contained.
A bit of experimenting should satisfy you that setting up Live Bindings in code is actually
quite simple, but sensitive to the order of steps. Far more so than using VCL and traditional db-aware controls, Live Bindings seems require connecting up exactly the right things in the right way for it to work correctly. Note that unlike your code, my code does not touch the BindSorce's Datasource property, because it just isn't necessary.
type
TForm2 = class(TForm)
ClientDataSet1: TClientDataSet;
Grid1: TGrid;
StringGrid1: TStringGrid;
procedure FormCreate(Sender: TObject);
private
public
end;
[...]
procedure TForm2.FormCreate(Sender: TObject);
var
AField : TField;
BindSourceDB1 : TBindSourceDB;
LinkGridToDataSourceBindSourceDB1 : TLinkGridToDataSource;
LinkGridToDataSourceBindSourceDB2 : TLinkGridToDataSource;
begin
AField := TIntegerField.Create(Self);
AField.FieldName := 'ID';
AField.FieldKind := fkData;
AField.DataSet := ClientDataSet1;
AField := TStringField.Create(Self);
AField.FieldName := 'Name';
AField.Size := 20;
AField.FieldKind := fkData;
AField.DataSet := ClientDataSet1;
BindSourceDB1 := TBindSourceDB.Create(Self);
BindSourceDB1.DataSet := ClientDataSet1;
LinkGridToDataSourceBindSourceDB1 := TLinkGridToDataSource.Create(Self);
LinkGridToDataSourceBindSourceDB1.DataSource := BindSourceDB1;
LinkGridToDataSourceBindSourceDB1.GridControl := Grid1;
LinkGridToDataSourceBindSourceDB2 := TLinkGridToDataSource.Create(Self);
LinkGridToDataSourceBindSourceDB2.DataSource := BindSourceDB1;
LinkGridToDataSourceBindSourceDB2.GridControl := StringGrid1;
ClientDataSet1.IndexFieldNames := 'ID';
ClientDataSet1.CreateDataSet;
ClientDataSet1.InsertRecord([1, 'AName']);
ClientDataSet1.InsertRecord([2, 'AnotherName']);
ClientDataSet1.InsertRecord([3, 'ThirdName']);
ClientDataSet1.InsertRecord([4, 'FourthName']);
ClientDataSet1.InsertRecord([5, 'FifthName']);
ClientDataSet1.First;
end;
Answering late, but maybe it helps someone. LinkGrid must be Activate by
aLinkTableToDataSource.Active:= True;

How do I create a checkbox on Ready page

I created a code which plays a slideshow or plays a background video when installing my program using Inno Setup.
But I want to add a checkbox to the Background Option selection wizard page (CurPageID=wpReady) that can disable the background video/slideshow from playing.
When the Checkbox I prefer to add is checked, I want it to stop playing background slideshow or video and only to show the installing progress page (CurPageID=wpInstalling).
I wrote this but the compiler keeps saying
Line 1053, Column 3, Identifier Expected
The script I wrote:
var
NoBackgroundCheckBox: TNewCheckBox;
procedure NoBackgroundCheckBoxClick(Sender: TObject);
begin
if NoBackgroundCheckBox.Checked then
begin
with WizardForm do
begin
FWAdd:=False
end else begin
with WizardForm do
begin
FWAdd:=True
end;
end;
with NoBackgroundCheckBox do
begin
Name := 'NoBackgroundCheckBox';
Parent := WizardForm;
Left := ScaleX(560);
Top := ScaleY(115);
Width := ScaleX(90);
Height := ScaleY(14);
Alignment := taLeftJustify;
Caption := 'No Background Option';
OnClick := #NoBackgroundCheckBoxClick;
end;
NoBackgroundCheckBox.TabOrder := 3;
end;
Thanks in advance.
Create the checkbox in the InitializeWizard. And test its state in the NextButtonClick(wpReady), to decide if to start the playback or not. Alternatively you can also use CurStepChanged(ssInstall).
var
NoBackgroundCheckBox: TNewCheckBox;
procedure InitializeWizard();
begin
{ shrink the "Ready" memo to make room for the checkbox }
WizardForm.ReadyMemo.Height := WizardForm.ReadyMemo.Height - ScaleY(24);
{ create the checkbox }
NoBackgroundCheckBox := TNewCheckBox.Create(WizardForm);
with NoBackgroundCheckBox do
begin
Parent := WizardForm.ReadyMemo.Parent;
Left := WizardForm.ReadyMemo.Left;
Top := WizardForm.ReadyMemo.Top + WizardForm.ReadyMemo.Height + ScaleY(8);
Height := ScaleY(Height);
Width := WizardForm.ReadyMemo.Width;
Caption := 'No Background Option';
end;
end;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
{ Next button was just clicked on the "Ready" page }
if CurPageID = wpReady then
begin
{ is the checkbox checked? }
if NoBackgroundCheckBox.Checked then
begin
Log('NoBackgroundCheckBox is checked, won''t play anything');
end
else
begin
{ the checkbox is not checked, here call your function to start the playback }
Log('NoBackgroundCheckBox is not checked, will play');
end;
end;
Result := True;
end;

What was picked within a columns.picklist?

I wasted a bit of time trying to work out something I figured would be simple.
I've got a database with multiple tables (MySQL). One table containing "Components" and another containing "Products". Products are built using Components, for example; Product ABC might be made up of 3 x Screws, 4 x bolts, 1 kilogram of fresh air... etc! Am I making sense so far?
The components are displayed in a DBGrid. If the user makes a mistake and wants to add another "Component" to a "Product" a Picklist appears listing all Components (from a different table) for them to select from.
Now, here's my problem! When something is selected from the column[i].picklist (this is part of a DBGrid) how do I know what was selected. I thought there would be an event fired, but there doesn't seem to be.
I need to know which item was selected so I can retrieve an appropriate description for the next field.
There are 3 fields, they are COMPONENT, DESCRIPTION, QUANTITY. Only COMPONENT and QUANTITY can be edited by the user.
I hope I'm making some sense here.
Here is the code I'm using now (as messy as it is);
procedure TForm1.CompletePolesSourceStateChange(Sender: TObject);
var
loop: Integer;
Tmp: string;
begin
case CompletePolesSource.state of
dsInsert:
begin
CompVals.Clear; // Is a tstringlist created elsewhere
CompVals.Delimiter := '|';
CompVals.QuoteChar := '"';
PoleComponentsGrid.Columns[0].readonly := false; // Is readonly when not in DSInsert
PoleComponentsGrid.Columns[0].PickList.Clear; // Clear other crap
{
Now add Parts to a Name / Value list (CODE / DESCRIPTION) so I can later
get the description without looking it up in the other table.
}
for loop := 1 to componentstable.RecordCount do // Get CODE from other table
begin
componentstable.RecNo := loop;
tmp := componentstable.Fieldbyname('CODE').asstring + '=' + componentstable.Fieldbyname('ITEM').asstring;
CompVals.Add(tmp);
PoleComponentsGrid.Columns.Items[0].PickList.Add(tmp);
end;
PoleComponentsGrid.Columns.Items[0].readonly := true;
end;
end;
end;
This will show the data of the selected rows of the DBGrid
procedure TFrmPrincipal.btnShowSelectedRowsClick(Sender: TObject);
var
i: Integer;
aux: string;
begin
for i := 0 to DBGrid1.SelectedRows.Count - 1 do
begin
ClientDataSet1.GotoBookmark(pointer(DBGrid1.SelectedRows.Items[i]));
aux := aux + IntToStr(ClientDataSet1.RecNo) + ' - ' +
ClientDataSet1.FieldByName('CUSTOMER').AsString + #13;
end;
ShowMessage(‘Selected Rows: ‘ + #13 + aux);
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