Customising Your Desktop with Visual Basic.NET 2005

Introduction

If you followed my previous article about changing the wallpaper on your computer (Wallpaper Changer), you would know by now that I'm quite lazy and like to make things easier for myself. With Wallpaper Genie (In the above link), I changed the wallpaper only; curiosity got the better of me, and I decided to take it one step further. With this article, I will demonstrate how launch, configure, and preview screen savers on your own form; I will also demonstrate how to change the computer's Visual style (along with the fonts, and colours pertaining to the particular visual style), determine whether your application is themed or not, as well as disabling a certain theme. Lastly, I will demonstrate how to determine the current screen resolution and change it. Sound exciting? Well then, it's time to get started.

Concepts

Okay, you won't start just yet. Let me first explain the intricacies surrounding what makes your desktop tick, and start off by explaining how the system wallpaper works and what you need to do to set it from your program.

Wallpaper

The current system wallpaper location is usually stored in the HKEY_CURRENT_USER\Control Panel\Desktop Registry key in the setting of Wallpaper. The wallpaper style (Tiled, Stretched, Center) is also stored in the same Registry key; these values are controlled by the TileWallpaper and WallpaperStyle settings, but I won't go into great detail here because all of this is explained in the Wallpaper Changer article. What you need to do to change the current wallpaper from your own application, would be to write the appropriate values to these keys in the Registry as well as to use the SystemParametersInfo API to set the new wallpaper and update the current system settings. Later on in this article, I will properly demonstrate how to do this.

Resolution

Getting the current resolution settings is actually surprisingly easy; all you need to do is to use the System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width property to get the resolution width, and the same applies to the height. Changing the resolution settings is not this simple, unfortunately, but it's luckily not too complicated either! What you will need to do is to use the EnumDisplaySettings and the ChangeDisplaySettings APIs; because of these API functions' parameters, you will also need various Constants, and of course the DevMode1 structure that takes care of the height and width settings.

Screen Saver

The current System screensaver is also stored in HKEY_CURRENT_USER\Control Panel\Desktop Registry key, in the setting SCRNSAVE.EXE, but only if a screensaver has been set. To run the current screensaver, all you need to use is the SendMessage API. Running a different screensaver is also quite simple; you achieve this just by using the Start method of a Process object. Now, to configure you need to start the screensaver process with the configure parameter; to preview a screensaver within your program, you need to call the preview parameter along with a handle to a picturebox object. (In other words, that is telling the screensaver where to show its preview.)

Theme & Visual Style

All Theme and Visual Styles settings are stored within the HKEY_USERS\.DEFAULT\Software\Microsoft\Windows\CurrentVersion\ThemeManager Registry key. The name & location are stored under DllName. The selected style colour available for the particular Visual Style is also stored in the same place, but under the name of ColorName. Some Styles & Themes may have more than one colour available. Another easy way to determine the various colours available is by opening the "C:\WINDOWS\Resources\Themes" folder. By doing that, you will see the names of all the various Styles on the System; open one of those folders (for example, Luna), within that folder, and you will see another folder named Shell. If you were to open the Shell folder, you will see more folders (yes, there are a lot of folders, so bear with me); for example NormalColor, HomeStead, and Metallic—within these folders there is usually a DLL file applying this colour. The Font used with the Theme is stored in the SizeName setting, also in the same key. The value for the SizeName setting can be any one of NormalSize, LargeFont, and ExtraLargeFont.

Starting Your Design

If you haven't fallen asleep during my explanations concerning what is needed and how things work, you are ready to continue designing your interface.

Design

Add the following controls with their settings to your form:

Control Property Setting Description
GroupBox Name grpWall Holds all the Wallpaper Settings Controls
  Text Wallpaper Settings
Button (Inside grpWall) Name btnWall Enables you to change the Wallpaper
  Text Change Wallpaper
GroupBox Name grpTheme Holds all the Theme Settings Controls
  Text Theme Settings
Button (Inside grpTheme) Name btnAppThemed Determines whether or not your Application is Themed
  Text Is Application Themed?
Button (Inside grpTheme) Name btnThemeActive Determines wWhether or not a theme is active
  Text Is Theme Active
Button (Inside grpTheme) Name btnDisTheme Disable the current Theme (System wide)
  Text Disable Theme
GroupBox Name grpScreenRes Holds all the Screen Resolution Settings controls
  Text Screen resolution Settings
Button (Inside grpScreenRes) Name btnGetRes Get current Screen Resolution
  Text Get Screen Resolution
Button (Inside grpScreenRes) Name btnChangeRes Change Screen Resolution
  Text Change Screen Resolution
ListBox (Inside grpScreenres) Name lstResolution Shows Available Screen Resolution settings
  Items 640 x 480
800 x 600
832 x 624
1024 x 768
1152 x 864
1280 x 600
1280 x 720
1280 x 768
1280 x 960
1280 x 1024
GroupBox Name grpVisStyles Holds all Visual Settings controls
  Text Visual Style Settings
Label (Inside grpVisStyles) Name Label1 Indicates Action to Take with cboStyles
  Text Select Visual Style
Label (Inside grpVisStyles) Name Label2 Indicates Action to Take with cboStyleColor
  Text Select Style Color
Label (Inside grpVisStyles) Name Label3 Indicates Action to Take with cboStyleFonts
  Text Select Style Font
ComboBox (Next to Label1, Inside grpVisStyles) Name cboStyles Display all the System Visual Styles
ComboBox (Next to Label2, Inside grpVisStyles) Name cboStyleColor Display all the available Style Colours
ComboBox (Next to Label3, Inside grpVisStyles) Name cboStyleFonts Display all the available Style Fonts
  Items Normal
Large Fonts
Extra Large Fonts
Button (Inside grpVisStyles) Name btnSetStyle Sets the new Visual Style for the system, with all specified Settings
  Text Apply Visual Style
GroupBox Name grpScreenSaver Holds all ScreenSaver Settings controls
  Text ScreenSaver Settings
ListView (Inside grpScreenSaver) Name lvScreen Lists the System's Screensavers, and shows descriptions of each
  View Details
lvScreen Column Name ScreenName Shows Screensaver name in lvScreen
  Text Screensaver
  Name ScreenDesc Shows Screensaver Description in lvScreen
  Text Description
GroupBox (Inside grpScreenSaver) Name grpSelScreen Enables you to work with selected (in ListView) screensaver
  Text Selected Screen Saver Settings
Button (Inside grpSelScreen) Name btnDiffScreen Start Selected Screensaver
  Text Start Selected Screen Saver
Button (Inside grpSelScreen) Name btnPreviewScreen Preview Selected Screensaver
  Text Preview Selected Screen Saver
Button (Inside grpSelScreen) Name btnConfScreen Configure Selected Screensaver
  Text Configure Selected Screen Saver
Button (Inside grpScreenSaver) Name btnDefScreen Starts Current System Screensaver
  Text Start Default Screen Saver
Button (Inside grpScreenSaver) Name btnStopScreen Stops all Screensaver Processes
  Text Stop Screen Saver Activities
PictureBox (Inside grpScreenSaver) Name picPreviewScreen Shows the Preview of selected Screensaver
  BorderStyle FixedSingle

Your design should now look like the following picture:

Customising Your Desktop with Visual Basic.NET 2005

Wallpaper Settings

