| File: System\Windows\ThemeDictionaryExtension.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Reflection; using System.Text; using System.Windows.Markup; namespace System.Windows { /// <summary> /// ThemeDictionaryExtension allows application authors to customize /// control styles based on the current system theme. /// /// </summary> [MarkupExtensionReturnType(typeof(Uri))] public class ThemeDictionaryExtension : MarkupExtension { #region Constructors /// <summary> /// Constructor that takes no parameters /// </summary> public ThemeDictionaryExtension() { } /// <summary> /// Constructor that takes the name of the assembly that contains the themed ResourceDictionary. /// </summary> public ThemeDictionaryExtension(string assemblyName) { ArgumentNullException.ThrowIfNull(assemblyName); _assemblyName = assemblyName; } #endregion #region Public Properties /// <summary> /// The name of the assembly that contains the themed ResourceDictionary. /// </summary> public string AssemblyName { get { return _assemblyName; } set { _assemblyName = value; } } #endregion #region Public Methods /// <summary> /// Return an object that should be set on the targetObject's targetProperty /// for this markup extension. For ThemeDictionaryExtension, this is the Uri /// pointing to theme specific dictionary in the specified assembly by AssemblyName. /// </summary> /// <param name="serviceProvider">ServiceProvider that can be queried for services.</param> /// <returns> /// The object to set on this property. /// </returns> public override object ProvideValue(IServiceProvider serviceProvider) { if (string.IsNullOrEmpty(AssemblyName)) { throw new InvalidOperationException(SR.ThemeDictionaryExtension_Name); } IProvideValueTarget provideValueTarget = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; if( provideValueTarget == null ) { throw new InvalidOperationException(SR.Format(SR.MarkupExtensionNoContext, GetType().Name, "IProvideValueTarget" )); } object targetObject = provideValueTarget.TargetObject; object targetProperty = provideValueTarget.TargetProperty; ResourceDictionary dictionary = targetObject as ResourceDictionary; PropertyInfo propertyInfo = targetProperty as PropertyInfo; // Allow targetProperty to be null or ResourceDictionary.Source if (dictionary == null || (targetProperty != null && propertyInfo != SourceProperty)) { throw new InvalidOperationException(SR.ThemeDictionaryExtension_Source); } Register(dictionary, _assemblyName); dictionary.IsSourcedFromThemeDictionary = true; return GenerateUri(_assemblyName, SystemResources.ResourceDictionaries.ThemedResourceName, MS.Win32.UxThemeWrapper.ThemeName); } // Build the Uri for the assembly: // /AssemblyName;Component/themes/<CurrentTheme>.<Color>.xaml private static Uri GenerateUri(string assemblyName, string resourceName, ReadOnlySpan<char> themeName) { StringBuilder uri = new StringBuilder(assemblyName.Length + 50); uri.Append('/'); uri.Append(assemblyName); // If assembly is PresentationFramework, append the Theme name if (assemblyName.Equals(SystemResources.PresentationFrameworkName, StringComparison.OrdinalIgnoreCase)) { uri.Append('.'); uri.Append(themeName); } uri.Append(";component/"); uri.Append(resourceName); uri.Append(".xaml"); return new Uri(uri.ToString(), System.UriKind.RelativeOrAbsolute); } internal static Uri GenerateFallbackUri(ResourceDictionary dictionary, string resourceName) { Span<Range> splitRegions = stackalloc Range[3]; for (int i = 0; i < _themeDictionaryInfos.Count; i++) { ThemeDictionaryInfo info = _themeDictionaryInfos[i]; if (!info.DictionaryReference.IsAlive) { // Remove from list _themeDictionaryInfos.RemoveAt(i); i--; continue; } if ((ResourceDictionary)info.DictionaryReference.Target == dictionary) { ReadOnlySpan<char> nameSpan = resourceName.AsSpan(); if (nameSpan.Split(splitRegions, '/') < 2) throw new IndexOutOfRangeException(); return GenerateUri(info.AssemblyName, resourceName, nameSpan[splitRegions[1]]); } } return null; } #endregion #region Data private string _assemblyName; #endregion #region Static Data // Keep track of all dictionaries that have ThemeDictionaryExtensions applied to them // When the theme changes update the Source uri to point to the new theme info // This is the ResourceDictionary.Source property info private static PropertyInfo _sourceProperty; private static PropertyInfo SourceProperty { get { if (_sourceProperty == null) { _sourceProperty = typeof(ResourceDictionary).GetProperty("Source"); } return _sourceProperty; } } private class ThemeDictionaryInfo { public WeakReference DictionaryReference; public string AssemblyName; } // Store a list of dictionaries and assembly info's. // When the theme changes, give the dictionaries a new Source Uri [ThreadStatic] private static List<ThemeDictionaryInfo> _themeDictionaryInfos; private static void Register(ResourceDictionary dictionary, string assemblyName) { Debug.Assert(dictionary != null, "dictionary should not be null"); Debug.Assert(assemblyName != null, "assemblyName should not be null"); if (_themeDictionaryInfos == null) { _themeDictionaryInfos = new List<ThemeDictionaryInfo>(); } ThemeDictionaryInfo info; for (int i = 0; i < _themeDictionaryInfos.Count; i++) { info = _themeDictionaryInfos[i]; if (!info.DictionaryReference.IsAlive) { // Remove from list _themeDictionaryInfos.RemoveAt(i); i--; continue; } if (info.DictionaryReference.Target == dictionary) { info.AssemblyName = assemblyName; return; } } // Not present, add to list info = new ThemeDictionaryInfo { DictionaryReference = new WeakReference(dictionary), AssemblyName = assemblyName }; _themeDictionaryInfos.Add(info); } internal static void OnThemeChanged() { // Update all resource dictionaries if (_themeDictionaryInfos != null) { for (int i = 0; i < _themeDictionaryInfos.Count; i++) { ThemeDictionaryInfo info = _themeDictionaryInfos[i]; if (!info.DictionaryReference.IsAlive) { // Remove from list _themeDictionaryInfos.RemoveAt(i); i--; continue; } // Provide the new dictionary URI ResourceDictionary dictionary = (ResourceDictionary)info.DictionaryReference.Target; dictionary.Source = GenerateUri(info.AssemblyName, SystemResources.ResourceDictionaries.ThemedResourceName, MS.Win32.UxThemeWrapper.ThemeName); } } } #endregion } }