Cross Platform Functionality for .NET

Microsoft has recently announced that it will port the .NET Framework to the Mac and Linux platforms. This is welcome news because more and more of my readers have expressed an interest in developing applications that run on multiple platforms. It’s the reason that I cover Windows, Linux, and Mac requirements in books such as Beginning Programming with Python For Dummies. Until now, I usually had to include some mention of alternative solutions, such Mono, to help my readers achieve cross-platform functionality. (For readers with older versions of my books, Mono is actually delivered by Xamarin now, see my announcement in the An Update About Mono post.) Even though Mono makes a valiant effort to make cross-platform a reality, it does have limits, so the Microsoft announcement is welcome. Now we have to see whether Microsoft actually delivers on its promises.

There has been a lot of analysis about the announcement. You can find some general information about the product on eWeek. The information is pretty much a reworded version of the Microsoft announcement, but I found it clear and succinct. The InfoWorld writeup provides additional information and takes Microsoft to task for not completely opening the .NET Framework. There are still some licensing issues to consider. For my part, I wonder when Microsoft will make it possible to fully use C# on any platform. At some point, Microsoft must make it possible to develop applications on a platform other than Windows or developers will continue to lose interest.

One of the biggest questions I’ll need to answer for you is whether any of my book examples will run on other platforms. Given how Microsoft has done things in the past, it seems unlikely that you’ll be able to use any of my existing book examples on other platforms. The code might possibly work, but the downloadable source would have to be redone to make it possible to compile the examples with the new tools. So, for now, I’m saying outright that you need to continue to use my books with the version of Visual Studio for which they are written and not assume that the examples will work on other platforms.

I do find the news exciting because there is finally a chance that I’ll be able to address your needs better when it comes to working with languages such as C#. Yes, working with solutions such as Mono did allow you to perform certain tasks across platforms, but there is not a potential for writing complete applications of nearly any type and having them work anywhere, which is where the world as a whole has been headed for a long time. I applaud Microsoft’s efforts to move forward.

Please do contact me with your questions regarding cross-platform functionality in .NET and how it affects by books at John@JohnMuellerBooks.com. No, I can’t answer your question about how Microsoft will implement cross-platform functionality in the new versions of .NET, but yes, I do want to hear about your ideas for book updates based on this technology. What I want to do is help you use this new functionality as soon as is possible.

 

Designing with the Entity Framwork

A number of factors have contributed toward the evolution of the Entity Framework (a technology used to map database entities to objects used within .NET application programs). However, one of the major factors is the way in which developers work. Not all developers work in the same way and not every situation requires the same solution. With this in mind, Microsoft ADO.NET Entity Framework Step by Step helps you understand how the evolution of the Entity Framework has been affected by these factors. It points out how the alternatives provided by the Entity Framework make your job easier by increasing the flexibility of the development environment.

One of the more important evolutionary steps is the inclusion of three modeling techniques within the Entity Framework: Model First (where you create a database based on a model you define), Database First (where you generate a model based on an existing database), and Code First (where you generate a database based on the objects in an existing application without using a designer-based model). You can read about these three models and how they affect your application development in my recent article, “Choosing the Right Entity Framework Workflow.” The purpose of this article is to help you make the best choice in modeling technique for your next Entity Framework project. The article also points out the need to combine techniques at times to obtain a desired result and helps you understand some of the pros/cons of each approach. Of course, there is no wrong or right approach—simply the approach that works best for you.

The Entity Framework is a necessary technology when dealing with the complexities of a large scale development. Modeling tools make it possible to understand complex interactions with greater ease and to explain those interactions to others who might not have development experience. In addition, you use modeling tools to document how objects in an application relate to the tables and other features of a database. Knowledge like this is incredibly important when working in a team environment where team members must communicate well with each other or the project is doomed to failure. Most importantly for the developer, using a modeling technology like the Entity Framework greatly reduces the confusion that can occur when developer moves outside the comfort of a familiar development environment.

Of course, there is a lot more to be said about the Entity Framework and this article (and even my book) are really just a starting point. I always like to get the reader perspective about materials that I write. What are your experiences in using the Entity Framework? Let me know at John@JohnMuellerBooks.com.

 

.NET Framework Version Error When Installing Visual Studio 2012

I received a few messages from readers recently asking about an error message they receive when trying to install the release version of Visual Studio 2012 on their Windows 8 system (I imagine that the same error will occur when installing on Windows Server 2012, but no one has contacted me about it). The error says that Visual Studio 2012 can’t install because the version of the .NET Framework 4.5 is wrong.

Unfortunately, the error message isn’t very helpful. You can’t install a new version of the .NET Framework 4.5 over the top of the existing installation. In addition, you can’t uninstall the old version and then install the new version because Windows 8 requires the .NET Framework 4.5 for certain operating system elements. In short, there isn’t any apparent way to fix the problem.

The issue will go away at some point because it originates as a conflict between the Windows 8 version and the Visual Studio 2012 requirements. Every reader who has had this problem is using a non-released version of Windows 8 (normally the RC version). You can’t install the release version of Visual Studio 2012 on a non-release version of Windows 8. I’m assuming that the same error occurs if you try to install a release version of Visual Studio 2012 on a non-release version of Windows Server 2012, but I’d like to hear if anyone has tried this out at John@JohnMuellerBooks.com.

What concerns me about this particular error is that it’s one time where Microsoft could have (and probably should have) come up with a substantially better error message or provided some sort of Knowledge Base article on the topic. As far as I know, there isn’t any such article and developers are currently having to rely on community support to fix this problem. It isn’t the first time Microsoft has left developers wondering. If you ever encounter this sort of problem, please let me know about it. If I can confirm the issue, I’ll put together a blog entry about it to get the word out to others in order to save them a bit of time.

 

Updates in the .NET Framework 4.5

I try hard to follow the latest developments in all of the technologies that interest me. Since my range of interests is large, sometimes it proves nearly impossible to track it all. So, like you, I rely on summaries of information at times. I’ve been working with Visual Studio 2012 quite a lot lately. Not only is it the tool of choice for other authors and the tool I’ll use to write books myself, but I also need to verify that the source code for my currently supported books still works. The new features in the associated .NET Framework 4.5 are of great interest to me, so I checked out an article about it entitled, “What’s New in the .NET 4.5 Base Class Library” by Immo Landwerth.

There are a lot of welcome updates. For example, asynchronous programming won’t be as difficult in the future because Microsoft has taken measures to simplify things. You can read the article yourself to see how much better the new technique is. I’m sure that many developers will welcome the change because it will make it possible for developers to create asynchronous routines with greater ease, which should make applications considerably more responsive. Given that one of the five tenets of Metro programming is to be fast and fluid, it doesn’t surprise me that .NET 4.5 contains this sort of change. I worked through the example and even tried a few of my own and the new asynchronous support works great! However, I have to wonder what the cost of this new support is. It isn’t often that you get simplicity without the loss of flexibility. As I explore this new feature, I’ll let you know if I do come up with reasons to use the old approach (as nice as the new approach is).

The new ZIP file support strikes me as something Microsoft added because everyone else already has it. Microsoft was late to the party with this one. However, the new support is quite welcome because ZIP files have proven to be a problem in the past. Not many applications can get by without this particular archive today. What I had hoped would happen is that Microsoft would build an extensible archive support feature that would support ZIP files, but would also make it incredibly easy to add support for other archive file types, such as JAR and TAR. Unfortunately, it looks like we’ll see one new archive file format supported at a time.

I’ll need to play with the read-only collections to determine how I’ll use them. Right now, it appears that this new feature will see limited use in my applications (if I use it at all). The first two improvements mentioned in this post have a definite use in my applications and I’m welcoming them with open arms. What I’d like to hear from you is how you’d use this new feature. Let me know what you think about it at John@JohnMuellerBooks.com.

Articles are problematic at times because the author often doesn’t have sufficient space to cover all of the details. I know that I’ve encountered this issue myself. The editor assigning the article might not have sufficient grasp of the material to make a good decision about it either. I wish that this article had been split into several parts because the author lists a number of miscellaneous improvements at the end of the article. If you read the article, make sure you check this list carefully because some of the improvements, such as Console.IsInputRedirected, Console.IsOutputRedirected, and Console.IsErrorRedirected properties, are quite interesting. I write a lot of console applications and utility application that provide a console interface. Having these properties available will make it possible for me to code applications that are more responsive to user needs.