As mentioned earlier, you need to store the new wallpaper name in the Registry, so that the next time the computer boots up, it still shows your new wallpaper. Because you are saving information into the Registry, you will need to make use of all the Registry functionalities available in the Microsoft.Win32 NameSpace. While you are busy adding Imports to your program, take the liberty of adding the System.Runtime.InterOpServices Namespace too. The reason you need to add this Import is to properly use the unmanaged code in the APIs in your .NET program. Now, add these two (2) Import Statements at the top of your class:

Imports System.Runtime.InteropServices    'For APIs
Imports Microsoft.Win32                   'The Registry stuff

After you have that in place, you need to add the SystemParametersInfo API. This API is commonly used for various system settings such as the wallpaper (in your case), or settings such as whether or not you want to show the underlined letters on menu bars. Declare the SystemParametersInfo API with its associated Constants:

   <DllImport("user32", EntryPoint:="SystemParametersInfo", _
                 CharSet:=CharSet.Auto, SetLastError:=True)> _
Private Shared Function SystemParametersInfo(ByVal uAction As Integer, _
   ByVal uParam As Integer, ByVal lpvParam As String, _
   ByVal fuWinIni As Integer) As Integer
   End Function    'API Used for System Settings

   Private Const SPI_SETDESKWALLPAPER = 20    'Set Wallpaper
   Private Const SPIF_UPDATEINIFILE   = &H1   'Update The INI File

Lastly, in your button that sets the wallpaper (btnWall), you need to indicate to the SystemParametersInfo function what file you want to use as the wallpaper. Sadly, the SystemParametersInfo API can only set bitmap (.bmp) file as wallpapers; for this example, I have included a bitmap file, and stored it in your application's Bin folder. After the call to the SystemParametersInfo function, you still need to store this picture's location in the Registry (HKEY_CURRENT_USER\Control Panel\Desktop\Wallpaper). It is perhaps an overkill using SystemParametersInfo as well as saving to the Registry because SystemParametersInfo will update the system settings in any case, but you can never be too sure, so it's better to cover both angles. Create a Click event handler for the btnWall button:

Private Sub btnWall_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles btnWall.Click

   'Open the Registry key which handles the current wallpaper settings
   Dim DWWallKey As Microsoft.Win32.RegistryKey = _
      Microsoft.Win32.Registry.CurrentUser.OpenSubKey _
      ("Control Panel", True)

   'Our Desired Wallpaper Name & Location
   Dim ImagePath As String = _
      Application.StartupPath & "\CurrentWall.bmp"

   'Set Parameters To Change The Wallpaper & To Update The Windows
   ' Setting
   SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, ImagePath, _
                        SPIF_UPDATEINIFILE)

   'Open Wallpaper Registry key
   DWWallKey = DWWallKey.OpenSubKey("Desktop", True)
   'Save New Wallpaper Location
   DWWallKey.SetValue("Wallpaper", ImagePath)

End Sub

That wasn't so tough, was it?

Theme Settings

All Themes and Visual Styles settings are stored within the HKEY_USERS\.DEFAULT\Software\Microsoft\Windows\CurrentVersion\ThemeManager Registry key, as already explained. To use and modify these settings, you can of course use the Microsoft.Win32 namespace again, and store all the information there, but luckily, there is an easier way. You can use all the functions available in UxTheme.dll. This DLL file is used to change all your Visual Styles and Themes on your system. UxTheme.dll contains the following functions:

CloseThemeData
DrawThemeBackground
DrawThemeBackgroundEx
DrawThemeEdge
DrawThemeIcon
DrawThemeParentBackground
DrawThemeText
EnableThemeDialogTexture
EnableTheming
GetCurrentThemeName
GetThemeAppProperties
GetThemeBackgroundContentRect
GetThemeBackgroundExtent
GetThemeBackgroundRegion
GetThemeBool
GetThemeColor
GetThemeDocumentationProperty
GetThemeEnumValue
GetThemeFilename
GetThemeFont
GetThemeInt
GetThemeIntList
GetThemeMargins
GetThemeMetric
GetThemePartSize
GetThemePosition
GetThemePropertyOrigin
GetThemeRect
GetThemeString
GetThemeSysBool
GetThemeSysBrush
GetThemeSysColor
GetThemeSysFont
GetThemeSysInt
GetThemeSysSize
GetThemeSysString
GetThemeTextExtent
GetThemeTextMetrics
GetWindowTheme
HitTestThemeBackground
IsAppThemed
IsThemeActive
IsThemeBackgroundPartiallyTransparent
IsThemeDialogTextureEnabled
IsThemePartDefined
OpenThemeData
OpenThemeFile
SetSystemVisualStyle
SetThemeAppProperties
SetWindowTheme

More information on all these functions can be found here by clicking on the appropriate links.

Customising Your Desktop with Visual Basic.NET 2005

Determine whether an application is themed or not

By default, all applications created in Visual Basic.NET 2005 are themed. You can, however, change this functionality from within Visual Basic 2005. To do this, follow these steps:

  1. Open/Create the project.
  2. Select the Project menu.
  3. Select ProjectName Properties.
  4. If necessary, select the Application button.
  5. Deselect/Select Enable XP Visual Styles.
  6. Save the project.

By having XP Visual Styles enabled, your form and the controls on the form will have the XP look and feel, so it's actually quite obvious to differentiate between themed applications and applications that those that aren't themed. In certain cases, though, you may need to determine whether the application is indeed themed or not. These can be applications written in earlier versions of Visual Basic, or applications that are skinned. To determine whether or not an application is themed, you can use the IsAppThemed function inside uxtheme.dll. To determine if a theme is currently active, you need to use the IsThemeActive function inside uxtheme.dll. Follow these steps to add both these abilities to our application.

First, declare the API:

<DllImport("UxTheme.dll", EntryPoint:="IsAppThemed", _
   CharSet:=CharSet.Auto, SetLastError:=True)> _
'Determine if Application is Themed / not
Private Shared Function IsAppThemed() As Boolean
   End Function

Here, you are importing the uxTheme.dll file. Then, you are specifying what function you want to use via the EntryPoint parameter.

Next, in btnAppThemed_Click, add the following:

   Private Sub btnAppThemed_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles btnAppThemed.Click

      If IsAppThemed() Then    'Is the Application Themed?

         'Yes It Is
         MessageBox.Show("Desktop Wizard Is Themed!", _
            "Desktop Wizard", _
            MessageBoxButtons.OK, MessageBoxIcon.Information)

      Else    'No It's Not

         MessageBox.Show("Desktop Wizard Is Not Themed!", _
            "Desktop Wizard", _
            MessageBoxButtons.OK, MessageBoxIcon.Information)

      End If

   End Sub

[DWThemed.png]

Nothing too complicated here. The IsAppThemed function returned a Boolean value of either True or False depending on whether or not the application is Themed.

Determine if a Theme is Active

You will need the following API:

<DllImport("UxTheme.dll", EntryPoint:="IsThemeActive", _
   CharSet:=CharSet.Auto, SetLastError:=True)> _
'API to determine if a theme is active
Private Shared Function IsThemeActive() As Boolean
   End Function

Then, in btnThemeActive_Click:

   Private Sub btnThemeActive_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles btnThemeActive.Click

      If IsThemeActive() Then    'Is A Theme Currently Active?

         'Yes It Is
         MessageBox.Show("A Theme Is Active!", "Desktop Wizard", _
            MessageBoxButtons.OK, MessageBoxIcon.Information)

      Else    'No It's Not

         MessageBox.Show("A Theme Is Not Active!", "Desktop Wizard", _
            MessageBoxButtons.OK, MessageBoxIcon.Information)

      End If

   End Sub

[DWActive.png]

Disable or Enable System Themes

