Exploring the GrabAPicture Application (Part 10)

In the previous post, Exploring the GrabAPicture Application (Part 9), I discussed the command line interface for the GrabAPicture application. This week we begin looking at the GUI controls for frmMain, which is the first form the user sees. Here’s how this form appears when you’re using it.

GrabAPicture1001

It’s important to analyze the functionality of this interface. You can divide the tasks that it performs into the following areas:

 

  • Dialog Box Management (dialog box configuration and Close button)
  • Direct Wallpaper Access (Wallpaper Location text box, radio buttons in the Style Selection group, and Set Value button)
  • Wallpaper Database Access (Random Sources button)
  • Command Line Interface Configuration (Use Random Local Sources and Use Random Remote Sources check boxes)

It’s helpful to perform such an analysis of any interface you design. Doing so makes it possible to look for ways to make the application more efficient (by reducing the number of controls when possible) and easier to use (by placing some controls on another form). Theoretically, you could make this application more efficient by making the Command Line Interface Configuration part of the command line interface. Likewise, you could make it easier to use by placing the Direct Wallpaper Access controls on a separate form. However, the form is relatively simple as is and most people won’t have trouble using it. This post discusses all four areas in the order shown.

Dialog Box Management

Part of the dialog box management task is to configure the form when you first start the application. This application, like most applications out there, uses the Load event to perform the task. In this case, it means using the frmMain_Load() handler shown here.

' Current application settings.
Dim CurrentSettings As GrabAPictureSettings
 
Private Sub frmMain_Load(ByVal sender As Object, _
                         ByVal e As System.EventArgs) _
                         Handles MyBase.Load
 
   ' Create an instance of the WinWallpaper class to gain
   ' access to the current Windows settings.
   Dim Wallpaper As New WinWallpaper
 
   ' Get the current wallpaper URI and display it.
   txtWallpaperURI.Text = _
      Wallpaper.WallpaperURI.ToString()
 
   ' Get the current wallpaper style.
   Select Case Wallpaper.Style
      Case WinWallpaper.Styles.Stretched
         rbStretched.Checked = True
         rbCentered.Checked = False
         rbTiled.Checked = False
      Case WinWallpaper.Styles.Centered
         rbStretched.Checked = False
         rbCentered.Checked = True
         rbTiled.Checked = False
      Case WinWallpaper.Styles.Tiled
         rbStretched.Checked = False
         rbCentered.Checked = False
         rbTiled.Checked = True
   End Select
 
   ' Initialize the stored settings.
   CurrentSettings = GrabAPictureSettings.LoadSettings()
 
   ' Configure the controls.
   If Not CurrentSettings Is Nothing Then
      cbLocal.Checked = CurrentSettings.LocalChecked
      cbRemote.Checked = CurrentSettings.RemoteChecked
   Else
      ' Define default settings.
      CurrentSettings = New GrabAPictureSettings
   End If
End Sub

The code begins by creating a global GrabAPictureSettings object, CurrentSettings, that contains the content of the user’s wallpaper database. Normally, I prefer not to use global variables because they’re prone to all sorts of problems, especially when the code your working with could be multithreaded (reentrancy problems are extremely tough to debug). However, using the global variable in this case shouldn’t be a problem.

The frmMain_Load() method begins by creating creating a WinWallpaper object, Wallpaper. The reason you must create this variable every time you need it is because external applications can indeed change the wallpaper. If you didn’t create these variable every time, the user could end up using old information. Admittedly, this wouldn’t be much of a problem when it comes to wallpaper, but it’s good practice to follow in every application where an external application could affect your application’s data. The code then uses Wallpaper to set the Wallpaper Location and Style Selection settings of the application.

At this point, the code instantiates CurrentSettings. You know from the Exploring the GrabAPicture Application (Part 8) post that the LoadSettings() method returns Nothing when the user hasn’t saved any settings before, which is always the case when the user starts the application for the first time. This code checks whether CurrentSettings is Nothing after the LoadSettings() call. If so, the application creates an entirely new GrabAPictureSettings object. Otherwise, it uses the existing settings to configure the Use Random Local Sources and Use Random Remote Sources check boxes.

The configuration process is complete. The only remaining task is to provide a means of ending the application. Clicking Close, which is btnCancel, performs this task. Here is the code used to accomplish this task.

Private Sub btnCancel_Click(ByVal sender As System.Object, _
                            ByVal e As System.EventArgs) _
                         Handles btnCancel.Click
   ' Exit with the Cancel code.
   Environment.Exit(DialogResult.Cancel)
End Sub

Notice that this application doesn’t simply close the form. Instead, it calls Environment.Exit() with an ending value of DialogResult.Cancel. The purpose for this approach is that GrabAPicture is designed for use in a batch file. Any batch file you create can rely on the ErrorLevel value to detect how the application exited (see my Understanding the Connection Between Application Output and ErrorLevel post for details), so you must provide a value to detect. It’s good practice not to simply close the form anyway—you should always provide some sort of exit value.

Direct Wallpaper Access

This application provides the means for the user to simply type the full path to a piece of wallpaper, set the style, and then click Set Value to use it. In short, the user could decide not to use any of the random option at all. There is a command line interface feature to allow the user to work this way as well. Here is the code used to provide direct wallpaper access.

Private Sub rbStretched_CheckedChanged(ByVal sender As System.Object, _
                                       ByVal e As System.EventArgs) _
                                    Handles rbStretched.CheckedChanged
   Dim Wallpaper As New WinWallpaper
 
   ' Set the new style.
   Wallpaper.Style = WinWallpaper.Styles.Stretched
End Sub
 
Private Sub rbCentered_CheckedChanged(ByVal sender As System.Object, _
                                      ByVal e As System.EventArgs) _
                                   Handles rbCentered.CheckedChanged
   Dim Wallpaper As New WinWallpaper
 
   ' Set the new style.
   Wallpaper.Style = WinWallpaper.Styles.Centered
End Sub
 
Private Sub rbTiled_CheckedChanged(ByVal sender As System.Object, _
                                   ByVal e As System.EventArgs) _
                                Handles rbTiled.CheckedChanged
   Dim Wallpaper As New WinWallpaper
 
   ' Set the new style.
   Wallpaper.Style = WinWallpaper.Styles.Tiled
End Sub
 
Private Sub btnOK_Click(ByVal sender As System.Object, _
                        ByVal e As System.EventArgs) _
                        Handles btnOK.Click
   Dim Wallpaper As New WinWallpaper
 
   ' Set the wallpaper URI.
   Try
      Wallpaper.WallpaperURI = New Uri(txtWallpaperURI.Text)
   Catch UFE As UriFormatException
      MessageBox.Show("Provide a valid URI!" & vbCrLf & _
                      UFE.Message)
   Catch ex As Exception
      MessageBox.Show(ex.Message)
   End Try
 
   ' Exit with the OK code.
   'Environment.Exit(DialogResult.OK)
End Sub

Each of the check box event handlers performs essentially the same task. They set the Wallpaper.Style property to the appropriate Wallpaper.Styles value. A possible enhancement to these event handlers would be to force them to make the change automatically. The user still has to click Set Value to make the change. After testing this example out with a few willing volunteers, I found it was less confusing if the application waited for the user to click Set Value, rather than have the change occur automatically. The strategy you pursue will likely depend on your users and the complexity of the application you create.

Clicking Set Value calls the btnOK_Click() event handler. In this case, the code performs a conversion of the text the user types into Wallpaper Location to a Uri object. This is the first potential source of an exception. If the text doesn’t form a useful URI, then the application generates a UriFormatException. The assignment could possibly generate other exceptions, so the code adds a general exception handler as well. The act of making the assignment changes the Desktop wallpaper.

This example used to close the dialog box automatically after the user clicked Set Value. A few testers complained about this practice—preferring to experiment with the wallpaper. Consequently, the current version of the example has this piece of code commented out. If you choose to enable the old method of doing things, you’ll want to note that the application will exit with a value of DialogResult.OK in this case, which you can easily trap using the ErrorLevel value in a batch file.

Wallpaper Database Access

Wallpaper database configuration is performed using a combination of frmConfigure (to display the database content) and frmAddEdit (to make changes). When the user clicks Random Sources, the application calls btnConfigure_Click(). This event handler displays frmConfigure so that the user can see the list of configured wallpapers and make changes to the list. The following code shows how to perform this task.

Private Sub btnConfigure_Click(ByVal sender As System.Object, _
                               ByVal e As System.EventArgs) _
                               Handles btnConfigure.Click
   ' Create a new configuration dialog box.
   Dim Config As New frmConfigure
 
   ' Display the dialog box on screen.
   Config.ShowDialog(Me)
 
   ' Update the current configuration settings.
   CurrentSettings = GrabAPictureSettings.LoadSettings()
 
   ' Get the current wallpaper URI and display it.
   Dim Wallpaper As New WinWallpaper
   txtWallpaperURI.Text = _
      Wallpaper.WallpaperURI.ToString()
 
   ' Define default settings if necessary.
   If CurrentSettings Is Nothing Then
      CurrentSettings = New GrabAPictureSettings
   End If
End Sub

The first part of this code looks like any dialog box display code you’ve used in the past. The example creates a frmConfigure object, Config. It then calls the Config.ShowDialog() method to display the dialog box with the current dialog box as the parent. A lot of developers create subordinate dialog boxes without the proper parent, which makes the application behave incorrectly. Always assign a parent to a subordinate dialog box when the subordinate should be dismissed before going back to the parent.

