How to change Windows Theme programmatically in XP

Hi all,

You may know already that there is no i.e. COM object or .NET class we can use to change the Windows Theme programmatically on Windows XP. You may also know the following VBScript which can be used to do this change without user intervention:

 Set OSHApp = CreateObject("Shell.Application")
Set oShell = CreateObject("Wscript.Shell")

' Set the Path to the Theme file
'
Theme = "\\SERVER\SHAREDFOLDER\FILE.THEME"
Theme = """" + Theme + """"
' Open the Theme in Display Properties
'
oSHApp.ControlPanelItem cstr("desk.cpl desk,@Themes /Action:OpenTheme /file: " & Theme)

' Loop and wait until Display Properties is loaded.
'
While OShell.AppActivate ("Display Properties") = FALSE
Wscript.Sleep 1000
Wend

' Loop and send the Enter key to Display Properties until Theme is applied 
'
While OShell.AppActivate ("Display Properties") = TRUE
    oShell.AppActivate "Display Properties"
    Wscript.Sleep 200
    oShell.sendkeys "{ENTER}"
Wend

If you try this script, it will sometimes fail. In some situations, the Display Properties dialog won't be closed automatically. You may "play" with the Sleep times and solve the issue in several machines, but it won't necessarily work in all of them.

The issue here is that "oShell.sendkeys "{ENTER}"" is not getting to the right window and there is any guarantee that it will do it.

Fortunately there is an alternate solution to this script and its SendKeys: send a BM_CLICK window message to the button we need to press in the required Theme window. This way we can warrantee that we close the right window by pressing the right button.

You will find a VB.NET sample below which gets all windows in the desktop (visible and invisible), the way Spy++ does it, in a tree. Then the sample also shows how if we select one of those windows in the tree manually (i.e. the print dialog of notepad), we can interact with it programmatically (i.e. changing the editbox with the number of copies to print and pressing the Print button).

It should be easy to adapt this sample to our needs so it first launches the Theme console to change the theme the way the script above does it (i.e. Running Windows XP Control Panel Applets from Visual Basic.NET 2005), and then it looks for the window we need to close, locates its OK button and sends a BM_CLICK message to it in order to close the window.

Here is the sample:

 Imports System.Runtime.InteropServices

Public Class Form1
    Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

    Public Sub New()
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call

        ' Get all windows and show them in the tree
        '
        GetWindows()
        TreeView1.Nodes.Item(0).Expand()
    End Sub

    'Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    Friend WithEvents TreeView1 As System.Windows.Forms.TreeView
    Friend WithEvents Button1 As System.Windows.Forms.Button
    Friend WithEvents Button2 As System.Windows.Forms.Button
     Private Sub InitializeComponent()
        Me.TreeView1 = New System.Windows.Forms.TreeView
        Me.Button1 = New System.Windows.Forms.Button
        Me.Button2 = New System.Windows.Forms.Button
        Me.SuspendLayout()
        '
        'TreeView1
        '
        Me.TreeView1.HideSelection = False
        Me.TreeView1.ImageIndex = -1
        Me.TreeView1.Location = New System.Drawing.Point(8, 48)
        Me.TreeView1.Name = "TreeView1"
        Me.TreeView1.SelectedImageIndex = -1
        Me.TreeView1.Size = New System.Drawing.Size(640, 504)
        Me.TreeView1.TabIndex = 0
        '
        'Button1
        '
        Me.Button1.Location = New System.Drawing.Point(8, 8)
        Me.Button1.Name = "Button1"
        Me.Button1.Size = New System.Drawing.Size(160, 32)
        Me.Button1.TabIndex = 1
        Me.Button1.Text = "Refresh"
        '
        'Button2
        '
        Me.Button2.Location = New System.Drawing.Point(176, 8)
        Me.Button2.Name = "Button2"
        Me.Button2.Size = New System.Drawing.Size(472, 32)
        Me.Button2.TabIndex = 2
        Me.Button2.Text = "Set ""Number of copies"" and click ""Print"" in selected notepad's Print dialog"
        '
        'Form1
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(656, 558)
        Me.Controls.Add(Me.Button2)
        Me.Controls.Add(Me.Button1)
        Me.Controls.Add(Me.TreeView1)
        Me.Name = "Form1"
        Me.Text = "Form1"
        Me.ResumeLayout(False)

    End Sub

#End Region

#Region "AlejaCMa"

    ' Constants
    '
    Private Const GW_CHILD = 5
    Private Const GW_HWNDNEXT = 2

    Private Const WM_SETTEXT = &HC
    Private Const BM_CLICK = &HF5

    ' API declarations
    '
    Private Declare Function GetDesktopWindow Lib "user32" () As IntPtr

    Private Declare Function GetWindow Lib "user32" ( _
        ByVal hWnd As IntPtr, _
        ByVal uCmd As Int32) _
    As IntPtr

    Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" ( _
        ByVal hWnd As IntPtr, _
        ByVal lpString As String, _
        ByVal nMaxCount As Int32) _
    As Int32

    Public Declare Function GetWindowThreadProcessId Lib "user32" Alias "GetWindowThreadProcessId" ( _
        ByVal hwnd As IntPtr, _
        ByRef lpdwProcessId As Int32) _
    As Int32

    Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" ( _
        ByVal hWnd As IntPtr, _
        ByVal lpClassName As String, _
        ByVal nMaxCount As Int32) _
    As Int32

    Private Declare Function IsWindowVisible Lib "user32" ( _
        ByVal hWnd As IntPtr) _
    As Int32

    Public Declare Auto Function SendMessage Lib "user32" Alias "SendMessageA" ( _
        ByVal hWnd As IntPtr, _
        ByVal wMsg As Int32, _
        ByVal wParam As Int32, _
         ByVal lParam As String) _
    As Int32

    ' Get windows, and show them in the tree
    '
    Private Sub GetWindows()
        ' Variables
        '
        Dim hWnd As IntPtr
        Dim node As TreeNode
        Dim windowText As String
        Dim className As String
        Dim c As Int32
        Dim nodeText As String

        ' Get desktop window
        '
        hWnd = GetDesktopWindow()

        ' Get window text and class name
        '
        windowText = Space(255)
        c = GetWindowText(hWnd, windowText, 255)
        windowText = Microsoft.VisualBasic.Left(windowText, c)

        className = Space(255)
        c = GetClassName(hWnd, className, 255)
        className = Microsoft.VisualBasic.Left(className, c)

        ' Add window to the tree
        '
        nodeText = String.Format("{0:X8} ""{1}"" {2} (Desktop)", hWnd.ToInt32, windowText, className)
        node = TreeView1.Nodes.Add(nodeText)
        node.ForeColor = Color.Black

        ' Search children by recursion
        '
        GetWindows(hWnd, node)
    End Sub

    Private Sub GetWindows(ByVal hParentWnd As IntPtr, ByVal parentNode As TreeNode)
        ' Variables
        '
        Dim hWnd As IntPtr
        Dim node As TreeNode
        Dim windowText As String
        Dim className As String
        Dim c As Int32
        Dim nodeText As String

        ' Get first child window
        '
        hWnd = GetWindow(hParentWnd, GW_CHILD)

        Do Until hWnd.Equals(IntPtr.Zero)
            ' Get the window text and class name
            '
            windowText = Space(255)
            c = GetWindowText(hWnd, windowText, 255)
            windowText = Microsoft.VisualBasic.Left(windowText, c)

            className = Space(255)
            c = GetClassName(hWnd, className, 255)
            className = Microsoft.VisualBasic.Left(className, c)

            ' Add window to the tree
            '
            nodeText = String.Format("{0:X8} ""{1}"" {2}", hWnd.ToInt32, windowText, className)
            node = parentNode.Nodes.Add(nodeText)
            If (IsWindowVisible(hWnd)) Then
                ' Visible windows are shown in black
                '
                node.ForeColor = Color.Black
            Else
                ' Invisible windows are shown in red
                '
                node.ForeColor = Color.Red
            End If

            ' Search children by recursion
            '
            GetWindows(hWnd, node)

            ' Get next child window
            '
            hWnd = GetWindow(hWnd, GW_HWNDNEXT)
        Loop
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        ' Get all windows and show them in the tree
        '
        TreeView1.Nodes.Clear()
        GetWindows()
        TreeView1.Nodes.Item(0).Expand()
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        ' Variables
        '
        Dim node As TreeNode
        Dim windowInfo As String
        Dim hWnd As IntPtr
        Dim pid As Int32
        Dim processName As String

        ' Get the selected window in the tree
        '
        node = TreeView1.SelectedNode

        ' Get PID (Process ID) for the selected window
        '
        hWnd = New IntPtr(CInt("&H" + Microsoft.VisualBasic.Left(node.Text, 8)))
        GetWindowThreadProcessId(hWnd, pid)

        ' Get process name from PID
        '
        processName = Process.GetProcessById(pid).ProcessName

        ' Check if the selected window in the tree is notepad's Print dialog:
        '   Window Handle = xxxxxxxx (variable)
        '   Window Text   = Print
        '   Class Name    = #32770
        '   Process Name  = "notepad"
        '
        windowInfo = Microsoft.VisualBasic.Right(node.Text, node.Text.Length - 9)
        If (windowInfo = """Print"" #32770" And processName = "notepad") Then
            ' The selected window is notepad's Print dialog

            ' Change the number of copies in "Number of &copies" edit box 
            '
            ' xxxxxxxx "Print" #32770       <-- Selected node
            '   - xxxxxxxx "General" #32770
            '       - xxxxxxxx "" #32770
            '            xxxxxxxx "" Edit   <-- We have to write here
            '
            hWnd = New IntPtr(CInt("&H" + Microsoft.VisualBasic.Left(node.Nodes(0).Nodes(13).Nodes(10).Text, 8)))
            SendMessage(hWnd, WM_SETTEXT, 0, "2")

            ' Press the print button
            '
            ' xxxxxxxx "Print" #32770       <-- Selected node
            '    xxxxxxxx "&Print" Button   <-- We have to press here
            '
            hWnd = New IntPtr(CInt("&H" + Microsoft.VisualBasic.Left(node.Nodes(1).Text, 8)))
            SendMessage(hWnd, BM_CLICK, 0, Nothing)
        Else
            ' The selected window is not notepad's Print dialog
            '
            MessageBox.Show("The selected window is not a notepad's Print dialog")
        End If
    End Sub
#End Region

End Class

I hope this helps.

Regards,

 

Alex (Alejandro Campos Magencio)