|
Registriert seit: 10. Jun 2002 Ort: Unterhaching 11.412 Beiträge Delphi 12 Athens |
#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 ... ![]()
Daniel Lizbeth
Ich bin nicht zurück, ich tue nur so |
![]() |
Ansicht |
![]() |
![]() |
![]() |
ForumregelnEs ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.
BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus. Trackbacks are an
Pingbacks are an
Refbacks are aus
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
![]() |
![]() |