/// <summary> /// Finds the <see cref="FrameworkElement"/> a specific style is based on. /// </summary> /// <param name="resourceDictionaryUri">The resource dictionary URI.</param> /// <param name="styleKey">The style key.</param> /// <returns> /// <see cref="Type"/> or <c>null</c> if the style is not based on a <see cref="FrameworkElement"/>. /// </returns> /// <exception cref="ArgumentNullException">The <paramref name="resourceDictionaryUri"/> is <c>null</c>.</exception> /// <exception cref="ArgumentNullException">The <paramref name="styleKey"/> is <c>null</c>.</exception> /// <remarks> /// This method is introduced due to the lack of the ability to use DynamicResource for the BasedOn property when /// defining styles inside a derived theme. /// Should be used in combination with the <see cref="RecreateDefaultStylesBasedOnTheme"/> method. /// </remarks> private static Type FindFrameworkElementStyleIsBasedOn(Uri resourceDictionaryUri, string styleKey) { Argument.IsNotNull("resourceDictionaryUri", resourceDictionaryUri); Argument.IsNotNull("styleKey", styleKey); return(_styleToFrameworkElementTypeCache.GetFromCacheOrFetch(styleKey, () => { try { var xmlDocInfo = GetResourceXmlDocument(resourceDictionaryUri); var doc = xmlDocInfo.Item1; var xmlNamespaceManager = xmlDocInfo.Item2; var xpath = string.Format("/ctl:ResourceDictionary/ctl:Style[@x:Key='{0}']/@BasedOn", styleKey); var xmlAttribute = doc.SelectSingleNode(xpath, xmlNamespaceManager) as XmlAttribute; if (xmlAttribute == null) { Log.Warning("Style '{0}' does not have the 'BasedOn' attribute defined", styleKey); return null; } var basedOnValue = xmlAttribute.Value; basedOnValue = basedOnValue.Replace("StaticResource", string.Empty); basedOnValue = basedOnValue.Replace("x:Type", string.Empty).Trim(' ', '{', '}'); #region Create xml type mapper var xamlTypeMapper = new XamlTypeMapper(new[] { "PresentationFramework" }); foreach (XmlAttribute namespaceAttribute in doc.DocumentElement.Attributes) { var xmlNamespace = namespaceAttribute.Name.Replace("xmlns", string.Empty).TrimStart(':'); var value = namespaceAttribute.Value; var clrNamespace = value; var assemblyName = string.Empty; if (clrNamespace.StartsWith("clr-namespace:")) { // We have a hit (formatting is normally one of the 2 below): // * clr-namespace:[NAMESPACE] // * clr-namespace:[NAMESPACE];assembly=[ASSEMBLY] if (clrNamespace.Contains(";")) { clrNamespace = clrNamespace.Split(';')[0]; } clrNamespace = clrNamespace.Replace("clr-namespace:", string.Empty); if (value.Contains(";")) { assemblyName = value.Split(';')[1].Replace("assembly:", string.Empty); } xamlTypeMapper.AddMappingProcessingInstruction(xmlNamespace, clrNamespace, assemblyName); } } #endregion var splittedType = basedOnValue.Split(':'); var typeNamespace = (splittedType.Length == 2) ? splittedType[0] : "http://schemas.microsoft.com/winfx/2006/xaml/presentation"; var typeName = (splittedType.Length == 2) ? splittedType[1] : splittedType[0]; var type = xamlTypeMapper.GetType(typeNamespace, typeName); if (type == null) { return null; } Log.Debug("Style '{0}' is based on type '{1}'", styleKey, type); if ((type == typeof(FrameworkElement)) || type.IsSubclassOf(typeof(FrameworkElement))) { return type; } return null; } catch (Exception ex) { Log.Error(ex, "Failed to find the framework element where style '{0}' is based on", styleKey); return null; } })); }
/// <summary> /// Finds the <see cref="FrameworkElement"/> a specific style is based on. /// </summary> /// <param name="resourceDictionaryUri">The resource dictionary URI.</param> /// <param name="styleKey">The style key.</param> /// <returns> /// <see cref="Type"/> or <c>null</c> if the style is not based on a <see cref="FrameworkElement"/>. /// </returns> /// <exception cref="ArgumentNullException">The <paramref name="resourceDictionaryUri"/> is <c>null</c>.</exception> /// <exception cref="ArgumentNullException">The <paramref name="styleKey"/> is <c>null</c>.</exception> /// <remarks> /// This method is introduced due to the lack of the ability to use DynamicResource for the BasedOn property when /// defining styles inside a derived theme. /// Should be used in combination with the <see cref="RecreateDefaultStylesBasedOnTheme"/> method. /// </remarks> private static Type FindFrameworkElementStyleIsBasedOn(Uri resourceDictionaryUri, string styleKey) { Argument.IsNotNull("resourceDictionaryUri", resourceDictionaryUri); Argument.IsNotNull("styleKey", styleKey); if (_styleToFrameworkElementTypeCache.ContainsKey(styleKey)) { return(_styleToFrameworkElementTypeCache[styleKey]); } try { XmlDocument doc; if (_resourceDictionaryCache.ContainsKey(resourceDictionaryUri)) { doc = _resourceDictionaryCache[resourceDictionaryUri]; } else { StreamResourceInfo streamResourceInfo = Application.GetResourceStream(resourceDictionaryUri); var reader = new XmlBamlReader(streamResourceInfo.Stream); doc = new XmlDocument(); doc.Load(reader); _resourceDictionaryCache.Add(resourceDictionaryUri, doc); } #region Create xml namespace manager // Create namespace manager (all namespaces are required) var xmlNamespaceManager = new XmlNamespaceManager(doc.NameTable); foreach (XmlAttribute namespaceAttribute in doc.DocumentElement.Attributes) { // Clean up namespace (remove xmlns prefix) string xmlNamespace = namespaceAttribute.Name.Replace("xmlns", "").TrimStart(new char[] { ':' }); xmlNamespaceManager.AddNamespace(xmlNamespace, namespaceAttribute.Value); } // Add a dummy node xmlNamespaceManager.AddNamespace("x", "http://schemas.microsoft.com/winfx/2006/xaml"); xmlNamespaceManager.AddNamespace("ctl", "http://schemas.microsoft.com/winfx/2006/xaml/presentation"); #endregion string xpath = string.Format("/ctl:ResourceDictionary/ctl:Style[@x:Key='{0}']/@BasedOn", styleKey); var xmlAttribute = doc.SelectSingleNode(xpath, xmlNamespaceManager) as XmlAttribute; if (xmlAttribute == null) { Log.Warning("Style '{0}' does not have the 'BasedOn' attribute defined", styleKey); _styleToFrameworkElementTypeCache.Add(styleKey, null); return(null); } string basedOnValue = xmlAttribute.Value; basedOnValue = basedOnValue.Replace("StaticResource", ""); basedOnValue = basedOnValue.Replace("x:Type", "").Trim(new[] { ' ', '{', '}' }); #region Create xml type mapper var xamlTypeMapper = new XamlTypeMapper(new[] { "PresentationFramework" }); foreach (XmlAttribute namespaceAttribute in doc.DocumentElement.Attributes) { string xmlNamespace = namespaceAttribute.Name.Replace("xmlns", "").TrimStart(new char[] { ':' }); string value = namespaceAttribute.Value; string clrNamespace = value; string assemblyName = string.Empty; if (clrNamespace.StartsWith("clr-namespace:")) { // We have a hit (formatting is normally one of the 2 below): // * clr-namespace:[NAMESPACE] // * clr-namespace:[NAMESPACE];assembly=[ASSEMBLY] if (clrNamespace.Contains(";")) { clrNamespace = clrNamespace.Split(new char[] { ';' })[0]; } clrNamespace = clrNamespace.Replace("clr-namespace:", ""); if (value.Contains(";")) { assemblyName = value.Split(new char[] { ';' })[1].Replace("assembly:", ""); } xamlTypeMapper.AddMappingProcessingInstruction(xmlNamespace, clrNamespace, assemblyName); } } #endregion string[] splittedType = basedOnValue.Split(new[] { ':' }); string typeNamespace = (splittedType.Length == 2) ? splittedType[0] : "http://schemas.microsoft.com/winfx/2006/xaml/presentation"; string typeName = (splittedType.Length == 2) ? splittedType[1] : splittedType[0]; var type = xamlTypeMapper.GetType(typeNamespace, typeName); if (type == null) { _styleToFrameworkElementTypeCache.Add(styleKey, null); return(null); } Log.Debug("Style '{0}' is based on type '{1}'", styleKey, type); if ((type == typeof(FrameworkElement)) || type.IsSubclassOf(typeof(FrameworkElement))) { _styleToFrameworkElementTypeCache.Add(styleKey, type); return(type); } _styleToFrameworkElementTypeCache.Add(styleKey, null); return(null); } catch (Exception ex) { Log.Error(ex, "Failed to find the framework element where style '{0}' is based on", styleKey); return(null); } }