On return, the user has supposedly made changes to the wallpaper database. With this in mind, the application updates CurrentSettings to match the new wallpaper list. In addition, the wallpaper itself may have changed, so the application updates the wallpaper settings as well. The last bit of code may seem confusing at first, but think about it for a minute. The user might have deleted the last wallpaper entry from the list. If this is the case, the application needs to set CurrentSettings to a new instance of GrabAPictureSettings.

Command Line Interface Configuration

The final task for frmMain is at hand. The application needs some method of configuring the command line interface to use the correct random settings. The Use Random Local Settings and Use Random Remote Settings check boxes perform the task. Here is the code for the associated event handlers.

Private Sub cbLocal_CheckStateChanged(ByVal sender As Object, _
                                      ByVal e As System.EventArgs) _
                                   Handles cbLocal.CheckStateChanged
   ' Save the current checked state.
   CurrentSettings.LocalChecked = cbLocal.Checked
   GrabAPictureSettings.SaveSettings(CurrentSettings)
End Sub
 
Private Sub cbRemote_CheckStateChanged(ByVal sender As Object, _
                                       ByVal e As System.EventArgs) _
                                    Handles cbRemote.CheckStateChanged
   ' Save the current checked state.
   CurrentSettings.RemoteChecked = cbRemote.Checked
   GrabAPictureSettings.SaveSettings(CurrentSettings)
End Sub

As you can see, the application sets the correct CurrentSettings property, and then calls GrabAPictureSettings.SaveSettings() with the CurrentSettings object as an argument to save the settings to disk. The result is a change to the user’s disk-based XML settings. A side effect of this process is that the user’s wallpaper list is also saved to disk. The SaveSettings() method saves all of the settings at one time.

Well, that’s it for frmMain. The next post will look at frmConfigure. Until that time, please let me know if you have any questions at John@JohnMuellerBooks.com. You can find the next post in this series at Exploring the GrabAPicture Application (Part 11).

Exploring the GrabAPicture Application (Part 9)

In the previous post, Exploring the GrabAPicture Application (Part 8), you saw how all of the GrabAPicture application settings are put together and stored on disk from a low level perspective. This post diverges from the topic of data for a while to consider the user interface that relies on the data. In this case, you discover how the command line interface for this application works. Of course, the first requirement is deciding why the application needs a command line interface. In order to support automatic desktop wallpaper changes, the application does indeed use a command line interface. You can use the command line interface to create a shortcut like the one shown here and add it to the Shortcut folder on your system.

GrabAPicture0901

Look at the Target field. The application is configured to use the /Random command line switch to display any of the configured wallpapers at random. You can configure GrabAPicture in other ways, such as selecting a specific wallpaper. As with most applications that sport a command line interface, GrabAPicture provides a full help screen. The command line interface for this application is as follows:

GrabAPicture /?

GrabAPicture /Random

GrapAPicture /Picture:<Picture URI> [/Style:Stretched | Tiled | Centered]

where:

 

  • /?: Displays the help screen.
  • /Random: Selects any of the configured wallpapers at random.
  • /Picture: Displays a specific graphic found at the specified URI.
  • /Style: Configures Windows to display the wallpaper in Stretched, Tiled, or Centered mode.

Many applications have a considerably more complex command line interface, but you’ll find that this one is more than complex enough. The command line interface resides in frmMain.vb in the Main() function. When you see the code for this part of the example your first thought will be that the code is overly complicated and you’ll never understand it, but this isn’t the case. The Main() function does have a number of decisions to make, but they really come down to a number of selections.

 

  1. If there aren’t any command line arguments, the start the GUI version of the application.
  2. If the user has entered the /? command line switch as the first argument (regardless of anything that follows), display the help screen.
  3. If the user has entered the /Picture command line argument, then process the specific picture.
    1. Obtain the specific URI as the second part of the first command line argument.
    2. Check for a /Style command line switch as a second command line argument.
    3. Obtain the specific style as the second part of the second command line argument.
    4. Display the graphic on screen.
  4. If the user has entered the /Random command line switch, determine which picture to use from the preconfigured selections.
    1. Check for a configuration file. If none exists, display an error message and exit.
    2. If the user has checked both local and remote options, make sure the configuration settings are usable, then obtain a wallpaper selection from the preconfigured choices.
    3. If the user has checked just the local option, make sure there are local wallpaper selections to use, and then obtain the wallpaper selection from the local choices.
    4. If the user has checked just the remote option, make sure there are remote wallpaper selections to use, and then obtain the wallpaper selection from the remote choices.
    5. When the user hasn’t selected either local or remote random selections, display an error message and exit.

That’s really all there is to the code—a number of choices. With that in mind, here’s the massive looking, but not all that difficult code for this part of the example (normally, I don’t present code segments this long, but this particular example requires that I show the entire segment so you can see how it works).

Shared Function Main(ByVal CmdArgs() As String) As Integer
   ' Process the command line arguments.
   If CmdArgs.Length > 0 Then
      ' Get the first argument.
      Dim CmdArg As String = CmdArgs(0)
 
      ' User is requesting help.
      If CmdArg = "/?" Then
         ' Create an output string.
         Dim Output As New StringBuilder
 
         ' Create the usage instructions.
         Output.Append("Usage:" + vbCrLf)
         Output.Append("GrabAPicture" + vbCrLf)
         Output.Append("GrabAPicture /?" + vbCrLf)
         Output.Append("GrabAPicture /Random" + vbCrLf)
         Output.Append("GrabAPicture /Picture:<Picture URI> " + _
                       "[/Style:Stretched | Centered | Tiled]" + _
                       vbCrLf + vbCrLf)
         Output.Append("When used alone, displays the configuration " + _
                       "interface" + vbCrLf)
         Output.Append("/? - Displays this help message." + vbCrLf)
         Output.Append("/Random - Displays one of the images provided as " + _
                       "random input through the configuration program." + _
                       vbCrLf)
         Output.Append("/Picture - Defines a local or Web location for a " + _
                       "picture." + vbCrLf)
         Output.Append("/Style - Determines the display style of the " + _
                       "picture. Use only with the /Picture switch." + vbCrLf)
 
         ' Display the help.
         MessageBox.Show(Output.ToString(),
                         "GrabAPicture Usage",
                         MessageBoxButtons.OK,
                         MessageBoxIcon.Information)
         Return 0
      End If
 
      ' User wants to display a particular picture.
      If CmdArg.Length > 8 Then
         If CmdArg.Substring(0, 8).ToUpper() = "/PICTURE" Then
 
            ' Wallpaper manipulation class.
            Dim Wallpaper As New WinWallpaper
 
            ' Split the string.
            Dim NewUri As Uri
            NewUri = New Uri(CmdArg.Substring(9))
 
            ' Determine whether there is a Style argument to process.
            If CmdArgs.Length > 1 Then
 
               ' Get the style argument.
               Dim StyleArg As String = CmdArgs(1)
 
               ' Change the style.
               If StyleArg.Substring(0, 6).ToUpper() = "/STYLE" Then
                  Select Case StyleArg.Substring(7).ToUpper()
                     Case "STRETCHED"
                        Wallpaper.Style = WinWallpaper.Styles.Stretched
                     Case "CENTERED"
                        Wallpaper.Style = WinWallpaper.Styles.Centered
                     Case "TILED"
                        Wallpaper.Style = WinWallpaper.Styles.Tiled
                  End Select
               End If
            End If
 
            ' Change the wallpaper.
            Wallpaper.WallpaperURI = NewUri
         End If
         Return 0
      End If
 
      If CmdArg.ToUpper() = "/RANDOM" Then
         ' Current application settings.
         Dim CurrentSettings As GrabAPictureSettings
 
         ' Number of settings to randomize.
         Dim Settings As Int32 = 0
 
         ' The selected image number.
         Dim Selection As Int32 = 0
 
         ' Contains the wallpaper location.
         Dim NewUri As Uri
 
         ' Wallpaper manipulation class.
         Dim Wallpaper As New WinWallpaper
 
         ' Initialize the stored settings.
         CurrentSettings = GrabAPictureSettings.LoadSettings()
 
         ' Verify that there are settings to use.
         If CurrentSettings Is Nothing Then
            MessageBox.Show( _
               "This application isn't configured to use random sources.")
            Return -1
         End If
 
         ' Obtain the number of settings to randomize.
         If CurrentSettings.LocalChecked And CurrentSettings.RemoteChecked Then
 
            ' Detect configuration errors.
            If CurrentSettings.LocalUris Is Nothing Or _
               CurrentSettings.RemoteUris Is Nothing Then
 
               MessageBox.Show("Application not set up to use " + _
                               "both local and remote locations.")
               Return -2
            End If
 
            ' Determine the number of settings.
            Settings = CurrentSettings.LocalUris.Length + _
               CurrentSettings.RemoteUris.Length - 1
 
            ' Randomize the settings.
            Randomize(DateTime.Now.Millisecond)
            Selection = Convert.ToInt32(Settings * Rnd())
 
            ' Obtain the style and location.
            If Selection < CurrentSettings.LocalUris.Length Then
 
               ' Choose the wallpaper style.
               Select Case CurrentSettings.LocalUris(Selection).Style
                  Case WinWallpaper.Styles.Stretched
                     Wallpaper.Style = WinWallpaper.Styles.Stretched
                  Case WinWallpaper.Styles.Centered
                     Wallpaper.Style = WinWallpaper.Styles.Centered
                  Case WinWallpaper.Styles.Tiled
                     Wallpaper.Style = WinWallpaper.Styles.Tiled
               End Select
 
               ' Create a wallpaper URI.
               NewUri = New Uri(CurrentSettings.LocalUris(Selection).Location)
            Else
               ' Subtract the local resources.
               Selection -= CurrentSettings.LocalUris.Length
 
               ' Choose the wallpaper style.
               Select Case CurrentSettings.RemoteUris(Selection).Style
                  Case WinWallpaper.Styles.Stretched
                     Wallpaper.Style = WinWallpaper.Styles.Stretched
                  Case WinWallpaper.Styles.Centered
                     Wallpaper.Style = WinWallpaper.Styles.Centered
                  Case WinWallpaper.Styles.Tiled
                     Wallpaper.Style = WinWallpaper.Styles.Tiled
               End Select
 
               ' Create a wallpaper URI.
               NewUri = New Uri(CurrentSettings.RemoteUris(Selection).Location)
            End If
 
            ' Set the wallpaper location.
            Wallpaper.WallpaperURI = NewUri
 
         ElseIf CurrentSettings.LocalChecked Then
 
            ' Detect configuration errors.
            If CurrentSettings.LocalUris Is Nothing Then
               MessageBox.Show("Application not set up " + _
                               "to use local locations.")
               Return -2
            End If
 
            ' Determine the number of settings.
            Settings = CurrentSettings.LocalUris.Length - 1
 
            ' Randomize the settings.
            Randomize(DateTime.Now.Millisecond)
            Selection = Convert.ToInt32(Settings * Rnd())
 
            ' Obtain the style and location.
            ' Choose the wallpaper style.
            Select Case CurrentSettings.LocalUris(Selection).Style
               Case WinWallpaper.Styles.Stretched
                  Wallpaper.Style = WinWallpaper.Styles.Stretched
               Case WinWallpaper.Styles.Centered
                  Wallpaper.Style = WinWallpaper.Styles.Centered
               Case WinWallpaper.Styles.Tiled
                  Wallpaper.Style = WinWallpaper.Styles.Tiled
            End Select
 
            ' Create a wallpaper URI.
            NewUri = New Uri(CurrentSettings.LocalUris(Selection).Location)
 
            ' Set the wallpaper location.
            Wallpaper.WallpaperURI = NewUri
 
         ElseIf CurrentSettings.RemoteChecked Then
 
            ' Detect configuration errors.
            If CurrentSettings.RemoteUris Is Nothing Then
               MessageBox.Show("Application not set up " + _
                               "to use remote locations.")
               Return -2
            End If
 
            ' Determine the number of settings.
            Settings = CurrentSettings.RemoteUris.Length - 1
 
            ' Randomize the settings.
            Randomize(DateTime.Now.Millisecond)
            Selection = Convert.ToInt32(Settings * Rnd())
 
            ' Obtain the style and location.
            ' Choose the wallpaper style.
            Select Case CurrentSettings.RemoteUris(Selection).Style
               Case WinWallpaper.Styles.Stretched
                  Wallpaper.Style = WinWallpaper.Styles.Stretched
               Case WinWallpaper.Styles.Centered
                  Wallpaper.Style = WinWallpaper.Styles.Centered
               Case WinWallpaper.Styles.Tiled
                  Wallpaper.Style = WinWallpaper.Styles.Tiled
            End Select
 
            ' Create a wallpaper URI.
            NewUri = New Uri(CurrentSettings.RemoteUris(Selection).Location)
 
            ' Set the wallpaper location.
            Wallpaper.WallpaperURI = NewUri
 
         Else
            MessageBox.Show("None of the random settings are selected.")
         End If
         Return 0
      End If
 
      ' Indicate an error condition for the command
      ' line switches.
      MessageBox.Show( _
         "Use the /? switch to see application usage.", _
         "GrabAPicture", _
         MessageBoxButtons.OK, _
         MessageBoxIcon.Error)
      Return -1
   Else
      ' Run the application for configuration purposes.
      Application.Run(New frmMain)
   End If
