Showing a splash image is blank some seconds - performance

In my application I have a splash image during connection to database and other initialization . It works fine to show the splash image, but there it is blank of a period.
So,
Splash image is loaded and shown a fraction of a second.
Splash image got blank 2-3 seconds.
Splash image is shown again some seconds.
Splash is closed.
Is there a clever thing to just show the image as quick as possible and remove the blank image ?
The code in the DPR-file:
Application.Initialize;
SplashForm := TSplashForm.Create(Application);
SplashForm.Show;
// Tried Splash.Update here but no difference.
SplashForm.SetPos(15);
// Init code
SplashForm.SetPos(30);
// More Init code
SplashForm.SetPos(100);
SplashForm.Close;
Application.Run;
And the splash unit:
procedure TSplashForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
procedure TSplashForm.FormCreate(Sender: TObject);
begin
pbLoading.Properties.Text := 'Loading ' + TClientConfig.Instance.AppTitle + '...';
end;
procedure TSplashForm.SetPos(aPos: Integer);
begin
pbLoading.Position := aPos;
UpDate;
end;
Regards
Roland

Your SplashForm needs to receive the WM_PAINT message in order to show itself, and that's not going to happen unless the message-pump is working.
Put Application.ProcessMessages after SplashForm.Show.

Perhaps it's not this obvious, but your code appears to show the splash screen and then immediately close it. What happens if you comment out the line that says SplashForm.Close?

I think I understand it now. My application use Bold and it takes some seconds to load the model. It is during that time the splash is blank. I managed to decrease that time a bit but I don't want to clutter the internals of Bolds SetFromModel with Application.ProcessMessages.
I think it is ok as this. But thanks for your comments. it points me in the right direction.
/Roland

Related

Delphi Paintbox Paint method is not updating image canvas[FMX]

I am working cross platform vnc project. Windows side is ok with VCL. But when i use FMX platform with same code, i having problems.
procedure TFrmScreenView.pbViewPaint(Sender: TObject);
begin
Client.DrawBitmap(pbView.Canvas);
end;
This code is updating to Paintbox Canvas for every new image packet from remote computer. This working on VCL no problem. But when i execute this project on FMX image repaint is not working. It just gets the first image and it doesn't update.
procedure TFrmScreenView.pbViewPaint(Sender: TObject; Canvas: TCanvas);
begin
Client.DrawBitmap(pbView.Canvas);
end;
Client Code:
procedure TClient.DrawBitmap(Canvas: TCanvas);
begin
if assigned(Bitmap) then // Bitmap is global variable
begin
Canvas.DrawBitmap(Bitmap,RectF(0,0,Bitmap.Width, Bitmap.Height),
RectF(0,0,Bitmap.Width, Bitmap.Height),1,True);
end;
end;
If i use timer paintbox is updateing for every image package
procedure TScreenViewFrm.Timer1Timer(Sender: TObject);
begin
pbScreenView.Repaint;
end;
I have to use Timer for repaint on my code but i dont want this and not working stable.
***Note: When i resize ScreenView form Paint box is updating. Why?
Do you have any idea?
Example Capture
https://gyazo.com/f880c2f172b0106122ea711389bf1659
After Client (I presume that is the packet receiver) has received a new image and it is stored in global Bitmap, do what you now do in the timer: pbScreenView.Repaint; (and remove the timer)
When drawing anything to a canvas in FMX you must use TCanvas.BeginScene and finish with TCanvas.EndScene, otherwise nothing will get drawn.
procedure TClient.DrawBitmap(Canvas: TCanvas);
begin
if assigned(Bitmap) then // Bitmap is global variable
begin
if Canvas.BeginScene then begin
try
Canvas.DrawBitmap(Bitmap,Bitmap.Bounds,Bitmap.Bounds,1,True);
finally
Canvas.EndScene;
end;
end;
end;
end;

How can I stop changing style in firemonkey in ScaleChanged message

I use Firemokey 10.2. In macOS, internally when you move your from from a normal display to a retina display it automatically changes style of the controls. I would like to stop this message which is TScaleChangedMessage. I would appreciate If you could help me how can I stop this message in my app. In other word how can I stop changing from normal styles to High-Resolution Styles
The easiest way would be to create a copy of the FMX.Platform.Mac unit, and modify the TFMXWindow.windowDidChangeBackingProperties method so that the message is never sent, e.g:
procedure TFMXWindow.windowDidChangeBackingProperties(notification: NSNotification);
begin
// if (Application = nil) or (Application.Terminated) then
// Exit;
// try
// TMessageManager.DefaultManager.SendMessage(nil, TScaleChangedMessage.Create(Wnd), True);
// except
// HandleException(Self);
// end;
end;
i.e. just comment out everything in it
Unfortunately if you're using Delphi 10.2 Update 1, it means you'll need to include all the FMX units in the project path (so that they're also recompiled), due to this issue:
https://quality.embarcadero.com/browse/RSP-18836
I found the solution,
changing this function
function TMacWindowHandle.GetScale: Single;
begin
//Result := Wnd.backingScaleFactor
result := 1;
end;
will solve this problem :)

Lazarus: The effect of Application.ProcessMessages

I have the following code and it correctly shows the InvoicingUnit on the message box. It also shows the value in the caption correctly.
ADItemRecord := GetInvItemRecord(txtItemCode.Text);
ShowMessage(ADItemRecord.InvoicingUnit );
lblUnit.Caption := ADItemRecord.InvoicingUni;
But the following change (i.e., removing the Message box), shows no caption. The caption is blank.
ADItemRecord := GetInvItemRecord(txtItemCode.Text);
lblUnit.Caption := ADItemRecord.InvoicingUni;
I believe this is to do with the program moving on to the next line before the data is ready in the record. So I did the following change hoping that the program will correctly complete the fetch and then move on.
ADItemRecord := GetInvItemRecord(txtItemCode.Text); //Fetch data from DB
Application.ProcessMessages; //Wait for it to complete (I think)
lblUnit.Caption := ADItemRecord.InvoicingUnit;
Application.ProcessMessages;
But the above change has no effect.
Am I correct to assume that calling Application.ProcessMessages will wait till the previous line correctly completes?
The function GetInvItemRecord is meant to fetch the record from the DB.
The program is built on Ubuntu with Postgres.
Application.ProcessMessages signals that the app can execute events from its event queue. Let's say that you have 2 buttons on a form with to onclick procedures assigned. The first procedure is a lengthly process (eg. repeat ... until true). The second button has only ShowMessage('haha').
Now, without appllication.processmessages inserted in the first procedure in the repeat statement, if you press the first button then you will not be able to press the seccond button (or anything else) until the repeat statement finishes. So the user interface is frozen.
With the application.processmessages inserted like
repeat
Application.ProcessMessages;
...
until true;
if you press the first button and then the second button the showmessage will happen! So, it is a way to fake a multithread app :-))
I hope that i was clear.
This was one of the difficult ones since I did not know what to look for. I've included my answer here so that someone else may also benefit from this.
I thought perhaps it was not the problem (delay) in calling the function to fetch data, but an issue with delayed screen painting or refreshing. Then I found these two links:
What's the difference between Refresh, Update and Repaint?
and this:
http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/Controls_TControl_Update.html
So I decided to call the Update procedure after assigning the value to the caption. That was the solution to my problem.
I still am not sure how Application.ProcessMessages works - smiles.

Delphi 6 cursor not changing

If I have a ButtonClick event which sets Cursor := crHourglass, Application.ProcessMessages, then use a TOpenDialog to choose a file, and then do something CPU-intensive, the cursor behaves differently depending on whether it is over the existing control when the Open Dialog closes. If the cursor is over the control then the cursor remains as an hourglass; if it's outside the application completely and then moved into the area while the intensive process is still taking place, the cursor remains as an arrow. One cannot click or do anything so it's confusing to the user to get an arrow but not be able to do anything with it.
Stepping through the debugger shows the Cursor is -11 everywhere it should be. Using Screen.Cursor instead of Cursor has the same effect.
Is there a solution?
procedure TMyForm.LoadButtonClick(Sender: TObject);
begin
Cursor := crHourglass;
Application.ProcessMessages;
if OpenDialog.Execute then begin
// Do something intensive
// Cursor = crHourglass here but what is displayed is different
end;
Cursor := crDefault;
end;
First, make sure to set the cursor only while the CPU-intensive operation is active. Don't change the cursor while choosing a file — you never see any other programs do that, after all.
Second, when you say Cursor in your code, you're referring to the form's property. You might wish to use Screen.Cursor instead so that your entire program displays the same cursor.
Third, there's no need to call Application.ProcessMessages. Messages are going to be processed as soon as you display a dialog box anyway, and besides, there are no particular messages you need processed.
Finally, consider protecting the cursor changes with a try-finally block so that problems in your processing don't leave the cursor in the wrong state:
if OpenDialog.Execute then begin
Screen.Cursor := crHourglass;
try
// TODO: something intensive
finally
Screen.Cursor := crDefault;
end;
end;
If the operation really uses a lot of time, consider moving it off to another thread. Then you don't have to worry about the GUI being unresponsive, and so you won't have to change the cursor in the first place.

How can I stop my application showing on the taskbar?