I’ve noticed that Microsoft didn’t provide a full number update to the revision number in this case and the minor upgrade number is warranted. There are some nice changes to the .NET Framework 4.5, but nothing so exciting that you should rush out right now and start implementing it in your applications. It would be wise to carefully add this update to your applications to determine when it provides a true advantage and when there is potentially a loss of functionality or flexibility that makes the update a bad idea.

 

Improving the GrabAPicture Application (Part 4)

Last week’s post, Improving the GrabAPicture Application (Part 3), began the process of modifying the forms to accept the special wallpaper settings supported by Windows 7 and newer versions of the Windows operating system. That post also stressed the need to make the forms flexible enough to support older versions of the operating system. The forms actually detect which operating system is in use and modify their appearance to match the capability of the operating system. This week you’ll see the code used to make the new settings work properly. Of course, these two posts together support the second objective outlined in the Improving the GrabAPicture Application (Part 1) post.

Modifying WinWallpaper.vb

The underlying code is less critical when it comes to detecting the operating system because the form already performs this task. The user can’t select an option that doesn’t work with the selected operating system if the forms don’t make that option available. Consequently, a lot of the changes in this example are straightforward. The first change is to extend the Styles enumeration initially discussed in the Exploring the GrabAPicture Application (Part 5) post as shown in the following code.

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

Notice that I add the new options after Unknown to ensure that they don’t inadvertently affect any of the application code. Some developers might be tempted to add the options before Unknown, but this would be a mistake because you can’t be absolutely certain about potential side effects of doing so.

Now that you have the right enumerations available, it’s time to modify the code that relies on the enumerations. Before you can do anything though, you need to determine what values to provide for the  two new enumerations. Remember that in the Exploring the GrabAPicture Application (Part 7) post that the HKEY_CURRENT_USER\Control Panel\Desktop registry key WallpaperStyle value contains the information you need. When you set the desktop to use the Fit style, this value becomes 6 as shown here.

GrabAPictureUpdate0401

Setting the desktop wallpaper to Fill changes this value to 10. Consequently, the first change you need to make is to the Style() property (discussed in the Exploring the GrabAPicture Application (Part 7) post) as shown here.

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
 
      ' Windows 7 and above-specific values.
      If StyleKey.GetValue("WallpaperStyle") = 6 And _
         StyleKey.GetValue("TileWallpaper") = 0 Then
         Return Styles.Fit
      End If
      If StyleKey.GetValue("WallpaperStyle") = 10 And _
         StyleKey.GetValue("TileWallpaper") = 0 Then
         Return Styles.Fill
      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")
 
            ' Provide Windows 7 and above-specific values.
         Case Styles.Fit
            StyleKey.SetValue("WallpaperStyle", "6")
            StyleKey.SetValue("TileWallpaper", "0")
         Case Styles.Fill
            StyleKey.SetValue("WallpaperStyle", "10")
            StyleKey.SetValue("TileWallpaper", "0")
      End Select
   End Set
End Property

All of the required changes are marked with comments so you can find them easily. In all cases, it’s simply a matter of making the right registry changes. The behavior of the code remains unchanged.

During testing, I found that if the value of the WallpaperStyle registry setting is set to either 6 or 10 on a Windows XP system, Windows assumes you mean stretched (the option normally associated with a value of 2). Testing on a Vista system shows the same thing happens. I’ll be interested to hear from readers whether this is the case on their systems. If not, the example may very well need to include additional operating system-specific code for the Style() property.

Modifying the Command Line Interface

There is one place where things can get incredibly messy, and that’s with the command line interface. The Exploring the GrabAPicture Application (Part 9) post describes the command line interface in detail. In addition, you’ve already seen on update to this interface in the Improving the GrabAPicture Application (Part 2) post. In order to add the new Windows 7 functionality to the command line, you’ll need to perform another update of it that includes operating system version checking similar to the checking provided for the forms. For now, we’ll leave the command line interface alone and save this update for another time.

However, in order to keep the command line interface from working improperly, you do need to implement a few changes to the /Random command line switch code. This code ensures that the Windows 7 and above-specific settings are handled when working with random wallpaper selections as shown here:

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
 
               ' Add the Windows 7 and above-specific options.
            Case WinWallpaper.Styles.Fit
               Wallpaper.Style = WinWallpaper.Styles.Fit
            Case WinWallpaper.Styles.Fill
               Wallpaper.Style = WinWallpaper.Styles.Fill
         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
 
               ' Add the Windows 7 and above-specific options.
            Case WinWallpaper.Styles.Fit
               Wallpaper.Style = WinWallpaper.Styles.Fit
            Case WinWallpaper.Styles.Fill
               Wallpaper.Style = WinWallpaper.Styles.Fill
         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
 
            ' Add the Windows 7 and above-specific options.
         Case WinWallpaper.Styles.Fit
            Wallpaper.Style = WinWallpaper.Styles.Fit
         Case WinWallpaper.Styles.Fill
            Wallpaper.Style = WinWallpaper.Styles.Fill
      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
 
            ' Add the Windows 7 and above-specific options.
         Case WinWallpaper.Styles.Fit
            Wallpaper.Style = WinWallpaper.Styles.Fit
         Case WinWallpaper.Styles.Fill
            Wallpaper.Style = WinWallpaper.Styles.Fill
      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

All of the Windows 7 additions are marked with comments. The code simply ensures that the stored settings are handled appropriately. There isn’t any change in command line syntax or behavior with these changes.

Modifying frmMain

The majority of the changes for this update appear in frmMain. The only way to accomplish the task is to go through the code one step at a time. The first change appears in frmMain_Load(), which was originally discussed in Exploring the GrabAPicture Application (Part 10). The Select statement needs to be updated to ensure that the form loads with the controls configured correctly as shown here.

' Get the current wallpaper style.
Select Case Wallpaper.Style
   Case WinWallpaper.Styles.Stretched
      rbStretched.Checked = True
      rbCentered.Checked = False
      rbTiled.Checked = False
      rbFit.Checked = False
      rbFill.Checked = False
   Case WinWallpaper.Styles.Centered
      rbStretched.Checked = False
      rbCentered.Checked = True
      rbTiled.Checked = False
      rbFit.Checked = False
      rbFill.Checked = False
   Case WinWallpaper.Styles.Tiled
      rbStretched.Checked = False
      rbCentered.Checked = False
      rbTiled.Checked = True
      rbFit.Checked = False
      rbFill.Checked = False
   Case WinWallpaper.Styles.Fit
      rbStretched.Checked = False
      rbCentered.Checked = False
      rbTiled.Checked = False
      rbFit.Checked = True
      rbFill.Checked = False
   Case WinWallpaper.Styles.Fill
      rbStretched.Checked = False
      rbCentered.Checked = False
      rbTiled.Checked = False
      rbFit.Checked = False
      rbFill.Checked = True
End Select

You must also add event handlers for the Fit and Fill radio buttons on frmMain. These event handlers as similar to those discussed for the other radio buttons as shown here.

Private Sub rbFit_CheckedChanged(sender As System.Object, _
                                 e As System.EventArgs) _
                              Handles rbFit.CheckedChanged
   Dim Wallpaper As New WinWallpaper
 
   ' Set the new style.
   Wallpaper.Style = WinWallpaper.Styles.Fit
End Sub
 
Private Sub rbFill_CheckedChanged(sender As System.Object, _
                                  e As System.EventArgs) _
                               Handles rbFill.CheckedChanged
   Dim Wallpaper As New WinWallpaper
 
   ' Set the new style.
   Wallpaper.Style = WinWallpaper.Styles.Fill
End Sub

Modifying frmConfigure

The two Select buttons on frmConfigure (discussed in the Exploring the GrabAPicture Application (Part 11) post) require modification to work with the new options. When a user clicks Select, the code configures the Desktop with the newly selected wallpaper. The updates merely make it possible for the configured sources to contain the new fit and fill options. The code for the local resources version of the Select button appears here.

Private Sub btnLSelect_Click(ByVal sender As System.Object, _
                             ByVal e As System.EventArgs) _
                          Handles btnLSelect.Click
   ' Wallpaper manipulation class.
   Dim Wallpaper As New WinWallpaper
 
   ' Choose the wallpaper style.
   Select Case CurrentSettings.LocalUris(lstLocal.SelectedIndex).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
 
         ' Add the Windows 7 and above-specific Styles.
      Case WinWallpaper.Styles.Fit
         Wallpaper.Style = WinWallpaper.Styles.Fit
      Case WinWallpaper.Styles.Fill
         Wallpaper.Style = WinWallpaper.Styles.Fill
   End Select
 
   ' Create a wallpaper URI.
   Dim NewUri As New Uri( _
      CurrentSettings.LocalUris(lstLocal.SelectedIndex).Location)
 
   ' Set the wallpaper location.
   Wallpaper.WallpaperURI = NewUri
