如何通过编程更改当前的Windows主题?

33
我希望允许我的用户在Aero和Windows Classic(1)之间切换当前用户主题。是否有一种可以通过编程实现的方式?
我不想弹出"显示属性",而且我对仅仅更改注册表表示怀疑。(这需要注销并重新登录才能生效)。
应用程序皮肤(使用Codejock库)也无法起作用。
有没有一种方法可以实现这个功能?
该应用程序在Windows Server 2008上通过RDP运行/托管。
(1) 所讨论的应用程序是一个托管的“远程应用程序”,我希望用户能够更改显示应用程序的外观以匹配他们的桌面。
13个回答

70

您可以使用以下命令进行设置:

rundll32.exe %SystemRoot%\system32\shell32.dll,Control_RunDLL %SystemRoot%\system32\desk.cpl desk,@Themes /Action:OpenTheme /file:"C:\Windows\Resources\Themes\aero.theme"

需要注意的是,这将显示主题选择对话框。您可以直接关闭该对话框。


10
生病了,你是怎么发现的? - Claudiu
6
我会给这个答案加上赏金,因为它是实际的答案。 - Claudiu
5
天啊,我甚至都记不得我是如何解决这个问题的了。我们用它来设置任务序列中的背景,以定制企业的背景图像而非标准图像。 - Campbell
2
我正在使用你的解决方案,但它打开了一个桌面图标设置窗口并没有更改主题...我找不到问题所在。在Windows 10上。 - Anoop Mishra
1
你可以直接关闭那个对话框。你能解释一下如何操作吗? - Max
显示剩余5条评论

20

想通过编程方式更改当前主题当然有很好的理由,例如自动化测试工具可能需要在各种主题之间切换以确保应用程序与所有主题都能正常工作。

作为用户,您可以通过在Windows Explorer中双击.theme文件,然后关闭弹出的控制面板小程序来更改主题。您也可以轻松地从代码中实现相同的功能。以下步骤适用于我,但我只在Windows 7上进行了测试。

  1. 使用SHGetKnownFolderPath()获取用户的“本地应用数据”文件夹。主题文件存储在Microsoft\Windows\Themes子文件夹中。存储在该文件夹中的主题文件直接应用,而存储在其他位置的主题文件则在执行时被复制。因此最好仅使用该文件夹中的文件。
  2. 使用ShellExecute()执行在步骤1中找到的.theme文件。
  3. 等待主题应用。我只是让我的应用程序休眠2秒钟。
  4. 调用FindWindow('CabinetWClass', 'Personalization')来获取在应用主题时弹出的控制面板窗口的句柄。在非美国英语版本的Windows上,“个性化”标题可能会有所不同。
  5. 调用PostMessage(HWND, WM_CLOSE, 0, 0) 来关闭控制面板窗口。

这并不是一个非常优雅的解决方案,但它可以完成工作。


2
我在Windows 8上找不到%LocalAppData%\Microsoft\Windows\Themes。有一个%AppData%\Microsoft\Windows\Themes,但里面没有主题文件。 - XP1
寻找 .themepack。下载或手动自定义主题,右键单击并选择“保存主题以共享”为 .themepack 文件。这可以被“运行”,并更改当前主题。 - Nigel Touch

11

我知道这是一个旧的工单,但今天有人问我如何做到这一点。所以从Mike上面的帖子开始,我整理了一下内容,添加了注释,并发布了完整的C#控制台应用程序代码:

<code>using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

using Microsoft.Win32;

namespace Windows7Basic
{
    class Theming
    {
        /// Handles to Win 32 API
        [DllImport("user32.dll", EntryPoint = "FindWindow")]
        private static extern IntPtr FindWindow(string sClassName, string sAppName);
        [DllImport("user32.dll")]
        private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        /// Windows Constants
        private const uint WM_CLOSE = 0x10;

        private String StartProcessAndWait(string filename, string arguments, int seconds, ref Boolean bExited)
        {
            String msg = String.Empty;
            Process p = new Process();
            p.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
            p.StartInfo.FileName = filename;
            p.StartInfo.Arguments = arguments;
            p.Start();

            bExited = false;
            int counter = 0;
            /// give it "seconds" seconds to run
            while (!bExited && counter < seconds)
            {
                bExited = p.HasExited;
                counter++;
                System.Threading.Thread.Sleep(1000);
            }//while
            if (counter == seconds)
            {
                msg = "Program did not close in expected time.";
            }//if

            return msg;
        }

        public Boolean SwitchTheme(string themePath)
        {
            try
            {    
                //String themePath = System.Environment.GetFolderPath(Environment.SpecialFolder.Windows) + @"\Resources\Ease of Access Themes\basic.theme";
                /// Set the theme
                Boolean bExited = false;
                /// essentially runs the command line:  rundll32.exe %SystemRoot%\system32\shell32.dll,Control_RunDLL %SystemRoot%\system32\desk.cpl desk,@Themes /Action:OpenTheme /file:"%WINDIR%\Resources\Ease of Access Themes\classic.theme"
                String ThemeOutput = this.StartProcessAndWait("rundll32.exe", System.Environment.GetFolderPath(Environment.SpecialFolder.System) + @"\shell32.dll,Control_RunDLL " + System.Environment.GetFolderPath(Environment.SpecialFolder.System) + "\\desk.cpl desk,@Themes /Action:OpenTheme /file:\"" + themePath + "\"", 30, ref bExited);

                Console.WriteLine(ThemeOutput);

                /// Wait for the theme to be set
                System.Threading.Thread.Sleep(1000);

                /// Close the Theme UI Window
                IntPtr hWndTheming = FindWindow("CabinetWClass", null);
                SendMessage(hWndTheming, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
            }//try
            catch (Exception ex)
            {
                Console.WriteLine("An exception occured while setting the theme: " + ex.Message);

                return false;
            }//catch
            return true;
        }

        public Boolean SwitchToClassicTheme()
        {
            return SwitchTheme(System.Environment.GetFolderPath(Environment.SpecialFolder.Windows) + @"\Resources\Ease of Access Themes\basic.theme");
        }

        public Boolean SwitchToAeroTheme()
        {
            return SwitchTheme(System.Environment.GetFolderPath(Environment.SpecialFolder.Windows) + @"\Resources\Themes\aero.theme");
        }

        public string GetTheme()
        {
            string RegistryKey = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes";
            string theme;
            theme = (string)Registry.GetValue(RegistryKey, "CurrentTheme", string.Empty);
            theme = theme.Split('\\').Last().Split('.').First().ToString();
            return theme;
        }

        // end of object Theming
    }

    //---------------------------------------------------------------------------------------------------------------

    class Program
    {
        [DllImport("dwmapi.dll")]
        public static extern IntPtr DwmIsCompositionEnabled(out bool pfEnabled);

        /// ;RunProgram("%USERPROFILE%\AppData\Local\Microsoft\Windows\Themes\themeName.theme")      ;For User Themes
        /// RunProgram("%WINDIR%\Resources\Ease of Access Themes\classic.theme")                     ;For Basic Themes
        /// ;RunProgram("%WINDIR%\Resources\Themes\aero.theme")                                      ;For Aero Themes

        static void Main(string[] args)
        {
            bool aeroEnabled = false;
            Theming thm = new Theming();
            Console.WriteLine("The current theme is " + thm.GetTheme());

            /// The only real difference between Aero and Basic theme is Composition=0 in the [VisualStyles] in Basic (line omitted in Aero)
            /// So test if Composition is enabled
            DwmIsCompositionEnabled(out aeroEnabled);

            if (args.Length == 0 || (args.Length > 0 && args[0].ToLower(CultureInfo.InvariantCulture).Equals("basic")))
            {
                if (aeroEnabled)
                {
                    Console.WriteLine("Setting to basic...");
                    thm.SwitchToClassicTheme();
                }//if
            }//if
            else if (args.Length > 0 || args[0].ToLower(CultureInfo.InvariantCulture).Equals("aero"))
            {
                if (!aeroEnabled)
                {
                    Console.WriteLine("Setting to aero...");
                    thm.SwitchToAeroTheme();
                }//if
            }//else if
        }

        // end of object Program
    }
}
</code>


我肯定喜欢这个解决方案。运作得很好!谢谢分享! - James Shaw

5

我不确定这是否是新功能,但你可以双击.theme文件,然后Windows 10会应用该主题。因此,你可以轻松使用PowerShell执行以下操作:

$Windows10Theme = "C:\Windows\Resources\Themes\aero.theme"
Invoke-Expression $Windows10Theme

1
这个非常好用。它可以在没有任何对话框弹出的情况下切换主题。 - howdoicode

2

我一直在尝试通过命令行更改Windows主题,发现通过执行主题文件可以被Windows 10应用。因此,在您的批处理文件中,您可以使用以下其中一行:

C:\Users\%USERNAME%\AppData\Local\Microsoft\Windows\Themes\Dark_Mode.theme

或者

C:\Users\%USERNAME%\AppData\Local\Microsoft\Windows\Themes\Light_Mode.theme

请注意,根据您的系统用户配置,可能需要调整主题文件的路径。我强烈建议使用不含空格名称保存您的主题,因为这样更容易前进。执行此行将使设置窗口保持打开状态。为了处理这个问题,我考虑使用VBS脚本。感谢用户Patrick Haugh user1390106,现在有一种更简单的方法来关闭设置窗口。
taskkill /F /IM systemsettings.exe

因此,升级后的批处理文件可能如下所示:
@echo off
if %1 == dark (
REM ================== Go Dark ==================
color 09
echo.
echo   Applying DARK MODE
echo   Windows Theme ...
C:\Users\%USERNAME%\AppData\Local\Microsoft\Windows\Themes\Dark_Mode.theme
timeout /T 1 /nobreak > nul
taskkill /F /IM systemsettings.exe > nul
echo   DONE
) else (
REM ============== Return to Light ==============
color 30
echo.
echo   Applying LIGHT MODE
echo   Windows Theme ...
C:\Users\%USERNAME%\AppData\Local\Microsoft\Windows\Themes\Light_Mode.theme
timeout /T 1 /nobreak > nul
taskkill /F /IM systemsettings.exe > nul
echo   DONE
)
REM ================== Goodbye ==================
echo.
echo   Goodbye
cls
exit

请注意,主题文件的路径可能需要根据您的系统用户配置进行调整。请将上述脚本保存为theme.bat并放在您的驱动器中的某个位置。
此批处理文件需要一个参数,该参数必须是dark或任何其他string。然后,您可以准备两个快捷方式到该批处理文件,每个快捷方式在其属性的“快捷方式”选项卡中的“目标”框中都有以下内容之一:
C:\full-path-to-your-batch-file\theme.bat dark

或者

C:\full-path-to-your-batch-file\theme.bat light

请将“full-path-to-your-batch-file”替换为实际批处理文件的路径。 以下是演示如何操作的视频链接: a) Going Dark - https://youtu.be/cBcDNhAmfyM b) Returning to the Light - https://youtu.be/2kYJaJHubi4 请注意,我在这些视频中的脚本还会激活/停用Chrome的Stylish插件。我省略了如何完成该部分的解释,因为它不是本文的主题。

2

较新的Windows版本(Windows 8和8.1,尚未在W10上尝试)的命令为:

rundll32.exe themecpl.dll,OpenThemeAction %1

或者使用完整路径:
C:\WINDOWS\system32\rundll32.exe C:\WINDOWS\system32\themecpl.dll,OpenThemeAction %LocalAppData%\Microsoft\Windows\Themes\yourtheme.theme

基本上,这是从注册表中提取的与.theme和.themepack扩展名相关的个性化CPL“打开”命令...

使用此命令后,您仍将获得打开的个性化窗口,因此要在程序中关闭它,您必须使用上面提到的建议方法之一...(我个人更喜欢Powershell脚本)


1

我认为最好的方法是打开目标的.msstyles文件(在c:\windows\resources\themes中),这将弹出显示属性框。此时,您可以使用窗口子类化程序来编程式地单击正确的按钮。


1

