<< Click to Display Table of Contents >> Navigation: Developer's Guide > Best Practices > N-Tier web applications > Synchronous and Asynchronous Operations |
By default, uniGUI applications are standard web applications working in asynchronous mode. It means that the server doesn't need to block a session thread while waiting for a user response coming from the client.
How to use ShowModal in this mode?
Let's use a very simple example. We will create a form for entering a person name with buttons for accepting or canceling the request. Another form will use the previous form for updating its own internal field FFullName.
unit Unit3;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, uniGUITypes, uniGUIAbstractClasses,
uniGUIClasses, uniGUIForm, uniButton, uniEdit, uniGUIBaseClasses, uniLabel;
type
TUniForm3 = class(TUniForm)
UniLabel1: TUniLabel;
edtFullName: TUniEdit;
btnOk: TUniButton;
btnCancel: TUniButton;
private
function GetFullName: string;
{ Private declarations }
public
{ Public declarations }
property FullName : string read GetFullName;
end;
function UniForm3: TUniForm3;
implementation
{$R *.dfm}
uses
MainModule, uniGUIApplication;
function UniForm3: TUniForm3;
begin
Result := TUniForm3(UniMainModule.GetFormInstance(TUniForm3));
end;
{ TUniForm3 }
function TUniForm3.GetFullName: string;
begin
if edtFullName.Text = '' then
Result := 'John Doe'
else
Result := edtFullName.Text;
end;
end.
This form will return the entered full name or 'John Doe' if the full name was empty. Its result will depend on pressing the button OK or Cancel (or closing the form). How we use it?
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, uniGUITypes, uniGUIAbstractClasses,
uniGUIClasses, uniGUIForm, uniGUIBaseClasses, uniButton;
type
TUniForm1 = class(TUniForm)
btnGetFullName: TUniButton;
procedure btnGetFullNameClick(Sender: TObject);
private
{ Private declarations }
FFullName : string;
public
{ Public declarations }
end;
function UniForm1: TUniForm1;
implementation
{$R *.dfm}
uses
MainModule, uniGUIApplication,
Unit3;
function UniForm1: TUniForm1;
begin
Result := TUniForm1(UniMainModule.GetFormInstance(TUniForm1));
end;
procedure TUniForm1.btnGetFullNameClick(Sender: TObject);
begin
UniForm3.ShowModal
(
procedure (Sender: TComponent; AResult: Integer)
begin
if TModalResult(AResult) = mrOk then
FFullName := (Sender as TUniForm3).FullName;
end
);
end;
end.
What happens here?
•We start by pressing the button GetFullName.
•As soon as we call UniForm3, an instance of the form TUniForm3 will be created and returned.
•The call to ShowModal will popup the form and it will behave like a client-side modal form (meaning that no further interaction is allowed outside of this form).
•However, server-side, the program will continue, which in this case means that it will exit the OnClick event handler.
•At this point, the server finished the previous request (that is, the OnClick event triggered client-side by the user).
•When the user finally clicks any of the buttons (Ok or Cancel) or close the form, another request will go to the server: handle the ShowModal result for that form.
•The server will identify the user session, and will execute the anonymous method which still keeps the reference to UniForm3 (standard behavior for anonymous methods).
•Once the anonymous method is executed, the form UniForm3 will be available for disposal (its lifetime being managed by the uniGUI framework).
It is always preferred to create the web application in asynchronous mode, but when migrating old VCL applications, the developer could find scenarios which couldn't be implemented or that will make the task too difficult. If necessary, uniGUI can be forced to work as a typical VCL application.
If the developer needs to reproduce the synchronous mode available in VCL applications, the property MainModule.EnableSynchronousOperations must be set to true.
How to use ShowModal in this mode?
procedure TUniForm1.btnGetFullNameClick(Sender: TObject);
begin
if UniForm3.ShowModal = mrOk then
FFullName := UniForm3.FullName;
end;
This is the same code we typically use for VCL applications. It also stops the execution after calling ShowModal until a result is returned. This kind of code requires spawning a thread in the server which will be waiting for the client request/answer. As you can expect, this kind of applications consumes more resources than a standard web application. Fortunately, uniGUI uses an internal thread-pool to minimize thread usage, but for each waiting modal window a thread will be consumed. After the modal window is closed, the waiting threads will be returned to the thread pool for future usage.
In addition to ShowModal, uniGUI allows to execute synchronous operations in other scenarios. Let's examine some demos.
There is one demo illustrating blocking modals, "BlockingModals".
\
The next code shows how each function is now a blocking call like it was in VCL.
procedure TMainForm.btnShowModalClick(Sender: TObject);
var
M : TModalResult;
begin
M := UniForm1.ShowModal;
case M of
mrOK : ShowMessage('OK');
mrCancel : ShowMessage('Cancel');
mrNone : ShowMessage('None');
end;
ShowMessage('Done, ' + UniForm1.UniEdit1.Text);
end;
procedure TMainForm.btnMessageDlgClick(Sender: TObject);
var
Res : Integer;
begin
Res := MessageDlg('Test', mtConfirmation, mbYesNoCancel);
case Res of
mrYes : ShowMessage('Yes');
mrNo : ShowMessage('No');
mrCancel : ShowMessage('Cancel');
end;
end;
procedure TMainForm.btnPromptClick(Sender: TObject);
var
sResult : string;
begin
if Prompt('Please type something?', 'Value',
mtInformation, mbYesNoCancel, sResult, True) = mrYes then
ShowMessage('Result: ' + sResult);
end;
procedure TMainForm.btnNestedCallsClick(Sender: TObject);
var
sResult : string;
begin
if UniForm1.ShowModal = mrOK then
if Prompt('Please type something?', '',
mtInformation, mbYesNoCancel, sResult, True) = mrYes then
if MessageDlg('Continue?', mtConfirmation, mbYesNoCancel) = mrYes then
ShowMessage('Result: ' + sResult + ' ' + UniForm1.UniEdit1.Text );
end;
procedure TMainForm.btnNestedForms1Click(Sender: TObject);
begin
if UniForm1.ShowModal = mrOK then
if UniForm2.ShowModal = mrOK then
if UniForm3.ShowModal = mrOK then
if UniForm4.ShowModal = mrOK then
begin
ShowMessage('All shown!');
end;
end;
procedure TMainForm.btnNestedForms2Click(Sender: TObject);
begin
if UniForm5.ShowModal() = mrOK then
ShowMessage('Completed');
end;
procedure TMainForm.btnFileUploadClick(Sender: TObject);
begin
if UniFileUpload1.Execute then
begin
ShowMessage('File upload completed.' +
^M^M'Filename: ' + UniFileUpload1.FileName +
^M^M'Temporary file is located under:' +
UniFileUpload1.CacheFile);
end;
end;
Demo "SyncClientUpdate-1" shows how to update a progress bar while executing a lengthy task.
Once the user clicks the button Start, this is the kind of code achieving the desired behavior:
begin
FCancelled := False;
UniProgressBar1.Min := 1;
UniProgressBar1.Max := MAX_FILES;
btnStart.Enabled := False;
btnCancel.Enabled := True;
// Reset Progressbar and Label
UpdateClient(0);
// Initial refresh before entering the loop.
// (Progressbar and Label will be refreshed)
UniSession.Synchronize;
N := 0;
try
for I := 1 to MAX_FILES do
begin
// Refresh the client at "X_INTERVAL" intervals
if UniSession.Synchronize(X_INTERVAL) then
UpdateClient(N);
// Check if operation is cancelled.
// (Either when Cancel button is pressed or Form is closed)
if FCancelled then Break;
// perform some tasks here
CreateDummyFile(I);
Inc(N); // +1 number of created files
end;
UpdateClient(N);
finally
btnStart.Enabled := True;
btnCancel.Enabled := False;
end;
end;
The call UniSession.Synchronize forces an immediate update. Passing an argument allows to synchronize at a controlled period (every some milliseconds).
Demo "SyncClientUpdate-3" executes a sequence of tasks with status updates and using ShowMask for avoiding user interaction with other visual controls.
The code for the Start button is:
begin
UniLabel1.Caption := '';
UniLabel2.Caption := '';
UniLabel3.Caption := '';
UniCheckBox1.Enabled := False;
UniImage1.Hide;
UniProgressBar1.Position := 0;
UniButton1.Enabled := False;
ShowMask('Level 1 in progress ..');
UniSession.Synchronize;
Sleep(1000);
UniProgressBar1.Position := 1;
UniLabel1.Caption := 'Level 1 Completed';
UniLabel1.Font.Style := [fsBold];
HideMask;
ShowMask('Level 2 in progress ....');
UniSession.Synchronize;
Sleep(1000);
UniProgressBar1.Position := 2;
UniLabel2.Caption := 'Level 2 Completed';
UniLabel2.Font.Style := [fsBold];
HideMask;
ShowMask('Level 3 in progress ......');
UniSession.Synchronize;
Sleep(1000);
UniProgressBar1.Position := 3;
UniLabel3.Caption := 'Level 3 Completed';
UniLabel3.Font.Style := [fsBold];
HideMask;
ShowMask('Last level in progress ........');
UniSession.Synchronize;
Sleep(1000);
UniProgressBar1.Position := 4;
UniImage1.Show;
UniButton1.Enabled := True;
UniCheckBox1.Enabled := True;
HideMask;
end;
Demo "SyncClientUpdate-5" shows the difference between Synchronize(Wait : boolean) and Synchronize(Delay : integer).
Pressing the button "Start No Wait" will move the UniPanel1 to the bottom right corner 5 pixels every 200 milliseconds (showing the movement).
Pressing the button "Start" will accomplish the same, but each step will happen only when some client-side event is detected (for example, pressing the dummy button "Press!" or resizing the form.
The code is:
procedure TMainForm.btnStartNoWaitClick(Sender: TObject);
begin
btnStart.Enabled := False;
btnStartNoWait.Enabled := False;
while UniPanel1.Top < Self.Height - 150 do
begin
if UniSession.Synchronize(200) then // wait 200 ms
begin
UniPanel1.Top := UniPanel1.Top + 5;
UniPanel1.Left := UniPanel1.Left + 5;
end
else
Sleep(10);
end;
UniPanel1.Top := 5;
UniPanel1.Left := 5;
btnStart.Enabled := True;
btnStartNoWait.Enabled := True;
end;
procedure TMainForm.btnStartClick(Sender: TObject);
begin
FStopped := False;
btnStart.Enabled := False;
btnPress.Enabled := True;
btnStop.Enabled := True;
while not FStopped do
begin
UniSession.Synchronize(True); // wait until next event
UniPanel1.Top := UniPanel1.Top + 5;
UniPanel1.Left := UniPanel1.Left + 5;
end;
UniPanel1.Top := 5;
UniPanel1.Left := 5;
btnStart.Enabled := True;
btnPress.Enabled := False;
btnStop.Enabled := False;
end;
procedure TMainForm.btnPressClick(Sender: TObject);
begin
// Dummy Event generator
end;