End Sub

The remote version of the Select button code appears here.

Private Sub btnSelect_Click(ByVal sender As System.Object, _
                            ByVal e As System.EventArgs) _
                         Handles btnSelect.Click
   ' Wallpaper manipulation class.
   Dim Wallpaper As New WinWallpaper
 
   ' Choose the wallpaper style.
   Select Case CurrentSettings.RemoteUris(lstRemote.SelectedIndex).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
 
         ' Add the Windows 7 and above-specific Styles.
      Case WinWallpaper.Styles.Fit
         Wallpaper.Style = WinWallpaper.Styles.Fit
      Case WinWallpaper.Styles.Fill
         Wallpaper.Style = WinWallpaper.Styles.Fill
   End Select
 
   ' Create a wallpaper URI.
   Dim NewUri As New Uri( _
      CurrentSettings.RemoteUris(lstRemote.SelectedIndex).Location)
 
   ' Set the wallpaper location.
   Wallpaper.WallpaperURI = NewUri
End Sub

Modifying frmAddEdit

The final set of changes for this update is to frmAddEdit, which is discussed in the Exploring the GrabAPicture Application (Part 12) post. It’s important to remember that unlike frmMain, the radio buttons on this form don’t have any event handlers. The settings are handled in a different way in the code. The first set of changes appear in frmAddEdit_Load() to ensure that the form is configured correctly when the user opens it as shown here.

' Set the form up for editing.
If _IsEdit Then
   ' Make sure you use the correct database.
   If _IsRemote Then
      txtName.Text = _RemoteSources(_RecordNumber).Name
      txtLocation.Text = _RemoteSources(_RecordNumber).Location
      Select Case _RemoteSources(_RecordNumber).Style
         Case Styles.Stretched
            rbStretched.Checked = True
            rbCentered.Checked = False
            rbTiled.Checked = False
            rbFit.Checked = False
            rbFill.Checked = False
         Case Styles.Centered
            rbStretched.Checked = False
            rbCentered.Checked = True
            rbTiled.Checked = False
            rbFit.Checked = False
            rbFill.Checked = False
         Case Styles.Tiled
            rbStretched.Checked = False
            rbCentered.Checked = False
            rbTiled.Checked = True
            rbFit.Checked = False
            rbFill.Checked = False
         Case Styles.Fit
            rbStretched.Checked = False
            rbCentered.Checked = False
            rbTiled.Checked = False
            rbFit.Checked = True
            rbFill.Checked = False
         Case Styles.Fill
            rbStretched.Checked = False
            rbCentered.Checked = False
            rbTiled.Checked = False
            rbFit.Checked = False
            rbFill.Checked = True
      End Select
   Else
      txtName.Text = _Resources(_RecordNumber).Name
      txtLocation.Text = _Resources(_RecordNumber).Location
      Select Case _Resources(_RecordNumber).Style
         Case Styles.Stretched
            rbStretched.Checked = True
            rbCentered.Checked = False
            rbTiled.Checked = False
            rbFit.Checked = False
            rbFill.Checked = False
         Case Styles.Centered
            rbStretched.Checked = False
            rbCentered.Checked = True
            rbTiled.Checked = False
            rbFit.Checked = False
            rbFill.Checked = False
         Case Styles.Tiled
            rbStretched.Checked = False
            rbCentered.Checked = False
            rbTiled.Checked = True
            rbFit.Checked = False
            rbFill.Checked = False
         Case Styles.Fit
            rbStretched.Checked = False
            rbCentered.Checked = False
            rbTiled.Checked = False
            rbFit.Checked = True
            rbFill.Checked = False
         Case Styles.Fill
            rbStretched.Checked = False
            rbCentered.Checked = False
            rbTiled.Checked = False
            rbFit.Checked = False
            rbFill.Checked = True
      End Select
   End If
End If

The second set of changes occurs in the btnAddEdit_Click() event handler, which determines what happens to the user selections when the user closes the form (no matter what the button caption might say at the time). Here are the changes for this event handler.

Private Sub btnAddEdit_Click(ByVal sender As System.Object, _
                             ByVal e As System.EventArgs) _
                          Handles btnAddEdit.Click
   If IsEdit Then
      If _IsRemote Then
         ' Change the array data.
         _RemoteSources(_RecordNumber).Name = txtName.Text
         _RemoteSources(_RecordNumber).Location = txtLocation.Text
         If rbStretched.Checked Then
            _RemoteSources(_RecordNumber).Style = Styles.Stretched
         End If
         If rbCentered.Checked Then
            _RemoteSources(_RecordNumber).Style = Styles.Centered
         End If
         If rbTiled.Checked Then
            _RemoteSources(_RecordNumber).Style = Styles.Tiled
         End If
 
         ' Add the Windows 7 and above-specific options.
         If rbFit.Checked Then
            _RemoteSources(_RecordNumber).Style = Styles.Fit
         End If
         If rbFill.Checked Then
            _RemoteSources(_RecordNumber).Style = Styles.Fill
         End If
      Else
         ' Change the array data.
         _Resources(_RecordNumber).Name = txtName.Text
         _Resources(_RecordNumber).Location = txtLocation.Text
         If rbStretched.Checked Then
            _Resources(_RecordNumber).Style = Styles.Stretched
         End If
         If rbCentered.Checked Then
            _Resources(_RecordNumber).Style = Styles.Centered
         End If
         If rbTiled.Checked Then
            _Resources(_RecordNumber).Style = Styles.Tiled
         End If
 
         ' Add the Windows 7 and above-specific options.
         If rbFit.Checked Then
            _Resources(_RecordNumber).Style = Styles.Fit
         End If
         If rbFill.Checked Then
            _Resources(_RecordNumber).Style = Styles.Fill
         End If
      End If
   Else
      If _IsRemote Then
         ' Add a new array element.
         If _RemoteSources Is Nothing Then
            ReDim _RemoteSources(0)
         Else
            ReDim Preserve _RemoteSources(_RemoteSources.Length)
         End If
 
         ' Instantiate a new array item.
         _RemoteSources(_RemoteSources.Length - 1) = New RemoteUri
 
         ' Add the data to the array.
         _RemoteSources(_RemoteSources.Length - 1).Name = txtName.Text
         _RemoteSources(_RemoteSources.Length - 1).Location = txtLocation.Text
         If rbStretched.Checked Then
            _RemoteSources(_RemoteSources.Length - 1).Style = Styles.Stretched
         End If
         If rbCentered.Checked Then
            _RemoteSources(_RemoteSources.Length - 1).Style = Styles.Centered
         End If
         If rbTiled.Checked Then
            _RemoteSources(_RemoteSources.Length - 1).Style = Styles.Tiled
         End If
 
         ' Add the Windows 7 and above-specific options.
         If rbFit.Checked Then
            _RemoteSources(_RemoteSources.Length - 1).Style = Styles.Fit
         End If
         If rbFill.Checked Then
            _RemoteSources(_RemoteSources.Length - 1).Style = Styles.Fill
         End If
      Else
         ' Add a new array element.
         If _Resources Is Nothing Then
            ReDim _Resources(0)
         Else
            ReDim Preserve _Resources(_Resources.Length)
         End If
 
         ' Instantiate a new array item.
         _Resources(_Resources.Length - 1) = New LocalUri
 
         ' Add the data to the array.
         _Resources(_Resources.Length - 1).Name = txtName.Text
         _Resources(_Resources.Length - 1).Location = txtLocation.Text
         If rbStretched.Checked Then
            _Resources(_Resources.Length - 1).Style = Styles.Stretched
         End If
         If rbCentered.Checked Then
            _Resources(_Resources.Length - 1).Style = Styles.Centered
         End If
         If rbTiled.Checked Then
            _Resources(_Resources.Length - 1).Style = Styles.Tiled
         End If
 
         ' Add the Windows 7 and above-specific options.
         If rbFit.Checked Then
            _Resources(_Resources.Length - 1).Style = Styles.Fit
         End If
         If rbFill.Checked Then
            _Resources(_Resources.Length - 1).Style = Styles.Fill
         End If
      End If
   End If
End Sub

As you can see, the new options work precisely the same as the existing ones. When the user adds or edits a local or remote wallpaper, the code makes the appropriate changes to the stored settings.

