Die Community zu .NET und Classic VB.
Menü

Bildschirmschoner erstellen

 von 

Übersicht 

Bildschirmschoner gibt es wie Sand am Meer. Zu Tausenden findet man sie im Internet oder auf Shareware CD-ROMs. Trotzdem möchte der eine oder andere vielleicht seinen eigenen Screensaver entwerfen.

Mit freundlichen Grüßen
Uwe Jorgel

Bildschirmschoner - heute und früher  

Braucht man eigentlich einen Bildschirm-"Schoner"? Die ersten Screensaver hatten tatsächlich eine Schutzfunktion zu erfüllen: Röhrenbildschirme waren damals viel empfindlicher als heute. Es konnte durchaus vorkommen, dass sich Buchstaben und Linien in den Monitor als "Geisterbild" einbrannten, weil sie zu lange auf die gleiche Stelle projiziert wurden. Bei den neuen Bildschirmen kann das nicht mehr passieren. Auch das Argument des Stromsparens - ein schwarzer Bildschirm braucht schließlich weniger Strom als ein weißer - hat spätestens mit den inzwischen gebräuchlichen Energiesparmonitoren, die sich nach einer gewissen Zeit automatisch in einen Standy-Modus schalten, sekundäre Bedeutung. Dennoch findet man heute noch auf fast jedem PC einen Screensaver: Ähnlich wie ein auffälliger Kaffeebecher bringen die bunten Bilder einfach eine persönliche Note in den sachlich-grauen Computerarbeitsplatz.

Bildschirmschoner selbermachen  

Der auffälligste Unterschied zwischen einem Screensaver und einem "normalen" Programm liegt in der Datei-Endung SCR [statt EXE]. Ansonsten unterscheidet sich ein Screensaver nur im Quellcode von anderen Programmen; er braucht nicht einmal aufwendig in die Registrierdatenbank eingetragen werden.

Installation

Damit das Betriebssystem den Screensaver im Bildschirmschonerdialog zur Auswahl anzeigt, muss dieser natürlich erst installiert werden. Dafür kann man im Windows-Explorer im Kontextmenü des Screensavers den Menüpunkt "installieren" anwählen. Das Betriebssystem ruft dann das Programm RunDll32.exe mit folgender Befehlszeile auf:

RunDll32.exe desk.cpl,InstallScreenSaver %1

Der Platzhalter %1 steht für den Dateinamen inkl. Pfad des Screensavers. Derselbe Aufruf kann verwendet werden, wenn man einen Screensaver von einem eigenen Programm aus installieren möchte:

Shell "RunDll32.exe desk.cpl,InstallScreenSaver C:\Windows\S.scr"

Mit dem Aufruf von "Installieren" wird der Bildschirmschoner als aktiviert registriert; er läuft im Regelfall an, wenn der Benutzer eine definierte Zeit lang weder die Maus noch die Tastatur betätigt hat. Dann steht er so lange in der Liste der Screensaver zur Auswahl, bis man ihn deaktiviert - anschließend jedoch kennt Windows ihn nicht mehr. Man sollte den Bildschirmschoner daher bei der Installation in das Windows-Verzeichnis kopieren. Die Liste der im System verfügbaren Screensaver wird für den Bildschirmschoner-Dialog jeweils neu aus den Namen der Dateien mit SCR-Typ im Windows-Verzeichnis zusammengestellt.

Der automatische Aufruf  

Normalerweise werden Screensaver nicht vom Benutzer gestartet. Das Betriebssystem ruft sie automatisch auf, sobald während einer eingestellten Zeitspanne keine Benutzeraktionen [Maus, Tastatur] registriert wurden. Aber ein Bildschirmschoner kennt noch andere Startvarianten: Die Vorschau im Bildschirmschoner-Dialog und den Konfigurations-Dialog, der sich über die Schaltfläche "Einstellungen..." im Bildschirmschoner-Dialog aufrufen lässt. Da der Screensaver wissen muss, wie er starten soll [als Screensaver, im Vorschaumodus oder als Konfigurations-Dialog], erfolgt sein Aufruf mit einem Befehlszeilenparameter. Da der Screensaver je nach Parameter eine andere Aufgabe erfüllen muss, ist es am besten, wenn man ihn über eine Sub Main-Prozedur in einem Modul starten lässt.