End Function

The Main() function begins by checking CmdArgs.Length, which contains the number of command line arguments passed from the command line by the user. When this value is 0, the example bypasses all of the rest of that command line processing code and calls the standard code, Application.Run(New frmMain).

Of course, you’re interested in finding out what happens when the user does provide a command line argument. The first thing you’ll normally check is the /? command line switch. In this case, all it does is create a StringBuilder object, Output, and display it to the user. Unlike most command line utilities, which display help screen at the command prompt, GrabAPicture does use a dialog box like the one shown here to display help.

GrabAPicture0902

Because Main() is a function, you need to return some sort of value that indicates success or failure. If you always return 0, then the utility isn’t very useful in a batch file. The example returns a value of 0 to indicate success. However, it uses other values to indicate errors:

 

  • -1: The application hasn’t been configured to use random sources.
  • -2: The application hasn’t been configured to use both local and/or remote random sources.

You can actually test for these errors in a batch file and then do something about it. Yes, batch files do offer limited capabilities for error trapping and even fewer options for recovery, but these outputs do provide the opportunity recover in at least some cases.

The next test is against a word, Picture, which can be in any case. Whether you use uppercase or lowercase is up to youI happen to prefer uppercase, so the next test looks for /PICTURE. The command line switch is only part of the argument though, so you must extract the switch from the URI of the image the user wants to use by calling CmdArg.Substring(0, 8).

On to the picture processing. The code begins by creating a new WinWallpaper object, Wallpaper. It then creates a Uri object, NewUri, from the second half of the first command line argument using CmdArg.Substring(9). This method skips the colon that separates /Picture from the URI of the picture.

Now, the application has to decide how to display the image. If the user doesn’t supply the /Style command line switch, the application uses whatever setting is in place. Otherwise, the application uses a Select Case block to choose the appropriate style.

Only after the application has done all of this other processing does it set the Wallpaper.WallpaperURI property value. Changing this value changes the wallpaper (see Exploring the GrabAPicture Application (Part 7) for details).

So, what happens with the /Random command line switch? The processing is more straightforward than you might think. There are four possibilities:

 

  • Both the Use Random Local Sources and Use Random Remote Sources check boxes are checked.
  • Only the Use Random Local Sources check box is checked.
  • Only the Use Random Remote Sources check box is checked.
  • Neither check box is checked.

In the last case, the program exits with an error message and an error code. All three other cases follow the same pattern.

 

  1. The application checks for the required resources.
  2. When the settings exist, the application obtains the number of available settings.
  3. The application then selects a random item from the list of available settings.
  4. The code sets the Wallpaper.Style property value.
  5. The code creates a Uri from the settings.
  6. The code sets the Wallpaper.WallpaperURI property value, which changes the wallpaper on the Desktop.

There is one final consideration for this example and you’re bound to miss it in all of that code. The user could enter a command line switch that’s pure gibberish. Users do that. In this case, you need to tell the user how to get help. This little piece of code keeps things from blowing up or frustrating the user.

' Indicate an error condition for the command
' line switches.
MessageBox.Show( _
   "Use the /? switch to see application usage.", _
   "GrabAPicture", _
   MessageBoxButtons.OK, _
   MessageBoxIcon.Error)
Return -1

This is a short course on the command line interface for the GrabAPicture application. If you have any questions about it, please contact me at John@JohnMuellerBooks.com. Next week we’ll look at the code behind the GrabAPicture frmMain controls. You can find the next post in this series at Exploring the GrabAPicture Application (Part 10).

Exploring the GrabAPicture Application (Part 8)

Last week, in the Exploring the GrabAPicture Application (Part 7) post, you discovered the Style and WallpaperURI properties of the WinWallpaper class. These complex properties are used to define two of three wallpaper entry values for each wallpaper used to decorate the Desktop. It may have surprised you to see how complex a task these settings are, but they require interaction with the Win32 API, among other APIs, in order to function properly. So far, you haven’t saved a single setting to disk though. This post takes things another step. We’ll discuss how to start working with settings within the application, which includes creating local and remote wallpaper lists.

Before you can do anything, you need to create another class named GrabAPictureSettings (contained in GrabAPictureSettings.vb). The Exploring the GrabAPicture Application (Part 5) post contains a set of steps you can use to create a new class. This new class contains everything needed to manage settings. It doesn’t actually perform any settings tasks, but it provides the resources to accomplish them.

The first step is to provide all of the Imports statements used for this class. In this case, the requirements are somewhat obvious. You’ll need resources to manage the XML files, serialize the data, and also perform I/O. Here are the three Imports you’ll need.

Imports System.Xml
Imports System.Xml.Serialization
Imports System.IO

The class also needs a number of private variables to manage the settings. As with all other private settings in this example, these settings use a leading underscore to show they’re private:

' Private local variables represent settings.
Private _LocalChecked As Boolean = False
Private _RemoteChecked As Boolean = False
Private _LocalUris() As LocalUri
Private _RemoteUris() As RemoteUri

The first two settings _LocalChecked and _RemoteChecked correspond to the Use Random Local Sources and Use Random Remote Sources checkboxes on the main GrabAPicture application dialog (see the Exploring the GrabAPicture Application (Part 1) and Exploring the GrabAPicture Application (Part 2) posts for details). These private variables make user changes to those checkboxes permanent. In addition, the state of these variables determines which sources the application uses to select wallpaper.

The second two settings, _LocalUris and _RemoteUris, are arrays of wallpaper entries. These arrays will ultimately provide the list of wallpaper entries that the application randomly chooses from to display on the Desktop. These arrays also provide the content for the Local Sources and Remote Sources lists in the Configure a Random Source dialog box (see the see the Exploring the GrabAPicture Application (Part 1) and Exploring the GrabAPicture Application (Part 3) posts for details).

These variables are private, so the GrabAPictureSettings class requires some way to make them public. It does so using these four properties.

' Works with the local random setting on frmMain.
Public Property LocalChecked() As Boolean
   Get
      Return _LocalChecked
   End Get
   Set(ByVal Value As Boolean)
      _LocalChecked = Value
   End Set
End Property
 
' Works with the remote random setting on frmMain
Public Property RemoteChecked() As Boolean
   Get
      Return _RemoteChecked
   End Get
   Set(ByVal Value As Boolean)
      _RemoteChecked = Value
   End Set
End Property
 
' Works with the collection of local resources.
Public Property LocalUris() As LocalUri()
   Get
      Return _LocalUris
   End Get
   Set(ByVal Value As LocalUri())
      _LocalUris = Value
   End Set
End Property
 
' Works with the collection of remote resources.
Public Property RemoteUris() As RemoteUri()
   Get
      Return _RemoteUris
   End Get
   Set(ByVal Value As RemoteUri())
      _RemoteUris = Value
   End Set
End Property

As you can see, there is nothing special about the properties. The reason these properties are relatively simple is that most of the work is done in the classes discussed in earlier posts. The WinWallpaper class, especially, does a lot of heavy lifting for this application.

It’s time to save the settings to disk. The result will be an XML file that contains two settings that determine the checked state of the Use Random Local Sources and Use Random Remote Sources checkboxes, along with two separate lists of wallpaper entries for local and remote sources. Here is the code used to save wallpaper settings to disk.

' Saves the settings to disk.
Public Shared Sub SaveSettings(ByVal Current As GrabAPictureSettings)
   ' Obtain a user-specific storage location for this application.
   Dim UserPath As String
   UserPath = _
      Environment.GetFolderPath( _
         Environment.SpecialFolder.ApplicationData) + _
         "\GrabAPicture"
 
   ' Create the path if necessary.
   If Not Directory.Exists(UserPath) Then
      Directory.CreateDirectory(UserPath)
   End If
 
   ' Create an XML serializer.
   Dim DataWrite As New XmlSerializer(GetType(GrabAPictureSettings))
 
   ' Create a stream writer to output the data.
   Dim Output As New StreamWriter(UserPath + "\AppData.Config")
 
   ' Save the settings.
   DataWrite.Serialize(Output, Current)
   Output.Close()
End Sub

The one question you might have at this point is how the application gets by with so little code. The answer is in the details, such as marking the LocalUri and RemoteUri classes serializable so that the .NET Framework does most of the work for you. In this case, the code begins by creating a user-specific path that includes the user’s application data folder and a subfolder named GrabAPicture. On my Windows 7 machine, this folder equates to C:\Users\John\AppData\Roaming\GrabAPicture. Of course, it’ll be different on your system (at least the name will be different unless we use the same log on name).

 

If you find that your knowledge of serialization is lacking, check out Rod Stephen’s site for additional information. Make sure you check out the Object Serialization tutorial as a starting point. Be sure to check out “Serialize and deserialize objects in Visual Basic .NET” and “Control serialization and deserialization with attributes in Visual Basic .NET” as well. In fact, Visual Basic developer’s should definitely check out Rod’s entire list of Software Engineering topics.

The example then checks for the existence of the target folder. If the folder doesn’t exist (it won’t the first time you use the application), the application creates it.

At this point, the application can create an XmlSerializer object of type GrabAPictureSettings named DataWrite. It’s important to remember that GrabAPictureSettings relies on all of the classes we’ve discussed to this point, including those Win32 API calls. Splitting the task up into a number of classes makes things look simple, when they’re really a little on the complex side.

In order to write data to disk, the code must also create a StreamWriter object named Output that points to the AppData.Config file in the UserPath folder discussed earlier. Finally, the code can save the Current settings (passed to SaveSettings() by the caller) to the Output using the DataWrite.Serialize() method. Here’s an example of what the output might look like on your system:

GrabAPicture0801

The output is actually quite readable and there isn’t anything unexpected in it. The XML file contains every wallpaper entry, both local and remote, that the user wants to use for the Desktop on a random basis.

Of course, you’re now asking yourself where this mysterious Content variable comes from. One way that the application generates it is to load settings from disk using the LoadSettings() method shown here (a future post will discuss techniques for working with content in situations where the data doesn’t already exist).

' Loads the settings from disk.
Public Shared Function LoadSettings() As GrabAPictureSettings
   Dim UserPath As String
   UserPath = _
      Environment.GetFolderPath( _
         Environment.SpecialFolder.ApplicationData) + _
         "\GrabAPicture"
 
   ' Determine whether the file exists.
   If Not File.Exists(UserPath + "\AppData.Config") Then
      ' It doesn't, so there aren't any settings.
      Return Nothing
   End If
 
   ' Create an XML serializer.
   Dim DataRead As New XmlSerializer(GetType(GrabAPictureSettings))
 
   ' Create a stream reader to load the data.
   Dim Input As New StreamReader(UserPath + "\AppData.Config")
 
   ' Load the settings.
   Dim Current As GrabAPictureSettings
   Current = CType(DataRead.Deserialize(Input), GrabAPictureSettings)
   Input.Close()
 
   ' Return the result.
   Return Current
End Function

The process is similar to saving the settings. The code begins by creating the appropriate UserPath variable, an XmlSerializer object named DataRead, and a StreamReader named Input. The main difference this time is that the application uses the DataRead.Deserialize() method to turn the XML in the AppData.Config file into settings that the application can use. Of course, this means converting the type of the output to type GrabAPictureSettings. The method returns Current as output to the caller.

Next week we’ll move back to frmMain. Now that you have a good idea of how the settings work, it’s time to see how the application puts them into practice. The GrabAPicture application has both a GUI and a command line interface. This next post will describe the command line interface so that you can begin to understand how this application makes Desktop wallpaper changes through a simple addition to the user’s Startup folder. In the meantime, please let me know if you have any questions at John@JohnMuellerBooks.com. You can find the next post in this series at Exploring the GrabAPicture Application (Part 9).

 

Exploring the GrabAPicture Application (Part 7)

Last week, in the Exploring the GrabAPicture Application (Part 6) post, we discussed the use of the SystemParametersInfo() function call. Of course, this call is part of the Windows API, so you have to use P/Invoke to access it. The previous post doesn’t really tell you how to use the SystemParametersInfo() call—that’s the topic of this post.

Wallpaper is defined by three elements in the GrabAPicture application: Name, Location, and Style. The Name property is easyit can be any string. So, you don’t have to work really hard with it. The Location property is the most complex because a location can’t be anywhere there is a graphic that the application can grabeither local or remote. The Style property is still complex because of the way Windows stores information, but it’s not absurdly difficult because Windows only supports so many styles. The following code looks at the Style property in the WinWallpaper class first.

Public Property Style() As Styles
   Get
      ' Contains the registry key that holds the
      ' desktop values.
      Dim StyleKey As RegistryKey
 
      ' Get the desktop key.
      StyleKey = _
         Registry.CurrentUser.OpenSubKey( _
            "Control Panel\Desktop", False)
 
      ' Obtain the current value.
      If StyleKey.GetValue("WallpaperStyle") = 2 And _
         StyleKey.GetValue("TileWallpaper") = 0 Then
         Return Styles.Stretched
      End If
      If StyleKey.GetValue("WallpaperStyle") = 1 And _
         StyleKey.GetValue("TileWallpaper") = 0 Then
         Return Styles.Centered
      End If
      If StyleKey.GetValue("WallpaperStyle") = 1 And _
         StyleKey.GetValue("TileWallpaper") = 1 Then
         Return Styles.Tiled
      End If
 
      ' This value should only show up if someone has
      ' tampered with the registry and set the values
      ' incorrectly.
      Return Styles.Unknown
   End Get
   Set(ByVal Value As Styles)
      ' Because Styles.Unknown signifies an error
      ' condition, the developer can't provide it as an
      ' input value.
      If Value = Styles.Unknown Then
         Throw New ArgumentException( _
            "Cannot use Styles.Unknown as an input argument")
      End If
 
      ' Contains the registry key that holds the
      ' desktop values.
      Dim StyleKey As RegistryKey
 
      ' Get the desktop key.
      StyleKey = _
         Registry.CurrentUser.OpenSubKey( _
            "Control Panel\Desktop", True)
 
      ' Select one of the other input values and set the
      ' registry keys as needed.
      Select Case Value
         Case Styles.Stretched
            StyleKey.SetValue("WallpaperStyle", "2")
            StyleKey.SetValue("TileWallpaper", "0")
         Case Styles.Centered
            StyleKey.SetValue("WallpaperStyle", "1")
            StyleKey.SetValue("TileWallpaper", "0")
         Case Styles.Tiled
            StyleKey.SetValue("WallpaperStyle", "1")
            StyleKey.SetValue("TileWallpaper", "1")
      End Select
   End Set
End Property

The Style property defines which style to use to display an image on the Desktop. The current version of the GetAPicture program is compatible with all versions of Windows, including Windows XP. A later post will show how to upgrade this application so it also allows use with Windows 7-specific features (Fill and Fit). As you can see, the get portion of this property begins by accessing the HKEY_CURRENT_USER\Control Panel\Desktop key of the registry as shown here.

GrabAPicture0701