我刚刚意识到,你可以双击主题,它就会自动切换 - 这样更简单,所以只需执行主题即可,例如批处理文件:

:: Reactivate my theme after an remote desktop session
:: We must select another theme first before we can select ours again and hence re-activate Aero, please wait..."
@echo Off
"C:\Windows\Resources\Themes\aero.theme"
::echo "Simulating a pause while"
ping 127.0.0.1 -n 10 > null && "D:\Users\danielsokolowski\Windows 7 Aero Themes\`danielsokolowski` Theme (without Glass).theme"
::or ping 127.0.0.1 -n 3 > null && "%userprofile%\AppData\Local\Microsoft\Windows\Themes\`danielsokolowski` Theme (without Glass).theme"

1
除了“Jan Goyvaerts”的帖子之外: 我使用SendMessage而不是PostMessage。区别在于SendMessage等待窗口接收命令。这意味着在SendMessages返回时,您知道主题对话框已关闭。
因此,如果您使用“Campbell”建议的庞大(但天才)rundll32.exe方法启动它。在发送WM_CLOSE之前应等待一秒钟。否则,主题将不会设置,应用程序立即关闭。
下面的代码片段从资源中提取文件(themepack)。然后使用rundll32.exe执行desk.cpl,等待3秒钟,然后发送WM_CLOSE(0x0010),等待命令处理(主题设置所需的时间)。
    private Boolean SwitchToClassicTheme()
    {
        //First unpack the theme
        try
        {
            //Extract the theme from the resource
            String ThemePath = System.Environment.GetFolderPath(Environment.SpecialFolder.Windows) + @"\Resources\Themes\ClassicTheme.themepack";
            //WriteFileToCurrentDirectory("ClassicTheme.theme", TabletConfigurator.Resources.ClassicTheme);
            if(File.Exists(ThemePath))
            {
                File.Delete(ThemePath);
            }
            if(File.Exists(ThemePath))
            {
                throw new Exception("The file '" + ThemePath + "' exists and can not be deleted. You can try to delete it manually.");
            }
            using (BinaryWriter sw = new BinaryWriter(new FileStream(ThemePath, FileMode.OpenOrCreate)))
            {
                sw.Write(TabletConfigurator.Resources.ClassicTheme);
                sw.Flush();
                sw.Close();
            }

            if(!File.Exists(ThemePath))
            {
                throw new Exception("The resource theme file could not be extracted");
            }

            //Set the theme file as like a user would have clicked it
            Boolean bTimedOut = false;
            String ThemeOutput = StartProcessAndWait("rundll32.exe", System.Environment.GetFolderPath(Environment.SpecialFolder.System) + @"\shell32.dll,Control_RunDLL " + System.Environment.GetFolderPath(Environment.SpecialFolder.System) + "\\desk.cpl desk,@Themes /Action:OpenTheme /file:\"" + ThemePath + "\"", ref bTimedOut);

            System.Threading.Thread.Sleep(3000);
            //Wait for the theme to be set
            IntPtr hWndTheming = FindWindow("CabinetWClass", null);
            SendMessage(hWndTheming, (uint)WM_CLOSE, 0, 0);

            //using (Bitmap bm = CaptureScreenShot())
            //{
            //    Boolean PixelIsGray = true;
            //    while (PixelIsGray)
            //    {
            //        System.Drawing.Color pixel = bm.GetPixel(0, 0)
            //    }
            //}

        }
        catch(Exception ex)
        {
            ShowError("An exception occured while setting the theme: " + ex.Message);
            return false;
        }
        return true;
    }

什么是FindWindow()?StartProcessAndWait()?SendMessage()?这段代码看起来很有用,但作为示例不完整。 - Leigh

1

针对Windows 10,我使用PowerShell编写了这个简单的解决方案(也可用于DSC)。

# Apply your theme
& "C:\Windows\Resources\Themes\Brand.theme"
# We need to wait for the theme to be applied
Start-Sleep -s 5
# Close the settings window that is opened by the action above
$window = Get-Process | Where-Object {$_.Name -eq "SystemSettings"}
Stop-Process -Id $window.Id

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接