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.
Public
Property
Style()
As
Styles
Get
' Contains the registry key that holds the
' desktop values.
Dim
StyleKey
As
RegistryKey
' Get the desktop key.
StyleKey = _
Registry.CurrentUser.OpenSubKey( _
"Control Panel\Desktop"
,
False
)
' Obtain the current value.
If
StyleKey.GetValue(
"WallpaperStyle"
) = 2
And
_
StyleKey.GetValue(
"TileWallpaper"
) = 0
Then
Return
Styles.Stretched
End
If
If
StyleKey.GetValue(
"WallpaperStyle"
) = 1
And
_
StyleKey.GetValue(
"TileWallpaper"
) = 0
Then
Return
Styles.Centered
End
If
If
StyleKey.GetValue(
"WallpaperStyle"
) = 1
And
_
StyleKey.GetValue(
"TileWallpaper"
) = 1
Then
Return
Styles.Tiled
End
If
' This value should only show up if someone has
' tampered with the registry and set the values
' incorrectly.
Return
Styles.Unknown
End
Get
Set
(
ByVal
Value
As
Styles)
' Because Styles.Unknown signifies an error
' condition, the developer can't provide it as an
' input value.
If
Value = Styles.Unknown
Then
Throw
New
ArgumentException( _
"Cannot use Styles.Unknown as an input argument"
)
End
If
' Contains the registry key that holds the
' desktop values.
Dim
StyleKey
As
RegistryKey
' Get the desktop key.
StyleKey = _
Registry.CurrentUser.OpenSubKey( _
"Control Panel\Desktop"
,
True
)
' Select one of the other input values and set the
' registry keys as needed.
Select
Case
Value
Case
Styles.Stretched
StyleKey.SetValue(
"WallpaperStyle"
,
"2"
)
StyleKey.SetValue(
"TileWallpaper"
,
"0"
)
Case
Styles.Centered
StyleKey.SetValue(
"WallpaperStyle"
,
"1"
)
StyleKey.SetValue(
"TileWallpaper"
,
"0"
)
Case
Styles.Tiled
StyleKey.SetValue(
"WallpaperStyle"
,
"1"
)
StyleKey.SetValue(
"TileWallpaper"
,
"1"
)
End
Select
End
Set
End
Property
The Style property defines which style to use to display an image on the Desktop. The current version of the GetAPicture program is compatible with all versions of Windows, including Windows XP. A later post will show how to upgrade this application so it also allows use with Windows 7-specific features (Fill and Fit). As you can see, the get portion of this property begins by accessing the HKEY_CURRENT_USER\Control Panel\Desktop key of the registry as shown here.

Eventually, you’ll use three values from this key: TileWallpaper, Wallpaper, and WallpaperStyle. For now, all you need to know about is TileWallpaper (used to define the wallpaper as Tiled) and WallpaperStyle (used for all of the other settings). The code examines the registry settings using the GetValue() method and returns a Styles enumeration value that reflects the current settings. If the code can’t recognize a setting, then it returns Styles.Unknown.
When it comes time to set the wallpaper style, the application verifies that the style is known and then uses two calls to SetValue() to configure the TitleWallpaper and WallpaperStyle settings in the registry. As you might imagine, updating the rudimentary functionality of the code won’t be hard, but to ensure that the code works properly with all versions of Windows, you need to add version checks. This is the part that will interest you the most in the future post.
The WallpaperURI property code is a lot more complex than you might imagine for a very good reason. If you simply try to set the wallpaper location using the registry values, you won’t see any change on screen. The system won’t know to perform the update. In order to see the update and ensure that the change is reflected in any running applications, you must use the SystemParametersInfo() call as shown in the following code.
Public
Property
WallpaperURI()
As
Uri
Get
' Create a variable to hold the result.
Dim
Result
As
New
StringBuilder(120)
' Get the wallpaper URI.
If
SystemParametersInfo( _
Convert.ToUInt32(SPI_GETDESKWALLPAPER), _
Convert.ToUInt32(Result.Capacity), _
Result, _
Convert.ToUInt32(0))
Then
' Return the result as a URI.
Return
New
Uri(Result.ToString())
Else
' Throw an exception if there is an error.
Throw
New
Exception( _
"Error Calling SystemParametersInfo: "
+ _
Marshal.GetLastWin32Error().ToString())
End
If
End
Get
Set
(
ByVal
Value
As
Uri)
' The usable, local path, location of the
' wallpaper.
Dim
Location
As
String
' A connection to the wallpaper online.
Dim
GetData
As
Stream
' The online image.
Dim
Img
As
Image
' Wallpaper on a Web page requires somewhat
' different processing than local wallpaper.
If
Value.Scheme.ToUpper() =
"HTTP"
Then
' Create a new path to save the image
' locally so it can be placed on the desktop.
Location = Path.GetTempPath() +
"WallPaper.BMP"
' The Web connection could fail, so use a Try
' block to obtain the file.
Try
' Obtain a connection to the wallpaper.
GetData = _
New
WebClient().OpenRead(Value.ToString())
' Read the wallpaper data.
Img = Image.FromStream(GetData)
' Save the image.
Img.Save(Location, Imaging.ImageFormat.Bmp)
Catch
ex
As
WebException
' The Web connection has failed.
' Use the existing Wallpaper.BMP
' file if it exists.
If
File.Exists(Location)
Then
' Read the existing file instead.
Img = Image.FromFile(Location)
Else
' Otherwise, use the local path instead.
Location = Value.LocalPath
' Verify the wallpaper is a BMP.
If
Not
Location.ToUpper().EndsWith(
"BMP"
)
Then
' Load the file from disk.
Img = Image.FromFile(Location)
' Create a new path to save the image
' locally so it can be placed on the desktop.
Location = Path.GetTempPath() +
"WallPaper.BMP"
' Save the image.
Img.Save(Location, Imaging.ImageFormat.Bmp)
End
If
End
If
End
Try
Else
' Assume that the wallpaper is local.
Location = Value.LocalPath
' Verify the wallpaper is a BMP.
If
Not
Location.ToUpper().EndsWith(
"BMP"
)
Then
' Load the file from disk.
Img = Image.FromFile(Location)
' Create a new path to save the image
' locally so it can be placed on the desktop.
Location = Path.GetTempPath() +
"WallPaper.BMP"
' Save the image.
Img.Save(Location, Imaging.ImageFormat.Bmp)
End
If
End
If
If
SystemParametersInfo( _
Convert.ToUInt32(SPI_SETDESKWALLPAPER), _
Convert.ToUInt32(Location.Length), _
New
StringBuilder(Location), _
Convert.ToUInt32(SPIF_UPDATEINIFILE
Or
SPIF_SENDWININICHANGE))
Then
Else
' Throw an exception if there is an error.
Throw
New
Exception( _
"Error Calling SystemParametersInfo: "
+ _
Marshal.GetLastWin32Error().ToString())
End
If
End
Set
End
Property
Working with a Windows API function requires that you do some special coding. Otherwise, the call will never work. It’s important to remember that the Windows API works mainly with numbers and not with other data types. If you want to work with a string, then the string parameter takes the form of a pointer to memory in the caller’s address space in most cases. Of course, Visual Basic doesn’t provide a means to do this directly. Creating a StringBuilder object tells Visual Basic to marshal the string as a pointer to the Windows API. In this case, the code creates a StringBuilder capable of holding 120 characters, which is normally enough to hold the location of the current wallpaper on disk.
PInvoke comes in handy for all sorts of purposes in Visual Basic. This application shows just one example. You can find another great example of working with PInvoke on Rod Stephen’s site with the “Remove the X Close button from a form’s system menu in Visual Basic .NET” example. I haven’t seen this particular use of PInvoke explained anywhere else, so you’ll definitely want to take a look. Of course, you can get all the information you need for working with PInvoke in my book, “.NET Framework Solutions: In Search of the Lost Win32 API.”
The call to SystemParametersInfo() comes next. Normally, you’d make a call that could fail in a try…catch block. However, a Windows API call won’t work that way. In this case, the call returns True when the call succeeds and False when it doesn’t (individual Windows API calls behave in different ways, so make sure you understand how errors are presented by individual call). When the call returns True, the property returns a new Uri containing the result provided by SystemParametersInfo(). Otherwise, the code throws an Exception. Unfortunately, you don’t know what happened. The only way to get this information is to call Marshal.GetLastWin32Error() and that information is only available when you set the value of the SetLastError property of the <DllImport> attribute (explained in the previous post) to True.
Some readers will take me to task for not providing a more specific exception, especially since I pound on this particular failing in many applications in my books. Because you don’t know the nature of the exception, you can’t provide anything better than an general Exception in most cases. The only way around this is to create code that laboriously tracks every sort of potential error returned by the Windows API and creates an appropriate exception for it. This may be a good topic for another post in the future, but definitely not the point of this post.
Setting the wallpaper location is complicated by the fact that the wallpaper can be either local or remote. The code pursues the remote route first. The example assumes that the remote wallpaper will be on an HTTP site. You’d need to modify the code to accommodate other schemes such as HTTPS. This might make a good future post, but let’s get into a common HTTP remote location first.
The first step is to create a copy of the wallpaper on the local drive in the user’s temporary folder. A call to Path.GetTempPath() obtains this information. The remote wallpaper is always stored in a file named Wallpaper.BMP (I chose the BMP format for compatibility purposes).
The code performs a number of tasks in a try…catch block. First, it attempts to read the file containing the image from the remote location into GetData (of type Stream). If this is successful (the image exists and you can read it), the code converts the stream into an Image object, Img. The image can be of any type that Image supports:
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).