Eventually, you’ll use three values from this key: TileWallpaper, Wallpaper, and WallpaperStyle. For now, all you need to know about is TileWallpaper (used to define the wallpaper as Tiled) and WallpaperStyle (used for all of the other settings). The code examines the registry settings using the GetValue() method and returns a Styles enumeration value that reflects the current settings. If the code can’t recognize a setting, then it returns Styles.Unknown.

When it comes time to set the wallpaper style, the application verifies that the style is known and then uses two calls to SetValue() to configure the TitleWallpaper and WallpaperStyle settings in the registry. As you might imagine, updating the rudimentary functionality of the code won’t be hard, but to ensure that the code works properly with all versions of Windows, you need to add version checks. This is the part that will interest you the most in the future post.

The WallpaperURI property code is a lot more complex than you might imagine for a very good reason. If you simply try to set the wallpaper location using the registry values, you won’t see any change on screen. The system won’t know to perform the update. In order to see the update and ensure that the change is reflected in any running applications, you must use the SystemParametersInfo() call as shown in the following code.

Public Property WallpaperURI() As Uri
   Get
      ' Create a variable to hold the result.
      Dim Result As New StringBuilder(120)
 
      ' Get the wallpaper URI.
      If SystemParametersInfo( _
         Convert.ToUInt32(SPI_GETDESKWALLPAPER), _
         Convert.ToUInt32(Result.Capacity), _
         Result, _
         Convert.ToUInt32(0)) Then
 
         ' Return the result as a URI.
         Return New Uri(Result.ToString())
      Else
         ' Throw an exception if there is an error.
         Throw New Exception( _
            "Error Calling SystemParametersInfo: " + _
            Marshal.GetLastWin32Error().ToString())
      End If
   End Get
   Set(ByVal Value As Uri)
      ' The usable, local path, location of the
      ' wallpaper.
      Dim Location As String
 
      ' A connection to the wallpaper online.
      Dim GetData As Stream
 
      ' The online image.
      Dim Img As Image
 
      ' Wallpaper on a Web page requires somewhat
      ' different processing than local wallpaper.
      If Value.Scheme.ToUpper() = "HTTP" Then
 
         ' Create a new path to save the image
         ' locally so it can be placed on the desktop.
         Location = Path.GetTempPath() + "WallPaper.BMP"
 
         ' The Web connection could fail, so use a Try
         ' block to obtain the file.
         Try
 
            ' Obtain a connection to the wallpaper.
            GetData = _
               New WebClient().OpenRead(Value.ToString())
 
            ' Read the wallpaper data.
            Img = Image.FromStream(GetData)
 
            ' Save the image.
            Img.Save(Location, Imaging.ImageFormat.Bmp)
 
         Catch ex As WebException
            ' The Web connection has failed.
            ' Use the existing Wallpaper.BMP
            ' file if it exists.
            If File.Exists(Location) Then
 
               ' Read the existing file instead.
               Img = Image.FromFile(Location)
 
            Else
               ' Otherwise, use the local path instead.
               Location = Value.LocalPath
 
               ' Verify the wallpaper is a BMP.
               If Not Location.ToUpper().EndsWith("BMP") Then
 
                  ' Load the file from disk.
                  Img = Image.FromFile(Location)
 
                  ' Create a new path to save the image
                  ' locally so it can be placed on the desktop.
                  Location = Path.GetTempPath() + "WallPaper.BMP"
 
                  ' Save the image.
                  Img.Save(Location, Imaging.ImageFormat.Bmp)
               End If
            End If
         End Try
      Else
         ' Assume that the wallpaper is local.
         Location = Value.LocalPath
 
         ' Verify the wallpaper is a BMP.
         If Not Location.ToUpper().EndsWith("BMP") Then
 
            ' Load the file from disk.
            Img = Image.FromFile(Location)
 
            ' Create a new path to save the image
            ' locally so it can be placed on the desktop.
            Location = Path.GetTempPath() + "WallPaper.BMP"
 
            ' Save the image.
            Img.Save(Location, Imaging.ImageFormat.Bmp)
         End If
      End If
 
 
      If SystemParametersInfo( _
         Convert.ToUInt32(SPI_SETDESKWALLPAPER), _
         Convert.ToUInt32(Location.Length), _
         New StringBuilder(Location), _
         Convert.ToUInt32(SPIF_UPDATEINIFILE Or SPIF_SENDWININICHANGE)) Then
      Else
         ' Throw an exception if there is an error.
         Throw New Exception( _
            "Error Calling SystemParametersInfo: " + _
            Marshal.GetLastWin32Error().ToString())
      End If
   End Set
End Property

Working with a Windows API function requires that you do some special coding. Otherwise, the call will never work. It’s important to remember that the Windows API works mainly with numbers and not with other data types. If you want to work with a string, then the string parameter takes the form of a pointer to memory in the caller’s address space in most cases. Of course, Visual Basic doesn’t provide a means to do this directly. Creating a StringBuilder object tells Visual Basic to marshal the string as a pointer to the Windows API. In this case, the code creates a StringBuilder capable of holding 120 characters, which is normally enough to hold the location of the current wallpaper on disk.

 

PInvoke comes in handy for all sorts of purposes in Visual Basic. This application shows just one example. You can find another great example of working with PInvoke on Rod Stephen’s site with the “Remove the X Close button from a form’s system menu in Visual Basic .NET” example. I haven’t seen this particular use of PInvoke explained anywhere else, so you’ll definitely want to take a look. Of course, you can get all the information you need for working with PInvoke in my book, “.NET Framework Solutions: In Search of the Lost Win32 API.”

The call to SystemParametersInfo() comes next. Normally, you’d make a call that could fail in a try…catch block. However, a Windows API call won’t work that way. In this case, the call returns True when the call succeeds and False when it doesn’t (individual Windows API calls behave in different ways, so make sure you understand how errors are presented by individual call). When the call returns True, the property returns a new Uri containing the result provided by SystemParametersInfo(). Otherwise, the code throws an Exception. Unfortunately, you don’t know what happened. The only way to get this information is to call Marshal.GetLastWin32Error() and that information is only available when you set the value of the SetLastError property of the <DllImport> attribute (explained in the previous post) to True.

 

Some readers will take me to task for not providing a more specific exception, especially since I pound on this particular failing in many applications in my books. Because you don’t know the nature of the exception, you can’t provide anything better than an general Exception in most cases. The only way around this is to create code that laboriously tracks every sort of potential error returned by the Windows API and creates an appropriate exception for it. This may be a good topic for another post in the future, but definitely not the point of this post.

Setting the wallpaper location is complicated by the fact that the wallpaper can be either local or remote. The code pursues the remote route first. The example assumes that the remote wallpaper will be on an HTTP site. You’d need to modify the code to accommodate other schemes such as HTTPS. This might make a good future post, but let’s get into a common HTTP remote location first.

The first step is to create a copy of the wallpaper on the local drive in the user’s temporary folder. A call to Path.GetTempPath() obtains this information. The remote wallpaper is always stored in a file named Wallpaper.BMP (I chose the BMP format for compatibility purposes).

The code performs a number of tasks in a try…catch block. First, it attempts to read the file containing the image from the remote location into GetData (of type Stream). If this is successful (the image exists and you can read it), the code converts the stream into an Image object, Img. The image can be of any type that Image supports:

 

  • BMP
  • GIF
  • JPEG
  • PNG
  • TIFF

The final step is to save the image, using Img.Save() to Wallpaper.BMP in the BMP format. If there is an exception in any of these steps, the application automatically recovers in one of two ways. First, it looks for an example Wallpaper.BMP and uses it when the file exists. Second, if this first technique fails, then the application looks at the current value for the local wallpaper selection and uses it instead. In this case, the application uses the Image.FromFile() method to obtain a copy of the image and saves it to Wallpaper.BMP.

When the wallpaper is local, the code first checks to see if the wallpaper is already in BMP format. If it is, the application simply places the information in Location. Otherwise, the application reads the file using Image.FromFile() and saves the image in BMP format to Wallpaper.BMP.

No matter how the information gets there, eventually Location contains a path and filename for the file you want to display on the Desktop. At this point, the code calls SystemParametersInfo() to set the wallpaper, which indirectly sets the Wallpaper value in the registry. The call also notifies Windows that it needs to update the Desktop and any applications that are currently running that they need to repaint their displays. The user sees the new wallpaper displayed on screen.

At this point, you have a good idea of how the WinWallpaper class works. In the next post, you learn about the GrabAPictureSettings class, which is used to interact with the settings you’ve learned about in the past several posts. Please let me know if you have any questions at John@JohnMuellerBooks.com. You can see the next post in this series at Exploring the GrabAPicture Application (Part 8).

 

Exploring the GrabAPicture Application (Part 6)

In the previous post, Exploring the GrabAPicture Application (Part 5), you learned about two classes used to store desktop wallpaper settings: LocalUri and RemoveUri. In addition, you learned about the WinWallpaper class that contains the Styles enumeration. We need to stop talking about data for just a little while and look at some special code used to access the underlying Windows functionality in the form of the SystemParametersInfo() function. In order to do this, you need to work with Platform Invoke (P/Invoke) code. A lot of developers feel that P/Invoke is complex and impossible to understand, but it really isn’t and functions such as SystemParametersInfo() are extremely useful to the developer, especially when working with newer versions of Windows where Microsoft hasn’t added the required functionality to the .NET Framework. My book, “.NET Framework Solutions: In Search of the Lost Win32 API” discusses how to work with P/Invoke in detail, but this post provides enough information to use P/Invoke with the GrabAPicture application.

Before you do anything else, you should have already created WinWallpaper.vb using the instructions in the Exploring the GrabAPicture Application (Part 5) post. At this top of this file, you need to add some Imports statements to access the required .NET Framework functionality:

Imports System.Runtime.InteropServices
Imports System.Text
Imports System.IO
Imports System.Net
Imports System.Drawing
Imports Microsoft.Win32

Now that you have the required imports, you can begin working with the SystemParametersInfo() function. This function interacts with Windows in a low-level manner to set and get system information, such as the Desktop wallpaper settings. In fact, this is an incredibly versatile function that you’ll encounter quite a lot if you work with P/Invoke very often. Here’s the declaration you’ll need to access it.

<DllImport("User32.DLL", SetLastError:=True)> _
<CLSCompliant(False)> _
Public Shared Function SystemParametersInfo( _
   ByVal uiAction As UInteger, _
   ByVal uiParam As UInteger, _
   ByVal pvParam As StringBuilder, _
   ByVal fWinIni As UInteger) _
As Boolean
   ' uiAction contains a constant value that defines
   ' the action to perform.
   ' uiParam contains the size of the buffer used to
   ' hold the URI on return from a get call.
   ' pvParam contains the name of the file.
   ' fWinIni contains the flags for changing the
   ' wallpaper information.
   ' The return value is a Boolean indicating success or
   ' failure.
End Function

The function declaration begins with two attributes. Every time you use P/Invoke, you must define where to obtain the function using the <DllImport> attribute. This function appears in User32.DLL. Optionally, you can tell .NET to provide you with full error information from Windows. From my perspective, this isn’t an option—you should always configure your P/Invoke calls to return full error information.

The second attribute, <CLSCompliant>, has become a requirement in newer versions of Visual Studio. Most Windows functions aren’t CLS compliant, so you must mark them a such or the compiler will complain. The SystemParametersInfo() function uses unsigned 32-bit values, so it isn’t CLS compliant.

Also note that this function uses the UInteger data type. Earlier versions of P/Invoke calls for the SystemParametersInfo() function relied on the UInt32 data type. Both data types provide the same 32-bit unsigned integer value, but newer versions of Visual Studio prefer that you use the UInteger type and will complain if you use UInt32.

Notice that the function declaration includes comments that document every one of the input arguments. This is a best practice any time you use P/Invoke. The Visual Basic help system won’t help you, nor will other IDE features. If you don’t document the arguments as part of your code, you’ll find yourself wasting a huge amount of time rediscovering what these arguments mean by locating the Windows API online. In addition, when you’re using a multitasking function such as SystemParametersInfo(), even the Microsoft information will be too generic to be useful. The comments provided with the example code are specific to how the example uses the function.

Now that you have a little better understanding of SystemParametersInfo() (future posts will provide examples of it’s usage and explain them to you), it’s time to look at the constants used to make the call. The following list of constants is specific to this application. The actual list is considerably longer.

' Define constants to use with the SystemParametersInfo
' function. All of these values come from WinUser.h.
 
' Defines the action to take--get or set the wallpaper.
Const SPI_SETDESKWALLPAPER As UInteger = &H14
Const SPI_GETDESKWALLPAPER As UInteger = &H73
 
' Update the user's information.
Const SPIF_UPDATEINIFILE As UInteger = &H1
 
' Tell other applications about the change in status.
Const SPIF_SENDWININICHANGE As UInteger = &H2

Again, it’s essential to document the constants fully. For example, you need to know where these constants come fromWinUser.h in this case. The constant names are precisely the same as the names in WinUser.h. If you don’t use the same names, you’ll find it difficult to work with the SystemParametersInfo() function later and to obtain additional information about it.

Notice that these constants all have a data type of UInteger. As mentioned earlier, older versions of Visual Basic would have relied on UInt32 or not provided a type at all. The code defines the constant values using hexadecimal notationthe same notation found in WinUser.h.

As you can see, these constants tell the SystemParametersInfo() function to either get or set the current Desktop wallpaper settings. When the application sets the Desktop wallpaper, you must also tell SystemParametersInfo() to update the user’s settings and to inform other applications about the change (so they’ll redraw their windows as needed). Let me know if you have any questions about the SystemParametersInfo() function at John@JohnMuellerBooks.com. You can find the next post in this series at Exploring the GrabAPicture Application (Part 7).

 

Exploring the GrabAPicture Application (Part 5)

At this point, most of the graphics are out of the way. The previous post, Exploring the GrabAPicture Application (Part 4), discussed some of the considerations for working with icons. If you have some additional questions about that post, please be sure to let me know. A future post will likely contain some refinements on those techniques, but for now, let’s begin looking at the data storage mechanism for this application.

This application stores two kinds of source information: local and remote URIs. In order to make it easier to work with these data sources, you’ll create special classes. Use these steps to create the class files:

 

  1. Right click the project entry in Solution Explorer and choose Add | New Item from the context menu. You’ll see the Add New Item dialog box shown here.

    GrabAPicture0501

  2. Highlight the Class template.
  3. Type LocalUri.vb for the first class in the Name field and click Add. Visual Studio creates a blank class with the appropriate name for you.
  4. Repeat Steps 1 through 3 for the RemoteUri.vb class.

Another class in this application holds all of the code used to manipulate the data and also change the wallpaper. We’re going to work on this class quite a bit as these posts progress. For now, use Steps 1 through 3 to create a third class called WinWallpaper.vb.

Interestingly enough, you actually need to add something to the WinWallpaper class before you can proceed with either the LocalUri or RemoteUri classes. This example relies heavily on enumerations to reduce the risk of entering the wrong information when working with a class. The Styles enumeration is central to many tasks, but all of these tasks are somehow associated with WinWallpaper, so you’ll place the following enumeration there.

' A list of wallpaper display styles.
Public Enum Styles As Integer
   Tiled
   Centered
   Stretched
   Unknown
End Enum

The purpose of this enumeration is to define how to display the wallpaper on screen. The default setting is Unknown. This value actually ends up acting as a means of checking for error conditions later in the application. For now, just remember that Unknown means that the style hasn’t been configured by the user or the application.

Now, let’s begin by looking at the LocalUri class. The following code shows everything needed to create the class (including the initial code that Visual Studio creates for you).

Imports System.Xml
Imports GrabAPicture.WinWallpaper
 
<Serializable()> _
Public Class LocalUri
   ' List of local values for each entry.
   Private _Name As String = ""
   Private _Location As String = ""
   Private _Style As Styles = Styles.Unknown
 
   ' Handle the item name.
   Public Property Name() As String
      Get
         Return _Name
      End Get
      Set(ByVal Value As String)
         _Name = Value
      End Set
   End Property
 
   ' Handle the item location.
   Public Property Location() As String
      Get
         Return _Location
      End Get
      Set(ByVal Value As String)
         ' This bit of code verifies that Value contains a
         ' valid URI. If it doesn't, the class will throw
         ' an exception during the double conversion to and
         ' from a URI.
         _Location = (New Uri(Value)).ToString()
      End Set
   End Property
 
   ' Handle the item display style.
   Public Property Style() As Styles
      Get
         Return _Style
      End Get
      Set(ByVal Value As Styles)
         If Value = WinWallpaper.Styles.Unknown Then
            ' The unknown style is only for error trapping.
            Throw New ArgumentException( _
               "Style cannot be Unknown")
         Else
            ' Set the correct style.
            _Style = Value
         End If
      End Set
   End Property
End Class

In order to use this class, you must import both the System.Xml namespace and the GrabAPicture.WinWallpaper class. Notice that this class is marked with the Serializable() attribute. This attribute makes it possible to perform certain XML-related tasks with considerable ease. Otherwise, there isn’t anything special about the class declaration.

The LocalUri class consists of three private fields and three public properties. Here is a description of each of the properties.

 

  • Name: Accepts any source name value. The application could probably add some error trapping for it, but the run-time will capture any major issues. Names can include any characters. About the only limit (and it would be an artificial one) would be to set the number of characters that a Name can contain.
  • Location: Accepts the location of the source as a String. The conversion from a String to a Uri also prevents someone from sending nonsense information. A String input that won’t convert to a Uri will always raise an exception.
  • Style: Defines the way in which the application displays the image on the Desktop. Because this isn’t a value that you can easily check any other way, the Style is always set to Unknown at the outset. If the value remains Unknown, the application raises an error. An image must have a style associated with it. Notice how the use of an enumeration simplifies the code in this case.

The RemoteUri class is similar to the LocalUri class. In fact, you won’t see any major differences in this version of the application. The main reason to use two separate classes is to make the code more readable and to make the application more flexible. For example, a future version of the application could obtain remote sources from a Web service, which would mean changing the RemoteUri class in ways that the LocalUri class wouldn’t require. Here is the RemoteUri class code.

Imports System.Xml
Imports GrabAPicture.WinWallpaper
 
<Serializable()> _
Public Class RemoteUri
   ' List of local values for each entry.
   Private _Name As String = ""
   Private _Location As String = ""
   Private _Style As Styles = Styles.Unknown
 
   ' Handle the item name.
   Public Property Name() As String
      Get
         Return _Name
      End Get
      Set(ByVal Value As String)
         _Name = Value
      End Set
   End Property
 
   ' Handle the item location.
   Public Property Location() As String
      Get
         Return _Location
      End Get
      Set(ByVal Value As String)
         ' This bit of code verifies that Value contains a
         ' valid URI. If it doesn't, the class will throw
         ' an exception during the double conversion to and
         ' from a URI.
         _Location = (New Uri(Value)).ToString()
      End Set
   End Property
 
   ' Handle the item display style.
   Public Property Style() As Styles
      Get
         Return _Style
      End Get
      Set(ByVal Value As Styles)
         If Value = WinWallpaper.Styles.Unknown Then
            ' The unknown style is only for error trapping.
            Throw New ArgumentException( _
               "Style cannot be Unknown")
         Else
            ' Set the correct style.
            _Style = Value
         End If
      End Set
   End Property
