![]() |
Tutorial: Threaded SplashScreen
Liste der Anhänge anzeigen (Anzahl: 1)
Liebe DP-Gemeinde,
schon lange war ich nicht mehr aktiv in der DP und die Gründe sind vielfältig. Aber dieses Jahr will ich Euch ein kleines Tutorial einstellen, zu einem Thema, welches immer wieder mal aufkommt. Die Idee zu dieser Methode kam mir als ich die neuen MS Office™ Produkte sah. Während eine der Anwendungen startet, kann man den Start jederzeit ohne Probleme abbrechen. Das wird dadurch ermöglicht, dass der SplashScreen zu jederzeit interaktiv ist und nicht im Hauptthread der Anwendung läuft. Außerdem war es mir wichtig, dass der SplashScreen sehr schnell auf dem Bildschirm erscheint. Das wird insbesondere dann zu einem Problem, wenn man viele und umfangreiche Packages in der Anwendung nutzt. Da diese Packages geladen werden, bevor auch nur die erste Zeile in der Anwendung ausgeführt wird, kann es schon mal ein paar Sekunden dauern, bevor die Anwendung ihren eigenen SplashScreen darstellen kann. Problem: Eine, des Öfteren vorgeschlagene Methode ist der Umweg über eine separate Starter-Anwendung. Aber seit Windows 7 ist das auch kein besonders praktikabler Weg, da der Nutzer Anwendungen direkt in der Taskleiste anheften kann. Ja nach Moment des Anheftens, würde der Nutzer entweder die Starter-Anwendung anheften oder die eigentliche Anwendung. Wenn es die Starteranwendung ist, dann erscheint zwar immer der SplashScreen, aber die eigentliche Anwendung erhält zur Laufzeit ihr eigenes Icon in der Taskleiste. Wenn es die eigentliche Anwendung war, die der Nutzer angeheftet hat, dann erscheint kein SplashScreen beim Start. Die Idee: Ein SplashScreen in einem eigenen Thread darstellen, welcher sofort zum Start der Anwendung ausgeführt wird. Der Haken: Die VCL von Delphi ist nicht thread-sicher. Ein VCL Form kann nicht in einem separaten Thread dargestellt werden. Die Lösung: Zwei Instanzen VCL der VCL in eigenen Anwendungen erstellen (siehe Problem), aber in eine Anwendung integrieren. Wenn man sich über Lösung jetzt mal einen Moment und in Ruhe Gedanken macht, dann liegt die Lösung auf der Hand. Der SplashScreen wird in eine eigene Anwendung ausgelagert. Da wir den Ansatz einer Starter-Anwendung bereits abgelehnt haben, müssen wir eine dynamische Anwendung nutzen, eine DLL. Delphi DLLs, wenn diese nicht(!) Packages nutzen und auch nicht als Package entwickelt wurden, verwalten die VCL komplett anonym zur ladenden Anwendung. Wenn wir unsere Anwendung also verpflichten eine DLL zu laden, welche die VCL enthält, dann erhalten wir zwei vollständig separate Instanzen der VCL. Somit können wir den SplashScreen in einem eigenen Thread darstellen. Damit der SplashScreen möglichst frühzeitig dargestellt wird, müssen wir das Early-Binding von Delphi nutzen. Das Gute ist, dass Delphi-Anwendungen DLLs vor Packages laden, wenn wir das Early-Binding nutzen. Gleich beim Laden der DLL starten wir einen neuen Thread, welcher den SplashScreen erstellt. Des Weiteren exportiert die DLL eine Method welche uns eine Schnittstelle auf den SplashScreen zurück liefert. Dieser implementiert ein eigenes Interface, im Beispiel ISplashScreen.
Delphi-Quellcode:
Wenn wir die Schnittstelle auf den SplashScreen ermittelt haben, dann können wir jederzeit auf diesen zugreifen und den Nutzer über den Stand des Ladevorganges informieren. Dabei muss beachtet werden, dass der Zugriff auf die VCL des SplashScreens nicht direkt durch die Anwendung erfolgt.
type
// Interface für die Interaktion mit dem SplashScreen ISplashScreen = interface ['{0E3C4BEE-F2B3-48A9-A41C-337AD8FB52A4}'] // Wenn es bei der Initialisierung zu Problemen kommt, dann kann der Abbruch // des Programmstarts an den SplashScreen mitgeteilt werden procedure CancelStart; // Liefert TRUE zurück, wenn der Programmstart (manuell oder programmatisch) // abgebrochen wurde, ansonsten FALSE. function IsStartCanceled: Boolean; // Schließt den SplashScreen und gibt alle Resourcen frei procedure CloseSplashScreen; // Erhöht den Schrittzählen im SplashScreen (TProgressBar) und gibt eine // Nachricht auf dem SplashScreen aus procedure NextStep(CurrentStep: PChar); // Blendet den SplashScreen kurzfristig aus procedure Show; // Zeigt den SplashScreen wieder an procedure Hide; end; Hinweis: Das automatische Referenzcounting ist für VCL-Formulare nicht implementiert! Wir müssen uns also selbst um die Freigabe des SplashScreens und seiner Resourcen kümmern. Die Lösung ist recht simpel. Wenn wir zum Beispiel neuen Text ausgeben wollen, dann müssen wir eine Variabel setzen, welche den neuen Text speichert. Das Form des SplashScreens überprüft regelmäßig (z.B.: mittels TTimer), ob es neue Anweisungen gibt und führt diese dann aus (z.B.: neuen Text darstellen). Kurz bevor die Anwendung gestartet wird (Application.Run in der DPR-Datei), müssen wir nochmals überprüfen, ob der Start evtl. abgebrochen wurde. Ist dieses der Fall, müssen wir noch das Hauptformular der Anwendung wieder freigeben, damit dieses nicht mehr angezeigt wird und die Anwendung sich selbst und ordentlich beendet. Nachfolgend die Implementation der wichtigsten Elemente der Anwendung. Im Anhang findet Ihr das komplette Projekt (getestet Delphi 2010, Delphi XE2). Der Projekt Quelltext
Delphi-Quellcode:
Auszüge aus der SplashScreen Form Unit
function GetSplashScreen(FinalStepCount: Integer): ISplashScreen; external 'OurSplash.dll';
var SplashScreen : ISplashScreen; begin // ReportMemoryLeaksOnShutdown := True; SplashScreen := GetSplashScreen(1005); Application.Initialize; Application.MainFormOnTaskbar := True; // langwierige Initialisierungen... if not SplashScreen.IsStartCanceled then begin Application.CreateForm(TfrmHello, frmHello); // noch mehr langwierige Initialisierungen... if SplashScreen.IsStartCanceled then frmHello.Free; end; SplashScreen.CloseSplashScreen; Application.Run; end.
Delphi-Quellcode:
Wie schon geschrieben, das vollständige und lauffähige Projekt ist mit allen Sourcen im Anhang :)
type
TThreadedSplashForm = class(TThread) protected procedure Execute; override; end; { TThreadedSplashForm } procedure TThreadedSplashForm.Execute; begin TfrmSplash.frmSplash := TfrmSplash.Create(); TfrmSplash.frmSplash.ShowModal; FreeOnTerminate := True; end; { TfrmSplash } procedure TfrmSplash.CancelStart; begin FCriticalSection.Acquire; try FDoCancel := True; finally FCriticalSection.Release; end; end; procedure TfrmSplash.CloseSplashScreen; begin FCriticalSection.Acquire; try FDoClose := True; finally FCriticalSection.Release; end; end; constructor TfrmSplash.Create(); begin inherited Create(nil); Position := poScreenCenter; FDoCancel := False; FIsCanceled := False; FDoInit := False; FDoClose := False; FDoHide := False; FDoShow := False; FMessage := 'Bibliotheken und Packages werden geladen'; FStepIncrement := 0; FCriticalSection := TCriticalSection.Create; end; destructor TfrmSplash.Destroy; begin FCriticalSection.Free; inherited Destroy; end; procedure TfrmSplash.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin CanClose := FDoClose; if not CanClose then CancelStart; end; procedure TfrmSplash.FormKeyPress(Sender: TObject; var Key: Char); begin if Key = Char(VK_ESCAPE) then CancelStart; end; class function TfrmSplash.GetSplashScreen(FinalStepCount: Integer): TfrmSplash; begin if not FSplashReturned then begin FSplashReturned := True; while frmSplash = nil do Sleep(50); frmSplash.DoInit(FinalStepCount); end; Result := frmSplash; end; function TfrmSplash.IsStartCanceled: Boolean; begin Result := FIsCanceled; end; procedure TfrmSplash.NextStep(CurrentStep: PChar); begin FCriticalSection.Acquire; try if FIsCanceled then Exit; FMessage := CurrentStep; Inc(FStepIncrement); finally FCriticalSection.Release; end; end; procedure TfrmSplash.tmrUpdateScreenTimer(Sender: TObject); begin if not (FDoInit or FDoCancel or FDoClose or (FStepIncrement <> 0)) then Exit; tmrUpdateScreen.Enabled := False; try FCriticalSection.Acquire; try DoCheckInit; DoCheckCancel; DoCheckProgress; DoHideSplash; DoShowSplash; finally FCriticalSection.Release; end; Application.ProcessMessages; finally tmrUpdateScreen.Enabled := not FDoClose; end; DoCheckClose; end; initialization TThreadedSplashForm.Create(False); end. sakura ...:cat:... |
AW: Tutorial: Threaded SplashScreen
Hallo Sakura,
dein Tutorial klingt sehr interessant - nur leider kann ich es mit meinem (historischen) Delphi 7 nicht kompilieren. Wäre es möglich, dass du es für ältere Delphi-Versionen abänderst, bzw. für ältere Versionen eine eigene Version zimmerst? Schon mal Danke im Voraus! |
AW: Tutorial: Threaded SplashScreen
Nicht wirklich, ich habe nichts älteres als Delphi 2010 mehr installiert.
An und für sich ist aber eigentlich nicht viel enthalten, was Probleme bereiten könnte. Evtl. mal das JPG aus dem SplashScreen-Form entfernen. Der Rest ist eigentlich Standard seit Delphi 4 oder so. ...:cat:... |
AW: Tutorial: Threaded SplashScreen
Zitat:
Dann gibt es TProgressBar.State noch nicht, das heißt du müsstest dir etwas anderes für einen Fehlerstatus einfallen lassen oder das über Windows Messages direkt in dem Progress Bar Control von Windows umschalten. Wo etwas zu ändern ist, sagt dir ja der Compiler... |
AW: Tutorial: Threaded SplashScreen
Was für eine bemerkenswerte, aufwändige Lösung für eine eigentlich simple Anforderung:
- DLL mit Early Binding - Shared Interfaces - und threaded Form mit Critical Section Isolierung Und obwohl man in diesem Beispiel eine DLL erhält, die genauso groß ist wie die eigentliche App, ist dieser Ansatz auch sehr gut für Non-Blocking-GUIs (z.B. eine Druck-Ausgabe) geeignet, zumal sich das gemeinsame Interface kinderleicht erweitern lässt. |
AW: Tutorial: Threaded SplashScreen
Liste der Anhänge anzeigen (Anzahl: 1)
hallo zusammen,
das beispielprojekt kann ich ganz normal ausführuen, nur wenn ich es in mein eigenens projekt kopieren möchte (OurSplash.dll + Anpassung Main.dpr Datei) startet die anwendung komplett nicht. Muss ich irgendwas noch an den Build-Konfigurationen einstellen? Wenn ich in dem von mir erstellen Projekt die rechte maustaste auf die OurSplash.dll mache (bei mir heißt diese SplashScreen.dll) bekomme ich folgenden fehler Anhang 42561 |
AW: Tutorial: Threaded SplashScreen
Was passiert denn, wenn Du die Anwendung startest?
Und wenn Du sie im Debugger ausführst, wie weit kommt sie denn? |
AW: Tutorial: Threaded SplashScreen
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:
Anhang 42562 ich vermute es liegt an der Instanziierung von
Delphi-Quellcode:
die warnung im Screenshot bitte ignorieren, das habe ich gefixed
SplashScreen := GetSplashScreen(1);
komisch ist nur das garkein fehler kommt... |
AW: Tutorial: Threaded SplashScreen
Fehler gefunden, es lag tatsächlich an den Build-Konfiguration - das Ausgabeverzeichnis musste noch auf den aktuellen Main(App) Output gesetzt werden.
|
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:13 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz