Last week, in the Exploring the GrabAPicture Application (Part 6) post, we discussed the use of the SystemParametersInfo() function call. Of course, this call is part of the Windows API, so you have to use P/Invoke to access it. The previous post doesn’t really tell you how to use the SystemParametersInfo() call—that’s the topic of this post.
Wallpaper is defined by three elements in the GrabAPicture application: Name, Location, and Style. The Name property is easy—it can be any string. So, you don’t have to work really hard with it. The Location property is the most complex because a location can’t be anywhere there is a graphic that the application can grab—either local or remote. The Style property is still complex because of the way Windows stores information, but it’s not absurdly difficult because Windows only supports so many styles. The following code looks at the Style property in the WinWallpaper class first.
The Style property defines which style to use to display an image on the Desktop. The current version of the GetAPicture program is compatible with all versions of Windows, including Windows XP. A later post will show how to upgrade this application so it also allows use with Windows 7-specific features (Fill and Fit). As you can see, the get portion of this property begins by accessing the HKEY_CURRENT_USER\Control Panel\Desktop key of the registry as shown here.
Eventually, you’ll use three values from this key: TileWallpaper, Wallpaper, and WallpaperStyle. For now, all you need to know about is TileWallpaper (used to define the wallpaper as Tiled) and WallpaperStyle (used for all of the other settings). The code examines the registry settings using the GetValue() method and returns a Styles enumeration value that reflects the current settings. If the code can’t recognize a setting, then it returns Styles.Unknown.
When it comes time to set the wallpaper style, the application verifies that the style is known and then uses two calls to SetValue() to configure the TitleWallpaper and WallpaperStyle settings in the registry. As you might imagine, updating the rudimentary functionality of the code won’t be hard, but to ensure that the code works properly with all versions of Windows, you need to add version checks. This is the part that will interest you the most in the future post.
The WallpaperURI property code is a lot more complex than you might imagine for a very good reason. If you simply try to set the wallpaper location using the registry values, you won’t see any change on screen. The system won’t know to perform the update. In order to see the update and ensure that the change is reflected in any running applications, you must use the SystemParametersInfo() call as shown in the following code.
Working with a Windows API function requires that you do some special coding. Otherwise, the call will never work. It’s important to remember that the Windows API works mainly with numbers and not with other data types. If you want to work with a string, then the string parameter takes the form of a pointer to memory in the caller’s address space in most cases. Of course, Visual Basic doesn’t provide a means to do this directly. Creating a StringBuilder object tells Visual Basic to marshal the string as a pointer to the Windows API. In this case, the code creates a StringBuilder capable of holding 120 characters, which is normally enough to hold the location of the current wallpaper on disk.
PInvoke comes in handy for all sorts of purposes in Visual Basic. This application shows just one example. You can find another great example of working with PInvoke on Rod Stephen’s site with the “Remove the X Close button from a form’s system menu in Visual Basic .NET” example. I haven’t seen this particular use of PInvoke explained anywhere else, so you’ll definitely want to take a look. Of course, you can get all the information you need for working with PInvoke in my book, “.NET Framework Solutions: In Search of the Lost Win32 API.”
The call to SystemParametersInfo() comes next. Normally, you’d make a call that could fail in a try…catch block. However, a Windows API call won’t work that way. In this case, the call returns True when the call succeeds and False when it doesn’t (individual Windows API calls behave in different ways, so make sure you understand how errors are presented by individual call). When the call returns True, the property returns a new Uri containing the result provided by SystemParametersInfo(). Otherwise, the code throws an Exception. Unfortunately, you don’t know what happened. The only way to get this information is to call Marshal.GetLastWin32Error() and that information is only available when you set the value of the SetLastError property of the <DllImport> attribute (explained in the previous post) to True.
Some readers will take me to task for not providing a more specific exception, especially since I pound on this particular failing in many applications in my books. Because you don’t know the nature of the exception, you can’t provide anything better than an general Exception in most cases. The only way around this is to create code that laboriously tracks every sort of potential error returned by the Windows API and creates an appropriate exception for it. This may be a good topic for another post in the future, but definitely not the point of this post.
Setting the wallpaper location is complicated by the fact that the wallpaper can be either local or remote. The code pursues the remote route first. The example assumes that the remote wallpaper will be on an HTTP site. You’d need to modify the code to accommodate other schemes such as HTTPS. This might make a good future post, but let’s get into a common HTTP remote location first.
The first step is to create a copy of the wallpaper on the local drive in the user’s temporary folder. A call to Path.GetTempPath() obtains this information. The remote wallpaper is always stored in a file named Wallpaper.BMP (I chose the BMP format for compatibility purposes).
The code performs a number of tasks in a try…catch block. First, it attempts to read the file containing the image from the remote location into GetData (of type Stream). If this is successful (the image exists and you can read it), the code converts the stream into an Image object, Img. The image can be of any type that Image supports:
The final step is to save the image, using Img.Save() to Wallpaper.BMP in the BMP format. If there is an exception in any of these steps, the application automatically recovers in one of two ways. First, it looks for an example Wallpaper.BMP and uses it when the file exists. Second, if this first technique fails, then the application looks at the current value for the local wallpaper selection and uses it instead. In this case, the application uses the Image.FromFile() method to obtain a copy of the image and saves it to Wallpaper.BMP.
When the wallpaper is local, the code first checks to see if the wallpaper is already in BMP format. If it is, the application simply places the information in Location. Otherwise, the application reads the file using Image.FromFile() and saves the image in BMP format to Wallpaper.BMP.
No matter how the information gets there, eventually Location contains a path and filename for the file you want to display on the Desktop. At this point, the code calls SystemParametersInfo() to set the wallpaper, which indirectly sets the Wallpaper value in the registry. The call also notifies Windows that it needs to update the Desktop and any applications that are currently running that they need to repaint their displays. The user sees the new wallpaper displayed on screen.
At this point, you have a good idea of how the WinWallpaper class works. In the next post, you learn about the GrabAPictureSettings class, which is used to interact with the settings you’ve learned about in the past several posts. Please let me know if you have any questions at John@JohnMuellerBooks.com. You can see the next post in this series at Exploring the GrabAPicture Application (Part 8).