Я пытался создать собственное окно, которое меняет свою тему на основе темы Windows 10 (светлая или темная). Мне удалось прослушать изменение темы Windows 10 во время выполнения. но когда я устанавливаю собственное значение свойства зависимости (WindowTheme), приложение выдает исключение:

Выброшенное исключение: 'System.InvalidOperationException' в WindowsBase.dll Исключение выброшено: 'System.Reflection.TargetInvocationException' в mscorlib.dll

Вот мой код:

Window.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using UWPHost.Enums;
using UWPHost.Utilities;
using static UWPHost.Utilities.NativeMethods;

namespace UWPHost
{
    public class Window:System.Windows.Window
    {
        public Window()
        {
            
        }  

        private void SetTheme()
        {
            ResourceDictionary resourceDict = Application.LoadComponent(new Uri("UWPHost;component//Themes/res/Window.xaml", System.UriKind.RelativeOrAbsolute)) as ResourceDictionary;
            Application.Current.Resources.MergedDictionaries.Clear();
            Application.Current.Resources.MergedDictionaries.Add(resourceDict);
            this.Style = (Style)Application.Current.Resources["GenericWindow"];
            new ThemeUtility().Init(this);
        }

        public void SwitchTheme(Theme theme)
        {
            WindowTheme = theme;
        }
        protected override void OnSourceInitialized(EventArgs e)
        {
            base.OnSourceInitialized(e);
            var hWndSource = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
            hWndSource.CompositionTarget.BackgroundColor = (Color)ColorConverter.ConvertFromString(CompositionTargetColor.ToString());

            var nonClientArea = new Margins
            {
                left = ResizeFrameWidth,
                top = (int)CaptionHeight,
                bottom = ResizeFrameWidth,
                right = ResizeFrameWidth
            };
            DwmExtendFrameIntoClientArea(hWndSource.Handle, ref nonClientArea);
            hWndSource.AddHook(new WndProc(this).GetWndProc);
            SetWindowPos(hWndSource.Handle, new IntPtr(), 0, 0, 0, 0, 0x0020 | 0x0002 | 0x0001);
            SetWindowLong(hWndSource.Handle, GWL_STYLE, GetWindowLong(hWndSource.Handle, GWL_STYLE) & ~WS_SYSMENU);
            SetTheme();
        }

        #region Dependency Properties

        public double CaptionHeight
        {
            get { return (double)GetValue(CaptionHeightProperty); }
            set { SetValue(CaptionHeightProperty, value); }
        }
        public static readonly DependencyProperty CaptionHeightProperty = DependencyProperty.Register("CaptionHeight", typeof(double), typeof(Window), new PropertyMetadata(33.0));

        public int ResizeFrameWidth
        {
            get { return (int)GetValue(ResizeFrameWidthProperty); }
            set { SetValue(ResizeFrameWidthProperty, value); }
        }
        public static readonly DependencyProperty ResizeFrameWidthProperty = DependencyProperty.Register("ResizeFrameWidth", typeof(int), typeof(Window), new PropertyMetadata(10));

        public String CompositionTargetColor
        {
            get { return (String)GetValue(CompositionTargetColorProperty); }
            set { SetValue(CompositionTargetColorProperty, value); }
        }
        public static readonly DependencyProperty CompositionTargetColorProperty = DependencyProperty.Register("CompositionTargetColor", typeof(String), typeof(Window), new PropertyMetadata("#FFFFFF"));

        public Theme WindowTheme    
        {
            get { return (Theme)GetValue(WindowThemeProperty); }
            set { SetValue(WindowThemeProperty, value); }
        }
        public static readonly DependencyProperty WindowThemeProperty = DependencyProperty.Register("WindowTheme", typeof(Theme), typeof(Window), new PropertyMetadata(Theme.Default));


        #endregion
    }
}

ThemeUtility.cs

using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Management;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using UWPHost.Enums;

namespace UWPHost.Utilities
{
    public class ThemeUtility
    {
        private const string ThemeRegistry= @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
        private const string RegistryValue= "AppsUseLightTheme";
        private readonly string query;
        private readonly WindowsIdentity CurrentUser;
        ManagementEventWatcher ThemeWatcher;
        Window window;

        public ThemeUtility()
        {
            CurrentUser = WindowsIdentity.GetCurrent();
            query= string.Format(CultureInfo.InvariantCulture,@"SELECT * FROM RegistryValueChangeEvent WHERE Hive = 'HKEY_USERS' AND KeyPath = '{0}\\{1}' AND ValueName = '{2}'", CurrentUser.User.Value,ThemeRegistry.Replace(@"\", @"\\"),RegistryValue);
        }

        public void Init(Window window)
        {
            this.window = window;
            InitSystemThemeWatcher();
        }

        private void InitSystemThemeWatcher()
        {
            try
            {
                ThemeWatcher = new ManagementEventWatcher(query);
                ThemeWatcher.EventArrived += (sender, args) =>
                {
                    //This code is executed when windows 10 theme changes
                    if(GetSystemTheme()==Theme.Dark)
                    {
                        //Here i'm setting the property of window
                        window.SwitchTheme(Theme.Dark);
                    }
                    else
                    {
                        window.SwitchTheme(Theme.Light);
                    }
                };
                ThemeWatcher.Start();
            }
            catch(Exception)
            {
                throw new Exception("Error Unable to add theme listener.");
            }

            Theme initialTheme = GetSystemTheme();
        }

        private static Theme GetSystemTheme()
        {
            using (RegistryKey key = Registry.CurrentUser.OpenSubKey(ThemeRegistry))
            {
                object registryValueObject = key?.GetValue(RegistryValue);
                if (registryValueObject == null)
                {
                    return Theme.Light;
                }
                int registryValue = (int)registryValueObject;
                return registryValue > 0 ? Theme.Light : Theme.Dark;
            }
        }
    }
}

Выброшено исключение: 'System.InvalidOperationException' в WindowsBase.dll

Из этой статьи я смог узнать, что это можно сделать с помощью INotifyPropertyChanged, но я не знаю, как это реализовать в моем случае.

Примечание. Свойство WindowTheme имеет тип enum и имеет значения Light, Dark, Default .

0
trickymind 14 Окт 2020 в 14:04

1 ответ

Лучший ответ

Обработчик ManagementEventWatcher.EventArrived очевидно не вызывается в потоке пользовательского интерфейса вашего приложения.

К свойству зависимости можно получить доступ только в потоке, в котором был создан объект-владелец.

Используйте диспетчер окна для маршалинга выполнения в поток, в котором было создано окно, то есть поток пользовательского интерфейса:

public void SwitchTheme(Theme theme)
{
    Dispatcher.Invoke(() => WindowTheme = theme);
}
1
Clemens 14 Окт 2020 в 13:33