Selbst einen Flickr Screensaver in VB.net programmieren

ScreensaverJa, ich lebe in einer dunklen Welt: in diese Welt sind die memory_limits noch auf 8MB, viele Pixel auf dem Monitor sind untot, Slashdot ist wegen zuviel Arbeit nicht mehr in der History meiner Browserleiste und aus hier nicht näher zu spezifizierenden Gründen muss ich mit Visual Basic 2005 Express Edition arbeiten. Und das in einer VMWare Umgebung unter Linux. Da kommt Freude auf!

Nundenn, da sucht man sich doch einfach mal ein nettes Projekt um den Einstieg etwas zu erleichtern. Meine Wahl fiel auf einen Bildschirmschoner, der in regelmässigen Abständen Bilder von flickr darstellen soll.

Benötigte Dinge

Um möglichst schmerzfrei auf Flicker zugreifen zu können, benutze ich die FlickrNet API Library von Codeplex. Diese Library ist relativ gut dokumentiert und hat einen irgendwie offiziellen Touch.

Ausserdem benutze ich das Screensaver Starter Kit, das bei Visual Basic dabei ist um das grundlegende Gerüst zu erzeugen. Es kann angewählt werden, wenn ein neues Projekt erstellt wird.

Lustige Details und Probleme

Generell kann man mal ganz viel aus dem generierten Code löschen. Interessierte seien auf das beiliegende Projekt verwiesen.

FlickrNet API Library

Zuerst muss die FlickerNet.dll in das Projekt kopiert werden. Neuere Versionen haben bei mir Probleme gemacht, was aber gar nichts heissen muss. Damit alle diese Fehler ausgeschlossen werden können, habe ich eine aus einem funktionierenden Beispiel herauskopiert. Bei My Project → Verweise muss diese DLL hinzugefügt und der Namespace importiert werden.
Bei Flickr muss noch einen API-Key gelöst werden und dann steht dem Einsatz nichts mehr im Wege.

Bildschirmschoner

Ein Screensaver ist nichts anderes als ein grosses Form ohne Fensterränder, das sich immer nach vorne zwängt und den Mauszeiger abschaltet. Ein Bildschirmschoner sollte drei Modi unterstützen:

  • Darstellen
  • Preview beim Windows-Konfigurationsdialog
  • Einstellungen

Die Hauptarbeit, nämlich die Darstellung des eigentlichen Screensavers wird im ScreenSaverForm gemacht, Die Einstellungen werden über das OptionsForm abgehandelt.