My application has an option for the users to run it only in the system tray, and not in the task bar. This worked fine when my application was built by Delphi 6. After switching to Delphi XE2 it no longer functions.
I've messed with it some, and I have this working for Windows 7, but when running on Windows XP I still have a problem. The application correctly hides from the task bar, and shows in the system tray. But when I create and show any additional form, the icon shows up in Windows XP.
procedure TfrmAppointment.HideWindowFromTaskbar;
var
TaskbarList: ITaskbarList;
begin
Application.MainFormOnTaskBar := False;
// Windows 7 seems to behave differently. This seems to fix it.
if (CheckWin32Version(6, 1)) then
begin
// We are in Win7, and we requested the tray.
TaskbarList := CreateComObject(CLSID_TaskbarList) as ITaskbarList;
TaskbarList.HrInit;
TaskbarList.DeleteTab(Application.Handle);
end
else
begin
// Previous code from D6 days
ShowWindow(Application.Handle, SW_HIDE);
SetWindowLong(Application.Handle, GWL_EXSTYLE, GetWindowLong(Application.Handle, GWL_EXSTYLE) or WS_EX_TOOLWINDOW);
ShowWindow(Application.Handle, SW_SHOWNOACTIVATE);
end;
end;
That code is ran if the user chooses the option to show the application in the system tray. It works fine on all versions of Windows I've tested on. On Windows XP, however, when I show any child form, the application instantly shows up in the taskbar. In Windows 7 all is fine.
Any ideas what I'm missing?
I should add that I know this is likely the same question as Hide the Main Form in a Delphi 2009 Application, however I already have the MainFormOnTaskBar being set, so that answer does not seem to apply.
[EDIT:] To be more specific, I'm adding additional information here. This application has two modes: Show in task bar, and show in system tray.
The first mode is the same as any normal application. The application exists only in the task bar. It minimizes to the task bar. It restores from the task bar.
The second mode behaves exactly the same, BUT that task bar icon instead exists in the system tray only. So, when a user minimizes the application, I intercept that message, grab the TRect for 'Shell_TrayWnd'/'TrayNotifyWnd', and call DrawAnimatedRects() to simulate the minimize to the tray. Then I hide the main form. On message from the system tray I draw the same animation rects in reverse, and make it visible again. While the form is visible it does not show in the task bar.
This all works perfectly fine in all Windows versions.
The specific issue I am having is that when any other form gets shown, Windows XP is creating the application icon in the task bar. Windows 7 does not do this. So if a Windows XP user only uses the application main form, no problems arise and both viewing modes work fine. If they open another window, the application icon appears, and stays there even after that window closes. Windows 7 does not do this, and the icon stays gone.
You should set
Application.MainFormOnTaskBar := True;
in your .dpr file and then never modify that setting.
Then, when you want to remove the main form from the taskbar you simply write
MainForm.Hide;
When you need to bring the main form out of hiding again write
MainForm.Show;
And that's it.
Naturally you'll want to show and hide your notification area icon in concert with hiding and showing the main form.
The code in HideWindowFromTaskbar is not necessary and you should remove it. When you application is in MainFormOnTaskBar equals True mode, the main form is an un-owned top-level window. And so it appears on the taskbar whenever it is visible. So you can remove the main form from the taskbar simply my hiding it.
The other forms in your application will be owned top-level windows. Typically they will be owned by your main form. By virtue of being owner, they will not appear on the taskbar.
By and large you should try hard to avoid fiddling with window styles. You can usually make your application behave the way you need without doing so. What's more, if ever you have to adjust window styles, you must do it in CreateParams. That way the window style will persist when the window gets re-created. But I re-iterate, avoid modifying window styles where you can.
The key MSDN references are:
Window Features.
The Taskbar.
Here's the smallest program I can produce that proves the point:
program MainFormHiding;
uses
Forms, StdCtrls;
var
MainForm, OtherForm: TForm;
Button: TButton;
type
TEventHandlerClass = class
class procedure ToggleMainFormVisible(Sender: TObject);
end;
class procedure TEventHandlerClass.ToggleMainFormVisible(Sender: TObject);
begin
MainForm.Visible := not MainForm.Visible;
end;
begin
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm, MainForm);
OtherForm := TForm.Create(Application);
MainForm.Caption := 'Main Form';
OtherForm.Visible := True;
OtherForm.Caption := 'Other Form';
Button := TButton.Create(OtherForm);
Button.Caption := 'Toggle';
Button.Parent := OtherForm;
Button.OnClick := TEventHandlerClass.ToggleMainFormVisible;
Application.Run;
end.
In the comments you make it clear that you want to be able to hide the taskbar window without hiding the main form. In that case I suggest that you set MainFormOnTaskbar to False. That will mean that Application.Handle will be the window associated with the taskbar button. You can then hide that window to remove it from the taskbar.
You will now need to explicitly set PopupParent for any auxiliary forms. If you want those windows to be owned by the main form, then you can set it up.
Here's my example adjusted for this scenario:
program MainFormHiding;
uses
Forms, StdCtrls, Windows;
var
MainForm, OtherForm: TForm;
Button: TButton;
type
TEventHandlerClass = class
class procedure ToggleTaskbarButton(Sender: TObject);
end;
class procedure TEventHandlerClass.ToggleTaskbarButton(Sender: TObject);
begin
if IsWindowVisible(Application.Handle) then
ShowWindow(Application.Handle, SW_HIDE)
else
ShowWindow(Application.Handle, SW_SHOW);
end;
begin
Application.MainFormOnTaskbar := False;
Application.CreateForm(TForm, MainForm);
OtherForm := TForm.Create(Application);
OtherForm.PopupParent := MainForm;
MainForm.Caption := 'Main Form';
Application.Title := MainForm.Caption;
OtherForm.Visible := True;
OtherForm.Caption := 'Other Form';
Button := TButton.Create(OtherForm);
Button.Caption := 'Toggle';
Button.Parent := OtherForm;
Button.OnClick := TEventHandlerClass.ToggleTaskbarButton;
Application.Run;
end.
Run this program and click on the toggle button. Now you will see main form and other form showing. And nothing in the taskbar. I included the toggle button to show that you can switch between your two modes of operation whilst the program is running. No need to restart it.
The key here is to make a window other than your visible forms be the window associated with the taskbar. Once you do that you can once again control taskbar presence by showing and hiding that window. In this case that window is the application window, Application.Handle. Because that's the window on the taskbar, you need to set its Title property to control its text.
I stress finally, once again, that interaction with the taskbar is best controlled with window owner and visibility. Always search for solutions using those methods rather than ITaskbarList, extended window styles etc.
Update
Hopefully the last word on the subject. As you have noticed, the code directly above has poor behaviour when the main form is minimised. When that happens, the application window is made visible again and so appears once more in the taskbar.
I'm not so sure of myself when it comes to suppressing this behaviour. The behaviour comes about because of the code in TApplication.Minimize which shows the application handle when the main form is minimized. The best solution that I have is to convert a main form minimize into a hide.
procedure WMSysCommand(var Msg: TWMSysCommand); message WM_SYSCOMMAND;
....
procedure TMainForm.WMSysCommand(var Msg: TWMSysCommand);
begin
if (Msg.CmdType and $FFF0)=SC_MINIMIZE then
begin
Hide;
exit;
end;
inherited;
end;
Or another way would be to suppress the application window show by means of an OnMinimize event handler for TApplication.
class procedure TEventHandlerClass.ApplicationMinimize(Sender: TObject);
begin
ShowWindow(Application.Handle, SW_HIDE);
end;
David's answer is correct. There were a couple minor issues with it, but I ran with it and got everything working. He posted his last update while I was figuring this out. I am posting some additional code samples here, and accepted his answer. First I assigned:
Application.OnMessage := AppMessage;
Then the procedure is as follows:
procedure TfrmAppointment.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin
// This first check decides if we are minimizing via the upper right button OR
// The context menu in the upper left hand corner of the window.
// Minimizing twice restores, so this can be a restore as well.
if ((((Msg.message = WM_NCLBUTTONDOWN) and (Msg.wParam = HTMINBUTTON)) or
((Msg.message = WM_SYSCOMMAND) and (Msg.wParam = SC_MINIMIZE))) and
(Screen.ActiveForm = Self)) then
begin
// This function is defined as (bool, bool) where the variables are:
// Param1: Mimimizing (true), Restoring (false)
// Param2: Draw animation rectangles for doing this or not
Handled := MinimizeOrRestore(Self.WindowState <> wsMinimized, True);
end
else if ((Msg.message = WM_SYSCOMMAND) and
(Msg.wParam = SC_RESTORE) and
(Screen.ActiveForm = Self)) then
begin
// Specifically, restore has been asked for
Handled := MinimizeOrRestore(False, True); // Minimize with animation
end
else if ((Msg.message = WM_SYSCOMMAND) and (Msg.wParam = SC_CLOSE)) then
begin
// The user just used the system menu to close the application
ApplicationIsClosing := True; // see below for this
end
end;
Then in my FormCloseQuery, I check for "ApplicationIsClosing" to be true. If it's FALSE, then I know the user hit the X, and I simply minimize the application calling the other function referenced here. If it's true, I allow the close.
Finally, MinimizeOnRestore grabs the TRect for the form itself, as well as the system tray and then executes DrawAnimatedRects. This doesn't work always on Vista or higher, but it doesn't error either. Next, it hides the main application window or makes it visible. It always returns true unless it encounters an error. Then it returns false.

Resources