If you compile your application at this point, you’ll find that you can now work with the Fit and Fill wallpaper settings in Windows 7. These settings will also be available when you move your application to Windows 8. All of these code changes come down to one thing really. The new settings involve additions to the WallpaperStyle registry settings: 6 for Fit and 10 for Fill. Let me know if you have any questions about these changes at John@JohnMuellerBooks.com.

 

Improving the GrabAPicture Application (Part 3)

The second feature described in the in the Improving the GrabAPicture Application (Part 1) post is to make it possible to use the Windows 7 extended wallpaper settings. It might seem like a simple task at first, but remember that the GrabAPicture application must continue to work on older systems that don’t have the required settings. Consequently, this feature requires that you not only add the new functionality, but that you also make it possible to detect the operating system and act accordingly. When the user has Windows 7 installed, the GrabAPicture application needs to allow and react to two new settings (my book, Professional Windows 7 Development Guide, explains these and other Windows 7-specific features in greater detail):

 

  • Fit: The graphic is resized to fit within the confines of the display, without modifying its aspect ration (as stretch will do).
  • Fill: The graphic is resized to completely fill the confines of the display, even if this means chopping off part of the graphic, without modifying its aspect ratio.


Modifying frmMain

There are a number of techniques you can use to implement features that are operating system version specific. The best way is to customize the handling of the feature so that users who have older, less capable, versions of the operating system don’t even realize the feature exists, which is the approach taken with this example. The first step in accomplishing this goal is to modify the interface so that the extra options are added with Windows 7 and above, but not with older versions. The new frmMain interface looks like this:

GrabAPictureUpdate0301

In order to achieve this update, you perform these steps:

 

  1. Change the frmMain.Size property to 328, 317.
  2. Change the cbRemote.Location property to 8, 256.
  3. Change the cbLocal.Location property to 8, 224.
  4. Change the GroupBox1.Size property to 192, 148.
  5. Add a new RadioButton control with the following property values:
    • (Name) = rbFit
    • Location = 16, 96
    • Modifiers = Friend
    • Size = 104, 24
    • TabIndex = 9
    • Text = &Fit
  6. Add a new RadioButton control with the following property values:
    • (Name) = rbFill
    • Location = 16, 120
    • Modifiers = Friend
    • Size = 104, 24
    • TabIndex = 10
    • Text = Fi&ll
  7. Change the cbRemote.TabIndex property to 11.
  8. Change the cbLocal.TabIndex property to 12.


However, you still need to maintain the ability to use the old interface. This means making it possible to hide the two new controls and changing settings for four existing controls back to the old state when working with an older operation system. These four settings are:

 


  • frmMain.Size = 328, 269

  • GroupBox1.Size = 192,100

  • cbLocal.Location = 8, 176

  • cbRemote.Location = 8, 208

With this in mind, you must modify the frmMain_Load() method by adding some code to the beginning of the method. This code checks the operating system version and then makes changes as shown here to return the application appearance to its original condition (you originally saw this code in the Exploring the GrabAPicture Application (Part 10) post).

' Detect the operating system version.
Dim OS As OperatingSystem = Environment.OSVersion
 
' Check the operating system version and modify the interface
' as needed.
If OS.Version.Major < 6 Or OS.Version.Minor < 1 Then
 
   ' Disable the two wallpaper and make them invisible.
   rbFill.Enabled = False
   rbFill.Visible = False
   rbFit.Enabled = False
   rbFit.Visible = False
 
   ' Change the properites back to their old configuration.
   Me.Size = New Size(328, 269)
   GroupBox1.Size = New Size(192, 100)
   cbLocal.Location = New Point(8, 176)
   cbRemote.Location = New Point(8, 208)
End If

When GrabAPicture runs, it displays the appearance that best matches the host operating system. It’s important to disable the rbFill and rbFit controls so that the user can’t access them accidentally. In addition, you make them invisible so that the user can’t see them either. Notice that the If statement relies on an Or condition because either value could trigger the code. For example, Vista is version 6.0, so it would trigger the code because the Version.Minor value is 0.

Modifying frmAddEdit

The changes you make to frmMain also apply to frmAddEdit. However, this form is less complex, so the changes are less extensive. Follow this procedure to make the changes to the form.

  1. Change the frmAddEdit.Size property to 300, 316.
  2. Change the grpStyleSelect.Size property to 192, 148.
  3. Add a new RadioButton control with the following property values:
    • (Name) = rbFit
    • Location = 16, 96
    • Modifiers = Friend
    • Size = 104, 24
    • TabIndex = 3
    • Text = &Fit
  4. Add a new RadioButton control with the following property values:
    • (Name) = rbFill
    • Location = 16, 120
    • Modifiers = Friend
    • Size = 104, 24
    • TabIndex = 10
    • Text = Fi&ll

At this point, frmAddEdit should look like this:

GrabAPictureUpdate0302

You’ll need to add code to the frmAddEdit_Load() method to change the user interface back to it simpler form when the user has an older version of Windows. As before, add the following code to the beginning of the method (you originally saw this code in the Exploring the GrabAPicture Application (Part 12) post).

' Detect the operating system version.
Dim OS As OperatingSystem = Environment.OSVersion
 
' Check the operating system version and modify the interface
' as needed.
If OS.Version.Major < 6 Or OS.Version.Minor < 1 Then
 
   ' Disable the two wallpaper and make them invisible.
   rbFill.Enabled = False
   rbFill.Visible = False
   rbFit.Enabled = False
   rbFit.Visible = False
 
   ' Change the properites back to their old configuration.
   Me.Size = New Size(300, 266)
   grpStyleSelect.Size = New Size(192, 100)
End If

As before, the code checks the OS version and makes a decision about whether it can use the new features. If not, the form is configured to allow for the older wallpaper setup.

This is a good stopping point for this week. Next week you’ll see how to finish this particular update. You’ll need to modify the underlying code to allow use of the new settings. However, there are a few surprises in this area—things you have to discover by looking at the registry. In the meantime, let me know if you have any questions about this part of the change at John@JohnMuellerBooks.com. You can see the next post in this series at Improving the GrabAPicture Application (Part 4).

 

Improving the GrabAPicture Application (Part 2)

Just before the year ended, I discussed some additions you might want to make to the GrabAPicture application in the Improving the GrabAPicture Application (Part 1) post. One of those additions is allowing the user to modify the application configuration from the command line, rather than use the GUI to do it. Configuration modification is a common application requirement and any application that administrators have to support should have a command line option to make these modifications. You don’t have to provide access to absolutely every configuration option, but you should provide access to the most common configuration options. The options you support in your application is determined by:

 

  • Administrator configuration needs: The administrator may be able to provide input on precisely which tasks the application will need to perform remotely. Remote tasks are usually good candidates for command line support.

  • Application complexity: When working with a simple application, you have the luxury of including all of the configuration options at the command line. As applications become more complex, you must pick and choose command line features. Using multiple levels of command line switches also becomes a requirement. As an example, look at the WMIC command line utility.
  • Application type: The tasks that the application performs partially dictate the command line interface. An application designed for management tasks (such as WMIC) will require a more robust command line than one used for localized data handling (such as Notepad).


For the GrabAPicture application, the main consideration is application complexity. There are only two configuration settings, so the command line will support both of them. Of course, you have multiple options for adding the command line switches. Because the interface is relatively simple, we’ll change the command line arguments so they appear like this:

GrabAPicture
GrabAPicture /?
GrabAPicture /Random
GrabAPicture /Picture:<Picture URI> [/Style:Stretched | Centered | Tiled]
GrabAPicture /Configure [/Local:[True | False]] [/Remote:[True | False]]

The new option, /Configure, displays the current configuration when used alone. Adding the /Local command line switch changes the Use Random Local Sources setting, while adding the /Remote command line switch changes the Use Random Remote Sources setting.

At this point, you can add the required functionality. Modifying the code is a three-step process:

 

  • Change the help information.
  • Add the configuration code.
  • Add the frmMain-specific code required to perform tasks.


With these steps in mind, lets begin with the help information. The following listing shows the updated help code in bold.

' 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)
   Output.Append("GrabAPicture /Configure " + _
                 "[/Local:[True | False]] " + _
                 "[/Remote:[True | False]]" + 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)
   Output.Append("/Configure - Provides a means of discovering the " + _
                 "configuration when used alone or modifying the " + _
                 "configuration when used with the /Local and/or " + _
                 "/Remote command line switches." + vbCrLf)
   Output.Append("/Local - Sets the application to use local random " + _
                 "sources when set to True." + vbCrLf)
   Output.Append("/Remote - Sets the application to use remote random " + _
                 "sources when set to True." + vbCrLf)
 
   ' Display the help.
   MessageBox.Show(Output.ToString(),
                   "GrabAPicture Usage",
                   MessageBoxButtons.OK,
                   MessageBoxIcon.Information)
   Return 0
