{"id":3143,"date":"2009-01-23T21:30:00","date_gmt":"2009-01-23T21:30:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/vbteam\/2009\/01\/23\/an-updated-screensaver-example-matt-gertz\/"},"modified":"2024-07-05T13:33:52","modified_gmt":"2024-07-05T20:33:52","slug":"an-updated-screensaver-example-matt-gertz","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/vbteam\/an-updated-screensaver-example-matt-gertz\/","title":{"rendered":"An Updated Screensaver Example (Matt Gertz)"},"content":{"rendered":"<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">I\u2019ve just completed a task that I set out to do about five years ago, and I am pretty proud (and tired)!<span>&nbsp; <\/span>I have just finished scanning in every single photo that I\u2019ve acquired over my 40+ years of life, fixing up their dates to reflect the date when the picture was taken, not when it was scanned.<span>&nbsp; <\/span>Furthermore, I have tagged every photo on the disk drive (scanned or originally digital) with metadata saying who was in the photo and where it was taken. All of my family videos have been scanned in as well, and all of that work (plus all of my music tracks and personal documents) is now backed up weekly onto a 1.5TB disk that I store in a fireproof safe.<span>&nbsp;&nbsp; <\/span>I\u2019ve now got over 16,000 photos and 20+ hours of video on the system, going all the way back to the year 1875 (a picture of my great-great-grandparents on their wedding day), all of which are (hopefully) now preserved against tragic circumstances.<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Of course, having done all that work, I wanted to relax and view the photos!<span>&nbsp; <\/span>I didn\u2019t want to buy one of those picture frames that can do slide shows, since they are expensive and I\u2019d have to find one that would support a big enough (and also expensive) memory card \u2013 it\u2019s 22GB worth of pictures.<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">My first choice was to use the screensaver that comes with Windows.<span>&nbsp; <\/span>I run both Windows Vista and Windows XP at home (and Windows7 at work!), and the photo screensaver that comes with them is pretty decent in both cases.<span>&nbsp; <\/span>But, after a while, I got tired of my kids asking \u201cWho is that?<span>&nbsp; <\/span>When was that?<span>&nbsp; <\/span>Where was that?\u201d over and over again.<span>&nbsp; <\/span>If only I had a setting on the screensaver that would show the \u201cDateTaken\u201d property, and the Tags associated with the photo\u2026<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Well, of course, this was a good opportunity for me to learn about screensavers, which I hadn\u2019t really ever thought much about before. <span>&nbsp;<\/span>And thus began the journey\u2026<\/font><\/p>\n<h2><font color=\"#4f81bd\" size=\"4\" face=\"Cambria\">What is a screensaver?<\/font><\/h2>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">A screensaver is just an application renamed to have a .SCR extension, and placed in the Windows system directory.<span>&nbsp; <\/span>That\u2019s it.<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Now, of course, the application should be a little special.<span>&nbsp; <\/span>It needs to take up the whole screen, it shouldn\u2019t have borders, menus, captions, or any other decorations, and it needs to turn itself off when the mouse moves.<span>&nbsp; <\/span>Furthermore, it needs to expose its options in a certain way, so that the user can set them in the screensaver control panel.<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">I found out that MSDN has an example of a screensaver built using VS2003-level functionality, but targeting .NET 2.0.<span>&nbsp; <\/span>You can find it <\/font><a href=\"http:\/\/www.microsoft.com\/downloads\/details.aspx?FamilyID=f0288df5-01d8-4246-b755-5237e08b0016&amp;displaylang=en\"><font color=\"#800080\" size=\"3\" face=\"Calibri\">here<\/font><\/a><font size=\"3\"><font face=\"Calibri\">, and I\u2019ll be referring back to it throughout this blog, much of which won\u2019t make sense unless you\u2019ve read through that code (consider it as homework <\/font><span><span>J<\/span><\/span><font face=\"Calibri\">).<span>&nbsp; <\/span>That screensaver draws random shapes on the screen and gives you a few options to change the speed and quality of the shapes.<span>&nbsp; <\/span>However, it doesn\u2019t leverage any of the new coolness we\u2019ve introduced in recent versions of Visual Basic, so I figured that it was time for a screensaver makeover.<span>&nbsp;&nbsp; <\/span>I copied it down to my machine and opened it up.<span>&nbsp; <\/span>It has three classes in it \u2013 an Options class, an Options form, and a Screensaver form. We\u2019ll discuss these in order.<\/font><\/font><\/p>\n<h2><font color=\"#4f81bd\" size=\"4\" face=\"Cambria\">The options class<\/font><\/h2>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">The options class defines settings that the user wants to persist between runs, and also provides a mechanism for strong them.<span>&nbsp; <\/span>The original settings were:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> m_Speed <span>As<\/span> <span>String<\/span> = <span>&#8220;Fast&#8221;<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> m_Shape <span>As<\/span> <span>String<\/span> = <span>&#8220;Ellipse&#8221;<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> m_IsTransparent <span>As<\/span> <span>Boolean<\/span> = <span>True<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">I changed these to:<\/font><\/p>\n<p class=\"MsoNormal\"><span>#Region<\/span><span> <span>&#8220;Member variables&#8221;<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> m_Speed <span>As<\/span> <span>Integer<\/span> = 6<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> m_ShowTags <span>As<\/span> <span>Boolean<\/span> = <span>True<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> m_ShowDates <span>As<\/span> <span>Boolean<\/span> = <span>True<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> m_Directory <span>As<\/span> <span>String<\/span> = <span>&#8220;&#8221;<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> m_UseSubdirectories <span>As<\/span> <span>Boolean<\/span> = <span>True<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>#End<\/span><span> <span>Region<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Basically, I\u2019m going to let the used specify the time (in seconds) that the picture should stay up, whether or not to show the tags and the date superimposed on the photo,<span>&nbsp; <\/span>where to look for photos, and whether or not to search subdirectories of that directory.<span>&nbsp; <\/span>I also went ahead &amp; generated get\/set properties for each of those, ripping out the old ones.<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Now, the rest of this file spends a lot of its code in opening up an options file, reading the data, writing to it, etc.<span>&nbsp; <\/span>Fortunately, we don\u2019t need any of that code anymore.<span>&nbsp; <\/span>To set up automatic setting storage, I simply right-clicked the project, chose \u201cProperties,\u201d navigated to the Settings tab, and entered in my five settings as given above.<span>&nbsp; <\/span>My entire persistence code then became:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Public<\/span> <span>Sub<\/span> LoadOptions()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.Speed = <span>My<\/span>.Settings.Speed<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.ShowDate = <span>My<\/span>.Settings.ShowDate<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.ShowTags = <span>My<\/span>.Settings.ShowTags<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.Directory = <span>My<\/span>.Settings.Directory<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>&nbsp;&nbsp;&nbsp;&nbsp;<\/span><span>Me<\/span>.UseSubdirectories = <span>My<\/span>.Settings.UseSubdirectories<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> <span>String<\/span>.IsNullOrEmpty(Directory) <span>Then<\/span> <span>&#8216; Default to &#8220;My Pictures&#8221; if not initialized<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.Directory = <span>My<\/span>.Computer.FileSystem.SpecialDirectories.MyPictures<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Sub<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Public<\/span> <span>Sub<\/span> SaveOptions()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>My<\/span>.Settings.Speed = <span>Me<\/span>.Speed<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>My<\/span>.Settings.ShowDate = <span>Me<\/span>.ShowDate<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>My<\/span>.Settings.ShowTags = <span>Me<\/span>.ShowTags<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> <span>Not<\/span> <span>String<\/span>.IsNullOrEmpty(Directory) <span>Then<\/span> <span>&#8216; Don&#8217;t bother saving<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>My<\/span>.Settings.Directory = <span>Me<\/span>.Directory<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>My<\/span>.Settings.UseSubdirectories = <span>Me<\/span>.UseSubdirectories<\/span><\/p>\n<p><span><span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>My<\/span>.Settings.Save() <span>&#8216; Necessary since we&#8217;ll be forcing application exits later<\/span><\/span><\/p>\n<p class=\"MsoNormal\">&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Sub<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><font size=\"3\"><font face=\"Calibri\">eliminating about 40 lines of code devoted to serializing out the class.<\/font><\/font><\/span><\/p>\n<h2><span><font size=\"4\"><font color=\"#4f81bd\"><font face=\"Cambria\">The options form<\/font><\/font><\/font><\/span><\/h2>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Of course, by that point, I was getting build errors all over the place, since I was now out of synch with the options form and the screensaver form.<span>&nbsp; <\/span>Let\u2019s take a look at the options form.<span>&nbsp; <\/span>For clarity\u2019s sake, I wanted to hide the form-initialization code that VS2003 used to shove into the class file.<span>&nbsp; <\/span>I created a new file in the project, named it \u201cfrmOptions.Designer.vb\u201d, and typed the following into it:<\/font><\/p>\n<p class=\"MsoNormal\"><span>&lt;<span>Global<\/span>.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()&gt; _<\/span><\/p>\n<p class=\"MsoNormal\"><span>Partial<\/span><span> <span>Class<\/span> frmOptions<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Inherits<\/span> System.Windows.Forms.Form<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span>End<\/span><span> <span>Class<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">I then copied all of the generated crud into that partial class and got it out of the way.<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">All I really needed to do now is to replace the existing controls with three checkboxes (tags,date, and subdirectories) and two edit boxes (speed and directory).<span>&nbsp; <\/span>That\u2019s easy enough to do.<span>&nbsp; <\/span>However, not content with doing it the easy way, I decided to limit the \u201cspeed\u201d text box so that it can only accept numbers.<span>&nbsp; <\/span>There\u2019s a VB snippet that does exactly that for combo boxes (\u201cWindows Forms Applications\u201d-&gt; Forms-&gt;Restrict a Control\u2019s Acceptable Keystrokes\u201d), and I\u2019ve modified it for my edit box:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Class<\/span> restrictedTextBoxClass<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Inherits<\/span> TextBox<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Const<\/span> WM_KEYDOWN <span>As<\/span> <span>Integer<\/span> = &amp;H100<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Protected<\/span> <span>Overrides<\/span> <span>Function<\/span> ProcessCmdKey _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>(<span>ByRef<\/span> msg <span>As<\/span> Message, _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>ByVal<\/span> keyData <span>As<\/span> Keys) <span>As<\/span> <span>Boolean<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> msg.Msg = WM_KEYDOWN <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Return<\/span> <span>Not<\/span> ((keyData &gt;= Keys.D0 <span>And<\/span> keyData &lt;= Keys.D9) _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Or<\/span> keyData = Keys.Back <span>Or<\/span> keyData = Keys.Left _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&nbsp;&nbsp;&nbsp;&nbsp;<\/span><span>Or<\/span> keyData = Keys.Right <span>Or<\/span> keyData = Keys.Up _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Or<\/span> keyData = Keys.Down <span>Or<\/span> keyData = Keys.Delete)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Return<\/span> <span>MyBase<\/span>.ProcessCmdKey(msg, keyData)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Function<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Class<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">&nbsp;<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">Then, given that, I reopened the <\/font><span>frmOptions.Designer.vb<\/span><font face=\"Calibri\"> file that I just created (<i>can\u2019t find it?<span>&nbsp; <\/span>Make sure that \u201cShow All Files\u201d is turned on in the Solution Explorer\u2019s tool bar<\/i>) and changed <b>TextBox<\/b> to <b>restrictedTextBoxClass<\/b> in two locations:<\/font><\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Friend<\/span> <span>WithEvents<\/span> txtSpeed <span>As<\/span> frmOptions.restrictedTextBoxClass<\/span><\/p>\n<p class=\"MsoNormal\"><span><font face=\"Calibri\">\u2026<\/font><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.txtSpeed = <span>New<\/span> ScreenSaver.frmOptions.restrictedTextBoxClass<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">I had to explicitly *rebuild* at this point \u2013 otherwise, the designer wouldn\u2019t have known how to render the control if I were I to switch to the designer.<\/font><\/p>\n<h2><font color=\"#4f81bd\" size=\"4\" face=\"Cambria\">The screensaver form<\/font><\/h2>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Here\u2019s where the real work happened.<span>&nbsp; <\/span>Using the error messages in the Error List, I ripped out the lines of code that referred to things that didn\u2019t exist anymore (like graphics, eliipses, etc).<span>&nbsp; <\/span>I also moved the initialization crud into a partial class file, like I did for the options dialog.<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">The form used doesn\u2019t actually start out with any controls on it. I set its <b>BackColor<\/b> to black, and then I added two controls (and the order was important):<\/font><\/p>\n<p class=\"MsoListParagraphCxSpFirst\"><span><span><font size=\"3\" face=\"Calibri\">(1)<\/font><span>&nbsp;&nbsp;&nbsp; <\/span><\/span><\/span><font size=\"3\" face=\"Calibri\">A PictureBox control which I sized to be the same size as the form.<span>&nbsp; <\/span>I set its <b>BackColor<\/b> to be black, and set its <b>SizeMode<\/b> property to <b>Zoom<\/b>.<span>&nbsp; <\/span>(I will not be doing any cutesy transition effects \u2013 I dislike those intensely, so I\u2019ll leave that as an exercise for the reader.)<\/font><\/p>\n<p class=\"MsoListParagraphCxSpLast\"><span><span><font size=\"3\" face=\"Calibri\">(2)<\/font><span>&nbsp;&nbsp;&nbsp; <\/span><\/span><\/span><font size=\"3\" face=\"Calibri\">A label control with the <b>BackColor<\/b> set to black and the <b>ForeColor<\/b> set to White.<span>&nbsp; <\/span>My default text for this control was \u201cLoading Pictures\u2026\u201d<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">There\u2019s nothing else I have to do to the form itself \u2013 it\u2019s already set up to fill the monitor (<b>WindowState<\/b>=<b>Maximized<\/b>), decorations are turned off (<b>ControlBox<\/b> = False, <b>MinimizeBox<\/b> and <b>MaximizeBox<\/b> are False, <b>ShowInTaskbar<\/b> = False, <b>SizeGripStyle<\/b>=Hide), and <b>TopMost<\/b> is equal to True.<span>&nbsp; <\/span>Note that you will want to temporarily change <b>TopMost<\/b> to be False when developing &amp; debugging; otherwise, you won\u2019t be able to see the debugger, unless you are running dual-monitor!<span>&nbsp; <\/span><\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\"><span>&nbsp;<\/span>I knew that I was going to need to use some .NET 3.0 functionality in order to dig out the picture metadata, so I right-clicked the project and chose \u201cProperties.\u201d<span>&nbsp; <\/span>On the \u201cCompile\u201d tab, I clicked the \u201cAdvanced Compiler Options\u201d button and, on the bottom of the resulting window, I changed the Target Framework to be the 3.5 version (3.0 would have worked also, but I like being current).<span>&nbsp; <\/span>I then used the \u201cAdd Reference\u201d menu item off of the project to add some needed references to the project \u2013 PresentationCore (found by navigating<span>&nbsp; <\/span>to<span>&nbsp; <\/span>C:\\Program Files\\Reference Assemblies\\Microsoft\\Framework\\v3.0\\PresentationCore.dll on the Browse tab) and WindowsBase (found on the .NET tab).<\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Now, it was all coding.<span>&nbsp; <\/span>My member variable had been whittled down to this:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> m_Options <span>As<\/span> <span>New<\/span> Options()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> m_Random <span>As<\/span> <span>New<\/span> Random()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> m_IsActive <span>As<\/span> <span>Boolean<\/span> = <span>False<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> m_MouseLocation <span>As<\/span> Point<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> m_files <span>As<\/span> ReadOnlyCollection(<span>Of<\/span> <span>String<\/span>)<span><\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> m_fileCount <span>As<\/span> <span>Integer<\/span> = 0<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Those would allow me to track the user options, generate random numbers, determine if the mouse was active and where it was at, hold onto the list of picture files that I wanted to display, and keeping a count of the latter as well.<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">I created a helper function to position my label.<span>&nbsp; <\/span>Basically, I always want it exactly at the bottom of the screen, centered horizontally:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Sub<\/span> SetLabelLocation()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.lblDescription.Top = <span>Me<\/span>.Size.Height &#8211; <span>Me<\/span>.lblDescription.Height<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.lblDescription.Left = (<span>Me<\/span>.Size.Width &#8211; <span>Me<\/span>.lblDescription.Width) \\ 2<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.lblDescription.Show()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Sub<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Now I could rewrite the form\u2019s Load event handler (comments in all of this code omitted for brevity, though you\u2019ll see them in the final product):<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Sub<\/span> frmSceenSaver_Load(<span>ByVal<\/span> sender <span>As<\/span> System.Object, <span>ByVal<\/span> e <span>As<\/span> System.EventArgs) <span>Handles<\/span> <span>MyBase<\/span>.Load<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>m_Options.LoadOptions()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>SetLabelLocation()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.tmrUpdateScreen.Interval = m_Options.Speed * 1000<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.tmrUpdateScreen.Enabled = <span>True<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>If<\/span><span> m_Options.UseSubdirectories = <span>True<\/span> <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>m_files = <span>My<\/span>.Computer.FileSystem.GetFiles(m_Options.Directory, FileIO.SearchOption.SearchAllSubDirectories, <span>&#8220;*.jpg&#8221;<\/span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Else<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>m_files = <span>My<\/span>.Computer.FileSystem.GetFiles(m_Options.Directory, FileIO.SearchOption.SearchTopLevelOnly, <span>&#8220;*.jpg&#8221;<\/span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> m_files <span>IsNot<\/span> <span>Nothing<\/span> <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/span>m_fileCount = m_files.Count<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Else<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.lblDescription.Text = <span>&#8220;No pictures found!&#8221;<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>SetLabelLocation()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>End<\/span><span> <span>Sub<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">This is pretty straightforward code:<span>&nbsp; <\/span>I load the options from the user settings, move the label to the correct position, translate to desired seconds of duration to milliseconds, and enable the Timer (which was already attached to the form in the original code).<span>&nbsp; <\/span>Then, I do a search for all JPG files in the desired directory (and in subdirectories, if the user chose that option) and collect them in the m_files object.<span>&nbsp; <\/span>If I got any files back, then I keep track of the count to use later on when generating a random number.<span>&nbsp; <\/span>Otherwise, I tell the user that I couldn\u2019t find any pictures.<span>&nbsp; <\/span>(The string should really go into the resources, but that makes the blog posts less readable.)<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">As with the original version that I downloaded, I wanted the application to end when the user moves or clicks the mouse.<span>&nbsp; <\/span>The problem, however, is that the label and picture are hiding the form, so that the form never gets mouse events.<span>&nbsp; <\/span>This was easily remedied by adding the appropriate events to the event handlers (I\u2019ve underlined those below), and otherwise this code is unchanged:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Sub<\/span> frmScreenSaver_MouseMove(<span>ByVal<\/span> sender <span>As<\/span> <span>Object<\/span>, _<\/span><\/p>\n<p class=\"MsoNormal\"><span>ByVal<\/span><span> e <span>As<\/span> System.Windows.Forms.MouseEventArgs) <span>Handles<\/span> <span>MyBase<\/span>.MouseMove, _<\/span><\/p>\n<p class=\"MsoNormal\"><u><span>PictureBox1.MouseMove, lblDescription.MouseMove<\/span><\/u><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">and<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Sub<\/span> frmScreenSaver_MouseDown(<span>ByVal<\/span> sender <span>As<\/span> <span>Object<\/span>, _<\/span><\/p>\n<p class=\"MsoNormal\"><span>ByVal<\/span><span> e <span>As<\/span> System.Windows.Forms.MouseEventArgs) <span>Handles<\/span> <span>MyBase<\/span>.MouseDown, _<\/span><\/p>\n<p class=\"MsoNormal\"><u><span>PictureBox1.MouseDown, lblDescription.MouseDown<\/span><\/u><\/p>\n<p class=\"MsoNormal\"><u><span><span>&nbsp;<\/span><\/span><\/u><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Periodically, the timer will fire, and that calls the Tick handler, wherein all I did was change the name of the helper function from DrawShape to DrawPicture (if any files exist to be drawn):<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Sub<\/span> tmrUpdateScreen_Tick(<span>ByVal<\/span> sender <span>As<\/span> System.Object, <span>ByVal<\/span> e <span>As<\/span> _<\/span><\/p>\n<p class=\"MsoNormal\"><span>System.EventArgs) <span>Handles<\/span> tmrUpdateScreen.Tick<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> m_fileCount &lt;&gt; 0 <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>DrawPicture()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Sub<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">As I mentioned above, DrawShape became DrawPicture, and I ripped out all of the code from it and entered the strange and inscrutable world of image metadata.<span>&nbsp;&nbsp; <\/span>Metadata such as \u201cDakeTaken\u201d and \u201cKeywords\u201d aren\u2019t available from the normal FileInfo; you have to dig for them, and it\u2019s not an intuitive process.<span>&nbsp; <\/span>I started out by getting a random number from 0 to m_fileCount &#8211; 1, and then determining if I need to grab metadata from the resulting file:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>Private<\/span> <span>Sub<\/span> DrawPicture()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> index <span>As<\/span> <span>Integer<\/span> = <span>Me<\/span>.m_Random.Next(0, m_fileCount &#8211; 1)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> m_Options.ShowDate = <span>True<\/span> <span>OrElse<\/span> m_Options.ShowTags = <span>True<\/span> <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">\u201cIndex\u201d is the index of the picture I\u2019ll be showing.<span>&nbsp; <\/span>Now, I needed to crack that file open:<\/font><\/p>\n<p class=\"MsoNormal\"><span>Dim<\/span><span> jpegStream <span>As<\/span> <span>New<\/span> System.IO.FileStream(m_files(index), _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp; <\/span>FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite)<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span>Dim<\/span><span> jpegDecoder <span>As<\/span> <span>New<\/span> JpegBitmapDecoder(jpegStream, _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp; <\/span>BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default)<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span>Dim<\/span><span> jpegFrame <span>As<\/span> BitmapFrame = jpegDecoder.Frames(0)<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span>Dim<\/span><span> jpegInplace <span>As<\/span> InPlaceBitmapMetadataWriter = _<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp; <\/span>jpegFrame.CreateInPlaceBitmapMetadataWriter()<\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">The first line should look pretty familiar \u2013 I\u2019m just opening up a file like normal.<span>&nbsp; <\/span>In the second line, I\u2019m attaching the resulting stream to a JpegBitmapDecoder, which allows me to treat the file as if it is a JPEG (which it is), so that I don\u2019t need to know or care about the JPEG format when parsing through it.<span>&nbsp; <\/span>The third line gets me the first frame of that JPEG (and yes, 99.9999% of the time, there\u2019s only one frame), and in the fourth line, I get a pointer to the metadata of that frame.<span>&nbsp; <\/span><\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">(BTW:<span>&nbsp; <\/span>Yes, the object is called a \u201cmetadata writer.\u201d<span>&nbsp; <\/span>There is no such thing as a \u201cmetadata reader\u201d \u2013 you use this object for both.<span>&nbsp; <\/span>Yes, that\u2019s silly.<span>&nbsp; <\/span>My screensaver just does reading, of course; if you\u2019re interested in how to actually *write* metadata into the file once you\u2019ve gotten this far, I\u2019ve coded up an example in the completed code, attached at the bottom of this post.)<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">The next bits retrieve metadata from the writer and combine them into a display string:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> sDescription <span>As<\/span> <span>String<\/span> = <span>&#8220;&#8221;<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> m_Options.ShowDate = <span>True<\/span> <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> <span>Not<\/span> <span>String<\/span>.IsNullOrEmpty(dt) <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> ddt <span>As<\/span> <span>Date<\/span> = <span>CDate<\/span>(<span>&#8220;#&#8221;<\/span> &amp; dt &amp; <span>&#8220;#&#8221;<\/span>)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp; <\/span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/span>sDescription = ddt.Date &amp; <span>&#8220;: &#8220;<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><font size=\"3\"><font face=\"Calibri\">\u201cDateTaken\u201d actually returns the time as well, and I just wanted the date, so I converted the DateTaken string to a true date and then retrieved the Date portion from it.<\/font><\/font><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> m_Options.ShowTags = <span>True<\/span> <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> jpegInplace.Keywords <span>IsNot<\/span> <span>Nothing<\/span> <span>AndAlso<\/span> jpegInplace.Keywords.Count &gt; 0 <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>For<\/span> <span>Each<\/span> keyword <span>As<\/span> <span>String<\/span> <span>In<\/span> jpegInplace.Keywords<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> <span>Not<\/span> keyword.StartsWith(<span>&#8221; &#8220;<\/span>) <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>sDescription += keyword &amp; <span>&#8220;, &#8220;<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Next<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><font size=\"3\"><font face=\"Calibri\">Tags are referred to as \u201cKeywords\u201d in metadata, and they are stored in a read-only collection which I have to iterate through.<span>&nbsp; <\/span>I ignore tags that start with a space character \u2013 this is because I personally mask out incorrect tags with spaces <span>&nbsp;<\/span>rather than removing them.<span>&nbsp; <\/span>This is because it\u2019s important to keep the overall size of the metadata constant when modifying the file in-place, and I (for whatever reason) frequently write in the wrong tag.<span>&nbsp; <\/span>(The proper way to remove bad tags is to create a copy of the file which lacks them, but that\u2019s heavy-weight.)<\/font><\/font><\/span><\/p>\n<p class=\"MsoNormal\"><span><font size=\"3\"><font face=\"Calibri\">After that, I do a bit of cosmetic work on the string:<\/font><\/font><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> sDescription.EndsWith(<span>&#8220;, &#8220;<\/span>) <span>OrElse<\/span> sDescription.EndsWith(<span>&#8220;: &#8220;<\/span>) <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>sDescription = Microsoft.VisualBasic.Left(sDescription, sDescription.Length &#8211; 2)<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><font size=\"3\"><font face=\"Calibri\">As you can see, I\u2019ve been concatentating the strings with \u201c: \u201c or \u201c; \u201c, and since I\u2019m too lazy to use fancy logic in the loop to look ahead to make sure I don\u2019t add an extra one, I just trim any that I don\u2019t need when all done.<\/font><\/font><\/span><\/p>\n<p class=\"MsoNormal\"><span><font size=\"3\" face=\"Calibri\">Then I set the text of the label to be the description I\u2019ve just created, center the label, and close the file:<\/font><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.lblDescription.Text = sDescription<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>SetLabelLocation()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>jpegStream.Close()<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">It was important for me to remember to close the file, since I\u2019m about to tell the picture control to use that same file, and they can\u2019t use it at the same time.<span>&nbsp; <\/span>But first, before loading up the picture, my code hides the label if the user didn\u2019t want to show any text at all:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Else<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Me<\/span>.lblDescription.Hide()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Now all that remains is to show the picture:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp; <\/span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/span><span>Me<\/span>.PictureBox1.Load(m_files(index))<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Sub<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\">And that\u2019s it; the old shape screensaver has been successfully transformed into a new picture screensaver that also shows the viewer the date the picture was taken, who is in the picture, and where they were.<span>&nbsp; <\/span>The last thing to do, after building and debugging the app, is to copy it up to the c:\\windows\\system32 direcotry and change its extension to be SCR instead of EXE.<span>&nbsp; <\/span><\/font><\/font><\/p>\n<h2><font color=\"#4f81bd\" size=\"4\" face=\"Cambria\">Calling the screensaver from Windows<\/font><\/h2>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">Note that I didn\u2019t need to change the \u201cMain\u201d function, but I\u2019ll briefly describe it here anyway.<span>&nbsp; <\/span>First, we note that this is a single-threaded application which can handle arguments passed to it:<\/font><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span>&lt;STAThread()&gt; <span>Shared<\/span> <span>Sub<\/span> Main(<span>ByVal<\/span> args <span>As<\/span> <span>String<\/span>())<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> args.Length &gt; 0 <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><font size=\"3\"><font face=\"Calibri\">Windows will pass one of three flags as arguments.<span>&nbsp; <\/span>The first possibility is \u201cp\u201d, which means \u201cshow a preview of the screensaver.\u201d<span>&nbsp; <\/span>As with the original version of this code, I didn\u2019t do any special here, but just exited, since previews are beyond the scope of this blog and<span>&nbsp; <\/span>frankly, if you need a preview of a slideshow, then you\u2019re not understanding the screensaver altogether:<\/font><\/font><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp; <\/span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/span><span>If<\/span> args(0).ToLower = <span>&#8220;\/p&#8221;<\/span> <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Application.Exit()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><font size=\"3\"><font face=\"Calibri\">The second option is \u201cc\u201d, which means that Windows wants to display the option settings.<span>&nbsp; <\/span>Once the user dismisses the options, I close down the application:<\/font><\/font><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp; <\/span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<\/span><span>If<\/span> args(0).ToLower.Trim().Substring(0, 2) = <span>&#8220;\/c&#8221;<\/span> <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> userOptionsForm <span>As<\/span> <span>New<\/span> frmOptions()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>userOptionsForm.ShowDialog()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Application.Exit()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span>&nbsp;<\/span><\/p>\n<p class=\"MsoNormal\"><span><font size=\"3\"><font face=\"Calibri\">The third option (and also the default option, if no arguments are supplied) is \u201cs\u201d, which means that I should show the screensaver itself, and then close it down once it\u2019s exited:<\/font><\/font><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>If<\/span> args(0).ToLower = <span>&#8220;\/s&#8221;<\/span> <span>Then<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> screenSaverForm <span>As<\/span> <span>New<\/span> ScreenSaver.frmScreenSaver()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>screenSaverForm.ShowDialog()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Application.Exit()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Else<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>Dim<\/span> screenSaverForm <span>As<\/span> <span>New<\/span> ScreenSaver.frmScreenSaver()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>screenSaverForm.ShowDialog()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span>Application.Exit()<\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>If<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><span><span>&nbsp;&nbsp;&nbsp; <\/span><span>End<\/span> <span>Sub<\/span><\/span><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">And there you have it.<span>&nbsp; <\/span>The completed code (along with another method which describes setting metadata) is attached to this blog post, and will also be posted on my <\/font><a href=\"http:\/\/code.msdn.microsoft.com\/templeofvb\"><font color=\"#800080\" size=\"3\" face=\"Calibri\">Temple of VB<\/font><\/a><font size=\"3\" face=\"Calibri\"> site.<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">\u2018Til next time,<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\"><font face=\"Calibri\"><span>&nbsp; <\/span>&#8211;Matt&#8211;*<\/font><\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\">[Edit:&nbsp; I re-posted the ZIP file and modified the blog so that the <strong>SizeMode<\/strong> for the Picture was correctly set to <strong>Zoom<\/strong> instead of <strong>Center Image, <\/strong>and I now force a save of the options when changes are applied so that Application.Exit doesn&#8217;t skip that step.]<\/font><\/p>\n<p class=\"MsoNormal\"><font size=\"3\" face=\"Calibri\"><\/font>&nbsp;<\/p>\n<p><a href=\"https:\/\/msdnshared.blob.core.windows.net\/media\/MSDNBlogsFS\/prod.evol.blogs.msdn.com\/CommunityServer.Components.PostAttachments\/00\/09\/37\/35\/41\/VB%20Screensaver.zip\">VB Screensaver.zip<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I\u2019ve just completed a task that I set out to do about five years ago, and I am pretty proud (and tired)!&nbsp; I have just finished scanning in every single photo that I\u2019ve acquired over my 40+ years of life, fixing up their dates to reflect the date when the picture was taken, not when [&hellip;]<\/p>\n","protected":false},"author":258,"featured_media":8818,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[22,195],"tags":[101,103,135,165,166],"class_list":["post-3143","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-matt-gertz","category-visual-basic","tag-matt-gertz","tag-metadata","tag-screen-savers","tag-vb2005","tag-vb2008"],"acf":[],"blog_post_summary":"<p>I\u2019ve just completed a task that I set out to do about five years ago, and I am pretty proud (and tired)!&nbsp; I have just finished scanning in every single photo that I\u2019ve acquired over my 40+ years of life, fixing up their dates to reflect the date when the picture was taken, not when [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts\/3143","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/users\/258"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/comments?post=3143"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/posts\/3143\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/media\/8818"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/media?parent=3143"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/categories?post=3143"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/vbteam\/wp-json\/wp\/v2\/tags?post=3143"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}