End Class

The next post will begin looking at some of the tasks performed with the WinWallpaper class (beside providing the Styles enumeration). For now, you know that the XML file that holds the data source information will include three entries for each source: Name, Location, and Style. These three values are enough to display any image on the Desktop. Let me know if you have any questions about these serializable classes at John@JohnMuellerBooks.com. You can see the next post in this series at Exploring the GrabAPicture Application (Part 6).

 

Exploring the GrabAPicture Application (Part 4)

In the previous post, Exploring the GrabAPicture Application (Part 3), you completed configuring the forms for this application. This post adds an icon to the application. Icons can be a problem because there is so much conflicting documentation about them. Some people actually go so far as to say you must create your icon in an external environment because Visual Studio doesn’t support 32-bit icon development.

One of the things I decided to experiment with is the minimum requirements for icons in Windows 7—the most demanding icon environment to date. Here are my conclusions:

 

  • You can development icons in Visual Studio without any problem.
  • Icons can be 4-bit graphics, despite what you have read elsewhere.
  • To see the icon in all Internet Explorer views, you must supply the following sizes:16×16, 32×32, 48×48, and 256×256.
  • All of the icons must appear in a separate file with a .ICO extension.

Anything else you might have heard is simply wrong. I created the icons for this example using Visual Studio with 4-bit graphics. They show up just fine in Internet Explorer, the Start Menu, the Taskbar, and everywhere else I’ve tried. I’m not much of an artist, but these icons will do just fine.

GrabAPicture0401

Now that you have an icon to use, you need to perform a number of tasks to see it.

 

  1. Save the icon to the application’s development folder on disk.
  2. Look for the icon in Solution Explorer. If you don’t see it, right click the project entry and choose Add | Existing Item from the context menu. Locate the .ICO file in the Add Existing Item dialog box and click Add.
  3. Select the icon in Solution Explorer and choose Embedded Resource in the Build Action property of the Properties window. Ensure that the Copy to Output Directory property is set to Do Not Copy.
  4. Select each form in turn and set its Icon property to the .ICO file you created. You’ll see the icon appear in the usual location in the upper left corner of the form.
  5. Right click the project entry in Solution Explorer and choose Properties from the context menu. You’ll see the project’s Property window open.
  6. Select the Application tab. In the application tab, set the Icon property to the .ICO file you created earlier. You’ll see the icon appear on the page as shown here.
    GrabAPicture0402


At this point, you can build your application and see the results. Unfortunately, Windows may not cooperate with you. You may see the original application icon. It seems that Microsoft decided to create an icon cache to make application performance faster. This cache causes problems because it stores your old icon, rather than the one you just created. The following steps will help you see your new icon (these steps will work in Windows 7 and should also work with VistaWindows XP developers will have to modify the steps to meet their needs).

 

  1. Close all copies of Internet Explorer and Windows Explorer.
  2. Right click the Desktop and choose Personalize from the context menu. You see the Personalization window.
  3. Set the theme to Windows 7 Basic. Windows changes the theme for you. Using this theme gives you access to an important feature.
  4. Click Window Color near the bottom of the Personalization window. You see the Window Color and Appearance dialog box shown here.
    GrabAPicture0403
  5. Select Icon in the Item field.
  6. Type 33 in the Size field and click Apply. This step forces Windows to clear the cache. However, the icons are now at an unusable size.
  7. Type 32 in the Size field and click OK. You’ve set the icon size back to its original size.


At this point, you should see your new icon used everywhere the application’s executable appears. Open a copy of Windows Explorer to see for yourself. If you still don’t see the new icon, close all copies of Internet Explorer and Windows Explorer manually and reboot the system. Let me know if you have any trouble with this process at John@JohnMuellerBooks.com. You can see the next post in this series at Exploring the GrabAPicture Application (Part 5).

 

Exploring the GrabAPicture Application (Part 3)

In the previous post, Exploring the GrabAPicture Application (Part 2), you created frmMain, the first form that the user sees when using GrabAPicture in GUI mode. Someone wrote in to tell me that I forgot to include the form settings in the table. I looked back and found they’re correct, so here’s the settings you need for frmMain:

Control Property Value
Form1 (Name) frmMain
  AcceptButton bntOK
  CancelButton btnCancel
  FormBorderStyle FixedDialog
  Size 328, 269
  Text GrabAPicture

The form also sports an Icon property setting that I’ll discuss in a separate post for all three forms. Notice that the form uses the FixedDialog style, which is something you should consider for any form like this one that doesn’t include re-sizable elements. To make the form keyboard friendly, make sure you always include the AcceptButton and CancelButton settings so that the form reacts as expected when the user presses Enter or Escape.

On to the frmConfigure form. This form contains the list of local and remote settings. I had originally thought about creating just one list. However, there are times where a person works without an Internet connection or would prefer to use Internet sources for the desktop wallpaper than local sources. A local source is one that the user can access through a local drive or a network connection, while a remote source requires a URL for access. The URL can be anything that the .NET Framework can process, including the output from a Web service as long as that output is an image. You can’t use an entire Web page as a source because the Web page would contain non-graphic elements, but you can access a particular image on the Web page. Here’s how the form appears in the designer.

GrabAPicture0301

As you can see, there are separate sets of controls for local and remote sources. The following table tells how to configure these controls.

Control Property Value
Form2 (Name) frmConfigure
  CancelButton btnCancel
  FormBorderStyle FixedDialog
  ShownInTaskbar False
  Size 425, 301
  Text Configure a Random Source
Button1 (Name) btnCancel
  DialogResult Cancel
  Location 336, 8
  Modifiers Friend
  Size 75, 23
  TabIndex 0
  Text Cl&ose
Label1 (Name) lblLocal
  Location 8, 8
  Modifiers Friend
  Size 100, 23
  TabIndex 1
  Text Local So&urces:
ListBox1 (Name) lstLocal
  Location 8, 32
  Modifiers Friend
  Size 232, 95
  TabIndex 2
Button2 (Name) btnLAdd
  Location 240, 32
  Modifiers Friend
  Size 75, 23
  TabIndex 3
  Text &Add
Button3 (Name) btnLEdit
  Location 240, 56
  Modifiers Friend
  Size 75, 23
  TabIndex 4
  Text &Edit
Button4 (Name) btnLDelete
  Location 240, 80
  Modifiers Friend
  Size 75, 23
  TabIndex 5
  Text De&lete
Button5 (Name) btnLSelect
  Location 240, 104
  Modifiers Friend
  Size 75, 23
  TabIndex 6
  Text &Select
Label2 (Name) lblRemote
  Location 8, 144
  Modifiers Friend
  Size 100, 23
  TabIndex 7
  Text &Remote Sources
ListBox2 (Name) lstRemote
  Location 8, 160
  Modifiers Friend
  Size 232, 95
  TabIndex 8
Button6 (Name) btnRAdd
  Location 240, 160
  Modifiers Friend
  Size 75, 23
  TabIndex 9
  Text A&dd
Button7 (Name) btnREdit
  Location 240, 184
  Modifiers Friend
  Size 75, 23
  TabIndex 10
  Text Ed&it
Button8 (Name) btnRDelete
  Location 240, 208
  Modifiers Friend
  Size 75, 23
  TabIndex 11
  Text Dele&te
Button9 (Name) btnRSelect
  Location 240, 232
  Modifiers Friend
  Size 75, 23
  TabIndex 12
  Text Sele&ct

In this case, the form doesn’t include an AcceptButton entry because there isn’t any default accept action available. One alternative would be to make the AcceptButton entry the same as the CancelButton entry.

Notice that this form isn’t displayed in the Taskbar. The ShownInTaskbar setting is set to False. You only want to display the main form in the Taskbar to avoid confusing the user.

The third, and final form, is a multiuse form. The application uses frmAddEdit in four different ways, but could potentially use it in other ways:

 

  • Add a Local Entry
  • Edit a Local Entry
  • Add a Remote Entry
  • Edit a Remote Entry

There isn’t any way to predict which task the user will perform most often, so the form uses the Add a Local Entry task as the focus for the controls. The calling form, frmConfigure, will change the frmAddEdit as needed to meet a specific need. The basic form looks like this.

GrabAPicture0302

This form has a few extras on it that you’ll learn more about when you write the code. For example, the ellipses (…) button only appears for local resources. It works with an OpenFileDialog control that isn’t shown on the form to help a user find a local resource on a local drive or the network. The following table contains the settings for this form.

Control Property Value
Form3 (Name) frmAddEdit
  AcceptButton btnAddEdit
  CancelButton btnCancel
  FormBorderStyle FixedDialog
  ShownInTaskbar False
  Size 300, 266
  Text Add Local Resource
Button1 (Name) btnAddEdit
  DialogResult OK
  Location 208, 8
  Modifiers Friend
  Size 75, 23
  TabIndex 0
  Text &Add
Button2 (Name) btnCancel
  DialogResult Cancel
  Location 208, 40
  Modifiers Friend
  Size 75, 23
  TabIndex 1
  Text &Cancel
Label1 (Name) lblName
  Location 8, 8
  Modifiers Friend
  Size 100, 23
  TabIndex 2
  Text &Name
TextBox1 (Name) txtName
  Location 8, 32
  Modifiers Friend
  Size 184, 20
  TabIndex 3
Label2 (Name) lblLocation
  Location 8, 64
  Modifiers Friend
  Size 100, 23
  TabIndex 4
  Text &Location
TextBox2 (Name) txtLocation
  Location 8, 88
  Modifiers Friend
  Size 160, 20
  TabIndex 5
Button3 (Name) btnGetResource
  Location 168, 88
  Modifiers Friend
  Size 24, 23
  TabIndex 6
  Text
GroupBox1 (Name) grpStyleSelect
  Location 8, 128
  Modifiers Friend
  Size 192, 100
  TabIndex 7
  Text Style Selection
RadioButton1 (Name) rbStretched
  Checked True
  Location 16, 24
  Modifiers Friend
  Size 104, 24
  TabIndex 0
  TabStop True
  Text &Stretched
RadioButton2 (Name) rbCentered
  Location 16, 48
  Modifiers Friend
  Size 104, 24
  TabIndex 1
  TabStop True
  Text &Centered
RadioButton3 (Name) rbTiled
  Location 16, 72
  Modifiers Friend
  Size 104, 24
  TabIndex 2
  TabStop True
  Text &Tiled
OpenFileDialog1 (Name) OFD
  Filter BMP Files|*.BMP|JPG Files|*.JPG|GIF Files|*.GIF|PNG Files|*.PNG|All Graphics Files|*.BMP;*.JPG;*.GIF;*.PNG
  Modifiers Friend

Notice that this form can have an AcceptButton and a CancelButton setting because there are default actions for both. As with frmConfigure, frmAddEdit doesn’t appear in the Taskbar.

Some people are thrown by the use of the GroupBox control in this part of the example. The Location property is relative to the location within the GroupBox when working with the three RadioButton controls. Likewise, the TabIndex is local to the GroupBox, so the numbering starts over with 0. There are several ways to determine whether the RadioButton is a tab stop. You can choose to set the GroupBox as the tab stop, the default RadioButton as the tab stop, or all of the RadioButton controls as potential tab stops. This example takes the last action, but you should try all three forms to see what you like best. The GroupBox also lacks a hot key because you’ll never select it directly—the focus is on the controls within the GroupBox, so that’s where the hot keys are.

The next installment will begin by discussing the icon used for this example. It’s nothing fancy, but there are some tricks you need to know about when working with icons. Let me know if you have any questions at John@JohnMuellerBooks.com. You can see the next post in this series at Exploring the GrabAPicture Application (Part 4).

 

Exploring the GrabAPicture Application (Part 2)

In the previous post, Exploring the GrabAPicture Application (Part 1), we discussed the basic parameters for the application—that it provides a means of changing the desktop wallpaper automatically, use online as well as local resources, and sports a command line interface (amongst other features). This post starts building the application.

You’ll start with the basic Windows Forms Application template. Rename the initial form as frmMain (this application will contain a number of forms) by right clicking the form’s entry in Solution Explorer and choosing Rename File from the context menu. This first form is the one that you’ll always see when you start the application from the command line. After you rename the initial form, add two more forms, frmAddEdit (used to add new entries and edit existing entries) and frmConfigure (used to display and manage the list of local and remote sources), by right clicking the project entry in Solution Explorer and choosing Add | New Item from the context menu. You saw the working version of these forms in the previous post.

 

I’m eliminating a few configuration features from the example for the sake of simplicity. For example, you should include tooltips with your version of the application to make it accessible. When the example is completely finished, you’ll be able to download the source code and see some of these additions for yourself.

Let’s begin with the design of frmMain.
Here’s how frmMain will appear.

GrabAPicture0201

This form lets the user type in the location of a piece of wallpaper and use it directly by typing something into Wallpaper Location, selecting a style in Style Selection, and clicking Set Value. The user can also click Random Sources to see a list of available wallpaper sources, which is actually the preferred method of interacting with the application because this technique lets the user save entries for future use. Close will close the form. The user can also use this form to choose whether to use local sources, remote sources, or both. The following table describes the control configuration for this form (add the controls to the form in the order shown to reduce the amount of configuration you must perform).

Control Property Value
Button1 (Name) btnOK
  DialogResult OK
  Location 208,8
  Modifiers Friend
  Size 104,23
  TabIndex 0
  Text Set &Value
Button2 (Name) btnConfigure
  Location 208,40
  Modifiers Friend
  Size 104,23
  TabIndex 1
  Text &Random Sources
Button3 (Name) btnCancel
  DialogResult Cancel
  Location 208,72
  Modifiers Friend
  Size 104,23
  TabIndex 2
  Text &Close
Label1 Location 8,8
  Modifiers Friend
  Size 144,23
  TabIndex 3
  Text &Wallpaper Location:
TextBox1 (Name) txtWallpaperURI
  Location 8,32
  Modifiers Friend
  Size 176,20
  TabIndex 4
GroupBox1 Location 8,64
  Modifiers Friend
  Size 192,100
  TabIndex 5
  Text Style Selection
RadioButton1 (Name) rbStretched
  Location 16,24
  Modifiers Friend
  Size 104,24
  TabIndex 6
  Text &Stretched
RadioButton2 (Name) rbCentered
  Location 16,48
  Modifiers Friend
  Size 104,24
  TabIndex 7
  Text &Centered
RadioButton3 (Name) rbTiled
  Location 16,72
  Modifiers Friend
  Size 104,24
  TabIndex 8
  Text &Centered
CheckBox1 (Name) cbLocal
  Location 8,176
  Modifiers Friend
  Size 184,24
  TabIndex 9
  Text Use Random &Local Sources
CheckBox2 (Name) cbRemote
  Location 8,208
  Modifiers Friend
  Size 184,24
  TabIndex 10
  Text &Use Random Remote Sources

Next time we’ll look at two other forms. The first helps the user manage local and remote sources. The second will provide the means for adding or editing individual entries. Let me know if you have any questions at John@JohnMuellerBooks.com. You can see the next post in this series at Exploring the GrabAPicture Application (Part 3).

 

Exploring the GrabAPicture Application (Part 1)

Sometimes it’s nice to explore a new kind of application. GrabAPicture is a Visual Basic.NET application I built for myself and have continually improved over a number of years. This application lets me record my favorite picture locations, whether they exist online, on a network, or on my location machine, and display them as wallpaper. Yes, I could have used Windows 7 functionality to create a rotating services of random pictures, but I wanted to create my own application. It does have some advantages over the Windows 7 option:

 

  • The command line interface makes it possible to add the application to the Startup folder, access the application from a shortcut, or execute the application from a command prompt.
  • The application has the ability to use both network and online resources.
  • I can individually adjust how I want each picture displayed. If one picture looks better tiled and another stretched, I have that option.


Of course, it’s always nice to play around and see what you can do. This example builds on techniques found in .NET Framework Solutions: In Search of the Lost Win32 API and Professional Windows 7 Development Guide. Some of the techniques, such as saving the settings to an XML file in the user’s C:\Users\UserName\AppData\Roaming\GrabAPicture folder are ported from C# applications I created in the past. I’m also trying out a few entirely new techniques in this application. In short, it’s a combination of techniques both old and new.

To achieve my goal, I’m using some exotic techniques, such as PInvoke to change the wallpaper. I’m also creating a serializable class to hold the application data and make it easy to work with. The example uses several forms that are specially configured to ensure GrabAPicture works as expected. As previously mentioned, it includes a fully functional command line interface that lets you adjust everything from the command line if desired. Of course, you can also work with the GUI.

You’ll find a new installment for this series every Friday. I have no idea right now of just how many installments the application will require—that depends on how many reader queries I receive and just how far I have to break the application down in order to make it easy to understand. When I’ve completed this series, you’ll be able to create your own version of GrabAPicture and hopefully improve on it. In fact, I’ll provide some ideas as we go along of things I’d like to add.

Let’s get started with a quick overview of the user interface. When you start GrabAPicture, you’ll see the current application settings as shown here.

GrabAPicture0101

If desired, you can type the location of a wallpaper file into the Wallpaper Location field, click Set Value, and have it change the wallpaper on your desktop. However, the more common way to use this application is with a random source, which you access by clicking Random Sources. This dialog box contains local and remote sources as shown here.

GrabAPicture0102

I chose to separate local sources (including those on the local network) from remote sources (those on the Internet) so that the application could continue to do its job using just local or just remote sources if desired. Each source has four buttons associated with it. You can add new sources, edit existing sources, delete a source you no longer want, and select a particular source for use immediately.

Selecting or deleting a source doesn’t require much additional work. However, the other two activities require that you obtain additional information from the user. Adding or editing a source displays a customized version of the following dialog box (the customization occurs in code—the application doesn’t actually use multiple forms).

GrabAPicture0103

You give each source a name and a location. The entry also includes a style selection that tells Windows how to display it. The example is currently compatible with older versions of Windows, so it includes the Stretched, Centered, and Tiled settings. In a later blog post, I’ll show how to update these settings to something that’s Vista or Windows 7 specific so it will include the Fill and Fit options.

Most command line utilities rely on a text-based help screen. I chose to include a GUI help screen for GrabAPicturejust to be different and to demonstrate how you’d perform this task (since there aren’t any other examples that I can find). When you type GrabAPicture /? and press Enter at the command line, you see the following help screen.

GrabAPicture0104

Well, that’s about it from a design perspective. Let me know if you have any questions at John@JohnMuellerBooks.com. You can see the next post in this series at Exploring the GrabAPicture Application (Part 2).