Как программно изменить текущую тему Windows?

Я хочу, чтобы мои пользователи могли переключать текущую пользовательскую тему между Aero и Windows Classic(1). Есть ли способ, которым я могу сделать это программно?

Я не хочу, чтобы всплыли "Свойства дисплея", и я сомневаюсь, просто изменить реестр. (Это требует выхода и повторного входа, чтобы изменения вступили в силу).

Скины приложений (с использованием библиотек Codejock) тоже не работают.

Есть ли способ сделать это?

Приложение размещается / запускается на Windows Server 2008 через RDP.

(1) Данное приложение является размещенным "Удаленным приложением", и я хочу, чтобы пользователи могли изменять внешний вид отображаемого приложения в соответствии со своим рабочим столом.

13 ответов

Решение

Вы можете установить его с помощью следующей команды:

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

Предостережение в том, что это покажет диалог выбора темы. Вы можете убить этот диалог сразу после.

Есть, конечно, веские причины для того, чтобы захотеть изменить текущую тему программно. Например, инструменту автоматического тестирования может потребоваться переключаться между различными темами, чтобы убедиться, что приложение работает со всеми из них правильно.

Как пользователь, вы можете изменить тему, дважды щелкнув .theme файл в Windwos Explorer и затем закрытие всплывающего апплета панели управления. Вы можете легко сделать то же самое из кода. Следующие шаги работают нормально для меня. Я тестировал только на Windows 7.

  1. использование SHGetKnownFolderPath() получить папку "Local AppData" для пользователя. Файлы тем хранятся в Microsoft\Windows\Themes вложенная папка. Файлы тем, хранящиеся там, применяются напрямую, тогда как файлы тем, хранящиеся в другом месте, дублируются при их выполнении. Поэтому лучше использовать файлы только из этой папки.
  2. использование ShellExecute() выполнить .theme файл, который вы нашли в шаге 1.
  3. Подождите, пока тема будет применена. Я просто позволил моему приложению спать в течение 2 секунд.
  4. Вызов FindWindow('CabinetWClass', 'Personalization') чтобы получить дескриптор окна панели управления, которое всплывало, когда тема была применена. Надпись "Персонализация", скорее всего, будет отличаться в не Windows-английских версиях Windows.
  5. Вызов PostMessage(HWND, WM_CLOSE, 0, 0) закрыть окно панели управления.

Это не очень элегантное решение, но оно делает свою работу.

Я знаю, что это старый билет, но кто-то спросил меня, как это сделать сегодня. Итак, начиная с поста Майка выше, я все исправил, добавил комментарии и опубликую полный код консольного приложения C#:

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
    }
}

Я не уверен, что это ново, но вы можете просто дважды щелкнуть файл.theme, и Windows 10 применит тему. Следовательно, вы можете легко сделать это с помощью PowerShell:

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

В дополнение к посту "Ян Гойваертс": я использую SendMessage вместо PostMessage. Разница в том, что SendMessage ожидает, пока окно не примет команду. Это означает, что в SendMessages возвращается, вы знаете, что диалог темы закрыт.

Так что, если вы начнете его с чудовищного (но гениального) метода rundll32.exe, предложенного "Campbell". Вам следует подождать секунду перед отправкой WM_CLOSE. В противном случае тема не будет установлена, и приложение сразу закроется.

Приведенный ниже фрагмент кода извлекает файл из ресурса (тематический пакет). Затем выполняет desk.cpl с помощью rundll32.exe, ожидает 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;
    }

Я считаю, что лучшее, что вы можете сделать, это открыть целевой файл.msstyles (в c:\windows\resources\themes), в результате чего появится окно свойств экрана. На этом этапе вы можете использовать подклассы окна, чтобы программно нажимать правые кнопки.

Команда для более новых версий 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

По сути, это команда open для Personalization CPL для расширений.theme & .themepack, взятых из реестра...

После использования этой команды вы по-прежнему будете открывать окно "Персонализация", поэтому, чтобы закрыть его программно, вам придется использовать один из предложенных методов, упомянутых выше... (лично я предпочитаю скрипт Powershell)

Я экспериментировал с изменением темы 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. Благодаря Патрику Хо, пользователю 1390106, есть гораздо более простой способ закрыть окно настроек.

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" фактическим путем к этому файлу. Вот ссылки на видео, показывающие, как это работает:

а) Темнеет - https://youtu.be/cBcDNhAmfyM

б) Возвращение к Свету - https://youtu.be/2kYJaJHubi4

Обратите внимание, что мой сценарий в этих видеороликах также активирует / деактивирует плагин Stylish для Chrome. Я не стал объяснять, как я выполнил эту часть, так как это не является предметом данной статьи.

Я только что понял, что вы можете дважды щелкнуть по теме, и она автоматически переключается - намного проще, так что просто выполнение темы работает, ex batch file:

:: 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"

Для Windows 10 я написал это простое решение (его также можно использовать в DSC) в PowerShell.

# 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

Хорошо, вот мое мнение об этом - сценарий VB. Это немного неприятно, но лучшее, что я могу придумать (к сожалению).

Для пользователя, который входит в систему, мы просто запускаем ChangeTheme.vbs как пользователь входит в систему (например, автозапуск). Скрипт запускается desk.cpl и передает ему необходимые параметры, а также название выбранной темы.

Можно запустить скрипт с параметрами или без параметров:

> ChangeTheme.vbs
> ChangeTheme.vbs AnyThemeName

Сценарий:

' ////////////////////////////////////////////////////////////////////
'
' Changes the theme.
'
' Name:
'     ChangeTheme.vbs
' Parameter 1: 
'     Theme name e.g. aero or anything 
'     located in in C:\Windows\Resources\Themes. 
'     If not present, a default theme will be used.
'
' Example: 
'     Inside a command line run
'     > ChangeTheme.vbs TheThemeName
'
' ////////////////////////////////////////////////////////////////////

If(Wscript.Arguments.Count <= 0) Then
  ' If no parameter was given we set the following theme as default
  selectedTheme = "aero"
Else
  ' Get theme via the first argument  
  selectedTheme = Wscript.Arguments(0)
End If

' Create WScript shell object
Set WshShell = WScript.CreateObject("WScript.Shell")

' Run the command to open the "theme application" (or whatever)
Set process = WshShell.Exec("rundll32.exe %SystemRoot%\system32\shell32.dll,Control_RunDLL %SystemRoot%\system32\desk.cpl desk,@Themes /Action:OpenTheme /file:""C:\Windows\Resources\Themes\" & selectedTheme & ".theme""")

' Wait for the application to start

Wscript.Sleep 250

Success = False
maxTries = 20
tryCount = 0

Do Until Success = True

  Wscript.Sleep 1000

  ' Set focus to our application    
  ' If this fails, or the application loses focus, it won't work!    
  Success = WshShell.AppActivate(process.ProcessId)

  tryCount = tryCount + 1

  If (tryCount >= maxTries) Then
    ' If it does not work after maxTries we give up ..
    MsgBox("Cannot change theme - max tries exceeded ..")
    Exit Do
  End If

Loop

' The crucial part: Send keys ALT + B for applying the theme    
WshShell.Sendkeys "%(B)"

' Send key "escape" to close the window
WshShell.Sendkeys "{ESCAPE}" 

Надеюсь, это поможет.

Работает на Windows 10.

это мой сценарий Это меняет тему и закрывает окно. Я сохраняю его в командный файл и запускаю этот файл патча из TaskScheduler:

C:\WINDOWS\system32\rundll32.exe C:\WINDOWS\system32\themecpl.dll,OpenThemeAction C:\Users\xxx\Misc_computer_stuff\themes\my_fav_gr.theme

TIMEOUT 1 & REM Waits 1 seconds before executing the next command

TASKKILL /F /IM systemsettings.exe & close window

exit

Вы можете просто открыть любой из файлов .theme, находящихся в C:\Windows\Resources\Themes\, чтобы изменить тему.

Единственная загвоздка в том, что после этого также открывается приложение настроек. Но мы можем убить его, используяStop-Processв PowerShell

      Invoke-Expression "C:\Windows\Resources\Themes\<theme_name>.theme"
Start-Sleep -Seconds 2
Stop-Process -Name SystemSettings

Например:

      Invoke-Expression "C:\Windows\Resources\Themes\dark.theme"
Другие вопросы по тегам