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
43 get { return _mergedWith; }
46 if (_mergedWith == value)
50 throw new ArgumentException("MergedWith can not be used with Source");
52 if (!typeof(ResourceDictionary).GetTypeInfo().IsAssignableFrom(value.GetTypeInfo()))
53 throw new ArgumentException("MergedWith should inherit from ResourceDictionary");
56 if (_mergedWith == null)
59 _mergedInstance = s_instances.GetValue(_mergedWith, (key) => (ResourceDictionary)Activator.CreateInstance(key));
60 OnValuesChanged(_mergedInstance.ToArray());
65 /// Gets or sets the URI of the merged resource dictionary.
67 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
68 [EditorBrowsable(EditorBrowsableState.Never)]
69 [TypeConverter(typeof(RDSourceTypeConverter))]
72 get { return _source; }
77 throw new InvalidOperationException("Source can only be set from XAML."); //through the RDSourceTypeConverter
82 /// To set and load source.
84 /// <param name="value">The source.</param>
85 /// <param name="resourcePath">The resource path.</param>
86 /// <param name="assembly">The assembly.</param>
87 /// <param name="lineInfo">The xml line info.</param>
88 /// Used by the XamlC compiled converter.
89 [EditorBrowsable(EditorBrowsableState.Never)]
90 public void SetAndLoadSource(Uri value, string resourcePath, Assembly assembly, System.Xml.IXmlLineInfo lineInfo)
93 if (_mergedWith != null)
94 throw new ArgumentException("Source can not be used with MergedWith");
96 //this will return a type if the RD as an x:Class element, and codebehind
97 var type = XamlResourceIdAttribute.GetTypeForPath(assembly, resourcePath);
99 _mergedInstance = s_instances.GetValue(type, (key) => (ResourceDictionary)Activator.CreateInstance(key));
101 _mergedInstance = DependencyService.Get<IResourcesLoader>()?.CreateFromResource<ResourceDictionary>(resourcePath, assembly, lineInfo);
102 OnValuesChanged(_mergedInstance.ToArray());
105 ICollection<ResourceDictionary> _mergedDictionaries;
107 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
108 [EditorBrowsable(EditorBrowsableState.Never)]
109 public ICollection<ResourceDictionary> MergedDictionaries
113 if (_mergedDictionaries == null)
115 var col = new ObservableCollection<ResourceDictionary>();
116 col.CollectionChanged += MergedDictionaries_CollectionChanged;
117 _mergedDictionaries = col;
119 return _mergedDictionaries;
123 internal IList<StyleSheets.StyleSheet> StyleSheets { get; set; }
125 void StyleSheetsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
129 case NotifyCollectionChangedAction.Add:
130 ValuesChanged?.Invoke(this, ResourcesChangedEventArgs.StyleSheets);
134 IList<ResourceDictionary> _collectionTrack;
136 void MergedDictionaries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
138 // Move() isn't exposed by ICollection
139 if (e.Action == NotifyCollectionChangedAction.Move)
142 _collectionTrack = _collectionTrack ?? new List<ResourceDictionary>();
143 // Collection has been cleared
144 if (e.Action == NotifyCollectionChangedAction.Reset)
146 foreach (var dictionary in _collectionTrack)
147 dictionary.ValuesChanged -= Item_ValuesChanged;
149 _collectionTrack.Clear();
154 if (e.NewItems != null)
156 foreach (var item in e.NewItems)
158 var rd = (ResourceDictionary)item;
159 _collectionTrack.Add(rd);
160 rd.ValuesChanged += Item_ValuesChanged;
161 OnValuesChanged(rd.ToArray());
166 if (e.OldItems != null)
168 foreach (var item in e.OldItems)
170 var rd = (ResourceDictionary)item;
171 rd.ValuesChanged -= Item_ValuesChanged;
172 _collectionTrack.Remove(rd);
177 void Item_ValuesChanged(object sender, ResourcesChangedEventArgs e)
179 OnValuesChanged(e.Values.ToArray());
182 void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
184 ((ICollection<KeyValuePair<string, object>>)_innerDictionary).Add(item);
185 OnValuesChanged(item);
188 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
189 [EditorBrowsable(EditorBrowsableState.Never)]
192 _innerDictionary.Clear();
195 bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
197 return ((ICollection<KeyValuePair<string, object>>)_innerDictionary).Contains(item)
198 || (_mergedInstance != null && _mergedInstance.Contains(item));
201 void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
203 ((ICollection<KeyValuePair<string, object>>)_innerDictionary).CopyTo(array, arrayIndex);
206 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
207 [EditorBrowsable(EditorBrowsableState.Never)]
210 get { return _innerDictionary.Count; }
213 bool ICollection<KeyValuePair<string, object>>.IsReadOnly
215 get { return ((ICollection<KeyValuePair<string, object>>)_innerDictionary).IsReadOnly; }
218 bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
220 return ((ICollection<KeyValuePair<string, object>>)_innerDictionary).Remove(item);
223 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
224 [EditorBrowsable(EditorBrowsableState.Never)]
225 public void Add(string key, object value)
227 if (ContainsKey(key))
228 throw new ArgumentException($"A resource with the key '{key}' is already present in the ResourceDictionary.");
229 _innerDictionary.Add(key, value);
230 OnValueChanged(key, value);
233 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
234 [EditorBrowsable(EditorBrowsableState.Never)]
235 public bool ContainsKey(string key)
237 return _innerDictionary.ContainsKey(key);
241 /// Gets or sets the value according to index.
243 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
244 [EditorBrowsable(EditorBrowsableState.Never)]
245 [IndexerName("Item")]
246 public object this[string index]
250 if (_innerDictionary.ContainsKey(index))
251 return _innerDictionary[index];
252 if (_mergedInstance != null && _mergedInstance.ContainsKey(index))
253 return _mergedInstance[index];
254 if (MergedDictionaries != null)
255 foreach (var dict in MergedDictionaries.Reverse())
256 if (dict.ContainsKey(index))
258 throw new KeyNotFoundException($"The resource '{index}' is not present in the dictionary.");
262 _innerDictionary[index] = value;
263 OnValueChanged(index, value);
267 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
268 [EditorBrowsable(EditorBrowsableState.Never)]
269 public ICollection<string> Keys
271 get { return _innerDictionary.Keys; }
274 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
275 [EditorBrowsable(EditorBrowsableState.Never)]
276 public bool Remove(string key)
278 return _innerDictionary.Remove(key);
281 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
282 [EditorBrowsable(EditorBrowsableState.Never)]
283 public ICollection<object> Values
285 get { return _innerDictionary.Values; }
288 IEnumerator IEnumerable.GetEnumerator()
290 return GetEnumerator();
293 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
294 [EditorBrowsable(EditorBrowsableState.Never)]
295 public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
297 return _innerDictionary.GetEnumerator();
300 internal IEnumerable<KeyValuePair<string, object>> MergedResources
304 if (MergedDictionaries != null)
305 foreach (var r in MergedDictionaries.Reverse().SelectMany(x => x.MergedResources))
307 if (_mergedInstance != null)
308 foreach (var r in _mergedInstance.MergedResources)
310 foreach (var r in _innerDictionary)
315 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
316 [EditorBrowsable(EditorBrowsableState.Never)]
317 public bool TryGetValue(string key, out object value)
319 return _innerDictionary.TryGetValue(key, out value)
320 || (_mergedInstance != null && _mergedInstance.TryGetValue(key, out value))
321 || (MergedDictionaries != null && TryGetMergedDictionaryValue(key, out value));
324 bool TryGetMergedDictionaryValue(string key, out object value)
326 foreach (var dictionary in MergedDictionaries.Reverse())
327 if (dictionary.TryGetValue(key, out value))
334 event EventHandler<ResourcesChangedEventArgs> IResourceDictionary.ValuesChanged
336 add { ValuesChanged += value; }
337 remove { ValuesChanged -= value; }
340 internal void Add(Style style)
342 if (string.IsNullOrEmpty(style.Class))
343 Add(style.TargetType.FullName, style);
346 IList<Style> classes;
348 if (!TryGetValue(Style.StyleClassPrefix + style.Class, out outclasses) || (classes = outclasses as IList<Style>) == null)
349 classes = new List<Style>();
351 this[Style.StyleClassPrefix + style.Class] = classes;
355 /// This will be public opened in tizen_5.0 after ACR done. Before ACR, need to be hidden as inhouse API.
356 [EditorBrowsable(EditorBrowsableState.Never)]
357 public void Add(ResourceDictionary mergedResourceDictionary)
359 MergedDictionaries.Add(mergedResourceDictionary);
362 internal void Add(StyleSheets.StyleSheet styleSheet)
364 StyleSheets = StyleSheets ?? new List<StyleSheets.StyleSheet>(2);
365 StyleSheets.Add(styleSheet);
366 ValuesChanged?.Invoke(this, ResourcesChangedEventArgs.StyleSheets);
369 void OnValueChanged(string key, object value)
371 OnValuesChanged(new KeyValuePair<string, object>(key, value));
374 void OnValuesChanged(params KeyValuePair<string, object>[] values)
376 if (values == null || values.Length == 0)
378 ValuesChanged?.Invoke(this, new ResourcesChangedEventArgs(values));
381 event EventHandler<ResourcesChangedEventArgs> ValuesChanged;
383 [Xaml.ProvideCompiled("Tizen.NUI.Xaml.Core.XamlC.RDSourceTypeConverter")]
384 internal class RDSourceTypeConverter : TypeConverter, IExtendedTypeConverter
386 object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider)
388 if (serviceProvider == null)
389 throw new ArgumentNullException(nameof(serviceProvider));
391 var targetRD = (serviceProvider.GetService(typeof(Xaml.IProvideValueTarget)) as Xaml.IProvideValueTarget)?.TargetObject as ResourceDictionary;
392 if (targetRD == null)
395 var rootObjectType = (serviceProvider.GetService(typeof(Xaml.IRootObjectProvider)) as Xaml.IRootObjectProvider)?.RootObject.GetType();
396 if (rootObjectType == null)
399 var lineInfo = (serviceProvider.GetService(typeof(Xaml.IXmlLineInfoProvider)) as Xaml.IXmlLineInfoProvider)?.XmlLineInfo;
400 var rootTargetPath = XamlResourceIdAttribute.GetPathForType(rootObjectType);
401 var uri = new Uri(value, UriKind.Relative); //we don't want file:// uris, even if they start with '/'
402 var resourcePath = GetResourcePath(uri, rootTargetPath);
404 targetRD.SetAndLoadSource(uri, resourcePath, rootObjectType.GetTypeInfo().Assembly, lineInfo);
408 internal static string GetResourcePath(Uri uri, string rootTargetPath)
410 //need a fake scheme so it's not seen as file:// uri, and the forward slashes are valid on all plats
411 var resourceUri = uri.OriginalString.StartsWith("/", StringComparison.Ordinal)
412 ? new Uri($"pack://{uri.OriginalString}", UriKind.Absolute)
413 : new Uri($"pack:///{rootTargetPath}/../{uri.OriginalString}", UriKind.Absolute);
415 //drop the leading '/'
416 return resourceUri.AbsolutePath.Substring(1);
419 object IExtendedTypeConverter.ConvertFrom(CultureInfo culture, object value, IServiceProvider serviceProvider)
421 throw new NotImplementedException();
424 public override object ConvertFromInvariantString(string value)
426 throw new NotImplementedException();