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.