Mit der folgenden Funktion wird die URL von ca. 100 Fotos im Feld allPhotos gespeichert:

    ''' <summary>
    ''' Lädt die URLs von einigen Bilder zum gegebenen Tag von Flickr in ein Feld
    ''' </summary>
    Private Sub LoadBackgroundImage()
        Dim f As Flickr = New Flickr(apikey)

        Dim options As PhotoSearchOptions = New PhotoSearchOptions()
        options.Tags = My.Settings.Tag
        Dim bgPhotos As Photos = f.PhotosSearch(options)

        allPhotos = bgPhotos.PhotoCollection
        currentImageIndex = 0

    End Sub

Ein Timer erhöht den Index des aktuell darzustellenden Bildes:

    Private Sub backgroundChangeTimerTick(ByVal sender As Object, ByVal e As EventArgs) Handles backgroundChangeTimer.Tick

        ' Hintergrundbild ändern und nächstes Bild verwenden.
        currentImageIndex = currentImageIndex + 1
        Refresh()
    End Sub

Dieses Codesnipped lädt ein Bild mit einem Index herunter uns stellt es dar:

    Protected Overrides Sub OnPaintBackground(ByVal e As PaintEventArgs)
        If allPhotos.Length > 0 Then
            currentImageIndex = currentImageIndex Mod allPhotos.Length
            ' Aktuelles Hintergrundbild gestreckt zeichnen, sodass es den gesamten Bildschirm ausfüllt
            Dim f As Flickr = New Flickr(apikey)
            Dim currentImage As Image
            currentImage = Image.FromStream(f.DownloadPicture(allPhotos.Item(currentImageIndex).MediumUrl))
            e.Graphics.DrawImage(currentImage, 0, 0, Size.Width, Size.Height)
        Else
            myError = "No Photos for this Tag"
        End If

    End Sub

Weiter werden Tastatureingaben und Mausbewegungen abgefangen. Wenn der Screensaver nicht im Previewmodus ist, beendet er sich bei einer Benutzerreaktion.

Das Preview

Beim Screen Saver Starter Kit fehlt das Preview. Irgendwo haben die Hersteller geschrieben, das sei Absicht, damit die Entwickler das erweitern könnten. <laut>Haha!</laut>. Der Grund wurde mir dann schon klar. Bei einem Aufruf im Preview-Modus wird ein Windowshandle des Elternfensters mitgegeben. Da muss man sich als Kind eintragen und das geht mit dem super .net nicht so einfach. Dank VB-Helper und msdner (leider offline: http://www.msdner.com/dev-archive/6/10-32-68963.shtm) habe ich dann eine Lösung gefunden und implementieren können.

Der grösste Teil dieses magischen Codes steht in MyEvents.vb. Um das zu sehen muss man im Menü Projekt → Alle Dateien anzeigen auswählen und den Ast My Project aufklappen. Dort drin befindet sich die Unterscheidung der Modis, etc:

        Public Declare Function GetClientRect Lib "user32" (ByVal hwnd As Integer, ByRef lpRect As RECT) As Integer
        Public Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Integer, ByVal nIndex As Integer) As Integer
        Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Integer, ByVal nIndex As Integer, ByVal dwNewInteger As Integer) As Integer
        Public Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Integer, ByVal hWndInsertAfter As Integer, ByVal x As Integer, ByVal y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal wFlags As Integer) As Integer
        Public Declare Function SetParent Lib "user32" (ByVal hWndChild As Integer, ByVal hWndNewParent As Integer) As Integer
        Public Declare Sub Sleep Lib "kernel32.dll" (ByVal dwMilliseconds As Long)
        Public Structure RECT
            Public left As Integer
            Public top As Integer
            Public right As Integer
            Public bottom As Integer
        End Structure
        Public Class Win32Corners
            Public Const SWP_NOACTIVATE As Object = &H10
            Public Const SWP_NOZORDER As Object = &H4
            Public Const SWP_SHOWWINDOW As Object = &H40
            Public Const GWL_STYLE As Object = -16
            Public Const WS_CHILD As Object = &H40000000
            Public Const GWL_HWNDPARENT As Object = -8
            Public Const HWND_TOP As Object = 0
        End Class

        Private Sub SetForm(ByRef f As ScreenSaverForm, ByRef arg As Long)
            Dim style As Integer
            Dim previewHandle As Integer = Int32.Parse(CType(arg, String))
            Dim r As New RECT
            GetClientRect(previewHandle, r)
            With f
                .WindowState = FormWindowState.Normal
                .FormBorderStyle = FormBorderStyle.None
                .Width = r.right
                .Height = r.bottom
            End With
            style = GetWindowLong(f.Handle.ToInt32, Win32Corners.GWL_STYLE)
            style = style Or Win32Corners.WS_CHILD
            SetWindowLong(f.Handle.ToInt32, Win32Corners.GWL_STYLE, style)
            SetParent(f.Handle.ToInt32, previewHandle)
            SetWindowLong(f.Handle.ToInt32, Win32Corners.GWL_HWNDPARENT, previewHandle)
            SetWindowPos(f.Handle.ToInt32, 0, r.left, 0, r.right, r.bottom, Win32Corners.SWP_NOACTIVATE Or Win32Corners.SWP_NOZORDER Or Win32Corners.SWP_SHOWWINDOW)
        End Sub

        Private Sub MyApplication_Startup(ByVal sender As Object, ByVal e As ApplicationServices.StartupEventArgs) Handles Me.Startup
            'Dunno why this is needed
            My.Settings.RunsAsPreview = False
            If e.CommandLine.Count > 0 Then
                ' Aus zwei Zeichen bestehendes Befehlszeilenargument abrufen
                Dim arg As String = e.CommandLine(0).ToLower(System.Globalization.CultureInfo.InvariantCulture).Trim().Substring(0, 2)
                Select Case arg
                    Case "/c"
                        ' Dialogfeld "Optionen" anzeigen
                        Me.MainForm = My.Forms.OptionsForm
                    Case "/p"
                        My.Settings.RunsAsPreview = True
                        Me.MainForm = My.Forms.ScreenSaverForm
                        SetForm(Me.MainForm, e.CommandLine(1))
                    Case "/s"
                        My.Settings.RunsAsPreview = False
                        Me.MainForm = My.Forms.ScreenSaverForm
                    Case Else
                        MessageBox.Show("Ungültiges Befehlszeilenargument:" + arg, "Ungültiges Befehlszeilenargument", MessageBoxButtons.OK, MessageBoxIcon.Error)
                End Select
            Else
                ' Wenn keine Argumente übergeben wurden, Bildschirmschoner anzeigen
                Me.MainForm = My.Forms.ScreenSaverForm
            End If

        End Sub

        'OnInitialize wird für die erweiterte Anpassung des eigenen Anwendungsmodells (MyApplication) verwendet.
        'Der Startcode für Ihre spezielle Anwendung sollte in einem Startereignishandler positioniert werden.
        <Global.System.Diagnostics.DebuggerStepThrough()> _
        Protected Overrides Function OnInitialize(ByVal commandLineArgs As System.Collections.ObjectModel.ReadOnlyCollection(Of String)) As Boolean
            Return MyBase.OnInitialize(commandLineArgs)
        End Function

Installation

Im bin/Release/ Verzeichnis gibt es nach dem Erstellen des Projektes mehrere Dateien. Damit die Geschichte läuft, muss man FlickrSaver.exe in FlickrSaver.scr und FlickrSaver.exe.config muss in FlickrSaver.scr.config umbenamsen. Danach können die 3 Dateien:

  • FlickrSaver.scr
  • FlickrSaver.scr.config
  • FlickerNet.dll

ins c:\WIndows\system32 Verzeichnis verschoben und als Bildschirmschoner aufgerufen werden.

Viel Spass mit dem Code

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.