Exploring the GrabAPicture Application (Part 6)

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

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

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

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

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

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

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

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

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

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

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

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

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

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