By disabling a system theme, your Windows XP will revert back to Classic Style, meaning it will have the same look and feel as Windows 98. Your Taskbar will not be coloured, and your Start button will not be green anymore. All your windows will look like windows in Windows 98. To disable a Theme, follow these steps:

Add the API:

<DllImport("UxTheme.dll", EntryPoint:="EnableTheming", _
   CharSet:=CharSet.Auto, SetLastError:=True)> _
'Enable / Disable Theme "look"
Private Shared Function EnableTheming(ByVal b As Boolean) As Long
   End Function

Call the EnableTheming API in btnDisTheme_Click:

   Private Sub btnDisTheme_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles btnDisTheme.Click

      EnableTheming(False)    'Disable Theming For The System

   End Sub

Screen Resolution Settings

In simple terms, screen resolution will make your work area bigger or smaller. With a low resolution of 640 x 480, all your icons will become extremely huge, and your work area will have become smaller. With a resolution of 1280 x 1024, your work area will have become much much bigger, but your icons would have become smaller as well. I always say different strokes for different folks. Some people prefer big icons, some don't; that is just personal preference.

Getting the Screen Resolution

Sometimes, you need to get the current screen resolution settings in your programs because you need to make sure your applications will fit on any screen resolution, and that each user get the same effect. You don't want our users to increase their resolution settings just so that they can see all the controls on your forms. Getting the current screen resolution settings is actually very easy (as mentioned earlier). You don't need any APIs for this; you can just use the System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width and System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height properties.

Add the following to btnGetRes_Click:

   Private Sub btnGetRes_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles btnGetRes.Click

      Dim DWCurrResX As Short    'Current Resolution Width
      Dim DWCurrResY As Short    'Current Resolution Height

      'Get Current Resolution Width
      DWCurrResX = System.Windows.Forms.Screen.PrimaryScreen. _
                   Bounds.Width
      'Get Current Resolution Height
      DWCurrResY = System.Windows.Forms.Screen.PrimaryScreen. _
                   Bounds.Height

      'Display Current Resolution Details
      MessageBox.Show("Horizontal Resolution = " & _
         DWCurrResX.ToString() _
         & Environment.NewLine & "Vertical Resolution = " _
         & DWCurrResY.ToString, "Desktop Wizard", _
            MessageBoxButtons.OK, _
         MessageBoxIcon.Information)

   End Sub

[DWGetRes.png]

As simple as that!

Setting/Changing the screen resolution

Changing the screen resolution is unfortunately not that easy (I wish it were). To change the resolution, you will need the EnumDisplaySettings API (which retrieves information about one of the graphics modes for a display device) and the ChangeDisplaySettings API (which changes the display settings to the specified graphics mode). That's not all; to make these APIs do their work, you will need to incorporate the use of the DEVMODE1 API structure, which contains information about the initialization and environment of a printer or a display device. To make things easier, I've created a Class utilising all these APIs and structures; then, you can easily make use of the class in your forms.

Creating the Class

To add a class to your project, select Project, Add Class, and give it a descriptive name such as clsDWRes. After adding the new class, add the following:

Imports System.Runtime.InteropServices    'API Stuff

Public Class clsDWRes
   '1)
   'Resolution APIs
   <DllImport("user32.dll")> _
Public Shared Function EnumDisplaySettings(ByVal deviceName As String, _
   ByVal modeNum As Integer, ByRef devMode As DEVMODE1) As Integer
   End Function    'retrieves information about one of the graphics
                   'modes for a display device

   <DllImport("user32.dll")> _
Public Shared Function ChangeDisplaySettings(ByRef devMode As DEVMODE1, _
   ByVal flags As Integer) As Integer
   'changes the display settings to the specified graphics mode
   End Function

   'Resolution Structure
   <StructLayout(LayoutKind.Sequential)> _
   'contains information about the initialization and environment
   'of a printer or a display device
   Public Structure DEVMODE1
      <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=32)> _
      'A zero-terminated character array that specifies the "friendly"
      'name of the printer or display
      Public dmDeviceName As String
      'Specifies the version number of the initialization data
      'specification on which the structure is based
      Public dmSpecVersion As Short
      'Specifies the driver version number assigned by the driver
      'developer
      Public dmDriverVersion As Short
      'Specifies the size, in bytes, of the DEVMODE structure, not
      'including any private driver-specific data that might follow
      'the structure's public members
      Public dmSize As Short
      'Contains the number of bytes of private driver-data that
      'follow this structure
      Public dmDriverExtra As Short
      'Specifies whether certain members of the DEVMODE structure
      'have been initialized
      Public dmFields As Integer
      'For printer devices only, selects the orientation of the paper.
      'This member can be either DMORIENT_PORTRAIT (1) or
      'DMORIENT_LANDSCAPE (2).
      Public dmOrientation As Short
      'For printer devices only, selects the size of the paper to
      'print on, for example DMPAPER_A4
      Public dmPaperSize As Short
     'For printer devices only, overrides the length of the paper
      'specified by the dmPaperSize member, either for custom paper
      'sizes or for devices such as dot-matrix printers that can
      'print on a page of arbitrary length
      Public dmPaperLength As Short
      'For printer devices only, overrides the width of the paper
      'specified by the dmPaperSize member
      Public dmPaperWidth As Short
      'Specifies the factor by which the printed output is to be
      'scaled. The apparent page size is scaled from the physical
      'page size by a factor of dmScale/100
      Public dmScale As Short
      'Selects the number of copies printed if the device supports
      'multiple-page copies
      Public dmCopies As Short
      'Specifies the paper source. To retrieve a list of the available
      'paper sources for a printer, use the DeviceCapabilities
      'function with the DC_BINS flag, for example: DMBIN_ENVELOPE
      Public dmDefaultSource As Short
      'Specifies the printer resolution. There are four predefined
      'device-independent values: DMRES_HIGH, DMRES_MEDIUM,
      'DMRES_LOW, and DMRES_DRAFT
      Public dmPrintQuality As Short
      'Switches between color and monochrome on color printers.
      'Following are the possible values: DMCOLOR_COLOR,
      'DMCOLOR_MONOCHROME
      Public dmColor As Short
      'Selects duplex or double-sided printing for printers capable
      'of duplex printing, for example: DMDUP_HORIZONTAL
      Public dmDuplex As Short
      'Specifies the y-resolution, in dots per inch, of the printer
      Public dmYResolution As Short
      'Specifies how TrueType fonts should be printed, for example:
      'DMTT_BITMAP
      Public dmTTOption As Short
      'Specifies whether collation should be used when printing
      'multiple copies.
      Public dmCollate As Short
      'A zero-terminated character array that specifies the name of
      'the form to use; for example, "Letter"
      <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=32)> _
      Public dmFormName As String
      'Specifies the number of pixels per logical inch
      Public dmLogPixels As Short
      'Specifies the color resolution, in bits per pixel, of the
      'display device (for example: 4 bits for 16 colors, 8 bits for
      '256 colors, or 16 bits for 65,536 colors)
      Public dmBitsPerPel As Short
      'Specifies the width, in pixels, of the visible device surface
      Public dmPelsWidth As Integer
      'Specifies the height, in pixels, of the visible device surface
      Public dmPelsHeight As Integer
      'Specifies the device's display mode, for example:
      'DM_INTERLACED
      Public dmDisplayFlags As Integer
      'Specifies the frequency, in hertz (cycles per second), of the
      'display device in a particular mode
      Public dmDisplayFrequency As Integer
      'Specifies how ICM is handled. For a non-ICM application, this
      'member determines if ICM is enabled or disabled
      Public dmICMMethod As Integer
      'Specifies which color matching method, or intent, should be
      'used by default
      Public dmICMIntent As Integer
      'Specifies the type of media being printed on, for example:
      'DMMEDIA_GLOSSY
      Public dmMediaType As Integer
      'Specifies how dithering is to be done, for example:
      'DMDITHER_LINEART
      Public dmDitherType As Integer
      Public dmReserved1 As Integer    'Not used; must be zero
      Public dmReserved2 As Integer    'Not used; must be zero
      'Only in XP, This member must be zero
      Public dmPanningWidth As Integer
      'Only in XP, This member must be zero
      Public dmPanningHeight As Integer
   End Structure
   '2)
   'Resolution Constants
   'Retrieve the current settings for the display device
   Public Const ENUM_CURRENT_SETTINGS As Integer = -1
   'Update registry
   Public Const CDS_UPDATEREGISTRY As Integer = 1
   'indicates whether the particular combination of resolution,
   'color depth, and refresh rate is valid
   Public Const CDS_TEST As Integer = 2
   'The settings change was successful
   Public Const DISP_CHANGE_SUCCESSFUL As Integer = 0
   'A Restart (Reboot is needed for settings to be applied)
   Public Const DISP_CHANGE_RESTART As Integer = 1
   'The display driver failed the specified graphics mode
   Public Const DISP_CHANGE_FAILED As Integer = -1

   Public Sub New(ByVal DWWidth As Integer, _
                  ByVal DWHeight As Integer)

      'Our Current Screen
      Dim DWScreen As Screen = Screen.PrimaryScreen
      '3)
      'Create new DevMode object
      Dim DWdev As DEVMODE1 = New DEVMODE1

      'Initialise Device name
      DWdev.dmDeviceName = New String(New Char(32) {})
      'Initialise form name
      DWdev.dmFormName = New String(New Char(32) {})
      DWdev.dmSize = CType(Marshal.SizeOf(DWdev), Short)    'Size
      '4)
      'If we have a display device, and got the settings
      If Not (0 = EnumDisplaySettings(Nothing, _
              ENUM_CURRENT_SETTINGS, DWdev)) Then

         DWdev.dmPelsWidth = DWWidth      'Get Current Width
         DWdev.dmPelsHeight = DWHeight    'Get Current Height
         '5)
         'Try to change the display settings
         Dim DWRet As Integer = ChangeDisplaySettings(DWdev, CDS_TEST)

         If DWRet = DISP_CHANGE_FAILED Then    'If change failed

            MessageBox.Show("Unable To Process Your Request.", _
               "Desktop Wizard", MessageBoxButtons.OK, _
               MessageBoxIcon.Information)

         Else    'Successful, update the Registry
            '6)
            DWRet = ChangeDisplaySettings(DWdev, CDS_UPDATEREGISTRY)

               'It was successful, so we don't need to do anything
               'further
               Select Case DWRet

                  Case DISP_CHANGE_SUCCESSFUL


                  'It will be successful after a restart, so let's
                  'try to reboot
                  Case DISP_CHANGE_RESTART

                     MessageBox.Show("A Reboot Is Needed For New _
                        Settings To Take Effect" , "Desktop Wizard", _
                        MessageBoxButtons.OK, _
                        MessageBoxIcon.Information)

                  'If still unsuccessful in changing display
                  'settings, inform the user
                  Case Else

                     MessageBox.Show("Unable To Change Resolution.", _
                        "Desktop Wizard", MessageBoxButtons.OK, _
                        MessageBoxIcon.Information)

            End Select

         End If

      End If

   End Sub

End Class

Customising Your Desktop with Visual Basic.NET 2005

What Happened Here?

If you haven't read the comments, let me try to explain step by step what was done:

  1. You created the APIs needed as well as the DEVMODE1 structure.
  2. You declared the necessary constants (which are used with the above APIs) ENUM_CURRENT_SETTINGS. Retrieves the current settings for the display device CDS_UPDATEREGISTRY. Updates the Registry with the new settings; CDS_TEST indicates whether the particular combination of resolution, color depth, and refresh rate is valid. Any of the DISP_CHANGE_SUCCESSFUL, DISP_CHANGE_RESTART, and DISP_CHANGE_FAILED constants may be the return value after the attempt to change the resolution. For example, if the change was successful. ChangeDisplaySettings returns DISP_CHANGE_SUCCESSFUL, if you need to reboot the machine for the changes to take effect, ChangeDisplaySettings returns DISP_CHANGE_RESTART.
  3. You created a DEVMODE1 object and initialised the Device name.
  4. You made sure you have a display device, and got the settings for the device successfully. You stored the dmPelsWidth and dmPelsHeight properties (from the DEVMODE1 Structure) in the DWWidth and DWHeight objects.
  5. You tried to change the Display settings, and got the result.
  6. You updated the Registry with the new settings and took the appropriate action based on the return result.

Now that you have a clearer picture of what your class does, you can use it in your form.

Declare the following variables in your form:

   Private DWChangeRes As clsDWRes 'Resolution Class Object

   Private DWNewResHeight As Integer 'Our New Resolution Height
   Private DWNewResWidth As Integer 'Our New Resolution Width

Getting the Desired Dimensions

To get your desired dimensions, you will need to have a look at what options were selected in the lstResolution Listbox. Remember, at design time, you added 10 different height and width combinations. You can feel free to add all the height and width combinations for your screen, because I've used my screen settings here. Also, a word of caution is needed: Some of these settings might not work on all monitors, so be sure to add only those options that appear in Desktop Properties, Settings.

Add the following code to lstResolution_SelectedIndexChanged:

Private Sub lstResolution_SelectedIndexChanged(ByVal sender _
   As System.Object, ByVal e As System.EventArgs) Handles _
   lstResolution.SelectedIndexChanged

   Dim ListSel As Integer    'Get Selected Item Index

   'If Something Selected In ListBox
   If lstResolution.SelectedIndex > -1 Then

      'Store Selected Item's Index
      ListSel = lstResolution.SelectedIndex

   End If

   'Determine Which Item Was Selected & Get Desired Resolution
   'Settings
   Select Case ListSel

      Case 0    '640 x 480

         DWNewResWidth  = 640
         DWNewResHeight = 480

      Case 1    '800 x 600

         DWNewResWidth  = 800
         DWNewResHeight = 600

      Case 2    '832 x 624

         DWNewResWidth  = 832
         DWNewResHeight = 624

      Case 3    '1024 x 768

         DWNewResWidth  = 1024
         DWNewResHeight =  768

      Case 4    '1152 x 864

         DWNewResWidth  = 1152
         DWNewResHeight =  864

      Case 5    '1280 x 600

         DWNewResWidth  = 1280
         DWNewResHeight =  600

      Case 6    '1280 x 720

         DWNewResWidth  = 1280
         DWNewResHeight =  720

      Case 7    ' 1280 x 768

         DWNewResWidth  = 1280
         DWNewResHeight =  768

      Case 8    '1280 x 960

         DWNewResWidth  = 1280
         DWNewResHeight =  960

      Case 9    '1280 x 1024

         DWNewResWidth  = 1280
         DWNewResHeight = 1024

   End Select

End Sub

And add the following to btnChangeRes_Click:

   Private Sub btnChangeRes_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles btnChangeRes.Click

      'Change Screen Resolution To New Height & New Width,
      'Via The clsDWRes Class
      DWChangeRes = New clsDWRes(DWNewResWidth, DWNewResHeight)

   End Sub