'Startprozedur des Screensavers
Private Sub Main()
  sCommand = Command
  sStartMode = UCase(Left(sCommand, 2))
  
  If sStartMode <> "/C" And App.PrevInstance Then
      End
  End If
  
  Select Case sStartMode
      Case "/A"
      'Aufruf für die Kennwortänderung
      Case "/C"
      'Konfigurationsdialog anzeigen
      Case "/P"
      'Preview-Modus
      Case "/S"
      'Screensaver-Modus
  End Select
End Sub

Listing 1: Beispiel

Zuerst wird überprüft, ob eine andere Instanz des Programms bereits läuft

[If ... App.PrevInstance = True ...]
. Wenn das der Fall ist, muss die gestartet Instanz wieder beendet werden. Das ist notwendig, da Windows den Screensaver nicht nur einmal aufruft, sondern immer wieder - und zwar in dem Zeitabstand, den man im Bildschirmschoner-Dialog einstellen kann. Verzichtet man auf diese Überprüfung, könnte es sein, dass der Screensaver mehrmals "übereinander" läuft.

Wenn der oberste beendet wird, läuft einfach der zweite weiter. Für den Benutzer sieht das dann zunächst so aus, als ob sich der Screensaver nie mehr beenden ließe. Die Prüfung, ob nur eine Instanz des Screensavers laufen darf, ist abhängig vom Startmodus. Bei Aufruf des Konfigurations-Dialogs dürfen [und müssen] durchaus zwei Instanzen parallel laufen. Von der Befehlszeile sind nur die ersten beiden Zeichen für eine Erkennung des Startmodus wichtig. Ihnen können jedoch noch Daten folgen, wie später noch dargestellt wird.

Anzeige des Konfigurations-Dialogs  

Beim Konfigurations-Dialog handelt es sich um ein einfaches Dialogfeld, das vom Entwickler selbst implementiert wird. Entsprechend trivial gestaltet sich der Aufruf innerhalb eines VB-Programms:

Case "/C": frmConfig.Show

Über den Konfigurations-Dialog kann der Benutzer die Eigenschaften des Screensavers verändern [z.B. Farbe, Schriftart o. ä.]. Man kann darin aber auch Copyright- oder sonstige Informationen anzeigen. Da der Screensaver bei jedem Start auf die Konfigurationsdaten zugreifen muss, ist es notwendig, die Einstellungen des Benutzers irgendwo zu speichern. Dafür bietet sich natürlich die Registry an - als Alternative kann man aber auch eine INI-Datei einsetzen. Auf die Registry greift man am einfachsten über die VB-Funktionen SaveSetting und GetSetting zu:

SaveSetting AppName, Section, Key, Setting
StringWert = GetSetting(AppName, Section, Key, [Default])

Listing 2

Die Daten, die im Konfigurations-Dialog erfasst und anschließend in der Registry gespeichert wurden, lädt man am besten unmittelbar beim Start des Screensavers im Form_Load-Ereignis der Form.

Private Sub Form_Load()
  tmrRefresh.Interval = Int(GetSetting("ScreenSaver", _
                              "Config", "Interval", 1))
End Sub

Listing 3

Damit kein Fehler auftritt, wenn der Screensaver zum ersten Mal aufgerufen wird, muss man auch hier die Default-Parameter setzen. Man sollte ebenfalls nicht vergessen die neuen Einstellungen zu speichern, wenn der OK-Button des Konfigurations-Dialogs angeklickt wurde.

Private Sub cmdAccept_Click()
  If Not IsNumeric(txtInterval.Text) Then
    MsgBox "Der Interval-Wert muß numerisch sein.", _
           vbCritical, "Fehler:"

    Exit Sub
  End If

  SaveSetting "ScreenSaver", "Config", "Interval", _
              txtInterval.Text

  Unload Me
End Sub

Listing 4

Man kann die Änderungen im Preview-Bildschirm sehen, sobald der Konfigurations-Dialog geschlossen wird. Der eine oder andere fragt sich jetzt vielleicht, warum das funktioniert, obwohl man keine Schnittstelle für das erneute Einlesen der Einstellungen implementiert hat. Da die Kodierung dieser Schnittstelle ziemlich kompliziert wäre, wendet das Betriebssystem einen einfachen Trick an: Sobald der Konfigurations-Dialog eingeblendet wird, wird die Instanz des Screensavers, die im Preview-Bildschirm läuft, beendet. Wenn man dann den Konfigurations-Dialog schließt, wird der Screensaver erneut mit dem Befehlszeilenparameter /P hWnd aufgerufen. Dadurch wird die Initialisierung des Screensavers - inzwischen mit den neuen Einstellungen - erneut ausgeführt.

Der Screensaver-Modus  

Ein Bildschirmschoner besteht im wesentlichen aus einer einzigen VB-Form, die den gesamten Bildschirm ausfüllt, solange der Screensaver aktiv ist. Beim Aufruf des Screensavers muss man daher nur diese Form in den Vordergrund vor alle anderen Fenster bringen. Für diesen Zweck eignet sich die API-Funktion SetWindowPos, die wie folgt deklariert wird:

Public Declare Function SetWindowPos Lib "user32"
       (ByVal hwnd As Long, ByVal hWndInsertAfter _
       As Long, ByVal x As Long, ByVal y As Long, _
       ByVal cx As Long, ByVal cy As Long, ByVal _
       wFlags As Long) As Long

Listing 5

Um den Screensaver in den Vordergrund zu bringen, muss der Parameter hWndInsertAfter auf HWND_TOPMOST gesetzt werden. Damit die Form dann den ganzen Bildschirm ausfüllt, werden x und y auf 0, cx auf Screen.Width und cy auf Screen.Height [jeweils umgerechnet in Pixel-Werte] gesetzt. Schließlich setzt man noch das Flag SWP_SHOWWINDOW, um die Form anzuzeigen. Hier der vollständige Funktionsaufruf:

SetWindowPos Me.hwnd, HWND_TOPMOST, 0, 0, Screen.Width / _
             Screen.TwipsPerPixelX, Screen.Height / _
             Screen.TwipsPerPixelY, SWP_SHOWWINDOW

Listing 6

Screensaver beenden

Damit man später noch weiß, ob das Programm im Screensaver-Modus oder im Preview-Modus läuft, kann man entweder eine boolesche Variable setzen oder einer String-Variablen einen bestimmten Wert zuweisen. Im Beispielprogramm wird der String-Variablen sMode der Wert "SCREENSAVER" zugewiesen, wenn das Programm im Screensaver-Modus läuft. Um den Mauszeiger auszublenden, ruft man die API-Funktion ShowCursor mit dem Parameter False auf:

Public Declare Function ShowCursor Lib "user32" _
       (ByVal bShow As Long) As Long

Listing 7

Naturgemäß soll sie der Screensaver abschalten, wenn die Maus bewegt oder eine Taste gedrückt wird. Dafür fügt man in der Screensaver-Form in den Ereignissen Form_Click, Form_KeyDown, Form_KeyPress, Form_KeyUp, Form_MouseDown, Form_MouseMove und Form_MouseUp folgenden Code ein:

If sMode = SCREENSAVER Then
  Unload Me
End If

Listing 8

Auf diese Weise wird das Programm nur beendet, wenn es sich im Screensaver-Modus befindet. Verzichtet man auf die Überprüfung des Modus, verschwindet auch die Vorschau, sobald man mit der Maus darüber fährt. Das VB-Ereignis Form_MouseMove wird übrigens auch dann aufgerufen, wenn eine Form angezeigt wird. Deshalb sollten Sie in diesem Ereignis nicht nur den Modus des Screensavers abfragen, sondern außerdem überprüfen, ob es schon mehr als einmal aufgerufen wurde.

Private Sub Form_MouseMove(Button As Integer, Shift _
                           As Integer, x As Single, _
                           y As Single)

  Static MouseMoveDummy As Byte

  If sMode <> "SCREENSAVER" Then
    Exit Sub
  End If

  MouseMoveDummy = MouseMoveDummy + 1
  If MouseMoveDummy > 1 Then
    Unload Me
  End If
End Sub

Listing 9

Wenn man den Mauszeiger zu Beginn ausgeblendet haben, sollte man ihn natürlich spätestens im Form_Unload-Ereignis mittels ShowCursor True wieder einblenden.

Animierte Screensaver-Inhalte

Falls man den Screensaver-Inhalt animieren will, empfiehlt sich im einfachsten Fall der Einsatz von Timer-Steuerelementen. Natürlich sind auch andere Vergehensweisen denkbar - etwa DirectX-Animation. Das Beispiel nutzt das Timer-Control.

Screensaver-Inhalte skalieren

Bei jeglicher grafischer Bildschirmausgabe, sollte man drauf achten, ob das Programm im Screensaver-Modus oder im Preview-Modus läuft. Wegen des viel kleineren Fensters im Preview-Modus muss man dort die Größe der gezeichneten Objekte anpassen. Dafür gibt es drei Möglichkeiten:

  1. Man kann die Größe [z. B. einer Schriftart] einfach um einen Faktor (zwischen 5 und 8) verkleinern:

    frmScreenSaver.FontSize = frmScreenSaver.FontSize / 5

    Listing 10

    Dabei ist zu beachten, dass die Schriftart nicht mit jedem Aufruf des Timer-Ereignisses verkleinert wird, sondern nur beim ersten Mal. Für solche Aufgaben wird häufig eine Boolean-Variable verwendet, die vor dem ersten Aufruf auf True und nach dem ersten Aufruf auf False gesetzt wird. In der Timer-Prozedur kann man dann den Wert dieser Variablen abfragen (

    If bFirstPaint = True Then ...
    ).

  2. Man gibt die Größe relativ zur Form an:

    frmScreenSaver.Circle (X, Y), frmScreenSaver.ScaleHeight / 10

    Listing 11

    Sobald die Form verkleinert wird, werden die neu gezeichneten Objekte automatisch um den gleichen Faktor geschrumpft.

  3. Man stellt die ScaleMode-Eigenschaft der Form auf 1 - Benutzerdefiniert. Wenn man der Form dann sowohl im Screensaver-Modus als auch im Preview-Modus die gleichen ScaleWidth- / ScaleHeight-Eigenschaften zuweist, werden alle gezeichneten Objekte im gleichen Verhältnis wie die Form verkleinert. Achtung: Diese Methode funktioniert nur bei Objekten, die sich mit ihrer Maßeinheit auf die ScaleMode-Eigenschaft der Form beziehen (z. B. Circle, Line usw.). Wenn man nicht weiß, ob sich die verwendeten Zeichenmethoden auf die ScaleMode-Eigenschaft bezieht, schlägt man am besten in der VB-Hilfe nach.

Der Preview-Modus  

Fenster-Handles werden zentral vom Betriebssystem verwaltet. Deshalb ist es auch möglich, auf ein Fenster eines anderen Prozesses zuzugreifen, wenn Sie dessen Handle kenne. Das wird beim Preview-Modus ausgenutzt, damit man die Parent-Eigenschaft der Screensaver-Form auf die Handle des Preview-Bidlschirms einstellen kann. Die Screensaver-Form wird dadurch an den Preview-Bildschirm "angeleimt". Dann braucht man nur noch die Größe der Screensaver-Form an die des Preview-Bildschirms anpassen - Die Vorschau befindet sich im Preview-Bildschirm.

Mit der Funktion ExtractHwnd, die sich im Modul des Beispielprogramms befindet, kann die Handle des Preview-Bildschirms aus der Befehlszeile herausgefiltert werden. Die Funktion trennt von links her immer mehr Zeichen vom String sCommand ab, bis sich ein numerischer Ausdruck ergibt.

Die Screensaver-Form an das Preview-Fenster anpassen

Zuerst ist die Größe des Preview-Bildschirms in einer Variablen vom Typ RECT zu speichern. Mit der API-Funktion GetClientRect kann man das Rechteck eines Fensters an eine solche Variable übergeben. Vorher muss man die API-Funktion und den Typ RECT deklarieren:

Public Declare Function GetClientRect Lib "user32" _
       (ByVal hwnd As Long, lpRect As RECT) As Long

Public Type RECT
  Left As Long
  Top As Long
  Right As Long
  Bottom As Long
End Type

Listing 12

Als ersten Parameter der Funktion GetClientRect übergibt man die Handle, die man aus der Befehlszeile extrahiert hat, und als zweiten Parameter eine Variable vom Typ RECT, in der das Recheck des Preview-Bildschirms gespeichert wird.

