Tech Zone » Tutorial Screen Saver
Screen Saver TutorialWas ist ein Screensaver?Ein Screensaver ist eine normale EXE-Datei (ein Windows-Programm), welche statt der Extension EXE die Extension SCR hat. Also einfach die EXE-Datei nach dem Kompilieren umbenennen - fertig. Dieses Programm wird nach einer definierten Zeit (Systemsteuerung->Anzeige->Bildschirmschoner) von Windows aufgerufen, wobei die durch Kommandozeilen-Parameter dem Bildschirmschoner mitgeteilt wird, was er zu tun hat. ParameterauswertungDem Bildschirmschoner wird ein Operationscode (Buchstabe) übergeben, der dem Bildschirmschoner mitteilt, was er zu tun hat. Bei gewissen Dingen wird noch ein weiterer Parameter mitgeliefert - ein Windows-Fenster-Handle. Es gibt folgende Operationen... Die Parameter werden gleich in der Projekt-Unit, noch bevor irgendeine Form oder das Application-Objekt erstellt wurde, ausgewertet. Hier erstmal eine kurze Routine, die ich für den Start des Savers brauche. Sie verwendet grundsätzlich PreventToRunTwice um den mehrfachen Start zu verhindern und initialisiert dann alles so, wie es Delphi bei einer normalen Applikation auch machen würde. Die Parameter enthalten die Formklasse und die Variable, welche das Hauptform dann speichert. Ich habe den Bildschirmschoner so gebaut, dass sich in den verschiedenen Modi einfach das Hauptformular ändert. procedure StartApp(InstanceClass: TFormClass; var Reference); Hier folgt nun ein Ausschnitt aus dem PSSS-Source, der die Parameterauswertung vornimmt. Keine spannende Geschichte. Zuerst wird mal alles geUPPERCASED (dann muss man sich nur um "grosses" kümmern ;) ein allfälliges '/' oder '-' gelöscht und danach einfach, je nach Parameter, ein anderes Form als Hauptfenster verwendet. if ParamCount >= 1 then begin Saver-FensterDas Fenster eines Bildschirmschoners muss folgende Eigenschaften haben :
Um das zu erreichen, setzt man im Object Inspector...
Damit hat man schon einiges erreicht. Damit's aber wirklich klappt habe ich noch folgende Methode hinzugefügt... Deklarationprotected
Die Methode bewirkt, dass neben den Delphi-Attributen auch bestimmte eigene Window-Styles gesetzt werden. Damit die Grösse stimmt, platziert man im FormCreate-Event... SetBounds(0,0,screen.width,screen.height); Tip Vor dem SchonenJetzt haben wir ein schönes Schoner-Fenster und müssen nur noch loslegen. Unter Windows 95, 98 (Me?) sollte man dem System nun mitteilen, dass ein Bildschirmschoner läuft (tja... Windows startet zwar den Schoner, ist aber derart vergesslich, dass man es ihm gleich wieder mitteilen muss :). Das erreicht man mit... SystemParametersInfo(SPI_SCREENSAVERRUNNING,Word(True),@dummy,0); Am besten platziert man diese Zeile im FormShow-Event. Sie bewirkt, dass unter den besagten Windows-Versionen die ALT-TAB-Taste gesperrt wird, damit der Benutzer nicht einfach so vom Schoner zu einer anderen Applikation wechseln kann. Dies ist vor allem wichtig, wenn der Passwortschutz aktiv ist - da dieser so kinderleicht umgangen werden könnte. Ich rufe diese Funktion unter jeder Windows-Version auf, obwohl Sie unter den NT-Abkömmlingen keinen Effekt mehr haben soll - wer weiss aber schon, was in den Untiefen von Windows wirklich passiert? Ach ja! Mauszeiger ausschalten nicht vergessen : ShowCursor(false); Auch diese Zeile ist im FormShow gut aufgehoben. Achtung Waehrend dem SchonenJetzt könnte man die "Show" starten. Fast jede Bildschirmschoner-Animation - Slideshows und Filmplayer, die man mithilfe einer Komponenten realisiert und einfach sagt : "Spiel ab", mal ausgenommen - basiert auf dem frame-weisen Zeichnen. D.h. man zeichnet x-mal pro Sekunde ein Bild und das ergibt dann eine Animation. Um das zu erreichen gibt es im wesentlichen zwei Methoden :
Ich habe in den ersten Versionen des PSSS mit Methode 1 gearbeitet. Doch der Overhead ist relativ gross und PSSS ist ein Schoner der jeden Taktzyklus gebrauchen kann :) Ich hätte statt eines normalen Timers aber auch einen Windows-Multimedia-Timer verwenden können. Diese Timer haben sehr wenig Overhead und sind viel genauer. Allerdings sind sie nicht gerade einfach in der Handhabung. Da gibt's verschiedene Auflösungen und Genauigkeiten, die von der Hardware abhängen usw. Darum hab ich mich für Methode 2 entschieden, weil sie keine speziellen Ressourcen beansprucht und recht einfach zu implementieren ist. Hier nun der Körper dieser Routine : procedure TfrmParticleSystems.DoAnimation; Nun das sieht auf den ersten Blick recht komisch aus. fRunning ist eine Variable vom Typ Boolean und wird im Form als private deklariert. Wir setzten Sie am Anfang der Routine auf true, um nun anzuzeigen, dass wir am animieren sind und damit wir in den nachfolgenden While-Schleife hängen bleiben. Wie kommt das Programm nun aus der Schleife raus? Das geschieht, indem ein anderer Teil des Programmes die Variable fRunning auf false stellt - zum Beispiel, wenn der Benutzer die Maus bewegt. Damit das klappt, muss unbedingt Application.ProcessMessages aufgerufen werden, denn nur so kann der Schoner auf andere Ereignisse reagieren. Jetzt stellt sich noch die Frage von wo aus DoAnimation aufgerufen werden soll? Ich selbst habe schlechte Erfahrungen damit gemacht, DoAnimation aus dem FormShow-Event aufzurufen, weil einerseits die Ereignisbehandlung von! FormShow? niemals verlassen wird (DoAnimation? wird ja erst wieder verlassen, wenn der Schoner beendet werden soll) und andererseits wegen dem Problem, welches ich in "Die Nachrichtenfalle" näher beschreibe. Um zu erreichen, dass der FormShow-Event komplett behandelt wird und DoAnimation eben danach gestartet wird, habe ich einen One-Shot-Timer verwendet. Ein One-Shot-Timer ist eine ganze normale Timerkomponente, bei welcher aber der Timer, sobald der Timer-Event ausgelöst wurde, gleich wieder abgeschaltet wird - das Timerereignis tritt also genau einmal auf. procedure TfrmParticleSystems.tmrStartAnimTimer(Sender: !TObject); Innerhalb des FormShow-Events wird die Animation gestartet, indem mit... tmrStartAnim.Enabled:=true; ...der Timer eingeschaltet wird. Man könnte das auch mittels einer eigenen Windows-Message und einer eigenen Nachrichtenbehandlungsmethode erreichen (ist wohl eleganter als der Timer) - allerdings hat man da nicht die Möglichkeit ein Intervall festzulegen. Nachrichten-FalleDiese Kapitel ist einem Umstand gewidmet, der mich fast an den Rand der Verzweiflung gebracht hat. PSSS kann die Bildschirmauflösung für das Schonen ändern und setzt diese nach Programmende wieder zurück. Eigentlich ist das gar keine so grosse Hexerei, wenn man sich mit der API-Funktion ChangeDisplaySettings und ein paar weiteren auseinandersetzt. Allerdings führt das ändern der Bildschirmauflösung dazu, dass MouseMove-Message und noch ein paar andere Nachrichten beim Bildschirmschoner eintrudeln (IIRC eine unmotivierte Close-Message). Diese Nachrichten führen unweigerlich dazu, dass der Bildschirmschoner meint, der Benutzer habe die Maus bewegt und beendet sich sofort. Auf eine akzeptable Lösung für das Problem, bin ich erst nach einigen Experimenten und Misserfolgen gekommen. Der Trick besteht darin, die Message-Behandlung für eine gewisse Zeit komplett abzuschalten. D.h. alle eingehenden Nachrichten bis zum offiziellen Start des Schoners zu ignorieren. Nun könnte man auf die Idee kommen, einfach alle Event-Handler mit einem "Ignore-It"-Mechanismus auszustatten. Dies ist aber recht mühsam und hat mir nicht gefallen. Stattdessen hab ich eine eigene allesübergreifende Nachrichtensenke eingebaut. Mit... Application.OnMessage:=MsgFilter; ...kann man eine eigene Routine einklinken, die jede Nachricht abfängt, bevor irgendeine andere Komponente die Nachricht erhält. Die Routine sieht so aus : procedure TfrmParticleSystems.MsgFilter(var Msg: TMsg;var Handled: Boolean); Sie blockiert mal alle Close-Messages, weil mir Windows unerwünschte Close-Messages geschickt und mir den Schoner so frühzeitig ins Nirwana befördert hat - ach ja, wenn man Handled auf True setzt, gilt die Nachricht als erledigt und niemand sieht mehr etwas von Ihr. Die MouseMove-Messages hingegen wollen wir nur für eine kurze Zeit blockieren. Darum eine die boolsche Variable fMsgFilterOn. Steht diese auf True, dann werden die MouseMove-Nachrichten gefiltert (d.h. ignoriert), andernfalls ganz normal weitergeleitet. Ich stelle nun im FormShow-Event den MsgFilter, unmittelbar bevor ich die Bildschirmauflösung ändere, auf True und am Anfang von DoAnimation wieder auf false. Dadurch kann ich die Auflösung ändern, ohne dass der Schoner gleich beendet wird. Beenden des Schoners Ein Bildschirmschoner soll in der Regel beendet werden, wenn eines der folgenden Ereignisse auftritt :
Den ersteren Fall kann man wie folgt behandeln... procedure TfrmParticleSystems.FormMouseMove(Sender: TObject;Shift: TShiftState; X, Y: Integer); Das ist ein Event-Handler für MouseMove-Ereignisse. Die Idee dahinter ist, dass der Benutzer die Maus mit einer gewissen Geschwindigkeit bewegen soll, bevor der Schoner sich schliesst - bewegt er sie zu langsam, so war das wahrscheinlich nur ein versehen (hat am Tisch gewackelt). Dazu merkt sich der Schoner in fLastX und fLastY jeweils die Mouse-Koordinate und kann Sie mit der Position beim letzten mal, als der Event aufgetreten ist, vergleichen. Unterscheiden sich diese nun um mehr als 10 Pixel, dann hat der Benutzer die Maus schnell genug bewegt (je höhe die Zahl um so schneller muss die Maus bewegt werden). Die beiden letzteren Ereignisse sind einfacher. Da muss muss nur Close aufgerufen werden. Jetzt gibt's da aber noch die ALT-Taste sowie die verschiedenen Windows-Tasten und man möchte doch, dass sich auch in diesem Fall der Schoner schliesst. Also auf und kurz eine eigene Message-Methode definiert : Deklarationprotected Implementierungprocedure TfrmParticleSystems.WMSysKeyDown(var Msg : TWMSysKeyDown); VorschauIm Vorschaumodus läuft alles sehr ähnlich wie beim normalen Schonen. Mit der Ausnahme, dass...
Wie schon weiter oben erwähnt, bekommt der Bildschirmschoner hinter dem 'P' für Preview auch noch eine Integerzahl - das Windows-Handle der Vorschaufensters im Bildschimschoner-Tab von Systemsteuerung->Anzeige. In dieses Fenster müssen wir unseres einpassen, aber dazu später mehr. Beim Fenster an und für sich gibt es auch ein paar Dinge zu beachten. Ich verwende für das Preview-Fenster folgende überschriebene CreateParams-Methode : procedure TfrmParticleSystemsPreview.CreateParams(var Params: TCreateParams); So, jetzt müssen wir dafür sorgen, dass wir in dem "Fernseher" der Systemsteuerung zu sehen sind. Das macht man mit... if ParamCount>1 then begin ...im Create-Event des Vorschaufensters. Im Show-Event können wir mit... if ParamCount>1 then begin ...die Grösse der Zeichenfläche ermitteln. Das Ergebnis ist in r (vom Typ TRect) und sollte dazu verwendet werden, die Animation auf diesen Bereich abzustimmen. Nebenbei : ich teste hier sicherheitshalber immer, ob auch genug Parameter da sind, obwohl der Aufruf des Schoners mit 'P' ohne Fensterhandle nicht sinnvoll ist. Und noch etwas. Mit... ShowWindow(Application.Handle,SW_HIDE); ...kann man verhindern, dass in der Taskbar der Fenstertitel steht. Im Vorschaumodus ist das besonders hässlich. Tip Passwort-GeschichteDiese Sache wurde in Windows recht komisch und mysteriös implementiert, und ich musste ein bisschen herumsuchen, bis ich Beispiele gefunden habe, die mir aufzeigten, wie das funktioniert. Dabei eines gleich vorweg : Die Passwortabfrage wird von NT und dessen Abkömmlingen selbst erledigt. Das heisst, dass die ganze Geschichte, die in diesem Kapitel vorgestellt wird, nur Windows 95, 98 und vermutlich ME betrifft. Microsoft hat die interessanterweise die Routinen für die Passwortabfrage in einer mpr.dll (Multiple Provider Router) und in password.cpl verpackt. Beide Dateien sind unter NT und NT-Abkömmlingen (Windows 2000) nicht vorhanden. Darum darf man diese Routinen nicht statisch, sondern erst zur Laufzeit dynamisch einbinden. Wird der Schoner mit dem Parameter 'A' aufgerufen, so muss man mit der folgenden Routine einen Passwort-Aendern-Dialog anzeigen. procedure SetPassword; Dabei wird am Anfang erst einmal versucht die DLL zu laden. Anschliessend sucht man sich mit GetProcAddress einen Pointer auf die gewünschte Funktion, die man eben bei Erfolg dann auch aufrufen kann. Man übergibt man der Routine neben dem Text 'SCRSAVE' (die Routine kann scheinbar mehrere Passwörter ändern, und wir wollen ihr mitteilen, dass es sich um das Bildschirmschonerkennwort handelt) noch das Handle der Elternfensters, das einem wiederum als Integerzahl im zweiten Parameter nach 'A' mitgegeben wird. Mit der folgenden Prozedur soll man das Passwort prüfen, wenn der Benutzer den Schoner beenden möchte. Der Mechanismus ist analog zu dem oben dargestellten Verfahren. function EnterPassword(const Form : TForm) : Boolean; Der Parameter Form wird benötigt, damit wir der Dialogroutine von Windows das Fensterhandle des Elternfensters übergeben können. Dadurch wird der Passwort-Eingabe-Dialog auch wie gewünscht mit unserem Fenster verknüpft. Damit wir wissen, ob der Benutzer Passwortschutz überhaupt beauftragt hat, fragen wir mal kurz in der Registry nach... function PasswordRequired : Boolean; Jetzt haben wir alles, was wir für die Passwortgeschichte brauchen. Jetzt schauen wir uns nochmals den Close-Handler unseres Schoner-Fensters an : procedure TfrmParticleSystems.FormClose(Sender: TObject;var Action: TCloseAction); Der erste Teil der If-Bedingung prüft, ob wir uns unter Windows NT oder einem Abkömmling befinden (siehe "Die Windows-Version ermitteln" im Anhang). Der zweite Teil benutzt die weiter oben vorgestellte Routine PasswordRequired, um herauszufinden, ob wir wirklich nach dem Passwort fragen müssen. Mit EnterPassword wird die Benutzereingabe geprüft. Falls der Benutzer ein falsches Passwort eingegeben hat, wird mit Action:=caNone verhindert, dass sich das Fenster schliesst und mit exit der Close-Handler gleich verlassen. So schonen wir dann einfach fröhlich weiter Anhang Mehrfachen Programmstart verhindern Die Idee ist ganz einfach. In Delphi hat jede Applikation ein verstecktes (unsichtbares) Hauptfenster, welches den Titel der Applikation trägt. Die Routine sucht nach dem Fenster mit der Fensterklasse "TApplication" und dem Titel der Applikation. Wichtig ist, dass man nicht auf die Idee kommt, den Titel via Application.Title abzufragen, weil das Application-Objekt zu diesem frühen Zeitpunkt noch nicht initialisiert ist. Natürlich könnte man den Test auch später machen; ich wollte aber möglichst früh einen Abbruch erreichen. procedure PreventToRunTwice; AnhangWMSysCommand-NachrichtNunja, in den Tiefen der Windows-Dokumentation findet man eine nebenläufige Bemerkung, dass Standard-Message-Handler von Screensavern für die WM_SYSCOMMAND-Message im Falle eines SC_SCREENSAVE oder SC_CLOSE-Kommandos false zurückliefern sollen. Und wenn schon Microsoft das so macht, dann sollten wir das auch tun - warum auch immer. Deklarationprotected Implementierungprocedure TfrmParticleSystems.WMSysCommand(var Msg : TWMSysCommand); Wenn man auf dem Internet und in der Windows-Dokumentation ein wenig herumsucht, findet man heraus, dass die WM_SYSCOMMAND-Nachricht im Zusammenhang mit SC_SCREENSAVE verwendet werden kann, um den Start des Bildschirmschoners zu verhindern. Windows schickt nämlich diese Nachricht an jede Applikation und startet den Schoner nicht, wenn die Nachricht mit Msg.Result:=-1 quittiert wird. Windows-Version ermittelngVersionInfo ist eine globale Variable vom Typ... TWindowsPlatform = (wpUnkown,wpWinNT,wpWin32,wpWin32s,wpWinCE); TWindowsVersionInfo = record ...und wird im Initialization Teil meiner WindowsTool-Unit mit folgender Routine... procedure GetWindowsVersion(var aVersionInfo : TWindowsVersionInfo); ...initialisiert. Details zu den API-Routinen findet man in der Windows-API-Dokumentation. Die Prozedur HandleWindowsError übernimmt die Behandlung von Windows-Fehlern und sieht so aus : function HandleWindowsError(aErrorInfo : string;aErrorCode : integer):boolean; Ich verwende diese Funktion, um einerseits nach Windows-Fehlern zu prüfen (wenn true zurückkommt, ist etwas schief gelaufen) und andererseits, um eine Fehlermeldung auszugeben (eine MessageBox für normale Anwendungen und eine Fehlermeldung ins auf die Standardausgabe bei Console-Anwendungen. Die Windows-Fehlercodes sind mühsam, wenn man Sie nachschlagen muss - darum lieber gleich eine Meldung ausgeben. |
Tech Zone » Tutorial Screen Saver