Ja, 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:
''' 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:
' 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:
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 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…