trickymind
trickymind

Reputation: 897

C# WPF Changing property of window from other class causes exception

I was trying to create a custom window which changes its theme based on the windows 10 theme (Light or Dark). I was able to listen to the windows 10 theme change at runtime. but when i set my custom dependency property value (WindowTheme), the application throws exception :

Exception thrown: 'System.InvalidOperationException' in WindowsBase.dll Exception thrown: 'System.Reflection.TargetInvocationException' in mscorlib.dll

Here is my Code:

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

Exception thrown: 'System.InvalidOperationException' in WindowsBase.dll

From this article i was able to know it can be done using INotifyPropertyChanged, but i dont know how to implement it in my case.

Note:The WindowTheme property is of type enum and have values Light,Dark,Default

Upvotes: 0

Views: 330

Answers (1)

Clemens
Clemens

Reputation: 128145

The ManagementEventWatcher.EventArrived handler is apparently not called in the UI thread of your application.

A dependency property can only be accessed in the thread in which the owning object was created.

Use the Window's Dispatcher to marshal the execution to the thread where the Window was created, i.e. the UI thread:

public void SwitchTheme(Theme theme)
{
    Dispatcher.Invoke(() => WindowTheme = theme);
}

Upvotes: 1

Related Questions