End If

As you can see, the updated code simply adds more help information to the existing help. The new help screen looks like this:

GrabAPictureUpdate0201

Now it’s time to add the code required to make the new switches work. This code appears directly before the /Picture command line switch code (the line that begins If CmdArg.Length > 8 Then in the Main() function):

' Work with the application configuration.
If CmdArg.ToUpper() = "/CONFIGURATION" Then
 
   ' Determine whether the user wants to see just the configuration
   ' information.
   If CmdArgs.Length = 1 Then
 
      ' Obtain the current settings.
      Dim CurrentSettings As GrabAPictureSettings
      CurrentSettings = GrabAPictureSettings.LoadSettings()
 
      ' Check to make sure there are settings to use.
      If CurrentSettings Is Nothing Then
 
         ' Tell the user there are no settings to display.
         MessageBox.Show("No Settings to Show!",
                         "Configuration Error",
                         MessageBoxButtons.OK,
                         MessageBoxIcon.Error)
         Return -2
      End If
 
      ' Display the settings information.
      MessageBox.Show("Local: " + CurrentSettings.LocalChecked.ToString() + _
                      vbCrLf + "Remote: " + _
                      CurrentSettings.RemoteChecked.ToString(),
                      "GrabAPicture Configuration Settings",
                      MessageBoxButtons.OK,
                      MessageBoxIcon.Information)
      Return 0
 
      ' There must be at least one settings argument.
   Else
      ' Create a variable to hold the current argument.
      Dim SettingArg As String()
 
      ' Create a varible to hold the new value.
      Dim NewValue As Boolean
 
      ' Obtain the current settings.
      Dim CurrentSettings As GrabAPictureSettings
      CurrentSettings = GrabAPictureSettings.LoadSettings()
 
      ' If there aren't any settings.
      If CurrentSettings Is Nothing Then
 
         ' Define default settings.
         CurrentSettings = New GrabAPictureSettings
      End If
 
      ' Process each settings argument in turn.
      For ArgNum As Int32 = 1 To CmdArgs.Length - 1
 
         ' Obtain the switch and argument.
         SettingArg = CmdArgs(ArgNum).Split(":")
 
         ' Verify that the user has actually included a value.
         If Not SettingArg.Length = 2 Then
            MessageBox.Show("You must provide the /Local or /Remote " + _
                            "command line switch followed by a colon (:), " + _
                            "followed by either True or False, such " + _
                            "as /Local:True or /Remote:True",
                            "Argument Error",
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Error)
            Return -3
         End If
 
         ' Check for a local setting.
         If SettingArg(0).ToUpper() = "/LOCAL" Then
 
            ' Check the setting.
            If Boolean.TryParse(SettingArg(1), NewValue) Then
 
               ' Change the setting.
               CurrentSettings.LocalChecked = NewValue
            End If
 
            ' Save the setting.
            GrabAPictureSettings.SaveSettings(CurrentSettings)
         End If
 
         ' Check for a remote setting.
         If SettingArg(0).ToUpper() = "/REMOTE" Then
 
            ' Check the setting.
            If Boolean.TryParse(SettingArg(1), NewValue) Then
 
               ' Change the setting.
               CurrentSettings.RemoteChecked = NewValue
            End If
 
            ' Save the setting.
            GrabAPictureSettings.SaveSettings(CurrentSettings)
         End If
      Next
   End If
 
   Return 0
End If

This code begins by checking for the /Configuration command line switch. If the user has included it, then the code checks the number of command line arguments. When there is just one argument, the user has used the /Configuration command line switch alone, so the application displays the current configuration information. In order to perform this task, the code must create a GrabAPictureSettings object, CurrentSettings, and load the settings into it. At this point, displaying the information is simply a matter of accessing the current information in CurrentSettings. Make sure you convert the Boolean values to strings.

Setting the configuration is only a little harder. The first thing to consider is that you don’t know whether the user has included just the /Local or /Remote command line switches, or both of them. In addition, you have no idea of which order the user has used for the switches. As a consequence, this application relies on a For loop that doesn’t assume anything. It simply grabs the first switch and then processes it.

The command line switch is supposed to be followed by a colon (:) and then a value. Unfortunately, the user could provide anything as input. The code begins by verifying that the user has provided a switch and value combination (or at least the right number of arguments). It then checks for either /Local or /Remote. The value could still be incorrect, so the application uses the Boolean.TryParse() method to convert the input to a Boolean value safely. If everything works out correctly, the application makes the setting change and saves the result to disk by calling GrabAPictureSettings.SaveSettings(CurrentSettings).

With these changes, the application can now set the configuration from the command line. Next week you’ll see another new addition. In the meantime, let me know if you have any questions about this update at John@JohnMuellerBooks.com.

Improving the GrabAPicture Application (Part 1)

If you haven’t read about the GrabAPicture application yet, you really need to do so before we embark on the update described in the upcoming series of posts. You can find a list of these posts using a simple search. The GrabAPicture application covers a lot of ground and shows how to perform a wide range of tasks using VB.NET. You’ll have to read the posts to see everything it does, but you’ll gain an understanding of working at the command line, working with Windows API using P/Invoke, and creating a solution that has both command line interface and GUI. You’ll even see how to create a shortcut to ask GrabAPicture to perform tasks automatically.

It isn’t often that I can even demonstrate a program this large in a book, so this series of entries has given me a chance to do something a little different. Being able to interact with some of you while I’ve been writing the posts has also been a new experience. So, this series of blog entries has been interesting for me too and I plan to do others in the future. The next application might rely on C#, but whatever language it uses, it’ll be interesting.

As capable as GrabAPicture is, it has some limitations that you might want to address. That’s the purpose of the series of posts that follow. Now, you can extend GrabAPicture in any number of desirable ways, but let’s start with these ideas:

 

  • Add configuration options to the command line.
  • Make it possible to configure wallpaper to use the new Windows 7 settings.
  • Add a browse button to the main form.
  • Provide a snapshot of the highlighted image.


These ideas would make a good starting point, but I’m open to any ideas you might have. If you’ve had a chance to play with the application a bit, you’ve probably noticed some deficiencies. I can’t guarantee I’ll correct them all, but I’ll give every deficiency you note some thought to determine whether a fix is doable. Let me know what you’d like to see as additions at John@JohnMuellerBooks.com. You can find the next post in this series at Improving the GrabAPicture Application (Part 2).

Exploring the GrabAPicture Application (Part 12)

We’re finally down to the last baseline post for the GrabAPicture application, frmAddEdit, which is the chameleon form. The Exploring the GrabAPicture Application (Part 11) post describes how the frmConfigure code modifies the appearance of the code for this form so it can perform several tasks. At the center of this capability are several versions of the constructor that allow for different configuration techniques as shown in the following code.

Public Sub New()
   MyBase.New()
 
   'This call is required by the Windows Form Designer.
   InitializeComponent()
 
   'Add any initialization after the InitializeComponent() call
 
End Sub
 
Public Sub New(ByVal Title As String)
   MyBase.New()
 
   'This call is required by the Windows Form Designer.
   InitializeComponent()
 
   'Change the title.
   Me.Text = Title
End Sub
 
Public Sub New(ByVal Title As String, _
               ByVal AddButtonTitle As String)
   MyBase.New()
 
   'This call is required by the Windows Form Designer.
   InitializeComponent()
 
   'Change the title.
   Me.Text = Title
 
   ' Change the add/edit button title.
   btnAddEdit.Text = AddButtonTitle
End Sub
 
Public Sub New(ByVal Title As String, _
               ByVal AddButtonTitle As String, _
               ByVal RemoteEdit As Boolean)
   MyBase.New()
 
   ' This call is required by the Windows Form Designer.
   InitializeComponent()
 
   ' Change the title.
   Me.Text = Title
 
   ' Change the add/edit button title.
   btnAddEdit.Text = AddButtonTitle
 
   ' Remove the directory button and resize the
   ' location field as needed.
   If RemoteEdit Then
      btnGetResource.Visible = False
      btnGetResource.Enabled = False
      txtLocation.Size = txtName.Size
      _IsRemote = True
   End If
End Sub

