OK, so who hasn’t done this a million times – adding a splash screen to a Delphi application. I’ve seen any number of “easy” ways to do this but during a session at Tech Ed ’08 this week I saw the latest in splash screen technology from Microsoft: A SplashScreen API and build action supported by WPF 3.5 and VS 2008.
I immediately thought of my Application psuedo-class and object in Deltics.Forms.
The basic requirements of a splash screen are:
1. Show up as soon as possible
2. Disappear when the application is ready for user input.
In a Delphi application this isn’t too difficult and usually ends up with code similar to this in your project dpr file:
program MyApp; begin splash := TSplash.Create(NIL); splash.Show; try Application.Initialize; Application.CreateForm(MainForm, TMainForm); finally FreeAndNIL(splash); end; Application.Run; end;
Quite often you find that even your supposedly sluggish application actually starts up so quickly that your beautiful splash screen is visible for barely enough time for your graphical prowess to be appreciated, so you add in a couple of Sleep() calls. You also usually find that the splash screen isn’t drawing properly so you need to force a repaint …. suddenly, instead of being a minor creative flourish your splash screen code is starting to somewhat dominate your project source!
The WPF/Visual Studio BuildAction approach starts to have some obvious appeal.
But the WPF implementation also has a rather significant drawback – it’s just a pretty picture.
Applications with the greatest need for splash screen are those with genuinely complex and lengthy initialisation needs, and those generally benefit from providing some sort of feedback to the user as to the progress of that initialisation, either as a progress bar or other feedback describing what is currently taking place.
Just throwing an image up is not good enough, but the usual approach in Delphi – although more flexible – is undeniably cumbersome.
Can we not devise something that is easy to use and flexible?
Of course we can, and what is more, I realised that I already had the framework in place to enable it.
Once More Unto The Breach, Deltics.Forms
You may recall we previously dipped into a unit of mine – Deltics.Forms. This unit provided some Vista compatibility code and in so doing introduced an extended Application object, providing a number of additional useful application services.
What is a splash screen if not just such a useful application service?
So I added two properties to my TApplication class:
property SplashClass: TFormClass read get_SplashClass write set_SplashClass; property SplashScreen: TForm read get_SplashScreen;
Of course, my TApplicationHelper class is effectively a class helper so I could not introduce any member variables, but since there is only one Application object I can use unit variables instead and provide getters and setters to “redirect” application properties to unit variables as required.
In this case, due to the way I intended to implement this feature, I only needed one additional variable:
var _SplashScreen: TForm = NIL; function TApplicationHelper.get_SplashClass: TFormClass; begin if Assigned(_SplashScreen) then result := TFormClass(_SplashScreen.ClassType) else result := NIL; end; function TApplicationHelper.get_SplashScreen: TForm; begin result := _SplashScreen; end;
The setter for SplashClass is a bit more involved because it takes care of instantiating the splash screen form itself and ensuring that it is correctly configured for use as a splash screen:
procedure TApplicationHelper.set_SplashClass(const aValue: TFormClass); begin ASSERT(NOT Assigned(_SplashScreen), 'Already showing a splash screen'); _SplashScreen := aValue.Create(NIL); with _SplashScreen do begin // Remove window frame and border decorations, set form to stay on // top and center on the screen. Caption := ''; BorderStyle := bsNone; BorderIcons := []; FormStyle := fsStayOnTop; Position := poScreenCenter; // Display the form and force painting Show; Update; end; // The application may not have much initialisation so to avoid // a blink and you miss it splash, we shall force a pause of // 1/2 a second Sleep(500); end;
I could take care of all the look and feel issues when designing the form destined for use as a splash screen, but why bother, and why have to do that for every splash screen I design, when I can put that housekeeping in a place where it is sure to be applied?
So, to have a splash screen in an application using Deltics.Forms all I need now is:
1) A form for use as a splash screen
2) One line of code in the project source
Something a little like this:
begin Application.SplashClass := TSplashScreenForm; Application.Initialize; Application.CreateForm(MainForm, TMainForm); Application.Run; end;
But isn’t this only half the story? We’ve seen that setting a splash class is enough to summon a splash screen, but how and when does the splash screen get dismissed?
Well, once again the TApplicationHelper provides the necessary “hook”. Application.Run gets called at the point that any application is ready for the user to take over, and since the Application here is a Deltics.Forms.TApplicationHelper I can replace the usual Run method with my own:
procedure TApplicationHelper.Run; begin // If we're showing a splash screen then we want to ensure that the // main form becomes visible first before the splash screen is // dismissed. Again, to avoid a blink and you miss it splash screen // we pause for 1/2 a second before dismissing the splashscreen and // actually allowing the application to run. if Assigned(_SplashScreen) then begin MainForm.Show; MainForm.Update; Sleep(500); FreeAndNIL(_SplashScreen); end; inherited; end;
The most important thing here is of course to remember to call inherited so that the “genuine” TApplication.Run method is called.
This also ensures that the application MainForm is presented for a reasonable (i.e noticeable, but not frustrating) period before the splash screen disappears. This might sound trivial but I personally find it a gratifyingly “correct” behaviour, although I can’t quite put my finger on why.
Advanced SplashScreen Feedback
Having invoked a splash screen by assigning a class to the SplashClass property, the actual splash form is then accessible via the Application.SplashScreen property.
At the most basic level this means that application startup code can get at the splash screen to provide any feedback that might be needed – this will require typecasting as things stand as the SplashScreen property is a simple TForm (actually, Deltics.Forms.TForm), so for example:
TSplashScreenForm(Application.SplashScreen).lblProcess := 'Loading packages...';
Whether I will devise a general mechanism for updating splash feedback, I am not sure. At the moment I’m thinking not. Most such splash screens I think will have fairly unique needs and the implementer of any more advanced splash screen form class could easily provide simple helper methods encapsulating the necessary incantations if required:
unit SplashScreenForm; // ... unit containing the splash screen form class also declares the below // procedure in the interface to be called during startup as required procedure SetSplashProcess(const aProcess: String); begin if Assigned(Application.SplashScreen) then TSplashScreenForm(Application.SplashScreen).lblProcess := aProcess + '...'; end; unit MainForm; : uses SplashScreenForm; : procedure TMainForm.FormShow(Sender: TObject); begin : SetSplashProcess('Loading packages'); : end;
Not Quite There Yet
I’m already pleased with how easy it proved to add this capability to my application helper. I’m even more pleased that I can already see that the application concept will enable me to take this a step further and provide “built-in” support to all my applications for a splash screen suppression command line parameter.
I haven’t implemented that yet, but it should be obvious that it will be a minor addition to the set_SplashClass property setter that will only instantiate a splash screen class if that suppression parameter is NOT present on the command line.
CodePlex – Not Going Well
Unfortunately as before, Deltics.Forms code is not yet available. The intention was to publish through CodePlex. The intention is still to publish, but CodePlex is falling out of favour. Performance of the Team Foundation repository that it uses is proving consistently diabolical over the net and it’s “querks”, to put it politely, intensely frustating.
But I am working on it.
Personally, I am annoyed by splash screens 🙂 Today’s computers should be fast enough for instant startup of modern apps. If the app still takes too much time, better optimize here (for example I don’t have any Form configured for auto-creation except my MainForm. Everything is instantiated when needed).
That’s a fair comment – when a splash screen is hiding needlessly inefficient startup code.
But there are legitimate reasons to have lengthy startup sequences (e.g. if initialisation involves handshaking with remote services etc), and a splash screen is a useful way to communicate to the user that something *is* going on (otherwise the user may keep trying to launch your app and end up with multiple instances that they didn’t actually want).
The WPF/Visual Studio approach is useless in those cases where a splash screen is actually justified, since it can only throw up a static image.
😉
In Deltics.Forms you could of course use Application.SplashClass to the same pointless end (although any app built with Deltics.Forms is also going to support a built in no-splash command line param, so any user will be able to turn a useless splash screen off if they wish).
And ultimately of course, you don’t have to use it at all if you don’t want to.
🙂
I have two open source projects hosted by CodePlex and one by Google Code. Talking about speed, CodePlex is no better than Google Code.
Think about it, it is much easier to use a SVN repository like Google Code, than using CodePlex’s TFS one with SVNBridge.
🙂
Hi Lex – I wonder if my performance problems don’t stem more the fact that I am in NZ and presumably the CodePlex servers are all in the US.
In which case I suspect I may have a similar experience with all the source hosting services. 🙁
FYI: I was using Team Explorer to talk to the CodePlex TFS system directly – or so I presumed – not using SVN Bridge.
Jolyon, as a temporary solution why not upload your code samples to CodeCentral?
http://cc.codegear.com
This is very useful, thanks 😉
This is very useful, thanks 😉
I’ve been looking at this article but I’m struggling to implement this in my own test application.
Where can I download an example app using this techinque using your Deltics.Forms unit?