In AllPagesExample.iss example file there's this part:
function PrepareToInstall(var NeedsRestart: Boolean): String;
begin
if SuppressibleMsgBox('Do you want to stop Setup at the Preparing To Install wizard page?', mbConfirmation, MB_YESNO, IDNO) = IDYES then
Result := 'Stopped by user';
end;
If PrepareToTinstall is an event function and I don't call it myself, how can I pass NeedsRestart parameter to it?
The NeedsRestart parameter is passed by a reference (the var keyword). So its value is returned from the function similarly to the return value of the function itself (Result).
As the documentation says:
Return a non empty string to instruct Setup to stop at the Preparing to Install wizard page, showing the returned string as the error message. Set NeedsRestart to True (and return a non empty string) if a restart is needed. If Setup is stopped this way, it will exit with a dedicated exit code as described in Setup Exit Codes.
So, what you want to do, is to assign a value to the parameter and Inno Setup will act accordingly once the event function exits.
function PrepareToInstall(var NeedsRestart: Boolean): String;
begin
Result := '';
if DoWeNeedRestartBeforeInstalling then
begin
Result := 'You need to restart your machine before installing';
NeedsRestart := True;
end;
end;
Related
I am trying to prompt the user to restart the computer after enabling .NET 3.5, which is necessary for MSSQLServer.
Example:
function PrepareToInstall(var NeedsRestart: Boolean): String;
begin
Log('PrepareToInstall() called');
GetWindowsVersionEx(Version);
if (Version.Major = 10) then begin
NeedsRestart :=True;
end
end;
Am i missing something?
NeedsRestart parameter is considered only if PrepareToInstall event function actually aborts an installation (by returning a non-empty string). What you probably do not want to do. In other words, you are abusing PrepareToInstall event function (and incorrectly anyway).
Use NeedRestart event function instead:
[Code]
function NeedRestart(): Boolean;
var
Version: TWindowsVersion;
begin
GetWindowsVersionEx(Version);
Result := (Version.Major >= 10);
end;
I have a Delphi (Windows) application created using Delphi 10 that has some blocking calls that I would like to convert to using threads. Unfortunately for me, these are not procedures, but functions. (And information on how to return function results from threads appears to be much more limited.) I am trying to familiarize myself with the OmniThreadLibrary, since it seems to be the most flexible and best supported threading library for Delphi, but I'm having trouble with understanding how to do this.
I have been able to get the various OmniThreadLibrary routines to work well with procedures, but when I try to set up a function, I get an error about capturing the result. When I use OmniThreadLibrary's Future example as a starting point, I can get the function to work, but I can't figure out how to connect to the event monitor, how to send messages from the task, etc. So, it seems as if I'm overlooking something no matter which way I try to solve this problem.
Currently, my program does something like this:
If myPing(IPAddress) then
begin
//Do other things hereā¦
end;
Because myPing is blocking, and I actually need it to wait until myPing returns true before processing further, the application gets sluggish during this process. I'd like to put the myPing call in a thread, which would solve the sluggishness problem, but I can't figure out how to do that in the form of a function using OmniThreadLibrary. (Unless I use a future, in which case I can't figure out how to connect to the Event Monitor.)
Edit 1: Since my original post, I have made a little progress. I was able to connect the Event Monitor to the Future by adding Parallel.TaskConfig.MonitorWith(Form1.OmniEventMonitor1) to my code, right after the function. However, I still can't figure out how to send messages to that event monitor from within the Future function.
Edit 2: I now have some sample code. My first attempt was similar to this:
function myPing(HostName: string): IOmniFuture<boolean>;
begin
Result := Parallel.Future<boolean>(function: boolean
begin
Result := False;
//Do actual ping here... Set Result := True if successful.
end
);
end;
The basic function worked, but did not allow me to send any messages to the TOmniEventMonitor. I was able to get that part working by changing the code to this:
function myPing(HostName: string): IOmniFuture<boolean>;
begin
Result := Parallel.Future<boolean>(function: boolean
begin
Result := False;
//Do actual ping here... Set Result := True if successful.
end,
Parallel.TaskConfig.MonitorWith(Form1.OmniEventMonitor1)
);
end;
Now, I can successfully monitor the OnTaskTerminated event, but I still can't send messages to the Event Monitor from the task. By changing my code once again, I can access the task itself and send messages using task.Comm.Send(), but the messages don't reach the EventMonitor:
function myPing(HostName: string): IOmniFuture<boolean>;
begin
Result := Parallel.Future<boolean>(function(const task: IOmniTask): boolean
begin
Result := False;
//Do actual ping here... Set Result := True if successful.
task.Comm.Send(0,'Test 1');
end,
Parallel.TaskConfig.MonitorWith(Form1.OmniEventMonitor1)
);
end;
Here's a simple example on how to retrieve the function result from the async call. It does not use an "OmniEventMonitor" but instead calls a function once the async call returns ("Ping" is defined in PingU.pas, but not of importance here):
unit MainFormU;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Menus;
type
TPingResultEvent = procedure (const bResult: Boolean) of object;
TOnTerminateTestForm = class(TForm)
LogMemo: TMemo;
MainMenu: TMainMenu;
PingMenu: TMenuItem;
procedure PingMenuClick(Sender: TObject);
private
procedure BackgroundPing (const sServer: String;
const OnResult: TPingResultEvent);
procedure PingResult (const bResult: Boolean);
public
{ Public declarations }
end;
var
OnTerminateTestForm: TOnTerminateTestForm;
implementation
{$R *.dfm}
uses PingU, OtlParallel, OtlTaskControl;
procedure TOnTerminateTestForm.PingMenuClick (Sender: TObject);
var
sServer : String;
begin
if (InputQuery ('Ping computer', 'Computer name:', sServer)) then
if (sServer <> '') then
begin
PingMenu.Enabled := false;
LogMemo.Lines.Add (Format ('Pinging %s',[sServer]));
BackgroundPing (sServer, PingResult);
end; { if }
end; { TOnTerminateTestForm.PingMenuClick }
procedure TOnTerminateTestForm.BackgroundPing (const sServer: String;
const OnResult: TPingResultEvent);
var
bResult : Boolean;
begin
Parallel.Async (
procedure
begin
bResult := Ping (sServer);
end,
Parallel.TaskConfig.OnTerminated(
procedure (const task: IOmniTaskControl)
begin
// executed in main thread after the async has finished
if Assigned (OnResult) then
OnResult (bResult);
end
)
);
end; { TOnTerminateTestForm.BackgroundPing }
procedure TOnTerminateTestForm.PingResult (const bResult: Boolean);
begin
PingMenu.Enabled := true;
LogMemo.Lines.Add ('Ping result = ' + BoolToStr (bResult, true));
end; { TOnTerminateTestForm.PingResult }
end.
Code source: Get a function result asynchronously in Delphi using Omni Thread Library
While using android app developed using java or android studio I discovered that the message dialog prompt stay execution of the next line until the dialog prompt is answered. I have been trying to do this using TDialogService.MessageDialog(AMessage, ADialogType, AButtons, ADefaultButton, 0, procedurexyz). While the prompt is on display, the next line is executed making the prompt useless as the user was suppose to decide the next action. I need help from anyone to get an active block message dialog prompt.
Embarcadero documentation says, that on Android platform you can use only non-blocking calls for ShowMessage, MessageDialog, myForm.ShowModal and etc.
To get "blocking" mode you can use workaround, like this:
function myMessageDialog(const AMessage: string; const ADialogType: TMsgDlgType;
const AButtons: TMsgDlgButtons; const ADefaultButton: TMsgDlgBtn): Integer;
var
mr: TModalResult;
begin
mr:=mrNone;
// standart call with callback anonimous method
TDialogService.MessageDialog(AMessage, ADialogType, AButtons,
ADefaultButton, 0,
procedure (const AResult: TModalResult)
begin
mr:=AResult
end);
while mr = mrNone do // wait for modal result
Application.ProcessMessages;
Result:=mr;
end;
After your suggestion #Kami I came up with this and its working very well for me, though am open to suggestions or additions.
function MsgBox(const AMessage: string; const ADialogType: TMsgDlgType; const AButtons: TMsgDlgButtons;
const ADefaultButton: TMsgDlgBtn ): Integer;
var
myAns: Integer;
IsDisplayed: Boolean;
begin
myAns := -1;
IsDisplayed := False;
While myAns = -1 do
Begin
if IsDisplayed = False then
TDialogService.MessageDialog(AMessage, ADialogType, AButtons, ADefaultButton, 0,
procedure (const AResult: TModalResult)
begin
myAns := AResult;
IsDisplayed := True;
end);
IsDisplayed := True;
Application.ProcessMessages;
End;
Result := myAns;
end;
I am trying to call a function from another unit/class which would take some time in performing the task and would return a string value. I couldn't find a good reference something similar to C# async-await like simple approach in Delphi. Using Omni Thread library seems a good idea for me.
A simple example will be a great start for me.
Sample approach:
procedure TForm1.button1Click(Sender: TObject);
begin
// notify before starting the task
memo1.Lines.Add('calling a asynchronous function..');
// call to the function that takes some time and returns a string value
memo1.Lines.Add(GetMagicString);
// notify that the task has been completed
memo1.Lines.Add('Results fetched successfully.');
end;
Here, the function GetMagicString should process the result asynchronously. Once the result is obtained, only then the program should notify that the task has been completed. By the way, I'm using Delphi-XE.
Edit1:
Here is what I tried. But I am still unable to figure out the proper way to make the job done. The problem is how to return the value.
.....
private
ResultValue: IOmniFuture<string>;
.........
.....
function TForm1.FutureGet: string;
begin
Sleep(3000);
Result := 'my sample magic string response ' + IntToStr(Random(9999));
end;
procedure TForm1.FutureGetTerminated;
begin
// This code fired when the task is completed
memo1.Lines.Add(ResultValue.Value);
end;
function TForm1.GetMagicString: string;
begin
ResultValue := Parallel.Future<string>(FutureGet,
Parallel.TaskConfig.OnTerminated(FutureGetTerminated));
end;
Here, using Result := ResultValue.Value feezes the UI.
Edit2
I made changes as per the answer provided.
MainForm Code:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Unit2;
type
TForm1 = class(TForm)
memo1: TMemo;
button1: TButton;
procedure button1Click(Sender: TObject);
private
FOnStringReceived: TMyEvent;
procedure StringReceived(const AValue: string);
property OnStringReceived: TMyEvent read FOnStringReceived write FOnStringReceived;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.button1Click(Sender: TObject);
var
MyObject: TMyClass;
begin
// notify before starting the task
memo1.Lines.Add('calling a asynchronous function..');
// call to the function that takes some time and returns a string value
MyObject := TMyClass.Create;
OnStringReceived := StringReceived;
try
MyObject.GetMagicStringInBackground(OnStringReceived);
finally
MyObject.Free;
end;
end;
procedure TForm1.StringReceived(const AValue: string);
begin
memo1.Lines.Add(AValue);
// notify that the task has been completed
memo1.Lines.Add('Results fetched successfully.');
end;
end.
Other Unit Code:
unit Unit2;
interface
uses SysUtils, OtlTask, OtlParallel, OtlTaskControl;
type
TMyEvent = procedure(const aValue: string) of object;
type
TMyClass = class
private
FOnStringReceived: TMyEvent;
function GetMagicString: string;
public
procedure GetMagicStringInBackground(AEvent: TMyEvent);
end;
implementation
{ TMyClass }
function TMyClass.GetMagicString: string;
begin
Sleep(3000);
Result := 'my sample magic string response ' + IntToStr(Random(9999));
end;
procedure TMyClass.GetMagicStringInBackground(AEvent: TMyEvent);
var
theFunctionResult: string;
begin
Parallel.Async(
procedure
begin
theFunctionResult := GetMagicString;
end,
Parallel.TaskConfig.OnTerminated(
procedure (const task: IOmniTaskControl)
begin
if Assigned(AEvent) then
AEvent(theFunctionResult);
end)
);
end;
end.
Yes, the code works as expected. I just want to know if this is the best way of doing what I really want to perform.
You would normally use a future in a case where you want something executed in the background but still need the result in the same execution path. It basically lets you do something in the background while doing another thing in the main thread and you can then use the result of the background thread.
What you need to use is the Async abstraction that TLama linked to:
In your case it would be:
procedure TForm1.DoSomething;
var
theFunctionResult: string;
begin
memo1.Lines.Add('calling a asynchronous function..');
Parallel.Async(
procedure
begin
// executed in background thread
theFunctionResult := GetMagicString;
end,
procedure
begin
// executed in main thread after the async has finished
memo1.Lines.Add(theFunctionResult);
// notify that the task has been completed
memo1.Lines.Add('Results fetched successfully.');
end
);
end;
This is a bit messy but you should get the idea. You need to make sure that your async code is completed before you destroy the form that owns this code (TForm1).
If you want to try and setup a system that will call an event when the code completes then you can do something like this:
type
TMyEvent = procedure(const aValue: string) of object;
procedure GetMagicStringInBackground(AEvent: TMyEvent);
var
theFunctionResult: string;
begin
Parallel.Async(
procedure
begin
// executed in background thread
theFunctionResult := GetMagicString;
end,
Parallel.TaskConfig.OnTerminated(
procedure (const task: IOmniTaskControl)
begin
// executed in main thread after the async has finished
if Assigned(AEvent) then
AEvent(theFunctionResult );
end
)
);
end;
You can then put the threaded code in the GetMagicString unit and just call the method above from your form passing in an event that will get called when it completes.
I have tried to upload +100 files to azure with Delphi. However, the calls block the main thread, so I want to do this with a async call or with a background thread.
This is what I do now (like explained here):
procedure TCloudManager.UploadTask(const input: TOmniValue;
var output: TOmniValue);
var
FileTask:TFileTask;
begin
FileTask := input.AsRecord<TFileTask>;
Upload(FileTask.BaseFolder, FileTask.LocalFile, FileTask.CloudFile);
end;
function TCloudManager.MassiveUpload(const BaseFolder: String;
Files: TDictionary<String, String>): TStringList;
var
pipeline: IOmniPipeline;
FileInfo : TPair<String,String>;
FileTask:TFileTask;
begin
// set up pipeline
pipeline := Parallel.Pipeline
.Stage(UploadTask)
.NumTasks(Environment.Process.Affinity.Count * 2)
.Run;
// insert URLs to be retrieved
for FileInfo in Files do
begin
FileTask.LocalFile := FileInfo.Key;
FileTask.CloudFile := FileInfo.Value;
FileTask.BaseFolder := BaseFolder;
pipeline.Input.Add(TOmniValue.FromRecord(FileTask));
end;//for
pipeline.Input.CompleteAdding;
// wait for pipeline to complete
pipeline.WaitFor(INFINITE);
end;
However this block too (why? I don't understand).
This blocks because you are calling WaitFor which waits for all pipeline stages to finish their work. During that wait, the GUI is blocked.
A proper way to do it is
Store interface returned from Parallel.Pipeline in a global storage (for example in a TCloudManager field).
Schedule work to the pipeline.
Don't WaitFor end but assign OnStop handler and do whatever cleanup you need here (don't forget to nil out the global storage holding the pipeline interface).
To do step 3 you'll need fresh OmniThreadLibrary from the SVN because I just added this functionality :)
procedure TCloudManager.MassiveUpload(const BaseFolder: String;
Files: TDictionary<String, String>);
var
FileInfo : TPair<String,String>;
FileTask:TFileTask;
begin
// set up pipeline
FPipeline := Parallel.Pipeline
.Stage(UploadTask)
.NumTasks(Environment.Process.Affinity.Count * 2)
.OnStop(
procedure begin
ShowMessage('All done');
FPipeline := nil;
end)
.Run;
// insert URLs to be retrieved
for FileInfo in Files do
begin
FileTask.LocalFile := FileInfo.Key;
FileTask.CloudFile := FileInfo.Value;
FileTask.BaseFolder := BaseFolder;
FPipeline.Input.Add(TOmniValue.FromRecord(FileTask));
end;//for
FPipeline.Input.CompleteAdding;
end;
Delphi has a .NET variant, right? Were you aware there's a .NET Managed API for the Azure Storage Service?
The CloudBlockBlob class has an async variant for upload/download, etc.
http://msdn.microsoft.com/en-us/library/windowsazure/microsoft.windowsazure.storageclient.cloudblockblob_methods.aspx