GetClientRect lPreviewHwnd, PreviewRect

Weiterhin benötigt man noch die API-Funktion GetWindowLong , SetWindowLong und SetParent , um den Preview -Bildschirm zum Parent der Screensaver-Form machen zu können.

Public Declare Function GetWindowLong Lib "user32" _
       Alias "GetWindowLongA" (ByVal hwnd As Long, _
       ByVal nIndex As Long) As Long

Public Declare Function SetWindowPos Lib "user32" _
       (ByVal hwnd As Long, ByVal hWndInsertAfter _
       As Long, ByVal x As Long, ByVal y As Long, _
       ByVal cx As Long, ByVal cy As Long, ByVal _
       wFlags As Long) As Long

Public Declare Function SetParent Lib "user32" _
       (ByVal hWndChild As Long, ByVal hWndNewParent _
       As Long) As Long

Listing 13

Der Window-Style der Screensaver-Form wird anschließend mittels Or mit der Konstanten WS_CHILD addiert, um das Fenster innerhalb des Previews anzuzeigen:

lStyle = GetWindowLong(frmScreenSaver.hwnd, GWL_STYLE)
lStyle = lStyle Or WS_CHILD
SetWindowLong frmScreenSaver.hwnd, GWL_STYLE, lStyle

Listing 14

Mit der SetParent-Funktion kann man den Parent der Screensaver-Form ändern. Man übergibt dazu als ersten Parameter die Handle der Form und als zweiten die Handle des Preview-Bildschirms. Mit der Funktion SetWindowLong kann man noch die Handle des neuen Parent-Fensters übergeben. Dazu setzt man den ersten Parameter wiederum auf die Handle der Screensaver-Form. Als zweiten Parameter gibt man die Konstante GWL_HWNDPARENT an, und den dritten Parameter belegt man mit der Handle des Preview-Bildschirms:

SetParent frmScreenSaver.hwnd, lPreviewHwnd
SetWindowLong frmScreenSaver.hwnd, GWL_HWNDPARENT, _
              lPreviewHwnd

Listing 15

Um die Screensaver-Form an die richtige Position zu bewegen, verwendet man nochmals die Funktion SetWindowPos. Da die Form - die anders als im Screensaver-Modus - nicht mehr zuoberst auf dem Bildschirm sein muss, kann man als zweiten Parameter eine 0 angeben. Die Parameter drei bis sechs sind für die Position des Fensters zuständig. Da jetzt der Preview-Bildschirm Parent der Screensaver-Form ist, stellt man den linken und den oberen Rand auf 0. Damit stimmt die Ecke links oberen mit der des Preview-Bildschirms überein. Als Höhe und Breite übergibt man die Werte der Variablen in der man das Rechteck des Preview-Bildschirms gespeichert hat, als Flags die Konstanten SWP_NOACTIVATE und SWP_SHOWWINDOW. Die erste Konstante bewirkt, dass das Fenster nicht aktiviert wird, die zweite Konstante zeigt das Fenster an.

SetWindowPos frmScreenSaver.hwnd, HWND_TOP, 0, _
             0, PreviewRect.Right, PreviewRect.Bottom, _
             SWP_NOACTIVATE Or SWP_SHOWWINDOW

Listing 16

Man kann anstatt des Screensavers in der Vorschau auch andere Sachen (z. B. Firmenlogo) anzeigen lassen. Hierfür muss das Handle des Screensavers einfach durch das Handle eines anderen Fensters ersetzt werden.

ScreenSaver als Download [30800 Bytes]

Schlusswort  

Ich hoffe, dass der eine oder andere mit meinem kleinen Tutorial etwas anfangen kann, und dass es verständlich genug beschrieben war. Das beigefügte Beispiel soll nochmals eine kleine Anregung für die Programmierung eigener Bildschirmschoner geben.

Gruß
Flipshot

Ihre Meinung  

Falls Sie Fragen zu diesem Tutorial haben oder Ihre Erfahrung mit anderen Nutzern austauschen möchten, dann teilen Sie uns diese bitte in einem der unten vorhandenen Themen oder über einen neuen Beitrag mit. Hierzu können sie einfach einen Beitrag in einem zum Thema passenden Forum anlegen, welcher automatisch mit dieser Seite verknüpft wird.