2 using System.Collections;
3 using System.Collections.Generic;
4 using System.Collections.ObjectModel;
5 using System.Collections.Specialized;
6 using System.ComponentModel;
7 using System.Globalization;
9 using System.Reflection;
10 using System.Runtime.CompilerServices;
12 using Tizen.NUI.Binding.Internals;
15 namespace Tizen.NUI.Binding
17 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
18 [EditorBrowsable(EditorBrowsableState.Never)]
19 public class ResourceDictionary : IResourceDictionary, IDictionary<string, object>
21 static ConditionalWeakTable<Type, ResourceDictionary> s_instances = new ConditionalWeakTable<Type, ResourceDictionary>();
22 readonly Dictionary<string, object> _innerDictionary = new Dictionary<string, object>();
23 ResourceDictionary _mergedInstance;
27 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
28 [EditorBrowsable(EditorBrowsableState.Never)]
29 public ResourceDictionary()
31 DependencyService.Register<IResourcesLoader, ResourcesLoader>();
35 /// Gets or sets the type of object with which the resource dictionary is merged.
37 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
38 [EditorBrowsable(EditorBrowsableState.Never)]
39 [TypeConverter(typeof(TypeTypeConverter))]
40 [Obsolete("Use Source")]
41 public Type MergedWith {
42 get { return _mergedWith; }
44 if (_mergedWith == value)
48 throw new ArgumentException("MergedWith can not be used with Source");
50 if (!typeof(ResourceDictionary).GetTypeInfo().IsAssignableFrom(value.GetTypeInfo()))
51 throw new ArgumentException("MergedWith should inherit from ResourceDictionary");
54 if (_mergedWith == null)
57 _mergedInstance = s_instances.GetValue(_mergedWith, (key) => (ResourceDictionary)Activator.CreateInstance(key));
58 OnValuesChanged(_mergedInstance.ToArray());
63 /// Gets or sets the URI of the merged resource dictionary.
65 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
66 [EditorBrowsable(EditorBrowsableState.Never)]
67 [TypeConverter(typeof(RDSourceTypeConverter))]
69 get { return _source; }
73 throw new InvalidOperationException("Source can only be set from XAML."); //through the RDSourceTypeConverter
78 /// To set and load source.
80 /// <param name="value">The source.</param>
81 /// <param name="resourcePath">The resource path.</param>
82 /// <param name="assembly">The assembly.</param>
83 /// <param name="lineInfo">The xml line info.</param>
84 /// Used by the XamlC compiled converter.
85 [EditorBrowsable(EditorBrowsableState.Never)]
86 public void SetAndLoadSource(Uri value, string resourcePath, Assembly assembly, System.Xml.IXmlLineInfo lineInfo)
89 if (_mergedWith != null)
90 throw new ArgumentException("Source can not be used with MergedWith");
92 //this will return a type if the RD as an x:Class element, and codebehind
93 var type = XamlResourceIdAttribute.GetTypeForPath(assembly, resourcePath);
95 _mergedInstance = s_instances.GetValue(type, (key) => (ResourceDictionary)Activator.CreateInstance(key));
97 _mergedInstance = DependencyService.Get<IResourcesLoader>()?.CreateFromResource<ResourceDictionary>(resourcePath, assembly, lineInfo);
98 OnValuesChanged(_mergedInstance.ToArray());
101 ICollection<ResourceDictionary> _mergedDictionaries;
103 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
104 [EditorBrowsable(EditorBrowsableState.Never)]
105 public ICollection<ResourceDictionary> MergedDictionaries {
107 if (_mergedDictionaries == null) {
108 var col = new ObservableCollection<ResourceDictionary>();
109 col.CollectionChanged += MergedDictionaries_CollectionChanged;
110 _mergedDictionaries = col;
112 return _mergedDictionaries;
116 internal IList<StyleSheets.StyleSheet> StyleSheets { get; set; }
118 void StyleSheetsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
121 case NotifyCollectionChangedAction.Add:
122 ValuesChanged?.Invoke(this, ResourcesChangedEventArgs.StyleSheets);
126 IList<ResourceDictionary> _collectionTrack;
128 void MergedDictionaries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
130 // Move() isn't exposed by ICollection
131 if (e.Action == NotifyCollectionChangedAction.Move)
134 _collectionTrack = _collectionTrack ?? new List<ResourceDictionary>();
135 // Collection has been cleared
136 if (e.Action == NotifyCollectionChangedAction.Reset) {
137 foreach (var dictionary in _collectionTrack)
138 dictionary.ValuesChanged -= Item_ValuesChanged;
140 _collectionTrack.Clear();
145 if (e.NewItems != null)
147 foreach (var item in e.NewItems)
149 var rd = (ResourceDictionary)item;
150 _collectionTrack.Add(rd);
151 rd.ValuesChanged += Item_ValuesChanged;
152 OnValuesChanged(rd.ToArray());
157 if (e.OldItems != null)
159 foreach (var item in e.OldItems)
161 var rd = (ResourceDictionary)item;
162 rd.ValuesChanged -= Item_ValuesChanged;
163 _collectionTrack.Remove(rd);
168 void Item_ValuesChanged(object sender, ResourcesChangedEventArgs e)
170 OnValuesChanged(e.Values.ToArray());
173 void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
175 ((ICollection<KeyValuePair<string, object>>)_innerDictionary).Add(item);
176 OnValuesChanged(item);
179 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
180 [EditorBrowsable(EditorBrowsableState.Never)]
183 _innerDictionary.Clear();
186 bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
188 return ((ICollection<KeyValuePair<string, object>>)_innerDictionary).Contains(item)
189 || (_mergedInstance != null && _mergedInstance.Contains(item));
192 void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
194 ((ICollection<KeyValuePair<string, object>>)_innerDictionary).CopyTo(array, arrayIndex);
197 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
198 [EditorBrowsable(EditorBrowsableState.Never)]
201 get { return _innerDictionary.Count; }
204 bool ICollection<KeyValuePair<string, object>>.IsReadOnly
206 get { return ((ICollection<KeyValuePair<string, object>>)_innerDictionary).IsReadOnly; }
209 bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
211 return ((ICollection<KeyValuePair<string, object>>)_innerDictionary).Remove(item);
214 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
215 [EditorBrowsable(EditorBrowsableState.Never)]
216 public void Add(string key, object value)
218 if (ContainsKey(key))
219 throw new ArgumentException($"A resource with the key '{key}' is already present in the ResourceDictionary.");
220 _innerDictionary.Add(key, value);
221 OnValueChanged(key, value);
224 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
225 [EditorBrowsable(EditorBrowsableState.Never)]
226 public bool ContainsKey(string key)
228 return _innerDictionary.ContainsKey(key);
232 /// Gets or sets the value according to index.
234 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
235 [EditorBrowsable(EditorBrowsableState.Never)]
236 [IndexerName("Item")]
237 public object this[string index]
241 if (_innerDictionary.ContainsKey(index))
242 return _innerDictionary[index];
243 if (_mergedInstance != null && _mergedInstance.ContainsKey(index))
244 return _mergedInstance[index];
245 if (MergedDictionaries != null)
246 foreach (var dict in MergedDictionaries.Reverse())
247 if (dict.ContainsKey(index))
249 throw new KeyNotFoundException($"The resource '{index}' is not present in the dictionary.");
253 _innerDictionary[index] = value;
254 OnValueChanged(index, value);
258 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
259 [EditorBrowsable(EditorBrowsableState.Never)]
260 public ICollection<string> Keys
262 get { return _innerDictionary.Keys; }
265 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
266 [EditorBrowsable(EditorBrowsableState.Never)]
267 public bool Remove(string key)
269 return _innerDictionary.Remove(key);
272 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
273 [EditorBrowsable(EditorBrowsableState.Never)]
274 public ICollection<object> Values
276 get { return _innerDictionary.Values; }
279 IEnumerator IEnumerable.GetEnumerator()
281 return GetEnumerator();
284 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
285 [EditorBrowsable(EditorBrowsableState.Never)]
286 public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
288 return _innerDictionary.GetEnumerator();
291 internal IEnumerable<KeyValuePair<string, object>> MergedResources {
293 if (MergedDictionaries != null)
294 foreach (var r in MergedDictionaries.Reverse().SelectMany(x => x.MergedResources))
296 if (_mergedInstance != null)
297 foreach (var r in _mergedInstance.MergedResources)
299 foreach (var r in _innerDictionary)
304 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
305 [EditorBrowsable(EditorBrowsableState.Never)]
306 public bool TryGetValue(string key, out object value)
308 return _innerDictionary.TryGetValue(key, out value)
309 || (_mergedInstance != null && _mergedInstance.TryGetValue(key, out value))
310 || (MergedDictionaries != null && TryGetMergedDictionaryValue(key, out value));
313 bool TryGetMergedDictionaryValue(string key, out object value)
315 foreach (var dictionary in MergedDictionaries.Reverse())
316 if (dictionary.TryGetValue(key, out value))
323 event EventHandler<ResourcesChangedEventArgs> IResourceDictionary.ValuesChanged
325 add { ValuesChanged += value; }
326 remove { ValuesChanged -= value; }
329 internal void Add(Style style)
331 if (string.IsNullOrEmpty(style.Class))
332 Add(style.TargetType.FullName, style);
335 IList<Style> classes;
337 if (!TryGetValue(Style.StyleClassPrefix + style.Class, out outclasses) || (classes = outclasses as IList<Style>) == null)
338 classes = new List<Style>();
340 this[Style.StyleClassPrefix + style.Class] = classes;
344 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
345 [EditorBrowsable(EditorBrowsableState.Never)]
346 public void Add(ResourceDictionary mergedResourceDictionary)
348 MergedDictionaries.Add(mergedResourceDictionary);
351 internal void Add(StyleSheets.StyleSheet styleSheet)
353 StyleSheets = StyleSheets ?? new List<StyleSheets.StyleSheet>(2);
354 StyleSheets.Add(styleSheet);
355 ValuesChanged?.Invoke(this, ResourcesChangedEventArgs.StyleSheets);
358 void OnValueChanged(string key, object value)
360 OnValuesChanged(new KeyValuePair<string, object>(key, value));
363 void OnValuesChanged(params KeyValuePair<string, object>[] values)
365 if (values == null || values.Length == 0)
367 ValuesChanged?.Invoke(this, new ResourcesChangedEventArgs(values));
370 event EventHandler<ResourcesChangedEventArgs> ValuesChanged;
372 [Xaml.ProvideCompiled("Tizen.NUI.Xaml.Core.XamlC.RDSourceTypeConverter")]
373 internal class RDSourceTypeConverter : TypeConverter, IExtendedTypeConverter
375 object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider)
377 if (serviceProvider == null)
378 throw new ArgumentNullException(nameof(serviceProvider));
380 var targetRD = (serviceProvider.GetService(typeof(Xaml.IProvideValueTarget)) as Xaml.IProvideValueTarget)?.TargetObject as ResourceDictionary;
381 if (targetRD == null)
384 var rootObjectType = (serviceProvider.GetService(typeof(Xaml.IRootObjectProvider)) as Xaml.IRootObjectProvider)?.RootObject.GetType();
385 if (rootObjectType == null)
388 var lineInfo = (serviceProvider.GetService(typeof(Xaml.IXmlLineInfoProvider)) as Xaml.IXmlLineInfoProvider)?.XmlLineInfo;
389 var rootTargetPath = XamlResourceIdAttribute.GetPathForType(rootObjectType);
390 var uri = new Uri(value, UriKind.Relative); //we don't want file:// uris, even if they start with '/'
391 var resourcePath = GetResourcePath(uri, rootTargetPath);
393 targetRD.SetAndLoadSource(uri, resourcePath, rootObjectType.GetTypeInfo().Assembly, lineInfo);
397 internal static string GetResourcePath(Uri uri, string rootTargetPath)
399 //need a fake scheme so it's not seen as file:// uri, and the forward slashes are valid on all plats
400 var resourceUri = uri.OriginalString.StartsWith("/", StringComparison.Ordinal)
401 ? new Uri($"pack://{uri.OriginalString}", UriKind.Absolute)
402 : new Uri($"pack:///{rootTargetPath}/../{uri.OriginalString}", UriKind.Absolute);
404 //drop the leading '/'
405 return resourceUri.AbsolutePath.Substring(1);
408 object IExtendedTypeConverter.ConvertFrom(CultureInfo culture, object value, IServiceProvider serviceProvider)
410 throw new NotImplementedException();
413 public override object ConvertFromInvariantString(string value)
415 throw new NotImplementedException();