As you can see, each of the constructors provides one more level of configuration. You might wonder why the application doesn’t simply use the most configurable of the four constructors, but this approach has some disadvantages.

 

  • Using just a single, complex constructor makes the application inflexible.
  • The developer would also need to provide the default values for title, button, and remote editing capability, even if these values didn’t change from the default dialog box.
  • Adding this requirement would make the application more susceptible to errors.
  • Using four different constructors also forces the developer to consider which entries actually do need to change. Giving someone the opportunity to think application design through is never a bad idea.

Notice that the fourth constructor contains special code for removing the browse button from view when working with a remote resource. The browse button wouldn’t do the user much good in this case and clicking it can only cause problems.

Using the custom constructors is really important in this case and makes the application much stronger. You can divide the rest of the code for this form into three areas:

 


  • Property creation and maintenance

  • Form loading

  • User controls

Most of the code for this form is straightforward, albeit a bit convoluted at time due to the form’s multipurpose nature. The following sections describe each of the coding areas.

Property Creation and Maintenance

This form is used in a number of ways, so it’s important that the application be able to track the form’s current state. Otherwise, the application could make an assumption about the form content that isn’t correct. The best way to provide state information outside of the form is to use properties. Relying on properties makes it possible to monitor and control interaction with form state information in a controlled way. The application uses the properties shown here.

' Private variables used for editing purposes.
Private _Resources As LocalUri()
Private _RemoteSources As RemoteUri()
Private _IsRemote As Boolean = False
Private _IsEdit As Boolean = False
Private _RecordNumber As Int32 = 0
 
' Tracks the current list of local resources.
Public Property Resources() As LocalUri()
   Get
      Return _Resources
   End Get
   Set(ByVal Value As LocalUri())
      _Resources = Value
   End Set
End Property
 
' Tracks the current list of remote resources
Public Property RemoteSources() As RemoteUri()
   Get
      Return _RemoteSources
   End Get
   Set(ByVal Value As RemoteUri())
      _RemoteSources = Value
   End Set
End Property
 
' Shows whether edit is for a local or remote resource.
Public ReadOnly Property IsRemote() As Boolean
   Get
      Return _IsRemote
   End Get
End Property
 
' Determines whether this is an add or edit action.
Public Property IsEdit() As Boolean
   Get
      Return _IsEdit
   End Get
   Set(ByVal Value As Boolean)
      _IsEdit = Value
   End Set
End Property
 
' Defines the edited record.
Public Property RecordNumber() As Int32
   Get
      Return _RecordNumber
   End Get
   Set(ByVal Value As Int32)
      ' Verify the input is positive.
      If Value >= 0 Then
 
         ' Perform separate checks for local and remote.
         If _IsRemote Then
 
            ' Verify the input is less than the number of
            ' records when using a remote resource. This
            ' check will also throw a null reference
            ' exception if the record source isn't
            ' properly initialized.
            If Value >= _RemoteSources.Length Then
 
               Throw New ArgumentOutOfRangeException( _
                  "Record Number", _
                  "Record number is too high.")
            End If
         Else
 
            ' Verify the input is less than the number of
            ' records when using a local resource. This
            ' check will also throw a null reference
            ' exception if the record source isn't
            ' properly initialized.
            If Value >= _Resources.Length Then
 
               Throw New ArgumentOutOfRangeException( _
                  "Record Number", _
                  "Local record number is too high.")
            End If
         End If
 
         ' Set the record number.
         _RecordNumber = Value
      Else
         Throw New ArgumentOutOfRangeException( _
            "Record Number", _
            "Provide a positive record number.")
      End If
   End Set
End Property

The properties are pretty straightforward. They serve the following purposes:

 


  • Resources: Defines a local URI.

  • RemoteSources: Defines a remote URI.

  • IsRemote: A read-only property that the application can use to determine whether to access the Resources or RemoteSources property for the current URI.
  • IsEdit: Controls whether the dialog box provides an adding or editing appearance. The adding dialog box is blank, while the editing dialog box contains the current local or remote record information.
  • RecordNumber: Contains the record number of the current local or remote source.

Form Loading

Form loading is important because the code in this area controls the initial appearance of the dialog box. A form used for adding a new record, whether local or remote, is always blank, so it doesn’t require any custom configuration. However, if the form is used to edit a current record, the form loading code must detect whether the form is editing a local or remote resource. In addition to URI differences, local resources have an additional button available for browsing the local hard drive. The following code shows how form loading works.

Private Sub frmAddEdit_Load(ByVal sender As Object, _
                            ByVal e As System.EventArgs) _
                         Handles MyBase.Load
   ' Set the form up for editing.
   If _IsEdit Then
      ' Make sure you use the correct database.
      If _IsRemote Then
         txtName.Text = _RemoteSources(_RecordNumber).Name
         txtLocation.Text = _RemoteSources(_RecordNumber).Location
         Select Case _RemoteSources(_RecordNumber).Style
            Case Styles.Stretched
               rbStretched.Checked = True
               rbCentered.Checked = False
               rbTiled.Checked = False
            Case Styles.Centered
               rbStretched.Checked = False
               rbCentered.Checked = True
               rbTiled.Checked = False
            Case Styles.Tiled
               rbStretched.Checked = False
               rbCentered.Checked = False
               rbTiled.Checked = True
         End Select
      Else
         txtName.Text = _Resources(_RecordNumber).Name
         txtLocation.Text = _Resources(_RecordNumber).Location
         Select Case _Resources(_RecordNumber).Style
            Case Styles.Stretched
               rbStretched.Checked = True
               rbCentered.Checked = False
               rbTiled.Checked = False
            Case Styles.Centered
               rbStretched.Checked = False
               rbCentered.Checked = True
               rbTiled.Checked = False
            Case Styles.Tiled
               rbStretched.Checked = False
               rbCentered.Checked = False
               rbTiled.Checked = True
         End Select
      End If
   End If
End Sub

As you can see, the first thing the code detect is whether the form is using for adding. If it is, then the application doesn’t do any configuration.

When the dialog box is used for editing, the application checks the status of _IsRemote to determine how to configure the dialog box. The code obtains the correct record and then displays the information on screen.

User Controls

There are three user controls, but you only need to provide code for two of them (btnCancel provides the default action when clicked). The Add/Edit button (btnAddEdit) is always visible. The relies on it to make any changes permanent. The browse button (btnGetResource) is only available when working with a local resource. The user relies on this button to locate resources on the local hard drive. The following code shows how these buttons work.

Private Sub btnAddEdit_Click(ByVal sender As System.Object, _
                             ByVal e As System.EventArgs) _
                          Handles btnAddEdit.Click
   If IsEdit Then
      If _IsRemote Then
         ' Change the array data.
         _RemoteSources(_RecordNumber).Name = txtName.Text
         _RemoteSources(_RecordNumber).Location = txtLocation.Text
         If rbStretched.Checked Then
            _RemoteSources(_RecordNumber).Style = Styles.Stretched
         End If
         If rbCentered.Checked Then
            _RemoteSources(_RecordNumber).Style = Styles.Centered
         End If
         If rbTiled.Checked Then
            _RemoteSources(_RecordNumber).Style = Styles.Tiled
         End If
      Else
         ' Change the array data.
         _Resources(_RecordNumber).Name = txtName.Text
         _Resources(_RecordNumber).Location = txtLocation.Text
         If rbStretched.Checked Then
            _Resources(_RecordNumber).Style = Styles.Stretched
         End If
         If rbCentered.Checked Then
            _Resources(_RecordNumber).Style = Styles.Centered
         End If
         If rbTiled.Checked Then
            _Resources(_RecordNumber).Style = Styles.Tiled
         End If
      End If
   Else
      If _IsRemote Then
         ' Add a new array element.
         If _RemoteSources Is Nothing Then
            ReDim _RemoteSources(0)
         Else
            ReDim Preserve _RemoteSources(_RemoteSources.Length)
         End If
 
         ' Instantiate a new array item.
         _RemoteSources(_RemoteSources.Length - 1) = New RemoteUri
 
         ' Add the data to the array.
         _RemoteSources(_RemoteSources.Length - 1).Name = txtName.Text
         _RemoteSources(_RemoteSources.Length - 1).Location = txtLocation.Text
         If rbStretched.Checked Then
            _RemoteSources(_RemoteSources.Length - 1).Style = Styles.Stretched
         End If
         If rbCentered.Checked Then
            _RemoteSources(_RemoteSources.Length - 1).Style = Styles.Centered
         End If
         If rbTiled.Checked Then
            _RemoteSources(_RemoteSources.Length - 1).Style = Styles.Tiled
         End If
      Else
         ' Add a new array element.
         If _Resources Is Nothing Then
            ReDim _Resources(0)
         Else
            ReDim Preserve _Resources(_Resources.Length)
         End If
 
         ' Instantiate a new array item.
         _Resources(_Resources.Length - 1) = New LocalUri
 
         ' Add the data to the array.
         _Resources(_Resources.Length - 1).Name = txtName.Text
         _Resources(_Resources.Length - 1).Location = txtLocation.Text
         If rbStretched.Checked Then
            _Resources(_Resources.Length - 1).Style = Styles.Stretched
         End If
         If rbCentered.Checked Then
            _Resources(_Resources.Length - 1).Style = Styles.Centered
         End If
         If rbTiled.Checked Then
            _Resources(_Resources.Length - 1).Style = Styles.Tiled
         End If
      End If
   End If