Simple enough and it works, yes, but, you may find that some of the resolution settings require a restart to take effect. To do things properly, I decided to include the functionality of forcing a reboot as well, if ChangeDisplaySettings returns DISP_CHANGE_RESTART.

Customising Your Desktop with Visual Basic.NET 2005

Forcing a Reboot

To programmatically reboot a Windows NT or Windows 2000 system, the process requires the SE_SHUTDOWN_NAME privilege. By default, Visual Basic applications do not have this privilege and therefore will not reboot the machine. To get ExitWindowsEx API to reboot the system under Windows NT or Windows 2000, the SE_SHUTDOWN_NAME privilege must be set.

Go back to your class (clsDWRes) and add the following APIs needed for the reboot:

   'Reboot APIs
   <DllImport("kernel32.dll", ExactSpelling:=True)> _
Private Shared Function GetCurrentProcess() As IntPtr
   End Function    'GetCurrentProcess

   <DllImport("advapi32.dll", SetLastError:=True)> _
Private Shared Function OpenProcessToken(ByVal h As IntPtr, _
   ByVal acc As Integer, ByRef phtok As IntPtr) As Boolean
   'The OpenProcessToken function opens the access token associated
   'with a process
   End Function

   <DllImport("advapi32.dll", SetLastError:=True)> _
Private Shared Function LookupPrivilegeValue(ByVal host As String, _
   ByVal name As String, ByRef pluid As Long) As Boolean
   'The LookupPrivilegeValue function retrieves the locally unique
   'identifier (LUID) used on a specified system to locally represent
   'the specified privilege name
   End Function

   <DllImport("advapi32.dll", ExactSpelling:=True, _
      SetLastError:=True)> _
Private Shared Function AdjustTokenPrivileges(ByVal htok As IntPtr, _
   ByVal disall As Boolean, ByRef newst As Luid, _
   ByVal len As Integer, _
   ByVal prev As IntPtr, ByVal relen As IntPtr) As Boolean
   'The AdjustTokenPrivileges function enables or disables
   'privileges in the specified access token
   End Function

   <DllImport("user32.dll", ExactSpelling:=True, SetLastError:=True)> _
Private Shared Function ExitWindowsEx(ByVal flg As Integer, _
   ByVal rea As Integer) As Boolean
   'Shuts down all processes running in the logon session of the
   'process that called the ExitWindowsEx function
   End Function

The above APIs are needed to determine the current process and obtain a Process Token. Once you have that, you need to adjust the Process's Privileges for the ExitWindowsEx API to succeed.

Add the following Structure and Constants:

   'Reboot Structure
   <StructLayout(LayoutKind.Sequential, Pack:=1)> _
   'The LUID structure is an opaque structure that specifies an
   'identifier that is guaranteed to be unique on the local machine
     Friend Structure Luid
        Public Count As Integer    'Count of privilege
        Public Luid  As Long       'locally unique identifier (LUID)
        Public Attr  As Integer    'Attributes
   End Structure

   'Reboot Constants
   'enabling the privilege
   Private Const SE_PRIVILEGE_ENABLED As Integer = &H2
   'Query the token's Privileges
   Private Const TOKEN_QUERY As Integer = &H8
   'Adjust privileges
   Private Const TOKEN_ADJUST_PRIVILEGES As Integer = &H20

   'Enable the SE_SHUTDOWN_NAME privilege in the process we're
   'trying to shutdown from
   Private Const SE_SHUTDOWN_NAME As String = "SeShutdownPrivilege"

   ' Exit Windows Constants
   Private Const EWX_LOGOFF As Integer = &H0      'Log Off
   Private Const EWX_SHUTDOWN As Integer = &H1    'Shut Down
   Private Const EWX_REBOOT As Integer = &H2      'Reboot
   'flag will ignore any process running. If this is removed,
   'shutting down, logging off or restarting actions will wait for
   'other process to complete
   Private Const EWX_FORCE As Integer = &H4
   'Shuts down the system and turns off the power. The system must
   'support the power-off feature
   Private Const EWX_POWEROFF As Integer = &H8
   'Forces processes to terminate if they do not respond to the
   'WM_QUERYENDSESSION or WM_ENDSESSION message. This flag is
   'ignored if EWX_FORCE is use
   Private Const EWX_FORCEIFHUNG As Integer = &H10d

These constants allow the APIs to do their job. They also assist in making sure process privileges are enabled, and can be adjusted. Once you have adjusted the process, we can youcall the ExitWindowsEx with any of its flags (for example, EWX_REBOOT and EWX_FORCE) to reboot the computer.

Add the following sub procedures:

' Exit Windows Sub
Private Sub DWRestartWindows(ByVal DWFlag As Integer)

   Dim DWLUID As Luid    'Get LUID
   'Get the current process handle
   Dim DWProc As IntPtr  = GetCurrentProcess()
   Dim DWToken As IntPtr = IntPtr.Zero    'Our Token

   'Open the token for adjusting and querying
   OpenProcessToken(DWProc, TOKEN_ADJUST_PRIVILEGES Or TOKEN_QUERY, _
      DWToken)

   DWLUID.Count = 1
   DWLUID.Luid  = 0
   DWLUID.Attr  = SE_PRIVILEGE_ENABLED    'Enable the Privilege

   'Find the LUID of the Shutdown privilege token
   LookupPrivilegeValue(Nothing, SE_SHUTDOWN_NAME, DWLUID.Luid)

   'Adjust the shutdown privileges & allow this process to shut down
   'the system
   AdjustTokenPrivileges(DWToken, False, DWLUID, 0, IntPtr.Zero, _
      IntPtr.Zero)

   'Call ExitWindowsEx with specified flag
   ExitWindowsEx(DWFlag, 0)

   End Sub

   ' Restart
   Private Sub DWRestart()

      DWRestartWindows(EWX_REBOOT Or EWX_FORCE)    'restart Windows

   End Sub

And modify your existing class Constructor (New) to look like the following:

Public Sub New(ByVal DWWidth As Integer, ByVal DWHeight As Integer)

   'Our Current Screen
   Dim DWScreen As Screen = Screen.PrimaryScreen

   'Create new DevMode object
   Dim DWdev As DEVMODE1 = New DEVMODE1

   'Initialise Device name
   DWdev.dmDeviceName = New String(New Char(32) {})
   'Initialise form name
   DWdev.dmFormName = New String(New Char(32) {})
   DWdev.dmSize = CType(Marshal.SizeOf(DWdev), Short)    'Size

   'If we have a display device, and got the settings
   If Not (0 = EnumDisplaySettings(Nothing, ENUM_CURRENT_SETTINGS, _
      DWdev)) Then

      DWdev.dmPelsWidth  = DWWidth     'Get Current Width
      DWdev.dmPelsHeight = DWHeight    'Get Current Height

      'Try to change the display settings
      Dim DWRet As Integer = ChangeDisplaySettings(DWdev, CDS_TEST)

      If DWRet = DISP_CHANGE_FAILED Then    'If change failed

         MessageBox.Show("Unable To Process Your Request.", _
            "Desktop Wizard", MessageBoxButtons.OK, _
            MessageBoxIcon.Information)

      Else    'Successful, update the Registry

         DWRet = ChangeDisplaySettings(DWdev, CDS_UPDATEREGISTRY)

         'It was successful, so we don't need to do anything further
         Select Case DWRet

            Case DISP_CHANGE_SUCCESSFUL


            'It will be successful after a restart, so let's try
            'to reboot
            Case DISP_CHANGE_RESTART

               Dim DWReboot As Short    'get messagebox result

                  DWReboot = MessageBox.Show("A Reboot Is Needed _
                     For New Settings To Take Effect" & _
                     Environment.NewLine & "Restart Now?", _
                     "Desktop Wizard", MessageBoxButtons.YesNoCancel, _
                     MessageBoxIcon.Question)

               Select Case DWReboot

                  Case DialogResult.Yes       'Restart if Yes clicked

                     DWRestart()

                  Case DialogResult.No        'Don't restart now

                     Exit Sub

                  Case DialogResult.Cancel    'Don't restart now

                     Exit Sub

               End Select

            'If still unsuccessful in changing display settings,
            'inform the user
            Case Else

               MessageBox.Show("Unable To Change Resolution.", _
                  "Desktop Wizard", MessageBoxButtons.OK, _
                  MessageBoxIcon.Information)

         End Select

      End If

   End If

