Introduction

This application is designed to surf through the photos you specify on your computer and present them with a collage/Polaroid type view. It's mostly finished. More or less. I didn't create the code that does the really cool Polaroid effect, that came from the below link. What I did do as add all the other stuff around it.

Original project came from:
http://channel9.msdn.com/coding4fun/articles/Photo-Screensaver

Sorry about sticking the source and binaries on CodePlex - the downloads are reasonably large.

Screenshots - Configuration

screen1.jpg

screen2.jpg

screen3.jpg

screen4.jpg

Screenshots - In Action!

Multi_Small.jpg

Couple things first

  • There may be profanity or bad, bad, bad words in the quotes and words.
  • In fact, there is. I haven't taken them out.
  • Some events or text may be too long and it gets cut off.
  • So keep your custom quotes of wisdom to smallish text.
  • This project came from an open source project which provided my starting point.
  • But was a pretty cool app to get me started on this.
  • I've rewritten the bulk of it and now it's VB.NET.
  • Has little resemblence to original project.
  • It displays quotes, events and word definitions. That's why it's just about 14MB.
  • It was originally written with most values hard coded.
  • I've put in the ability to change a lot of what you can do.
  • Some font combos won't work, really dependant on font you choose.
  • If you're scared, just go with the defaults.
  • They've been tried and tested.
  • If you stuff things up, hit reset.
  • There is a single thread for painting. For each form.
  • Most collections and thread-safe and sync locks are used.
  • .NET 4.0 is required. I'll be surprised if you're reading this in the app without it.
  • Give us a shot if you want something done to it.
  • Source code is available to those who want it.
  • Data is Unicode. So go crazy with special characters.
  • Alot of options can be configured - not everything, but you can customize it quite abit.

A note on photo dates and captions

  • The date comes from the Date Taken of a photo.
  • This isn't available on all photos, in case of null, nothing is displayed.
  • The caption comes from the photo title (again, metadata).
  • If that's not available, the filename is used.
  • If odd characters are present, the caption is not used.

Directories

  • Enter directories to search here.
  • If you include a directory with child directories, they are included automatically.
  • If you enter a child directory when you have entered the parent, that's handled.

Sidebar

  • The sidebar displays quotes, events, your quotes, and word definitions.
  • You can turn these on or off. Depending on your fancy.
  • Play around with the values to see what happens.
  • Use fonts wisely. Big fonts will probably screw things up.

Custom Quotes

  • Enter your own quotes in here, something like: Quote text|Quote Author Quote text>Another line|Quote Author
  • Use > character for new line.
  • If you actually want the > character, use another one. Like › or something else.

Advanced

  • When the text on the right is drawn, it piggy backs on the event for when a photo is drawn.
  • There is a reason why the times are linked (ie. read above point).
  • Opacity is well... opacity.
  • Caption date format is handy to change.
  • Enclose characters in double quotes when you want to use them.
  • Like "date:" (the d won't be considered the day then).
  • Fonts can be changed, but change them wisely.
  • Bold fonts on the photos only suit certain fonts.
  • Forcing the text to lowercase wasn't done on the captions.
  • That's because casing is sometimes wanted for captions.
  • Like OMG. Or I feel SUPER DUPER Captain AWESOME!!!!
  • Note the use of capitals.
  • Layout can't be changed.
  • Percentages were put in so not to use all of the data.
  • Percentages indicate a ceiling is used on data, but the arrays are shuffled.
  • That means the same data is not used all the time.

How This Works

OK, now we've gotten some of the basic stuff out of the way, how does this work? If you didn't know, screensavers tend to just be EXE's renamed as SCR files. Stick it in a directory like C:\Windows, and it just runs. You do have to do some extra coding, but for the most part apps run fine. Basically when the app starts up, this is done:

    <stathread()> _
    Public Shared Sub Main(ByVal args() As String)
        ' no args, if the exe is a SCR, run a screensaver.
        ' otherwise start as app.
        If args.Length <= 0 Then
            _RunningAsScreensaver = Application.ExecutablePath.ToUpper().EndsWith(".SCR")
            If RunningAsScreensaver Then
                ShowScreenSaver()
            Else
                ShowDesktopApp()
            End If
        Else
            ' we have args, determine what todo.
            Dim str As String = args(0).ToLower().Trim().Substring(0, 2)
            Select Case str
                Case "/c"
                    ' start in config mode.
                    ' read in full quote database.
                    ReadAllData(True)
                    Dim form As New ConfigForm()
                    form.ShowDialog()
                    Return

                Case "/p"
                    Return

                Case "/d"
                    ' start as desktop app.
                    ShowDesktopApp()
                    Return

                Case "/s"
                    ' start as screensaver app.
                    ShowScreenSaver()
                    Return
            End Select
            MessageBox.Show("Invalid command line argument :" & str, "Invalid Command Line Argument", MessageBoxButtons.OK, MessageBoxIcon.Hand)
        End If
    End Sub
</stathread()>
So we first check to see if we have arguments, if we don't check the extension. If we've got an SCR, run as a screensaver. If we don't, run the app as a desktop application. If we run as a screensaver, check to see if /c was passed in. If it has, we start in config mode (Windows will pass in a /c for the settings button when selecting a screensaver).

If running as a desktop app, we just show the screensaver with the exception you need to press Escape to exit. We also only show the app on one monitor. There is a form which loads which asks the user which screen to use (this code is from the original article).

If running as a screensaver, we enumerate through the screens and we create a screensaver form for each screen on the system (so, multi-monitor friendly).

There is not a whole lot more done on app start up, except for reading in data. In addition to displaying photos, you can display quotes, your own quotes, world event data (from Wikipedia, collected around 2009ish) and word definitions. Be warned there are younger audience unfriendly words in there. Most of the code is straight forward, some of the code is pretty messsy (this was an app just for me when I made it, decided not to be selfish!). Some of the more cooler aspects include array shuffling and using a ceiling on the amount of quotes and words to use. I put this in in case you have your own quotes so that the chances of getting your quotes used rather than system quotes would be greater.

    Private Shared Sub ShuffleArray(ByVal MyArray As List(Of DisplayItem))
        SyncLock MyArray
            Dim num As Integer = 0
            For i As Integer = 0 To (MyArray.Count - 1) - 1
                Do While num = i
                    num = _MyRandom.Next(0, MyArray.Count)
                Loop
                Dim info As DisplayItem = MyArray(i)
                MyArray(i) = MyArray(num)
                MyArray(num) = info
                num = i + 1
            Next i
        End SyncLock
    End Sub

The code above shuffles the array of DisplayItem's. Pretty nifty stuff as it further randomizes what data is picked.

    Public Shared Sub ConfigureCeilings()
        If My.Settings.PercentQuotes <> 100 Then
            Dim percent As Double = My.Settings.PercentQuotes * 0.01
            _QuoteCeiling = CInt(Fix(Program.Quotes.Count * percent))
        Else
            _QuoteCeiling = Program.Quotes.Count
        End If
        '
        If My.Settings.PercentWords <> 100 Then
            Dim percent As Double = My.Settings.PercentWords * 0.01
            _WordsCeiling = CInt(Fix(Program.Words.Count * percent))
        Else
            _WordsCeiling = Program.Words.Count
        End If
    End Sub

This code is what configures the ceilings for how much data is used. At all times, all the data is read in, we just put a limit on it.

Once we've read in all the data, we then create a photo queue. The job of this is to read in all the images in the directories specified. This is the biggest change from the original project. I wanted to manually specify what pictures to show rather than have it come from somewhere else. Supported images are bmp, gif, jpg/jpeg.

A photo queue consists of a list of PhotoInfo classes. The PhotoInfo classes are important because they represent a photo on the collage. This class basically does the image reading, image resizing and also extracts metadata from the image. We basically try to get the date taken and title of the photo from metadata. Some photos don't have that info though and in that case we default to the file name.

The Screensaver Form

The bulk of the work is done in the screensaver form. This is just a normal Form that looks out for mouse movement and keystrokes to close the screensaver. The constructor is a little different and when you create an instance of a form, it needs to know what screen to run on and the source of photos. For multimonitor setups, multiple forms are created and displayed on each screen.

Initialization is done on this form and if figures out the delay interval for each new photo and also the reset interval which will clear the desktop back to it's original state (that state being the snapshot taken when the screensaver was first started. It also starts the background thread which manages the calling of methods to put new photos on.

If we in debugging mode, we set the opacity to make life easier while debugging:

            If Debugger.IsAttached Then
                MyBase.Opacity = 0.8
                MyBase.TopMost = False
            End If
When we get the background image, we can do this via:
        Public Function GetBackgroundImage() As Bitmap
            Dim image As Bitmap = Nothing
            Dim graphics As Graphics
            Dim bounds As Rectangle = Me._HomeScreen.Bounds
            image = New Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb)
            graphics = graphics.FromImage(image)
            Using graphics
                graphics.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size, CopyPixelOperation.SourceCopy)
            End Using
            Return image
        End Function

When the background worker thinks an image needs to be drawn onto the collage, it creates a snapshot of the image (CreateSnapshotImage), draws the image rotated (DrawImageRotated), and then calls the refresh handler (_RefreshHandler). So at this point we have a new image that needs to be drawn, but we need to actually now draw that image onto the main canvas (the form). To trigger that, all we do is call Refresh on the form and this code will kick in:

        Protected Overrides Sub OnPaintBackground(ByVal e As PaintEventArgs)
            Dim currentImage As Bitmap = Me.GetCurrentImage()
            e.Graphics.DrawImage(currentImage, 0, 0, currentImage.Width, currentImage.Height)
            '
            Try
                If (My.Settings.AllBlacksMode) Then
                    Dim y As Integer
                    Dim x As Integer
                    '
                    If (My.Settings.SidebarPosition.ToLower() = "left") Then
                        x = (Me._HomeScreen.Bounds.Width - My.Resources.AllBlacks.Width) - 50
                    Else
                        x = 50
                    End If
                    y = CInt((Me._HomeScreen.Bounds.Height - My.Resources.AllBlacks.Height) / 2)
                    '
                    e.Graphics.FillRectangle(Me._DrawBrushSidebar, 0, 0, Me._HomeScreen.Bounds.Width, Me._HomeScreen.Bounds.Height)
                    e.Graphics.DrawImage(My.Resources.AllBlacks, x, y, My.Resources.AllBlacks.Width, My.Resources.AllBlacks.Height)
                Else
                    ' if we have no photos at all, draw a background.
                    If (Me._PhotoSource.PhotoCount = 0) Then
                        e.Graphics.FillRectangle(Me._DrawBrushSidebar, 0, 0, Me._HomeScreen.Bounds.Width, Me._HomeScreen.Bounds.Height)
                    End If

                    ' dim the background if user wants.
                    ' don't do it twice though (if we have no photos).
                    If (My.Settings.DimScreen AndAlso Me._PhotoSource.PhotoCount > 0) Then
                        e.Graphics.FillRectangle(Me._DrawBrushSidebar, 0, 0, Me._HomeScreen.Bounds.Width, Me._HomeScreen.Bounds.Height)
                    End If
                End If
                '
                Select Case My.Settings.BarDrawFormat.ToLower()
                    Case "none"
                        ' don't do anything
                        Exit Select
                    Case "all"
                        ' draw on all monitors.
                        Me.DrawSidebar(e)
                        Exit Select
                    Case "nonprimary (s)"
                        If Me._HomeScreen.Primary = False Then
                            Me.DrawSidebar(e)
                        End If
                        Exit Select
                    Case "primary"
                        If Me._HomeScreen.Primary Then
                            Me.DrawSidebar(e)
                        End If
                        Exit Select
                    Case Else
                        Me.DrawSidebar(e)
                        Exit Select
                End Select

            Catch ex As Exception
            End Try
        End Sub

The job of this code is to get the currently drawn image which is just the collage of photos, no overlay and draw that onto the canvas. Depending on the options the user has enabled, we draw the awesome All Blacks mode, dim the screen and draw the sidebar.

The DrawSidebar will then draw the date and time and also the quote/event/word text.

That's about it really! Most of the collage code itself comes from the link at the top, I didn't create that code and I've probably butchered a lot of that code, but I just wanted to take that project and show photos from directories I specified and display some cool info.

Points of Interest

  • Don't stick periods/full stops in the file name apart from extension.
  • Periods tend to confuse Windows as to what the screensaver name.
  • When creating .NET screensaver apps, I found it easier to not use the Application Framework because I wanted access to Main and not actually have a main form.

One actual problem that took me a bit to figure out was how to retrieve the events that occurred today. What I ended up doing for that was:

    Public Shared ReadOnly Property RandomEvent() As DisplayItem
        Get
            If Events.Count <= 0 Then
                Return Nothing
            Else
                ' get a filtered list of events that happened on this day and month.
                Dim FilteredEvents As List(Of DisplayItem)
                FilteredEvents = Events.FindAll(
                    Function(dItem As DisplayItem) dItem.Day = DateTime.Now.Day AndAlso dItem.Month = DateTime.Now.Month)

                ' we have our list, get a random one in that list.
                Dim pos As Integer = _MyRandom.Next(0, FilteredEvents.Count)
                Return FilteredEvents(pos)
            End If
        End Get
    End Property
Basically this code will return a random event that happened on the day the code executes.
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"