End Sub
 
Private Sub btnGetResource_Click(ByVal sender As System.Object, _
                                 ByVal e As System.EventArgs) _
                              Handles btnGetResource.Click
   If OFD.ShowDialog() = DialogResult.OK Then
      txtLocation.Text = OFD.FileName
   End If
End Sub

Let’s discuss the simple button first. When the user clicks the browse button, the application displays an OpenFileDialog control, OFD. If the user selects a file and clicks OK, the code places the name of the file into txtLocation.Text. Theoretically, a little customization would make this dialog box friendlier, but it works well as presented.

The btnAddEdit_Click() event handler is a little more complex. It actually performs one of four actions depending on the dialog box options.

 

  • Edit/Remote: The code changes the record information found in _RemoteSources for the current record pointed at by _RecordNumber.
  • Edit/Local: The code changes the record information found in _Resources for the current record pointed at by _RecordNumber.
  • Add/Remote: The code either creates a new _RemoteSources array or adds an element to the existing _RemoteSources array. In both cases, the code adds the new record to the end of the array. Note that because the array is zero-based, you must subtract 1 from the array length in order to find the last record of the new array.
  • Add/Local: The
    code either creates a new _Resources array or adds an element to
    the existing _Resources array. In both cases, the code adds the new
    record to the end of the array.

Well, now you have the full story on the base application—the application I started with. The next post will discuss some of the additions we’ll pursue in the new year. For now, please keep those comments coming. Let me know if you have any questions at all about this form at John@JohnMuellerBooks.com.

 

Exploring the GrabAPicture Application (Part 11)

The previous post in this series, Exploring the GrabAPicture Application (Part 10), discussed frmMain, which allows the user to perform application configuration and manually configure the Desktop wallpaper. The focus of this application, however, is automated configuration, which is the purview of frmConfigure—the topic of discussion today. The user relies on frmConfigure to add, edit, delete, and select either local or remote graphic sources for the Desktop wallpaper as shown here.

GrabAPicture1101

There are really two sets of four buttons here: the local set and the remote set. Each set performs the same sets of tasks with a different part of the same XML database. The information about this database appears in previous posts, but the main post of concern is Exploring the GrabAPicture Application (Part 8). When adding or editing a database element, the user sees a form similar to the one shown here:

GrabAPicture1102

Even though frmConfigure looks a little complex, there are really only three groups of tasks to perform:

 

  • Load the current database
  • Manage local database resources
  • Manage remote database resources

Load the Current Database

The first task is to load the current database. In this case, the application creates and configures a global variable that provides access to the preconfigured wallpaper choices as shown here.

' Current application settings.
Dim CurrentSettings As GrabAPictureSettings
 
Private Sub frmConfigure_Load(ByVal sender As Object, _
                              ByVal e As System.EventArgs) _
                              Handles MyBase.Load
   ' Initialize the stored settings.
   CurrentSettings = GrabAPictureSettings.LoadSettings()
 
   ' Configure the controls.
   If Not CurrentSettings Is Nothing Then
      If Not CurrentSettings.LocalUris Is Nothing Then
         For Each Item As LocalUri In CurrentSettings.LocalUris
            lstLocal.Items.Add(Item.Name)
         Next
      End If
      If Not CurrentSettings.RemoteUris Is Nothing Then
         For Each Item As RemoteUri In CurrentSettings.RemoteUris
            lstRemote.Items.Add(Item.Name)
         Next
      End If
   Else
      ' Define default settings.
      CurrentSettings = New GrabAPictureSettings
   End If
End Sub

The global variable, CurrentSettings, is of type GrabAPictureSettings. The application begins trying to fill CurrentSettings by calling GrabAPictureSettings.LoadSettings(). When this is the first application use and the user hasn’t added any wallpaper yet, the result of this call is that CurrentSettings equals Nothing, so the application creates a new database by creating a new GrabAPictureSettings database.

When there are settings to use, the application must determine which settings are available. The database may only contain local or remote settings, but not both. The next step determines whether CurrentSettings.LocalUris contains Nothing. If there are local settings, the application places the name of each of these entries in the lstLocal list box. The application performs a similar task for the remote settings. As a result, the user sees the names of all of the available local and remote wallpaper sources when the application dialog first appears on screen.

Manage Local Database Resources

After the form appears on screen, the user sees the local resources at the top of the form in the Local Sources list. The user can add, edit, delete, or select local resources using the associated controls as shown here:

Private Sub btnLAdd_Click(ByVal sender As System.Object, _
                          ByVal e As System.EventArgs) _
                       Handles btnLAdd.Click
   ' Create a form to add the record.
   Dim AddItem As New frmAddEdit
 
   ' Set the resources when available.
   If Not CurrentSettings.LocalUris Is Nothing Then
      AddItem.Resources = CurrentSettings.LocalUris
   End If
 
   ' If the user makes changes, add the information to
   ' the settings database and display it on screen.
   If AddItem.ShowDialog(Me) = DialogResult.OK Then
      CurrentSettings.LocalUris = AddItem.Resources
      GrabAPictureSettings.SaveSettings(CurrentSettings)
      lstLocal.Items.Add(AddItem.Resources(AddItem.Resources.Length - 1).Name)
   End If
End Sub
 
Private Sub btnLEdit_Click(ByVal sender As System.Object, _
                           ByVal e As System.EventArgs) _
                        Handles btnLEdit.Click
   ' Make sure the user has selected an entry.
   If lstLocal.SelectedIndex = -1 Then
      MessageBox.Show("Select an entry to edit.")
      Return
   End If
 
   ' Create a new form for editing the data.
   Dim EditItem As New frmAddEdit("Edit Local Resource", "&Edit")
   EditItem.IsEdit = True
 
   ' Make sure there is data to edit.
   If CurrentSettings.LocalUris Is Nothing Then
      MessageBox.Show("No Records to Edit")
      Return
   Else
      ' Provide the records and choose the one to edit.
      EditItem.Resources = CurrentSettings.LocalUris
      EditItem.RecordNumber = lstLocal.SelectedIndex
   End If
 
   ' If the user changes the data, update the settings
   ' database and show changes on screen.
   If EditItem.ShowDialog(Me) = DialogResult.OK Then
      CurrentSettings.LocalUris = EditItem.Resources
      GrabAPictureSettings.SaveSettings(CurrentSettings)
      lstLocal.Items(lstLocal.SelectedIndex) = _
         EditItem.Resources(lstLocal.SelectedIndex).Name
   End If
End Sub
 
Private Sub btnLDelete_Click(ByVal sender As System.Object, _
                             ByVal e As System.EventArgs) _
                             Handles btnLDelete.Click
   ' Make sure the user has selected an entry.
   If lstLocal.SelectedIndex = -1 Then
      MessageBox.Show("Select an entry to remove.")
      Return
   End If
 
   ' Create a temporary record array.
   Dim Temp(CurrentSettings.LocalUris.Length - 2) As LocalUri
 
   ' Remove the requested record.
   Dim Count As Int32 = 0
   For Each Item As LocalUri In CurrentSettings.LocalUris
 
      ' Add the record if it doesn't match the target.
      If Not Item.Name = lstLocal.Items(lstLocal.SelectedIndex) Then
         Temp(Count) = Item
         Count += 1
      End If
   Next
 
   ' Update the settings.
   CurrentSettings.LocalUris = Temp
   GrabAPictureSettings.SaveSettings(CurrentSettings)
 
   ' Update the display.
   lstLocal.Items.RemoveAt(lstLocal.SelectedIndex)
End Sub
 
Private Sub btnLSelect_Click(ByVal sender As System.Object, _
                             ByVal e As System.EventArgs) _
                          Handles btnLSelect.Click
   ' Wallpaper manipulation class.
   Dim Wallpaper As New WinWallpaper
 
   ' Choose the wallpaper style.
   Select Case CurrentSettings.LocalUris(lstLocal.SelectedIndex).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.
   Dim NewUri As New Uri( _
      CurrentSettings.LocalUris(lstLocal.SelectedIndex).Location)
 
   ' Set the wallpaper location.
   Wallpaper.WallpaperURI = NewUri