End Sub

[DWReboot.png]

You still determine what the result was returned with the ChangeDisplaySettings API, but if a DISP_CHANGE_RESTART result was received, you call the DWReboot sub if the user chose yes in the "Restart Now ?" MessageBox.

Customising Your Desktop with Visual Basic.NET 2005

Visual Style Settings

Before I continue (to avoid confusion), let me first explain the difference between Themes and Visual Styles.

Themes

Themes consist of a collection of settings that include wallpaper, cursors, fonts, sounds, and icons.

Visual Styles

Visual styles were introduced with Windows XP. Visual styles are specifications for the appearance of controls. For example, a visual style can define the overall appearance of controls, and enable software developers to configure the visual interface to coordinate with an application's appearance. Additionally, visual styles provide a mechanism for all Windows-based applications to apply visual styles. The following are some characteristics of visual styles.

Changing the Visual Styles

To change or make use of your system's Visual Styles, you have to use the now-familiar DLL called UxTheme.dll. A Visual Style consists of three major components:

  • Style Name
  • Style Colours
  • Style Fonts

Getting the Style Name

As you know, all Visual Style names are stored in HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\ThemeManager\Dllname, or, in "C:\WINDOWS\Resources\Themes\Visual Style Name". By going to the Desktop Properties and selecting the Themes tab, you will find all the Themes and Visual Styles in the Themes drop down box. The approach I took to load the Style names into your cboStyles combobox was to scan through all the folders present in the "C:\WINDOWS\Resources\Themes" folder, then adding those folder names I've found to the cboStyles combobox.

Add the following Namespace, so that you can make use of some File functions needed:

Imports System.IO    'Import System.IO For File Operations

Add the following Declarations:

'Location Of Our Themes & Visual Styles
Private StyleDir As String = "C:\WINDOWS\Resources\Themes"
Private StyleNames() As String    'Holds All Visual Styles' Locations
'The Font Style For The Visual Style (Selected From Fonts ComboBox),
'For Example: LargeFonts For Luna
Private StyleFont As String
'Colour Selected From Colours ComboBox, For Example: Metallic For Luna
Private StyleColor As String

Get the functionality present in the SetSystemVisualStyle API present in the UxTheme.dll:

<DllImport("UxTheme.DLL", BestFitMapping:=False, _
   CallingConvention:=CallingConvention.Winapi, _
   CharSet:=CharSet.Unicode, EntryPoint:="#65")> _
Private Shared Function SetSystemVisualStyle _
   (ByVal pszFilename As String, _
   ByVal pszColor As String, ByVal pszSize As String, _
   ByVal dwReserved As Integer) As Integer
   End Function    'Set the Visual style, for example Luna.msstyles

Loading the Styles

