A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

232 lines
6.3 KiB

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using Avalonia.Collections;
using Avalonia.Controls.Templates;
#nullable enable
namespace Avalonia.Controls
{
/// <summary>
/// An indexed dictionary of resources.
/// </summary>
public class ResourceDictionary : AvaloniaDictionary<object, object?>, IResourceDictionary
{
private IResourceHost? _owner;
private AvaloniaList<IResourceProvider>? _mergedDictionaries;
/// <summary>
/// Initializes a new instance of the <see cref="ResourceDictionary"/> class.
/// </summary>
public ResourceDictionary()
{
CollectionChanged += OnCollectionChanged;
}
/// <summary>
/// Initializes a new instance of the <see cref="ResourceDictionary"/> class.
/// </summary>
public ResourceDictionary(IResourceHost owner)
: this()
{
Owner = owner;
}
public IResourceHost? Owner
{
get => _owner;
private set
{
if (_owner != value)
{
_owner = value;
OwnerChanged?.Invoke(this, EventArgs.Empty);
}
}
}
public IList<IResourceProvider> MergedDictionaries
{
get
{
if (_mergedDictionaries == null)
{
_mergedDictionaries = new AvaloniaList<IResourceProvider>();
_mergedDictionaries.ResetBehavior = ResetBehavior.Remove;
_mergedDictionaries.ForEachItem(
x =>
{
if (Owner is object)
{
x.AddOwner(Owner);
}
},
x =>
{
if (Owner is object)
{
x.RemoveOwner(Owner);
}
}, null);
}
return _mergedDictionaries;
}
}
bool IResourceNode.HasResources
{
get
{
if (Count > 0)
{
return true;
}
if (_mergedDictionaries?.Count > 0)
{
foreach (var i in _mergedDictionaries)
{
if (i.HasResources)
{
return true;
}
}
}
return false;
}
}
public event EventHandler? OwnerChanged;
public void Add(object key, Func<IServiceProvider?, object> loader)
{
Add(key, new LazyItem(loader));
}
public bool TryGetResource(object key, out object? value)
{
if (TryGetValue(key, out value))
{
return true;
}
if (_mergedDictionaries != null)
{
for (var i = _mergedDictionaries.Count - 1; i >= 0; --i)
{
if (_mergedDictionaries[i].TryGetResource(key, out value))
{
return true;
}
}
}
return false;
}
void IResourceProvider.AddOwner(IResourceHost owner)
{
owner = owner ?? throw new ArgumentNullException(nameof(owner));
if (Owner != null)
{
throw new InvalidOperationException("The ResourceDictionary already has a parent.");
}
Owner = owner;
var hasResources = Count > 0;
if (_mergedDictionaries is object)
{
foreach (var i in _mergedDictionaries)
{
i.AddOwner(owner);
hasResources |= i.HasResources;
}
}
if (hasResources)
{
owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
}
void IResourceProvider.RemoveOwner(IResourceHost owner)
{
owner = owner ?? throw new ArgumentNullException(nameof(owner));
if (Owner == owner)
{
Owner = null;
var hasResources = Count > 0;
if (_mergedDictionaries is object)
{
foreach (var i in _mergedDictionaries)
{
i.RemoveOwner(owner);
hasResources |= i.HasResources;
}
}
if (hasResources)
{
owner.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
}
}
protected override object? GetItemCore(object key)
{
var item = base.GetItemCore(key);
if (item is LazyItem lazy)
{
var value = lazy.Load();
this[key] = value;
return value;
}
else
{
return item;
}
}
protected override bool TryGetValueCore(object key, out object? value)
{
if (base.TryGetValueCore(key, out value))
{
if (value is LazyItem lazy)
{
value = lazy.Load();
this[key] = value;
}
return true;
}
return false;
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
private class LazyItem
{
private Func<IServiceProvider?, object?> _load;
public LazyItem(Func<IServiceProvider?, object?> load) => _load = load;
public object? Load()
{
var result = _load(null);
return result is TemplateResult<object> t ? t.Result : result;
}
}
}
}