End Sub

Let’s look at each of these event handlers in turn. The btnLAdd_Click() event handler is the simplest of the group. It begins by creating a new frmAddEdit using the default constructor. When there are local resources to provide in CurrentSettings.LocalUris, the program supplies them to the AddItem.Resources property. At this point, the application displays the dialog box. If the user clicks Add, rather than Cancel, in the Add Local Resource dialog box (which produces a return value of DialogResult.OK), the application obtains the updated list of resources from AddItem.Resources and places them in CurrentSettings.LocalUris. The application then saves the database by calling GrabAPictureSettings.SaveSettings() with CurrentSettings. Finally, the application displays the newly added item in the Local Sources list.

The btnLEdit_Click() event handler begins by checking whether the user has actually selected an entry to edit by checking lstLocal.SelectedIndex (a value of -1 indicates no selection). If not, the application displays an error message and exits. It then creates a new frmAddEdit() using a special constructor that allows modification of the title bar text and the btnAddEdit.Text property, so that the resulting dialog box is completely customized. You’ll find that there are actually four constructors for frmAddEdit:

 

  • No modification
  • Modify the title
  • Modify both title and btnAddEdit.Text property
  • Modify both title and btnAddEdit.Text property in a remote source setting

Providing this sort of customization reduces the work you need to do and yet provides a better interface for the user. The next step is to set the EditItem.IsEdit property to True, which tells frmAddEdit to fill the fields with data to edit, rather than present a blank form. Again, this is the type of customization that will actually save you time later. (A second property, EditItem.IsRemote, determines whether the data should come from the local or remote wallpaper database—the default is to use the local database.)

The application has to make two decisions when editing a record. First, it has to check whether there is a record to edit. If there aren’t any records, the application displays a dialog box stating as much and exits. Second, it must determine which record to edit. The code passes the value in lstLocal.SelectedIndex onto EditItem.RecordNumber. As the application is currently configured, the contents of lstLocal must precisely match the contents of CurrentRecords, which means that lstLocal remains unsorted. A future update will make it possible to sort the list, if desired, but for now, the application is designed for simplicity of understanding, rather than aesthetic appeal. The remainder of the btnLEdit_Click() code works just like the btnLAdd_Click() event handler, with the result that any changes are immediately saved to the database.

Deleting a record means checking for the required record in the database by name and then removing that particular record. The btnLDelete_Click() event handler begins by checking whether the user has actually selected a record (lstLocal.SelectedIndex must equal a value other than -1). It then creates a temporary array, Temp, of type LocalUri, that will hold one record less than the current number of records. The next bit of code may look confusing at first, but all it really does is move all of the records in CurrentSettings.LocalUris that don’t have the name of the selected record as defined by lstLocal.Items(lstLocal.SelectedIndex) to Temp. The result is Temp has one less record than CurrentSettings.LocalUris when the process is finished. The code then copies the new list of records from Temp to CurrentSettings.LocalUris and saves the result to disk. The final step is to remove the name from the Local Sources list.

Selecting a specific local source is relatively straightforward. The btnLSelect_Click() event handler begins by creating Wallpaper, which is type WinWallpaper. It’s important to remember that modifying the Wallpaper object automatically changes the Desktop wallpaper. You saw how this works in the previous post and it works the same here. The application relies on the index provided by the user’s selection, lstLocal.SelectedIndex, to select the correct wallpaper in CurrentSettings.LocalUris. The application then sets the wallpaper style and provides it with a location in the form of a Uri object, NewUri. The result is that the user sees the selected wallpaper on screen.

Manage Remote Database Resources

The code for managing the remote database resources is almost precisely the same as the code for managing local database resources. In fact, the modifications are mainly cosmetic, such as changes to the frmAddEdit title and button text. However, there are a few distinct differences to note as shown here.

Private Sub btnRAdd_Click(ByVal sender As System.Object, _
                          ByVal e As System.EventArgs) _
                       Handles btnRAdd.Click
   ' Create a form to add the record.
   Dim AddItem As New frmAddEdit("Add Remote Resource", "&Add", True)
 
   ' Set the resources when available.
   If Not CurrentSettings.RemoteUris Is Nothing Then
      AddItem.RemoteSources = CurrentSettings.RemoteUris
   End If
 
   ' If the user makes changes, add the information to
   ' the settings database and display it on screen.
   If AddItem.ShowDialog(Me) = DialogResult.OK Then
      CurrentSettings.RemoteUris = AddItem.RemoteSources
      GrabAPictureSettings.SaveSettings(CurrentSettings)
      lstRemote.Items.Add( _
         AddItem.RemoteSources(AddItem.RemoteSources.Length - 1).Name)
   End If
End Sub
 
Private Sub btnEdit_Click(ByVal sender As System.Object, _
                          ByVal e As System.EventArgs) _
                       Handles btnEdit.Click
   ' Make sure the user has selected an entry.
   If lstRemote.SelectedIndex = -1 Then
      MessageBox.Show("Select an entry to edit.")
      Return
   End If
 
   ' Create a new form for editing the data.
   Dim EditItem As New frmAddEdit("Edit Remote Resource", "&Edit", True)
   EditItem.IsEdit = True
 
   ' Make sure there is data to edit.
   If CurrentSettings.RemoteUris Is Nothing Then
      MessageBox.Show("No Records to Edit")
      Return
   Else
      ' Provide the records and choose the one to edit.
      EditItem.RemoteSources = CurrentSettings.RemoteUris
      EditItem.RecordNumber = lstRemote.SelectedIndex
   End If
 
   ' If the user changes the data, update the settings
   ' database and show changes on screen.
   If EditItem.ShowDialog(Me) = DialogResult.OK Then
      CurrentSettings.RemoteUris = EditItem.RemoteSources
      GrabAPictureSettings.SaveSettings(CurrentSettings)
      lstRemote.Items(lstRemote.SelectedIndex) = _
         EditItem.RemoteSources(lstRemote.SelectedIndex).Name
   End If
End Sub
 
Private Sub btnRDelete_Click(ByVal sender As System.Object, _
                             ByVal e As System.EventArgs) _
                          Handles btnRDelete.Click
   ' Make sure the user has selected an entry.
   If lstRemote.SelectedIndex = -1 Then
      MessageBox.Show("Select an entry to remove.")
      Return
   End If
 
   ' Create a temporary record array.
   Dim Temp(CurrentSettings.RemoteUris.Length - 2) As RemoteUri
 
   ' Remove the requested record.
   Dim Count As Int32 = 0
   For Each Item As RemoteUri In CurrentSettings.RemoteUris
 
      ' Add the record if it doesn't match the target.
      If Not Item.Name = lstRemote.Items(lstRemote.SelectedIndex) Then
         Temp(Count) = Item
         Count += 1
      End If
   Next
 
   ' Update the settings.
   CurrentSettings.RemoteUris = Temp
   GrabAPictureSettings.SaveSettings(CurrentSettings)
 
   ' Update the display.
   lstRemote.Items.RemoveAt(lstRemote.SelectedIndex)
End Sub
 
Private Sub btnSelect_Click(ByVal sender As System.Object, _
                            ByVal e As System.EventArgs) _
                         Handles btnSelect.Click
   ' Wallpaper manipulation class.
   Dim Wallpaper As New WinWallpaper
 
   ' Choose the wallpaper style.
   Select Case CurrentSettings.RemoteUris(lstRemote.SelectedIndex).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.
   Dim NewUri As New Uri( _
      CurrentSettings.RemoteUris(lstRemote.SelectedIndex).Location)
 
   ' Set the wallpaper location.
   Wallpaper.WallpaperURI = NewUri
End Sub

Of course, everything is done using CurrentSettings.RemoteUris in this case because you’re working with the remote settings. In addition, every one of the dialog boxes uses the most complex frmAddEdit constructor, which configures the application for a remote source. I’ll leave it to you to explore this code based on the conversation of the local source workings.

Well, that’s it for frmConfigure. The next post will complete the basic application. We’ll look at how frmAddEdit works. In the meantime, feel free to contact me about any questions at John@JohnMuellerBooks.com. I’ll also start entertaining some additions to this basic application. I have a few in mind, but I’d love to hear your thoughts on the subject. You can find the last post in this series at: Exploring the GrabAPicture Application (Part 12).