AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Tutorials Tutorial: Threaded SplashScreen
Tutorial durchsuchen
Ansicht
Themen-Optionen

Tutorial: Threaded SplashScreen

Ein Tutorial von sakura · begonnen am 24. Dez 2011 · letzter Beitrag vom 13. Feb 2015
 
Benutzerbild von sakura
sakura

Registriert seit: 10. Jun 2002
Ort: Unterhaching
11.412 Beiträge
 
Delphi 12 Athens
 
#1

Tutorial: Threaded SplashScreen

  Alt 24. Dez 2011, 14:49
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:
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;
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.

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:
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.
Auszüge aus der SplashScreen Form Unit
Delphi-Quellcode:
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.
Wie schon geschrieben, das vollständige und lauffähige Projekt ist mit allen Sourcen im Anhang

sakura
......
Angehängte Dateien
Dateityp: zip DP X-MAS.zip (300,5 KB, 306x aufgerufen)
Daniel Lizbeth
Ich bin nicht zurück, ich tue nur so
  Mit Zitat antworten Zitat
 

 

Forumregeln

Es 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

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 00:20 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