Private Sub LoadStyles()

   'Shows Final "Style Name" (After Some String Manipulation),
   'In The Styles ComboBox
   Dim StyleDisplayName As String
   Dim FilePath As String      'File Path
   Dim Xtension As String      'File Extensions
   Dim FolderPath As String    'Folder Path
   Dim NumFiles As Integer     'How Many Files?
   'Holds Style Name, Without The Full Path
   Dim TruncName As String

   'Search Sub Folders In StyleDir ("C:\WINDOWS\Resources\Themes")
   For Each FolderPath In Directory.GetDirectories(StyleDir, "*")

      'Search Files Within Sub Folders
      For Each FilePath In Directory.GetFiles(FolderPath)

         'Identify File Extension
         Xtension = IO.Path.GetExtension(FilePath)

         'Check For ".msstyles" (Visual Style) Extension
         If Xtension = ".msstyles" Then

            'Remove Path From Name
            TruncName = FilePath.Substring(FilePath.LastIndexOf("\") + 1)
            'Remove Extension (.msstyles) From File Name
            StyleDisplayName = TruncName.Substring(0, _
               TruncName.LastIndexOf("."))

            'Add Finale "Style Name" ( Without Path & Extension )
            cboStyles.Items.Add(StyleDisplayName)

            'Redimension Array To Store All The Visual Style Paths
            ReDim Preserve StyleNames(NumFiles)

            'Store The Paths As We Progress Through The Loop
            StyleNames(NumFiles) = FilePath

            NumFiles = NumFiles + 1    'Increment File Count
         End If

      Next

   Next

End Sub

[DWVisStyle.png]

Style Colours

Loading the Colours

The Style Colours can be found in: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\ThemeManager\ColorName, or, in the Visual Style's Shell folder (for example, "C:\WINDOWS\Resources\Themes\NAME_OF_STYLE\Shell") . Each Style has a Shell folder in it; in the Shell folder there may be more folders (for example, Homestead, Metallic, and so forth in Luna); in those folders there is a DLL that is used to change the Style's color.

Private Sub LoadStyleColors()

   'Where Our Visual Style Colours Are Stored, For Example:
   '"C:\WINDOWS\Resources\Themes\NAME_OF_STYLE\Shell"
   Dim StyleColorDir As String = ""
   Dim CurrentStyle As String    'To Hold Selected Style Name
   Dim FolderPath   As String    'Folder Path
   Dim TruncName    As String    'Holds Style Colour Folder's Name

   'Clear The ComboBox Every time, So That It Doesn't Keep
   'Appending New Style Colours
   cboStyleColor.Items.Clear()

   'If A Style Is Selected In The Styles ComboBox
   If cboStyles.SelectedIndex > -1 Then

      'Store Selected Style Name
      CurrentStyle = cboStyles.SelectedItem.ToString()

      'Get All Colours Present For Each Style (All Colour Details
      'Are Stored In Each Visual Styles Shell Folder)
      StyleColorDir = StyleDir & "\" & CurrentStyle & "\Shell"

   End If

   'Search Sub Folders In StyleColorDir
   For Each FolderPath In Directory.GetDirectories(StyleColorDir, "*")

      'Remove Full Path From, Hold Only Each Colour's Name
      TruncName = FolderPath.Substring(FolderPath.LastIndexOf("\") + 1)

      'Add All Available Style Colors To The Color ComboBox,
      'For Example: HomeStead, Metallic, NormalColor For Luna
      cboStyleColor.Items.Add(TruncName)

   Next

End Sub

[DWStyleColor.png]

Call the LoadStyles sub in Form_Load:

Private Sub frmScreenStuff_Load(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles MyBase.Load

   LoadStyles()   'Populate Visual Styles List

End Sub

Style Fonts

Changing the Style Font

The currently selected Font Size for the current style is located at HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\ThemeManager\SizeName. The available Font Sizes for each style can be found in In Desktop Properties, Appearance, Font Size.

Edit the cboStyleFonts_SelectedIndexChanged event to look like the following:

Private Sub cboStyleFonts_SelectedIndexChanged(ByVal sender As Object, _
   ByVal e As System.EventArgs) _
   Handles cboStyleFonts.SelectedIndexChanged

   'This ComboBox Is Populated At Design Time With The Font Names
   'Found In Desktop Properties, Appearance, Font Size
   'Currently Selected Fontsize for current theme, can be found in:
   'HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\
   'ThemeManager\SizeName

   Dim i As Short    'Selected Item Index

   If cboStyleFonts.SelectedIndex > -1 _
      Then i = cboStyleFonts.SelectedIndex

   Select Case i

      Case 0                              'First Item Is Selected

         StyleFont = "NormalSize"         'Normal Size

      Case 1

         StyleFont = "LargeFonts"         'Large Fonts

      Case 2

         StyleFont = "ExtraLargeFonts"    'Extra Large Fonts

   End Select

End Sub

[DWStyleFont.png]

Setting the Colour

Private Sub cboStyleColor_SelectedIndexChanged(ByVal sender As Object, _
   ByVal e As System.EventArgs) _
   Handles cboStyleColor.SelectedIndexChanged

   'The Visual Style Color Names Can Be Found In:
   'HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\
    'ThemeManager\ColorName
   'Or, In The Visual Style's Shell Folder. Each Style Has a Shell
   'Folder In It, In The Shell Folder
   'There May Be More Folders (For Example Homestead, Metallic etc.),
   'In Those Folders There Is a DLL
   'That Is Used To Change The Styles Color

   'For example, Homestead = Olive Green, Metallic = Silver etc.

   'If not all settings are set for the theme (Themename, color, font)
   'it will show Theme name ( Modified ) in themes of display props
   If cboStyleColor.SelectedIndex > -1 Then

        StyleColor = cboStyleColor.SelectedItem.ToString()

   End If

End Sub

Setting the Style

Private Sub cboStyles_SelectedIndexChanged(ByVal sender As Object, _
   ByVal e As System.EventArgs) _
   Handles cboStyles.SelectedIndexChanged

   'The Name Of The Current Visual Style Can Be Found In:
   'HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\
   'ThemeManager\DllName
   LoadStyleColors()

End Sub

Private Sub btnSetStyle_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles btnSetStyle.Click

   Private Sub btnSetStyle_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles btnSetStyle.Click

      Try
         SetSystemVisualStyle(StyleNames(cboStyles.SelectedIndex), _
            StyleColor, StyleFont, 0)    'Set New Visual Style
         ' relates to :
         ' SetSystemVisualStyle("C:\WINDOWS\resources\Themes\Luna\
         '    Luna.msstyles", "Metallic", "NormalSize", 0)

      Catch ex As Exception
         MessageBox.Show("Please Select All Appropriate Visual _
            Style Settings Before Applying!", "Desktop Wizard", _
            MessageBoxButtons.OK, MessageBoxIcon.Warning)
      End Try
   End Sub

What I did here was to load all the available Style Names from the system into the Style ComboBox. Based on a selection of a certain style (in the Style ComboBox), you load its colours. It is senseless loading a pre-defined set of colours here because not all themes have the same colours. Some themes have only one colour (NormalColor); others, such as Luna has more than one (NormalColor, HomeStead and Metallic). The Style Fonts (NormalSize, LargeFont, ExtraLargeFont) should be present in all Styles (at least, that's the impression I got, because you can set it for almost all Visual Styles). After a selection has been made on all three of these settings, and the btnSetStyle has been clicked, the new Style will be applied via the call to the SetSystemVisualStyle API. If you were to only choose a Style name, and a Font Size for example (in other words, only one or two of the three settings have been set), the style will indeed be applied, but it will show Theme name (Modified); for example, Windows XP (Modified) in the Themes section of Desktop Properties, Themes, instead of just the Theme Name (for example, Windows XP).

ScreenSaver Settings

A screensaver is a computer program originally designed to prevent a "Phosphor burn-in" on CRT and plasma computer monitors by blanking the screen or filling it with moving images or patterns when the computer was not in use. Today, screensavers are primarily used for entertainment or security purposes, according to wikipedia. A screensaver makes use of three parameters to do its work:

/s This parameter is used to invoke the screensaver
/p This is used when we are previewing the screensaver
/c This is used when we are configuring the screensaver's settings

Starting the default (current system ScreenSaver)

As I stated earlier in this article, you will need to use the SendMessage API, and send a WM_SYSCOMMAND message to start SC_SCREENSAVE (which is the current screensaver).

Add the SendMessage API and constants:

<DllImport("user32.dll")> _
Private Shared Function SendMessage(ByVal hwnd As Int32, _
   ByVal wMsg As Int32, ByVal wParam As Int32, _
   ByVal lParam As Int32) As Int32
   End Function    'API used to start the default screensaver

   'To send a "Windows" message
   Private Const WM_SYSCOMMAND As Integer = &H112
   'Constant for the Screensaver
   Private Const SC_SCREENSAVE As Integer = &HF140

Add the following to btnDefScreen_Click:

Private Sub btnDefScreen_Click(ByVal sender As System.Object, _
  ByVal e As System.EventArgs) Handles btnDefScreen.Click

   Dim ScreenResult As Int32    'Returned Result For SendMessage API

   'Start Current System Screen Saver
   ScreenResult = SendMessage(Me.Handle.ToInt32, WM_SYSCOMMAND, _
      SC_SCREENSAVE, 0)

End Sub

Customising Your Desktop with Visual Basic.NET 2005

Starting Different Screensavers

To start a screensaver that is not currently set as the computer's screensaver, all you need to do is to make use of the Start method of a Process object, specify the appropriate file to run, and away you go. But, at this stage in your program you don't know the names of all the screensavers. Also, another issue that needs to be considered is the fact that not everyone has the same screensavers, so how would you go about starting any screensaver on any system?

The answer is simple. You just need to load all the screensavers into your program, and then you can start them. Screensavers are usually stored in "C:\WINDOWS\system32\", so all you need to do is to search through the System32 folder for .scr (Screensaver) files. As you find them, you could load them into a listbox or ListView, for example. After you do that, you can easily start them.

Loading all the system screen savers

'Holds All ScreenSavers' Locations
Private ScreenNames() As String

Private Sub LoadScreenSavers()

   'Location Of ScreenSavers
   Dim ScreenDir As String = "C:\WINDOWS\system32\"
   Dim FilePath As String     'File Path
   Dim Xtension As String     'File Extensions
   Dim NumFiles As Integer    'How Many Files?
   'Holds Final ScreenSaver Name, Without The Full Path
   Dim TruncName As String

   'Search Files Within ScreenDir ("C:\WINDOWS\system32\")
   For Each FilePath In Directory.GetFiles(ScreenDir)

      'Identify File Extension
      Xtension = IO.Path.GetExtension(FilePath)

      'Check For ".scr" (ScreenSaver) Extension
      If Xtension = ".scr" Then

         'Remove Path From Name
         TruncName = FilePath.Substring(FilePath.LastIndexOf("\") + 1)

         'Add Truncated Name To ListView
         lvScreen.Items.Add(TruncName)
         'Add ScreenSaver Description As Well
         lvScreen.Items(NumFiles).SubItems.Add(FileVersionInfo. _
            GetVersionInfo(FilePath).FileDescription)

        'Redimension Array To Store All The ScreenSaver Paths
         ReDim Preserve ScreenNames(NumFiles)

         'Store The Paths As We Progress Through The Loop
         ScreenNames(NumFiles) = FilePath

         NumFiles = NumFiles + 1    'Increment File Count

      End If

   Next

End Sub

[DWRun.jpg]

With the above sub procedure, I scanned the entire System32 folder for all files with an .scr extension; as I found them, I stored them into an array. This array (ScreenNames) holds the full path to each screensaver, which will later be used with the Process object. After storing each screensaver path into ScreenNames, I truncated the path, so that the ListView only shows the File name, instead of the full path. With the LoadScreenSavers sub, you also loaded each screensaver's description through FileVersionInfo.GetVersionInfo(FilePath).FileDescription, so that you have an idea of what the screensaver does.

The LoadScreenSavers sub needs to be called at Form_Load, so that it loads all the screensavers as the form loads:

Private Sub frmScreenStuff_Load(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles MyBase.Load

    LoadScreenSavers()    'Populate ScreenSavers List
    LoadStyles()          'Populate Visual Styles List

End Sub

Starting the Screensaver

Private Sub btnDiffScreen_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles btnDiffScreen.Click

   Dim LVSel As Integer    'Used To Store Selected Item

   'If Something Selected In ListView
   If lvScreen.SelectedItems.Count > 0 Then

      'Get Selected Item's Index
      LVSel = lvScreen.SelectedIndices(0)

   End If

   Process.Start(ScreenNames(LVSel))    'Start Selected ScreenSaver

End Sub

All you did here was to make sure an item was selected in the ListView, and then start the selected screensaver.

Configuring the Screensaver

As stated earlier, by configuring a screensaver, you are actually starting the screensaver, but with the configure parameter ("/c"). The catch here is that if you were to start the screensaver with the following command:

Process.Start(ScreenNames(LVSel), "/c")

It will not work. Yes, logically it should, but it doesn't. The reason for this is because of the Process' StartInfo.UseShellExecute property being True as a default setting, meaning that if you don't set the UseShellExecute property, it defaults to true, and starts the screen saver automatically. The UseShellExecute property is commonly used to open files in their default (associated) programs. For example, if I wanted to open a XLS (Microsoft Excel) file in Microsoft Excel, I would use UseShellExecute to get back to the Screensaver configuration. To start the Configuration dialog box for a certain screensaver, you need to set the UseShellExecute property to False before starting the particular process.

Add the following to your btnConfScreen_Click event:

Private Sub btnConfScreen_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles btnConfScreen.Click

   Dim LVSel As Integer    'Used To Store Selected Item


   'If Something Selected In ListView
   If lvScreen.SelectedItems.Count > 0 Then

      'Get Selected Item's Index
      LVSel = lvScreen.SelectedIndices(0)

   End If

   Dim ScreenConfProc As New Process    'Create New Process Object

   'The File To Start
   ScreenConfProc.StartInfo.FileName = ScreenNames(LVSel)
   'Screensaver Parameter To Configure The ScreenSaver
   ScreenConfProc.StartInfo.Arguments = "/c"
   'Otherwise ScreenSaver Will Start, Instead Of Showing The
   'Configuration Settings
   ScreenConfProc.StartInfo.UseShellExecute = False
   ScreenConfProc.Start()    'Go for It!

End Sub

[DWConf.png]

Here, you created a New Process named ScreenConfProc. You specified which file to run, the configuration parameter, and you set UseShellExecute to False. Now, you are able to run the Configuration dialog from within your program.

Previewing the Screensaver

The Previewing of the screensaver is basically done exactly the same as configuring the Screensaver. You also need to set UseShellExecute to false, and you also still need to specify the appropriate parameter, which is "/p" in this case. There is one thing to keep in mind here: where the preview of the selected screensaver must be shown. You want to be able to preview the screensaver in your picturebox named picPreviewScreen. Luckily, it is not that complicated; you just need to include the Handle to the picPreviewScreen Picturebox in the creation of the Process object, and it will look like the following statement:

Dim ScreenPrevProcInfo As New ProcessStartInfo(ScreenNames(LVSel), _
   "/p " & picPreviewScreen.Handle.ToString())

Now, edit the btnPreviewScreen_Click event to include this functionality:

Private Sub btnPreviewScreen_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles btnPreviewScreen.Click

    Dim LVSel As Integer    'Used To Store Selected Item

   'If Something Selected In ListView
    If lvScreen.SelectedItems.Count > 0 Then

        'Get Selected Item's Index
        LVSel = lvScreen.SelectedIndices(0)

   End If

   Dim ScreenPrevProc As Process    'Create New Process Object

   'Screensaver Parameter To Preview The ScreenSaver, And Where To
   'Show The Preview, In This Case It Is In picPreviewScreen
   Dim ScreenPrevProcInfo As New ProcessStartInfo(ScreenNames(LVSel), _
      "/p " & picPreviewScreen.Handle.ToString())

   'Otherwise ScreenSaver Will Start, Instead Of Showing The
   'Preview Settings
   ScreenPrevProcInfo.UseShellExecute = False
   ScreenPrevProc = Process.Start(ScreenPrevProcInfo)    'Go for It!

End Sub

[DWPrev.jpg]

Customising Your Desktop with Visual Basic.NET 2005

Stopping all Screensaver Processes

You will now find that you are able to preview the selected screensaver in the PictureBox. There is a slight problem, however; if I select a screensaver and click the Preview Button, everything is perfect. The moment I select a different screensaver from the ListView, and select Preview again, the Picturebox doesn't clear the previous screensaver's "picture"—this is not what you want! To only show one Preview at a time, you can Clear the picturebox, but I decided against that because the selected screensaver's Process may still be running (without you even knowing about it). That is why I decided to include a button that kills all the screensaver processes still running.

[DWPrevWrong.jpg]

Add an event handler for btnStopScreen, and edit the code as follows:

Private Sub btnStopScreen_Click(ByVal sender As System.Object, _
   ByVal e As System.EventArgs) Handles btnStopScreen.Click

   'Get All Currently Running Processes
   Dim AllProcesses() As Process = Process.GetProcesses()

   'Determine Which Are ScreenSaver Processes
   For Each ScreenProc As Process In AllProcesses

      'If Current Process Name Ends With The Characters scr,
      'IOW. ScreenSaver
      If ScreenProc.ProcessName.EndsWith("scr") Then

         ScreenProc.Kill()    'Kill It!

      End If

   Next ScreenProc    'Loop

End Sub

This ensures that there aren't any screensaver processes still running in the background.

Special Thank You's

Thank you very much, JonnyPoet, for identifying a serious bug before it was too late!

Conclusion

Well, that concludes Customising Your Desktop Settings with Visual Basic.NET 2005. I sincerely hope you have enjoyed reading my article, and learned something useful. All source codes are available in the attachment, so feel free to download it, and play around with it. Till next time, cheers!



About the Author

Hannes du Preez

Hannes du Preez is a Microsoft MVP for Visual Basic. He is a trainer at a South African-based company. He is the co-founder of hmsmp.co.za, a community for South African developers.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: July 30, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT You may already know about some of the benefits of Bluemix, IBM's open platform for developing and deploying mobile and web applications. Check out this upcoming eSeminar that focuses on building an Android application using the MobileData service, with a walk-through of the real process and workflow used to build and link the MobileData service within your application. Join IBM's subject matter experts as they show you the way to build a base …

  • Mobile is introducing sweeping changes throughout your workplace. As a senior stakeholder driving mobile initiatives in your organization, you may be lost in a sea of technologies and claims from vendors promising rapid delivery of applications to your employees, customers, and partners. To help explain some of the topics you will need to be aware of, and to separate the must-haves from the nice-to-haves, this reference guide can help you with applying a mobile strategy in the context of application …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds