Improving the GrabAPicture Application (Part 6)

This week is the last for the GrabAPicture application. After this post, you’ll have a fully functional application that demonstrates a significant number of intermediate level Visual Basic .NET programming techniques. In this post, you see the implementation of the last improvement described in the Improving the GrabAPicture Application (Part 1) post, adding a means to see a small version of the desktop wallpaper you’ve selected in the frmConfigure (Configure a Random Source) dialog box.

To begin this process, you need to modify frmConfigure to include a PictureBox control. Fortunately, this control will not only resize the image to make it presentable for you, but also accepts URIs, so you don’t have to use any special coding techniques. Use these steps to add the PictureBox control.

 

  1. Modify the frmConfigure size by changing the Size.Height property value to 600.
  2. Add the PictureBox control to the form.
  3. Modify the PictureBox control settings as follows:
    • (Name) = pbDisplayImage
    • Location = 8, 261
    • Locked = True
    • Margin.All = 0
    • ScrollBarsEnabled = False
    • Size = 300, 300
    • SizeMode = Zoom


Now that you have the control configured, you can begin writing code for it. The use of the PictureBox control makes the exercise almost too easy.

Private Sub PictureSelect(ByVal sender As ListBox, _
                          ByVal e As System.EventArgs) _
                       Handles lstLocal.Click, lstRemote.Click
 
   ' Determine which control has focus.
   If sender.Name = "lstLocal" Then
 
      ' Load the selected item.
      pbDisplayImage.Load( _
         CurrentSettings.LocalUris(lstLocal.SelectedIndex).Location)
   Else
 
      ' Load the selected item.
      pbDisplayImage.Load( _
         CurrentSettings.RemoteUris(lstRemote.SelectedIndex).Location)
   End If
End Sub

The one tricky part is that this single event handler handles both lstLocal.Click and lstRemote.Click. It’s actually easier just to write the code, rather than rely on the GUI to make the connections for you in this case.

Because this one event handler works with both events, it must detect the correct control using the sender.Name property. The code then chooses the correct image based on the lstLocal or lstRemote selection. Here’s a typical view of what you’ll see with this update to your application.

GrabAPictureUpdate0601

A number of readers have asked me to compile this entire series of posts in an easy-to-use form. Admittedly, trying to wade through 18 posts to find what you need is going to prove a little difficult. With this in mind, I’ve decided to create an e-book that includes the following items:


  • All 18 of the posts on my blog in an easy-to-read form.

  • My personal notes for the GrabAPicture application.

  • Additional modifications that you can try.

  • The URL for a downloadable version of the complete GrabAPicture application.

 

I’m working on the e-book now and will let you know when it becomes available.  The e-book title will be, “Discovering Intermediate Visual Basic Programming Through the GrabAPicture Application” and the pricing will be determined at a future date. In the meantime, if there are any questions on these posts or if there is something you’d really like to see in the e-book when it’s complete, please let me know at John@JohnMuellerBooks.com.

Next week we’ll start looking at a new application. This one will be written in C# and provide some interesting features not found in the GrabAPicture application. As with the GrabAPicture application, the TypingBuddy application has been part of my Windows Desktop for a number of years, during which time I have continued to refine it. It’s my hope that you’ll find TypingBuddy as interesting as you have GrabAPicture.

Improving the GrabAPicture Application (Part 5)

This week’s post discusses the third item on the list in the Improving the GrabAPicture Application (Part 1), which is to add a browse button to frmMain. This is a design issue that I shouldn’t need to address now. I previously raked Microsoft across the coals for changing a perfectly good user interface in my Examining the Calculator in Windows 7 post, well, this is an examination of a flaw in my original design for GrabAPicture that shouldn’t have occurred either. A reader wrote and told me about it and the moment I read the message I knew that the omission was of the sort that anyone working with the application long enough would notice. Unfortunately, I had gotten into the bad habit of cutting and pasting the paths to images I wanted to test, so it was an omission that I didn’t notice . So, here in all its glory is the post for the browse button that should have originally appeared on frmMain.

The first thing you’ll need to do is modify the user interface to include the button. Begin by changing the txtWallpaperURI.Size.Width property value to 160. Then, add a new button called btnGetResource with these property values:

 

  • Location.X = 168
  • Location.Y = 32
  • Size.Width = 24
  • Size.Height = 20
  • TabIndex = 5
  • Text = …

The new user interface will look like this:

GrabAPictureUpdate0501

You also need to add a new OpenFileDialog component named OFD. This is a non-visual component, so you want see it in the user interface. Assign these property values to OFD:

 

  • Filter = BMP Files|*.BMP|JPG Files|*.JPG|GIF Files|*.GIF|PNG Files|*.PNG|All Graphics Files|*.BMP;*.JPG;*.GIF;*.PNG
  • FilterIndex = 1


Double click btnGetResource to create a new Click event handler. Add this code to the event handler:

Private Sub btnGetResource_Click(sender As System.Object, _
                                 e As System.EventArgs) _
                              Handles btnGetResource.Click
 
   ' If the user selects something and clicks OK.
   If OFD.ShowDialog() = DialogResult.OK Then
 
      ' Assign the value to the textbox.
      txtWallpaperURI.Text = OFD.FileName
   End If
End Sub

The resulting code makes it possible to obtain a file for use on the Desktop using an Open dialog box as shown here.

GrabAPictureUpdate0502

Even though this isn’t a huge change in functionality, it’s a necessary change. Making things easy on the user is always an essential part of creating a great application. Let me know if you have any questions about this post (especially my decision to highlight a design flaw in this manner) at John@JohnMuellerBooks.com.

 

Understanding the LINQ Question

A few readers have written to ask me about the question I keep discussing when talking about LINQ. I use the idea of a question in both LINQ for Dummies and Start Here! Learn Microsoft Visual C# 2010 Programming to describe the concept behind LINQ. In fact, if you have problems understanding this concept, you’re not alone. Many people have trouble understanding just how it is that declarative languages such as LINQ and SQL perform their job. The idea that you can describe what you want and let the computer decide how to do it is foreign to many people because most people rely on procedures to understand many issues. LINQ and SQL don’t offer procedures. At the root of the problem is the word “how.” We want to know how things work and how to do things.

Imagine for a moment that someone tells you to go to 123 Anywhere Street and pick up a widget for them from the grocery store. They don’t tell you how to get to 123 Anywhere Street or anything else about the request—they simply assume that you’ll figure it out on your own. Most people would eventually get a map, work out a route, and get to 123 Anywhere Street. Once there, they’d go to the grocery store and ask for a widget from the owner. This sequence of events is akin to what is happening with both LINQ and SQL. You tell the computer to get the widget from the grocery store at 123 Anywhere Street. The “how” is unimportant and you truly don’t care how the computer accomplishes the task.

So, where does the question part of this discussion come into play? Developers want data to manipulate within their applications, not widgets. When a developer requests data from the computer using a language such a LINQ, the developer does so in the form of a question or a query. In fact, declarative languages often use the word query as part of their nomenclature: Language INtegrated Query (LINQ) and Structured Query Language (SQL). These languages specialize in asking questions and telling the computer to use any means necessary to answer them. The developer doesn’t care how the language does its work—all the developer cares about is the data.

Declarative languages free the developer from having to think about how to perform tasks. A developer can spend more time thinking about data manipulations and less time thinking about the mechanics of language. This means that the developer can become a lot more productive and write applications significantly faster. The use of declarative languages also makes application development easier. A less skilled developer can write a more complex application.

Don’t get the idea that LINQ is a wimpy language that limits what you can do in the interest of simplicity. In fact, LINQ users can create extremely complex scenarios that would require considerable time to figure out without LINQ. For example, I wrote an article about using LINQ Extensions some type past. You’ll want to review this article if you need to see one of the more technical ways to use LINQ (and even this article doesn’t discuss the topic in depth). While you’re at it, check my other articles on using LINQ Expression Trees and using LINQ with Resource Description Framework (RDF) files.

The bottom line is that LINQ is about queries, or questions. You ask the computer to supply data based on some criteria. The computer decides how best to obtain the data for you, so you can concentrate on issues that the computer can’t decide, such as data manipulation and presentation. If you have additional questions about why someone would use LINQ or why I explain LINQ as answering questions, please let me know at John@JohnMuellerBooks.com.

 

Examining the Calculator in Windows 7

Almost every book I write on programming has some element of application design in it. If you don’t create applications with a great design, users are less likely to use them. I examine general design issues in C# Design and Development. However, books like C++ All-In-One Desk Reference For Dummies, LINQ for Dummies, Professional IronPython, Start Here! Learn Microsoft Visual C# 2010 Programming, and Web Matrix Developer’s Guide all have design elements in them because I feel application design is extremely important. Even RibbonX for Dummies and VBA for Dummies have design discussions in them because they both talk about user interfaces. The most specialized book I’ve created about application design is Accessibility for Everybody: Understanding the Section 508 Accessibility Requirements, which discusses user interface requirements for those with special needs. All of these books have one thing in common, they all try to quantify what makes for good user design. They all ask the question, “How can you as a developer write an application that users will enjoy using and use efficiently?” Unfortunately, examples of poor design abound and seem to be more common as time goes on, which is the point of this post.

The Calculator application in Windows 7 is a perfect example of an application that has gone the wrong direction when it comes to the user interface. Sure, the Standard view is the same in both cases as shown here.

Calculator02 Calculator01

The Windows 95 version of Calculator is on the left and the Windows 7 version is on the right. As you can see, the buttons are the same, but someone at Microsoft felt obliged to rearrange them, just to make things difficult for anyone used to using the old version of Calculator. There isn’t a good reason for the change except to change for change’s sake. The Windows 7 version does have larger numbers, but at the expense of providing room for larger calculations. Personally, I’d prefer the Windows 95 version because sometimes I do need to perform really large calculations. Both versions of Calculator offer features such as digit grouping, so there isn’t any change here either. In short, Windows 7 offers less capability in a rearranged format that makes it hard for people who are used to the old version of Calculator to use it—breaking the rules of good design.

The problems get worse, unfortunately. I never used the Standard view of Calculator because I need to perform programmer math and scientific calculations relatively often. Switching to Scientific view in the Windows 95 version of Calculator, you see the perfect interface as shown here.

Calculator03

During the time I worked with Windows 95, I never switched out of this view. All I did was start Calculator whenever I needed it and did the math that I needed to do. Never once did the view I was in enter my mind. That’s great design! Users shouldn’t have to think about how your application works—the user’s only thought should be how to get work done.

The Windows 7 version of Calculator now has four modes: Standard, Scientific, Programmer, and Statistics. Every time I use Calculator now, I must first figure out what I want to do and change the view appropriately—wasting time and effort in the process. The views unnecessarily limit my options. For example, look at the Programmer view.

Calculator04

I do gain access to the RoL (rotate left) and RoR (rotate right) buttons, but I can’t think of when I’ll need them. The modern languages that most developers use don’t actually require these buttons. If I were using assembler, they could see some use, but I don’t remember ever using rotate left or rotate right with C#, Visual Basic (any version), Python, or even C++. So, these buttons are extraneous and only serve to complicate the interface. In addition, I do gain a bit display, which could prove helpful at some point, but I found that the Bin view on the old version of Calculator worked just fine and served my needs well. However, notice that the decimal point button is disabled. The loss of this button means that every time I have to perform any sort of math with a decimal point, I have to switch to another view. Programmers do use floating point numbers! So, a capable programmer calculator has been replaced with something less useful—something that makes me irritable and work harder.

Now let’s switch to Scientific view. Remember, I used to be able to do everything in one view. If I want to raise a value to a power now as part of checking application output, I have to switch to the Scientific view shown here.

Calculator05

As usual, someone felt that it was absolutely required to move the buttons around so that it takes a while to relearn how to use this view. For the most part, I don’t use any of the new features provided by this view. The few times I needed to use this feature with the old Calculator required a switch to Standard view, but the switches were seldom indeed. The Scientific view does include a number of additional features, all of which are likely helpful to someone, but the cost is one of having to switch views when you need to do something else.

Before someone writes to tell me that the new calculator has square root, cubed root, and root to any power buttons, the Windows 95 Calculator has these features as well. You just had to know how to use a calculator to understand them. Let’s say you want to find the cubed root of 27 using the Windows 95 Calculator. Simply type 27, check Inv, and click the x^3 button. You’ll see the cubed root answer of 3. Not convinced? OK, let’s try a quad root. Type 16, check Inv, and click the x^y button. Now, type 4 (the quad root) and press the = button. You’ll see the quad root of 16 is 2, which is the correct answer. The Windows 7 calculator adds nothing new in this regard—just more buttons to hunt and peck!

In short, the new Calculator is an example of a failed interface update. Most people will find that they have to constantly switch between views to get anything done. When you force people to think too much about the interface, they tend to get grumpy, less productive, and end up tossing out the application at some point. Personally, every since  I found the Windows 95 version of Calculator runs just fine on my Windows 7 system, I’ve been using it instead of the broken product Microsoft has offered. The Windows 95 version truly is the superior application. Let me know your thoughts on Calculator and on application design in general at John@JohnMuellerBooks.com.

 

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).