committed by
GitHub
49 changed files with 2848 additions and 349 deletions
@ -1,6 +1,7 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<DefaultTestTimeout>1000</DefaultTestTimeout> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
<PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,9 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<IgnoredTests> |
|||
<NamedTestSelector> |
|||
<TestName>Avalonia.Markup.UnitTests.Data.Plugins.DataAnnotationsValidationPluginTests.Produces_Aggregate_BindingNotificationsx</TestName> |
|||
</NamedTestSelector> |
|||
</IgnoredTests> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -1,6 +1,7 @@ |
|||
<ProjectConfiguration> |
|||
<Settings> |
|||
<DefaultTestTimeout>1000</DefaultTestTimeout> |
|||
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
|||
<PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully> |
|||
</Settings> |
|||
</ProjectConfiguration> |
|||
@ -0,0 +1,19 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// An indexed dictionary of resources.
|
|||
/// </summary>
|
|||
public interface IResourceDictionary : IResourceProvider, IDictionary<object, object> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a collection of child resource dictionaries.
|
|||
/// </summary>
|
|||
IList<IResourceProvider> MergedDictionaries { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Represents resource provider in a tree.
|
|||
/// </summary>
|
|||
public interface IResourceNode : IResourceProvider |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the parent resource node, if any.
|
|||
/// </summary>
|
|||
IResourceNode ResourceParent { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// Represents an object that can be queried for resources.
|
|||
/// </summary>
|
|||
public interface IResourceProvider |
|||
{ |
|||
/// <summary>
|
|||
/// Raised when resources in the provider are changed.
|
|||
/// </summary>
|
|||
event EventHandler<ResourcesChangedEventArgs> ResourcesChanged; |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether the element has resources.
|
|||
/// </summary>
|
|||
bool HasResources { get; } |
|||
|
|||
/// <summary>
|
|||
/// Tries to find a resource within the provider.
|
|||
/// </summary>
|
|||
/// <param name="key">The resource key.</param>
|
|||
/// <param name="value">
|
|||
/// When this method returns, contains the value associated with the specified key,
|
|||
/// if the key is found; otherwise, null.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// True if the resource if found, otherwise false.
|
|||
/// </returns>
|
|||
bool TryGetResource(string key, out object value); |
|||
} |
|||
} |
|||
@ -0,0 +1,101 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Specialized; |
|||
using System.Linq; |
|||
using Avalonia.Collections; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
/// <summary>
|
|||
/// An indexed dictionary of resources.
|
|||
/// </summary>
|
|||
public class ResourceDictionary : AvaloniaDictionary<object, object>, IResourceDictionary |
|||
{ |
|||
private AvaloniaList<IResourceProvider> _mergedDictionaries; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="ResourceDictionary"/> class.
|
|||
/// </summary>
|
|||
public ResourceDictionary() |
|||
{ |
|||
CollectionChanged += OnCollectionChanged; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged; |
|||
|
|||
/// <inheritdoc/>
|
|||
public IList<IResourceProvider> MergedDictionaries |
|||
{ |
|||
get |
|||
{ |
|||
if (_mergedDictionaries == null) |
|||
{ |
|||
_mergedDictionaries = new AvaloniaList<IResourceProvider>(); |
|||
_mergedDictionaries.ResetBehavior = ResetBehavior.Remove; |
|||
_mergedDictionaries.ForEachItem( |
|||
x => |
|||
{ |
|||
if (x.HasResources) |
|||
{ |
|||
OnResourcesChanged(); |
|||
} |
|||
|
|||
x.ResourcesChanged += MergedDictionaryResourcesChanged; |
|||
}, |
|||
x => |
|||
{ |
|||
if (x.HasResources) |
|||
{ |
|||
OnResourcesChanged(); |
|||
} |
|||
|
|||
x.ResourcesChanged -= MergedDictionaryResourcesChanged; |
|||
}, |
|||
() => { }); |
|||
} |
|||
|
|||
return _mergedDictionaries; |
|||
} |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
bool IResourceProvider.HasResources |
|||
{ |
|||
get => Count > 0 || (_mergedDictionaries?.Any(x => x.HasResources) ?? false); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool TryGetResource(string 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; |
|||
} |
|||
|
|||
private void OnResourcesChanged() |
|||
{ |
|||
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); |
|||
} |
|||
|
|||
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => OnResourcesChanged(); |
|||
private void MergedDictionaryResourcesChanged(object sender, ResourcesChangedEventArgs e) => OnResourcesChanged(); |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
using System; |
|||
using System.Reactive; |
|||
using System.Reactive.Linq; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public static class ResourceProviderExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Finds the specified resource by searching up the logical tree and then global styles.
|
|||
/// </summary>
|
|||
/// <param name="control">The control.</param>
|
|||
/// <param name="key">The resource key.</param>
|
|||
/// <returns>The resource, or <see cref="AvaloniaProperty.UnsetValue"/> if not found.</returns>
|
|||
public static object FindResource(this IResourceNode control, string key) |
|||
{ |
|||
if (control.TryFindResource(key, out var value)) |
|||
{ |
|||
return value; |
|||
} |
|||
|
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Tries to the specified resource by searching up the logical tree and then global styles.
|
|||
/// </summary>
|
|||
/// <param name="control">The control.</param>
|
|||
/// <param name="key">The resource key.</param>
|
|||
/// <param name="value">On return, contains the resource if found, otherwise null.</param>
|
|||
/// <returns>True if the resource was found; otherwise false.</returns>
|
|||
public static bool TryFindResource(this IResourceNode control, string key, out object value) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(control != null); |
|||
Contract.Requires<ArgumentNullException>(key != null); |
|||
|
|||
var current = control; |
|||
|
|||
while (current != null) |
|||
{ |
|||
if (current is IResourceNode host) |
|||
{ |
|||
if (host.TryGetResource(key, out value)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
current = current.ResourceParent; |
|||
} |
|||
|
|||
value = null; |
|||
return false; |
|||
} |
|||
|
|||
public static IObservable<object> GetResourceObservable(this IResourceNode target, string key) |
|||
{ |
|||
return Observable.FromEventPattern<ResourcesChangedEventArgs>( |
|||
x => target.ResourcesChanged += x, |
|||
x => target.ResourcesChanged -= x) |
|||
.StartWith((EventPattern<ResourcesChangedEventArgs>)null) |
|||
.Select(x => target.FindResource(key)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class ResourcesChangedEventArgs : EventArgs |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using Avalonia.Controls; |
|||
|
|||
namespace Avalonia.Styling |
|||
{ |
|||
/// <summary>
|
|||
/// Defines an interface through which a <see cref="Style"/>'s parent can be set.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// You should not usually need to use this interface - it is for internal use only.
|
|||
/// </remarks>
|
|||
public interface ISetStyleParent : IStyle |
|||
{ |
|||
/// <summary>
|
|||
/// Sets the style parent.
|
|||
/// </summary>
|
|||
/// <param name="parent">The parent.</param>
|
|||
void SetParent(IResourceNode parent); |
|||
|
|||
/// <summary>
|
|||
/// Notifies the style that a change has been made to resources that apply to it.
|
|||
/// </summary>
|
|||
/// <param name="e">The event args.</param>
|
|||
/// <remarks>
|
|||
/// This method will be called automatically by the framework, you should not need to call
|
|||
/// this method yourself.
|
|||
/// </remarks>
|
|||
void NotifyResourcesChanged(ResourcesChangedEventArgs e); |
|||
} |
|||
} |
|||
@ -1,42 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
|
|||
namespace Avalonia.Styling |
|||
{ |
|||
public static class StyleExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Tries to find a named style resource.
|
|||
/// </summary>
|
|||
/// <param name="control">The control from which to find the resource.</param>
|
|||
/// <param name="name">The resource name.</param>
|
|||
/// <returns>
|
|||
/// The resource if found, otherwise <see cref="AvaloniaProperty.UnsetValue"/>.
|
|||
/// </returns>
|
|||
public static object FindStyleResource(this IStyleHost control, string name) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(control != null); |
|||
Contract.Requires<ArgumentNullException>(name != null); |
|||
Contract.Requires<ArgumentException>(!string.IsNullOrWhiteSpace(name)); |
|||
|
|||
while (control != null) |
|||
{ |
|||
if (control.IsStylesInitialized) |
|||
{ |
|||
var result = control.Styles.FindResource(name); |
|||
|
|||
if (result != AvaloniaProperty.UnsetValue) |
|||
{ |
|||
return result; |
|||
} |
|||
} |
|||
|
|||
control = control.StylingParent; |
|||
} |
|||
|
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
} |
|||
} |
|||
@ -1,90 +0,0 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Styling |
|||
{ |
|||
/// <summary>
|
|||
/// Holds resources for a <see cref="Style"/>.
|
|||
/// </summary>
|
|||
public class StyleResources : IDictionary<string, object>, IDictionary |
|||
{ |
|||
private Dictionary<string, object> _inner = new Dictionary<string, object>(); |
|||
|
|||
public object this[string key] |
|||
{ |
|||
get { return _inner[key]; } |
|||
set { _inner[key] = value; } |
|||
} |
|||
|
|||
public int Count => _inner.Count; |
|||
|
|||
ICollection<string> IDictionary<string, object>.Keys => _inner.Keys; |
|||
|
|||
ICollection<object> IDictionary<string, object>.Values => _inner.Values; |
|||
|
|||
bool ICollection<KeyValuePair<string, object>>.IsReadOnly => false; |
|||
|
|||
object IDictionary.this[object key] |
|||
{ |
|||
get { return ((IDictionary)_inner)[key]; } |
|||
set { ((IDictionary)_inner)[key] = value; } |
|||
} |
|||
|
|||
ICollection IDictionary.Keys => _inner.Keys; |
|||
|
|||
ICollection IDictionary.Values => _inner.Values; |
|||
|
|||
bool ICollection.IsSynchronized => false; |
|||
|
|||
object ICollection.SyncRoot => ((IDictionary)_inner).SyncRoot; |
|||
|
|||
bool IDictionary.IsFixedSize => false; |
|||
|
|||
bool IDictionary.IsReadOnly => false; |
|||
|
|||
public void Add(string key, object value) => _inner.Add(key, value); |
|||
|
|||
public void Clear() => _inner.Clear(); |
|||
|
|||
public bool ContainsKey(string key) => _inner.ContainsKey(key); |
|||
|
|||
public bool Remove(string key) => _inner.Remove(key); |
|||
|
|||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => _inner.GetEnumerator(); |
|||
|
|||
public bool TryGetValue(string key, out object value) => _inner.TryGetValue(key, out value); |
|||
|
|||
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item) |
|||
{ |
|||
return ((IDictionary<string, object>)_inner).Contains(item); |
|||
} |
|||
|
|||
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item) |
|||
{ |
|||
((IDictionary<string, object>)_inner).Add(item); |
|||
} |
|||
|
|||
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex) |
|||
{ |
|||
((IDictionary<string, object>)_inner).CopyTo(array, arrayIndex); |
|||
} |
|||
|
|||
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item) |
|||
{ |
|||
return ((IDictionary<string, object>)_inner).Remove(item); |
|||
} |
|||
|
|||
void ICollection.CopyTo(Array array, int index) => ((IDictionary)_inner).CopyTo(array, index); |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator(); |
|||
|
|||
IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)_inner).GetEnumerator(); |
|||
|
|||
void IDictionary.Add(object key, object value) => ((IDictionary)_inner).Add(key, value); |
|||
|
|||
bool IDictionary.Contains(object key) => ((IDictionary)_inner).Contains(key); |
|||
|
|||
void IDictionary.Remove(object key) => ((IDictionary)_inner).Remove(key); |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
using System; |
|||
using System.ComponentModel; |
|||
using Avalonia.Controls; |
|||
using Portable.Xaml.ComponentModel; |
|||
using Portable.Xaml.Markup; |
|||
|
|||
namespace Avalonia.Markup.Xaml.Data |
|||
{ |
|||
/// <summary>
|
|||
/// Loads a resource dictionary from a specified URL.
|
|||
/// </summary>
|
|||
public class ResourceInclude : MarkupExtension, IResourceProvider |
|||
{ |
|||
private Uri _baseUri; |
|||
private IResourceDictionary _loaded; |
|||
|
|||
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged; |
|||
|
|||
/// <summary>
|
|||
/// Gets the loaded resource dictionary.
|
|||
/// </summary>
|
|||
public IResourceDictionary Loaded |
|||
{ |
|||
get |
|||
{ |
|||
if (_loaded == null) |
|||
{ |
|||
var loader = new AvaloniaXamlLoader(); |
|||
_loaded = (IResourceDictionary)loader.Load(Source, _baseUri); |
|||
|
|||
if (_loaded.HasResources) |
|||
{ |
|||
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs()); |
|||
} |
|||
} |
|||
|
|||
return _loaded; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the source URL.
|
|||
/// </summary>
|
|||
public Uri Source { get; set; } |
|||
|
|||
/// <inhertidoc/>
|
|||
bool IResourceProvider.HasResources => Loaded.HasResources; |
|||
|
|||
/// <inhertidoc/>
|
|||
bool IResourceProvider.TryGetResource(string key, out object value) |
|||
{ |
|||
return Loaded.TryGetResource(key, out value); |
|||
} |
|||
|
|||
/// <inhertidoc/>
|
|||
public override object ProvideValue(IServiceProvider serviceProvider) |
|||
{ |
|||
var tdc = (ITypeDescriptorContext)serviceProvider; |
|||
_baseUri = tdc?.GetBaseUri(); |
|||
return this; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.ComponentModel; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Data; |
|||
using Portable.Xaml; |
|||
using Portable.Xaml.ComponentModel; |
|||
using Portable.Xaml.Markup; |
|||
|
|||
namespace Avalonia.Markup.Xaml.MarkupExtensions |
|||
{ |
|||
public class DynamicResourceExtension : MarkupExtension, IBinding |
|||
{ |
|||
private IResourceNode _anchor; |
|||
|
|||
public DynamicResourceExtension() |
|||
{ |
|||
} |
|||
|
|||
public DynamicResourceExtension(string resourceKey) |
|||
{ |
|||
ResourceKey = resourceKey; |
|||
} |
|||
|
|||
public string ResourceKey { get; set; } |
|||
|
|||
public override object ProvideValue(IServiceProvider serviceProvider) |
|||
{ |
|||
var context = (ITypeDescriptorContext)serviceProvider; |
|||
var provideTarget = context.GetService<IProvideValueTarget>(); |
|||
|
|||
if (!(provideTarget.TargetObject is IResourceNode)) |
|||
{ |
|||
_anchor = GetAnchor<IResourceNode>(context); |
|||
} |
|||
|
|||
return this; |
|||
} |
|||
|
|||
InstancedBinding IBinding.Initiate( |
|||
IAvaloniaObject target, |
|||
AvaloniaProperty targetProperty, |
|||
object anchor, |
|||
bool enableDataValidation) |
|||
{ |
|||
var control = target as IResourceNode ?? _anchor; |
|||
|
|||
if (control != null) |
|||
{ |
|||
return new InstancedBinding(control.GetResourceObservable(ResourceKey)); |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private T GetAnchor<T>(ITypeDescriptorContext context) where T : class |
|||
{ |
|||
var schemaContext = context.GetService<IXamlSchemaContextProvider>().SchemaContext; |
|||
var ambientProvider = context.GetService<IAmbientProvider>(); |
|||
var xamlType = schemaContext.GetXamlType(typeof(T)); |
|||
|
|||
// We override XamlType.CanAssignTo in BindingXamlType so the results we get back
|
|||
// from GetAllAmbientValues aren't necessarily of the correct type.
|
|||
return ambientProvider.GetAllAmbientValues(xamlType).OfType<T>().FirstOrDefault(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Reflection; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Markup.Xaml.Data; |
|||
using Portable.Xaml; |
|||
using Portable.Xaml.ComponentModel; |
|||
using Portable.Xaml.Markup; |
|||
|
|||
namespace Avalonia.Markup.Xaml.MarkupExtensions |
|||
{ |
|||
public class StaticResourceExtension : MarkupExtension |
|||
{ |
|||
public StaticResourceExtension() |
|||
{ |
|||
} |
|||
|
|||
public StaticResourceExtension(string resourceKey) |
|||
{ |
|||
ResourceKey = resourceKey; |
|||
} |
|||
|
|||
public string ResourceKey { get; set; } |
|||
|
|||
public override object ProvideValue(IServiceProvider serviceProvider) |
|||
{ |
|||
var context = (ITypeDescriptorContext)serviceProvider; |
|||
var schemaContext = context.GetService<IXamlSchemaContextProvider>().SchemaContext; |
|||
var ambientProvider = context.GetService<IAmbientProvider>(); |
|||
var resourceProviderType = schemaContext.GetXamlType(typeof(IResourceNode)); |
|||
var ambientValues = ambientProvider.GetAllAmbientValues(resourceProviderType); |
|||
|
|||
// Look upwards though the ambient context for IResourceProviders which might be able
|
|||
// to give us the resource.
|
|||
//
|
|||
// TODO: If we're in a template then only the ambient values since the root of the
|
|||
// template wil be included here. We need some way to get hold of the parent ambient
|
|||
// context and search that. See the test:
|
|||
//
|
|||
// StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File
|
|||
//
|
|||
foreach (var ambientValue in ambientValues) |
|||
{ |
|||
// We override XamlType.CanAssignTo in BindingXamlType so the results we get back
|
|||
// from GetAllAmbientValues aren't necessarily of the correct type.
|
|||
if (ambientValue is IResourceNode resourceProvider) |
|||
{ |
|||
if (resourceProvider is IControl control && control.StylingParent != null) |
|||
{ |
|||
// If we've got to a control that has a StylingParent then it's probably
|
|||
// a top level control and its StylingParent is pointing to the global
|
|||
// styles. If this is case just do a FindResource on it.
|
|||
return control.FindResource(ResourceKey); |
|||
} |
|||
else if (resourceProvider.TryGetResource(ResourceKey, out var value)) |
|||
{ |
|||
return value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// The resource still hasn't been found, so add a delayed one-time binding.
|
|||
var provideTarget = context.GetService<IProvideValueTarget>(); |
|||
|
|||
if (provideTarget.TargetObject is IControl target && |
|||
provideTarget.TargetProperty is PropertyInfo property) |
|||
{ |
|||
DelayedBinding.Add(target, property, GetValue); |
|||
return AvaloniaProperty.UnsetValue; |
|||
} |
|||
|
|||
throw new KeyNotFoundException($"Static resource '{ResourceKey}' not found."); |
|||
} |
|||
|
|||
private object GetValue(IControl control) |
|||
{ |
|||
return control.FindResource(ResourceKey); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,217 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.Controls.Presenters; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Styling; |
|||
using Avalonia.UnitTests; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Controls.UnitTests |
|||
{ |
|||
public class ControlTests_Resources |
|||
{ |
|||
[Fact] |
|||
public void FindResource_Should_Find_Control_Resource() |
|||
{ |
|||
var target = new Control |
|||
{ |
|||
Resources = |
|||
{ |
|||
{ "foo", "foo-value" }, |
|||
} |
|||
}; |
|||
|
|||
Assert.Equal("foo-value", target.FindResource("foo")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FindResource_Should_Find_Control_Resource_In_Parent() |
|||
{ |
|||
Control target; |
|||
|
|||
var root = new Decorator |
|||
{ |
|||
Resources = |
|||
{ |
|||
{ "foo", "foo-value" }, |
|||
}, |
|||
Child = target = new Control(), |
|||
}; |
|||
|
|||
Assert.Equal("foo-value", target.FindResource("foo")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FindResource_Should_Find_Application_Resource() |
|||
{ |
|||
Control target; |
|||
|
|||
var app = new Application |
|||
{ |
|||
Resources = |
|||
{ |
|||
{ "foo", "foo-value" }, |
|||
}, |
|||
}; |
|||
|
|||
var root = new TestRoot |
|||
{ |
|||
Child = target = new Control(), |
|||
StylingParent = app, |
|||
}; |
|||
|
|||
Assert.Equal("foo-value", target.FindResource("foo")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FindResource_Should_Find_Style_Resource() |
|||
{ |
|||
var target = new Control |
|||
{ |
|||
Styles = |
|||
{ |
|||
new Style |
|||
{ |
|||
Resources = |
|||
{ |
|||
{ "foo", "foo-value" }, |
|||
} |
|||
} |
|||
}, |
|||
Resources = |
|||
{ |
|||
{ "bar", "bar-value" }, |
|||
}, |
|||
}; |
|||
|
|||
Assert.Equal("foo-value", target.FindResource("foo")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FindResource_Should_Find_Styles_Resource() |
|||
{ |
|||
var target = new Control |
|||
{ |
|||
Styles = |
|||
{ |
|||
new Styles |
|||
{ |
|||
Resources = |
|||
{ |
|||
{ "foo", "foo-value" }, |
|||
} |
|||
} |
|||
}, |
|||
Resources = |
|||
{ |
|||
{ "bar", "bar-value" }, |
|||
}, |
|||
}; |
|||
|
|||
Assert.Equal("foo-value", target.FindResource("foo")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FindResource_Should_Find_Application_Style_Resource() |
|||
{ |
|||
Control target; |
|||
|
|||
var app = new Application |
|||
{ |
|||
Styles = |
|||
{ |
|||
new Style |
|||
{ |
|||
Resources = |
|||
{ |
|||
{ "foo", "foo-value" }, |
|||
}, |
|||
} |
|||
}, |
|||
Resources = |
|||
{ |
|||
{ "bar", "bar-value" }, |
|||
}, |
|||
}; |
|||
|
|||
var root = new TestRoot |
|||
{ |
|||
Child = target = new Control(), |
|||
StylingParent = app, |
|||
}; |
|||
|
|||
Assert.Equal("foo-value", target.FindResource("foo")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Adding_Resource_Should_Call_Raise_ResourceChanged_On_Logical_Children() |
|||
{ |
|||
Border child; |
|||
|
|||
var target = new ContentControl |
|||
{ |
|||
Content = child = new Border(), |
|||
Template = ContentControlTemplate(), |
|||
}; |
|||
|
|||
var raisedOnTarget = false; |
|||
var raisedOnPresenter = false; |
|||
var raisedOnChild = false; |
|||
|
|||
target.Measure(Size.Infinity); |
|||
target.ResourcesChanged += (_, __) => raisedOnTarget = true; |
|||
target.Presenter.ResourcesChanged += (_, __) => raisedOnPresenter = true; |
|||
child.ResourcesChanged += (_, __) => raisedOnChild = true; |
|||
|
|||
target.Resources.Add("foo", "bar"); |
|||
|
|||
Assert.True(raisedOnTarget); |
|||
Assert.False(raisedOnPresenter); |
|||
Assert.True(raisedOnChild); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Adding_Resource_To_Styles_Should_Raise_ResourceChanged() |
|||
{ |
|||
var target = new Decorator(); |
|||
var raised = false; |
|||
|
|||
target.ResourcesChanged += (_, __) => raised = true; |
|||
target.Styles.Resources.Add("foo", "bar"); |
|||
|
|||
Assert.True(raised); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Adding_Resource_To_Nested_Style_Should_Raise_ResourceChanged() |
|||
{ |
|||
Style style; |
|||
var target = new Decorator |
|||
{ |
|||
Styles = |
|||
{ |
|||
(style = new Style()), |
|||
} |
|||
}; |
|||
|
|||
var raised = false; |
|||
|
|||
target.ResourcesChanged += (_, __) => raised = true; |
|||
style.Resources.Add("foo", "bar"); |
|||
|
|||
Assert.True(raised); |
|||
} |
|||
|
|||
private IControlTemplate ContentControlTemplate() |
|||
{ |
|||
return new FuncControlTemplate<ContentControl>(x => |
|||
new ContentPresenter |
|||
{ |
|||
Name = "PART_ContentPresenter", |
|||
[!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty], |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Media; |
|||
using Avalonia.UnitTests; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Markup.Xaml.UnitTests.Data |
|||
{ |
|||
public class ResourceIncludeTests |
|||
{ |
|||
public class StaticResourceExtensionTests |
|||
{ |
|||
[Fact] |
|||
public void ResourceInclude_Loads_ResourceDictionary() |
|||
{ |
|||
var includeXaml = @"
|
|||
<ResourceDictionary xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</ResourceDictionary> |
|||
";
|
|||
using (StartWithResources(("test:include.xaml", includeXaml))) |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Resources> |
|||
<ResourceDictionary> |
|||
<ResourceDictionary.MergedDictionaries> |
|||
<ResourceInclude Source='test:include.xaml'/> |
|||
</ResourceDictionary.MergedDictionaries> |
|||
</ResourceDictionary> |
|||
</UserControl.Resources> |
|||
|
|||
<Border Name='border' Background='{StaticResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
} |
|||
|
|||
private IDisposable StartWithResources(params (string, string)[] assets) |
|||
{ |
|||
var assetLoader = new MockAssetLoader(assets); |
|||
var services = new TestServices(assetLoader: assetLoader); |
|||
return UnitTestApplication.Start(services); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,660 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Presenters; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Markup.Xaml.Data; |
|||
using Avalonia.Media; |
|||
using Avalonia.Styling; |
|||
using Avalonia.UnitTests; |
|||
using Avalonia.VisualTree; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions |
|||
{ |
|||
public class DynamicResourceExtensionTests |
|||
{ |
|||
[Fact] |
|||
public void DynamicResource_Can_Be_Assigned_To_Property() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Resources> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</UserControl.Resources> |
|||
|
|||
<Border Name='border' Background='{DynamicResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
DelayedBinding.ApplyBindings(border); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_Can_Be_Assigned_To_Attached_Property() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Resources> |
|||
<x:Int32 x:Key='col'>5</x:Int32> |
|||
</UserControl.Resources> |
|||
|
|||
<Border Name='border' Grid.Column='{DynamicResource col}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
DelayedBinding.ApplyBindings(border); |
|||
|
|||
Assert.Equal(5, Grid.GetColumn(border)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_From_Style_Can_Be_Assigned_To_Property() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Styles> |
|||
<Style> |
|||
<Style.Resources> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</Style.Resources> |
|||
</Style> |
|||
</UserControl.Styles> |
|||
|
|||
<Border Name='border' Background='{DynamicResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
DelayedBinding.ApplyBindings(border); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_From_MergedDictionary_Can_Be_Assigned_To_Property() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Resources> |
|||
<ResourceDictionary> |
|||
<ResourceDictionary.MergedDictionaries> |
|||
<ResourceDictionary> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</ResourceDictionary> |
|||
</ResourceDictionary.MergedDictionaries> |
|||
</ResourceDictionary> |
|||
</UserControl.Resources> |
|||
|
|||
<Border Name='border' Background='{DynamicResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
DelayedBinding.ApplyBindings(border); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_From_MergedDictionary_In_Style_Can_Be_Assigned_To_Property() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Styles> |
|||
<Style> |
|||
<Style.Resources> |
|||
<ResourceDictionary> |
|||
<ResourceDictionary.MergedDictionaries> |
|||
<ResourceDictionary> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</ResourceDictionary> |
|||
</ResourceDictionary.MergedDictionaries> |
|||
</ResourceDictionary> |
|||
</Style.Resources> |
|||
</Style> |
|||
</UserControl.Styles> |
|||
|
|||
<Border Name='border' Background='{DynamicResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
DelayedBinding.ApplyBindings(border); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_From_Application_Can_Be_Assigned_To_Property_In_Window() |
|||
{ |
|||
using (StyledWindow()) |
|||
{ |
|||
Application.Current.Resources.Add("brush", new SolidColorBrush(0xff506070)); |
|||
|
|||
var xaml = @"
|
|||
<Window xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Border Name='border' Background='{DynamicResource brush}'/> |
|||
</Window>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var window = (Window)loader.Load(xaml); |
|||
var border = window.FindControl<Border>("border"); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_From_Application_Can_Be_Assigned_To_Property_In_UserControl() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
|||
{ |
|||
Application.Current.Resources.Add("brush", new SolidColorBrush(0xff506070)); |
|||
|
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Border Name='border' Background='{DynamicResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
// We don't actually know where the global styles are until we attach the control
|
|||
// to a window, as Window has StylingParent set to Application.
|
|||
var window = new Window { Content = userControl }; |
|||
window.Show(); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_Can_Be_Assigned_To_Setter() |
|||
{ |
|||
using (StyledWindow()) |
|||
{ |
|||
var xaml = @"
|
|||
<Window xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Window.Resources> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</Window.Resources> |
|||
<Window.Styles> |
|||
<Style Selector='Button'> |
|||
<Setter Property='Background' Value='{DynamicResource brush}'/> |
|||
</Style> |
|||
</Window.Styles> |
|||
<Button Name='button'/> |
|||
</Window>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var window = (Window)loader.Load(xaml); |
|||
var button = window.FindControl<Button>("button"); |
|||
var brush = (SolidColorBrush)button.Background; |
|||
|
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_From_Style_Can_Be_Assigned_To_Setter() |
|||
{ |
|||
using (StyledWindow()) |
|||
{ |
|||
var xaml = @"
|
|||
<Window xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Window.Styles> |
|||
<Style> |
|||
<Style.Resources> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</Style.Resources> |
|||
</Style> |
|||
<Style Selector='Button'> |
|||
<Setter Property='Background' Value='{DynamicResource brush}'/> |
|||
</Style> |
|||
</Window.Styles> |
|||
<Button Name='button'/> |
|||
</Window>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var window = (Window)loader.Load(xaml); |
|||
var button = window.FindControl<Button>("button"); |
|||
var brush = (SolidColorBrush)button.Background; |
|||
|
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_Can_Be_Assigned_To_Setter_In_Styles_File() |
|||
{ |
|||
var styleXaml = @"
|
|||
<Styles xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Styles.Resources> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</Styles.Resources> |
|||
|
|||
<Style Selector='Border'> |
|||
<Setter Property='Background' Value='{DynamicResource brush}'/> |
|||
</Style> |
|||
</Styles>";
|
|||
|
|||
using (StyledWindow(assets: ("test:style.xaml", styleXaml))) |
|||
{ |
|||
var xaml = @"
|
|||
<Window xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Window.Styles> |
|||
<StyleInclude Source='test:style.xaml'/> |
|||
</Window.Styles> |
|||
<Border Name='border'/> |
|||
</Window>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var window = (Window)loader.Load(xaml); |
|||
var border = window.FindControl<Border>("border"); |
|||
var brush = (SolidColorBrush)border.Background; |
|||
|
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File() |
|||
{ |
|||
var styleXaml = @"
|
|||
<Styles xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Styles.Resources> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</Styles.Resources> |
|||
|
|||
<Style Selector='Button'> |
|||
<Setter Property='Template'> |
|||
<ControlTemplate> |
|||
<Border Name='border' Background='{DynamicResource brush}'/> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
</Styles>";
|
|||
|
|||
using (StyledWindow(assets: ("test:style.xaml", styleXaml))) |
|||
{ |
|||
var xaml = @"
|
|||
<Window xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Window.Styles> |
|||
<StyleInclude Source='test:style.xaml'/> |
|||
</Window.Styles> |
|||
<Button Name='button'/> |
|||
</Window>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var window = (Window)loader.Load(xaml); |
|||
var button = window.FindControl<Button>("button"); |
|||
|
|||
window.Show(); |
|||
|
|||
var border = (Border)button.GetVisualChildren().Single(); |
|||
var brush = (SolidColorBrush)border.Background; |
|||
|
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_Can_Be_Assigned_To_Resource_Property() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Resources> |
|||
<Color x:Key='color'>#ff506070</Color> |
|||
<SolidColorBrush x:Key='brush' Color='{DynamicResource color}'/> |
|||
</UserControl.Resources> |
|||
|
|||
<Border Name='border' Background='{DynamicResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
DelayedBinding.ApplyBindings(border); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
|
|||
[Fact] |
|||
public void DynamicResource_Can_Be_Assigned_To_ItemTemplate_Property() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Resources> |
|||
<DataTemplate x:Key='PurpleData'> |
|||
<TextBlock Text='{Binding Name}' Background='Purple'/> |
|||
</DataTemplate> |
|||
</UserControl.Resources> |
|||
|
|||
<ListBox Name='listBox' ItemTemplate='{DynamicResource PurpleData}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var listBox = userControl.FindControl<ListBox>("listBox"); |
|||
|
|||
DelayedBinding.ApplyBindings(listBox); |
|||
|
|||
Assert.NotNull(listBox.ItemTemplate); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_Tracks_Added_Resource() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Border Name='border' Background='{DynamicResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
DelayedBinding.ApplyBindings(border); |
|||
|
|||
Assert.Null(border.Background); |
|||
|
|||
userControl.Resources.Add("brush", new SolidColorBrush(0xff506070)); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.NotNull(brush); |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_Tracks_Added_Style_Resource() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Border Name='border' Background='{DynamicResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
DelayedBinding.ApplyBindings(border); |
|||
|
|||
Assert.Null(border.Background); |
|||
|
|||
userControl.Styles.Resources.Add("brush", new SolidColorBrush(0xff506070)); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.NotNull(brush); |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_Tracks_Added_Nested_Style_Resource() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Styles> |
|||
<Style> |
|||
</Style> |
|||
</UserControl.Styles> |
|||
<Border Name='border' Background='{DynamicResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
DelayedBinding.ApplyBindings(border); |
|||
|
|||
Assert.Null(border.Background); |
|||
|
|||
((Style)userControl.Styles[0]).Resources.Add("brush", new SolidColorBrush(0xff506070)); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.NotNull(brush); |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_Tracks_Added_MergedResource() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Resources> |
|||
<ResourceDictionary> |
|||
<ResourceDictionary.MergedDictionaries> |
|||
<ResourceDictionary/> |
|||
</ResourceDictionary.MergedDictionaries> |
|||
</ResourceDictionary> |
|||
</UserControl.Resources> |
|||
<Border Name='border' Background='{DynamicResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
DelayedBinding.ApplyBindings(border); |
|||
|
|||
Assert.Null(border.Background); |
|||
|
|||
((IResourceDictionary)userControl.Resources.MergedDictionaries[0]).Add("brush", new SolidColorBrush(0xff506070)); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.NotNull(brush); |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_Tracks_Added_MergedResource_Dictionary() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Border Name='border' Background='{DynamicResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
DelayedBinding.ApplyBindings(border); |
|||
|
|||
Assert.Null(border.Background); |
|||
|
|||
var dictionary = new ResourceDictionary |
|||
{ |
|||
{ "brush", new SolidColorBrush(0xff506070) }, |
|||
}; |
|||
|
|||
userControl.Resources.MergedDictionaries.Add(dictionary); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.NotNull(brush); |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_Tracks_Added_Style_MergedResource_Dictionary() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Styles> |
|||
<Style> |
|||
</Style> |
|||
</UserControl.Styles> |
|||
<Border Name='border' Background='{DynamicResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
DelayedBinding.ApplyBindings(border); |
|||
|
|||
Assert.Null(border.Background); |
|||
|
|||
var dictionary = new ResourceDictionary |
|||
{ |
|||
{ "brush", new SolidColorBrush(0xff506070) }, |
|||
}; |
|||
|
|||
((Style)userControl.Styles[0]).Resources.MergedDictionaries.Add(dictionary); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.NotNull(brush); |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void DynamicResource_Can_Be_Found_Across_Xaml_Style_Files() |
|||
{ |
|||
var style1Xaml = @"
|
|||
<Style xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Style.Resources> |
|||
<Color x:Key='Red'>Red</Color> |
|||
</Style.Resources> |
|||
</Style>";
|
|||
var style2Xaml = @"
|
|||
<Style xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Style.Resources> |
|||
<SolidColorBrush x:Key='RedBrush' Color='{DynamicResource Red}'/> |
|||
</Style.Resources> |
|||
</Style>";
|
|||
using (StyledWindow( |
|||
("test:style1.xaml", style1Xaml), |
|||
("test:style2.xaml", style2Xaml))) |
|||
{ |
|||
var xaml = @"
|
|||
<Window xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Window.Styles> |
|||
<StyleInclude Source='test:style1.xaml'/> |
|||
<StyleInclude Source='test:style2.xaml'/> |
|||
</Window.Styles> |
|||
<Border Name='border' Background='{DynamicResource RedBrush}'/> |
|||
</Window>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var window = (Window)loader.Load(xaml); |
|||
var border = window.FindControl<Border>("border"); |
|||
var borderBrush = (ISolidColorBrush)border.Background; |
|||
|
|||
Assert.NotNull(borderBrush); |
|||
Assert.Equal(0xffff0000, borderBrush.Color.ToUint32()); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Control_Property_Is_Updated_When_Parent_Is_Changed() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Resources> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</UserControl.Resources> |
|||
|
|||
<Border Name='border' Background='{DynamicResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
DelayedBinding.ApplyBindings(border); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
|
|||
userControl.Content = null; |
|||
|
|||
Assert.Null(border.Background); |
|||
|
|||
userControl.Content = border; |
|||
|
|||
brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
private IDisposable StyledWindow(params (string, string)[] assets) |
|||
{ |
|||
var services = TestServices.StyledWindow.With( |
|||
assetLoader: new MockAssetLoader(assets), |
|||
theme: () => new Styles |
|||
{ |
|||
WindowStyle(), |
|||
}); |
|||
|
|||
return UnitTestApplication.Start(services); |
|||
} |
|||
|
|||
private Style WindowStyle() |
|||
{ |
|||
return new Style(x => x.OfType<Window>()) |
|||
{ |
|||
Setters = |
|||
{ |
|||
new Setter( |
|||
Window.TemplateProperty, |
|||
new FuncControlTemplate<Window>(x => |
|||
new ContentPresenter |
|||
{ |
|||
Name = "PART_ContentPresenter", |
|||
[!ContentPresenter.ContentProperty] = x[!Window.ContentProperty], |
|||
})) |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,476 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.Presenters; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Media; |
|||
using Avalonia.Styling; |
|||
using Avalonia.UnitTests; |
|||
using Avalonia.VisualTree; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions |
|||
{ |
|||
public class StaticResourceExtensionTests |
|||
{ |
|||
[Fact] |
|||
public void StaticResource_Can_Be_Assigned_To_Property() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Resources> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</UserControl.Resources> |
|||
|
|||
<Border Name='border' Background='{StaticResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void StaticResource_Can_Be_Assigned_To_Attached_Property() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Resources> |
|||
<x:Int32 x:Key='col'>5</x:Int32> |
|||
</UserControl.Resources> |
|||
|
|||
<Border Name='border' Grid.Column='{StaticResource col}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
Assert.Equal(5, Grid.GetColumn(border)); |
|||
} |
|||
|
|||
[Fact] |
|||
public void StaticResource_From_Style_Can_Be_Assigned_To_Property() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Styles> |
|||
<Style> |
|||
<Style.Resources> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</Style.Resources> |
|||
</Style> |
|||
</UserControl.Styles> |
|||
|
|||
<Border Name='border' Background='{StaticResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void StaticResource_From_Application_Can_Be_Assigned_To_Property_In_Window() |
|||
{ |
|||
using (StyledWindow()) |
|||
{ |
|||
Application.Current.Resources.Add("brush", new SolidColorBrush(0xff506070)); |
|||
|
|||
var xaml = @"
|
|||
<Window xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Border Name='border' Background='{StaticResource brush}'/> |
|||
</Window>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var window = (Window)loader.Load(xaml); |
|||
var border = window.FindControl<Border>("border"); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void StaticResource_From_MergedDictionary_Can_Be_Assigned_To_Property() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Resources> |
|||
<ResourceDictionary> |
|||
<ResourceDictionary.MergedDictionaries> |
|||
<ResourceDictionary> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</ResourceDictionary> |
|||
</ResourceDictionary.MergedDictionaries> |
|||
</ResourceDictionary> |
|||
</UserControl.Resources> |
|||
|
|||
<Border Name='border' Background='{StaticResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void StaticResource_From_MergedDictionary_In_Style_Can_Be_Assigned_To_Property() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Styles> |
|||
<Style> |
|||
<Style.Resources> |
|||
<ResourceDictionary> |
|||
<ResourceDictionary.MergedDictionaries> |
|||
<ResourceDictionary> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</ResourceDictionary> |
|||
</ResourceDictionary.MergedDictionaries> |
|||
</ResourceDictionary> |
|||
</Style.Resources> |
|||
</Style> |
|||
</UserControl.Styles> |
|||
|
|||
<Border Name='border' Background='{StaticResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void StaticResource_From_Application_Can_Be_Assigned_To_Property_In_UserControl() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
|||
{ |
|||
Application.Current.Resources.Add("brush", new SolidColorBrush(0xff506070)); |
|||
|
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Border Name='border' Background='{StaticResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
// We don't actually know where the global styles are until we attach the control
|
|||
// to a window, as Window has StylingParent set to Application.
|
|||
var window = new Window { Content = userControl }; |
|||
window.Show(); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void StaticResource_Can_Be_Assigned_To_Setter() |
|||
{ |
|||
using (StyledWindow()) |
|||
{ |
|||
var xaml = @"
|
|||
<Window xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Window.Resources> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</Window.Resources> |
|||
<Window.Styles> |
|||
<Style Selector='Button'> |
|||
<Setter Property='Background' Value='{StaticResource brush}'/> |
|||
</Style> |
|||
</Window.Styles> |
|||
<Button Name='button'/> |
|||
</Window>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var window = (Window)loader.Load(xaml); |
|||
var button = window.FindControl<Button>("button"); |
|||
var brush = (SolidColorBrush)button.Background; |
|||
|
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void StaticResource_From_Style_Can_Be_Assigned_To_Setter() |
|||
{ |
|||
using (StyledWindow()) |
|||
{ |
|||
var xaml = @"
|
|||
<Window xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Window.Styles> |
|||
<Style> |
|||
<Style.Resources> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</Style.Resources> |
|||
</Style> |
|||
<Style Selector='Button'> |
|||
<Setter Property='Background' Value='{StaticResource brush}'/> |
|||
</Style> |
|||
</Window.Styles> |
|||
<Button Name='button'/> |
|||
</Window>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var window = (Window)loader.Load(xaml); |
|||
var button = window.FindControl<Button>("button"); |
|||
var brush = (SolidColorBrush)button.Background; |
|||
|
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void StaticResource_Can_Be_Assigned_To_Setter_In_Styles_File() |
|||
{ |
|||
var styleXaml = @"
|
|||
<Styles xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Styles.Resources> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</Styles.Resources> |
|||
|
|||
<Style Selector='Border'> |
|||
<Setter Property='Background' Value='{StaticResource brush}'/> |
|||
</Style> |
|||
</Styles>";
|
|||
|
|||
using (StyledWindow(assets: ("test:style.xaml", styleXaml))) |
|||
{ |
|||
var xaml = @"
|
|||
<Window xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Window.Styles> |
|||
<StyleInclude Source='test:style.xaml'/> |
|||
</Window.Styles> |
|||
<Border Name='border'/> |
|||
</Window>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var window = (Window)loader.Load(xaml); |
|||
var border = window.FindControl<Border>("border"); |
|||
var brush = (SolidColorBrush)border.Background; |
|||
|
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void StaticResource_Can_Be_Assigned_To_Resource_Property() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Resources> |
|||
<Color x:Key='color'>#ff506070</Color> |
|||
<SolidColorBrush x:Key='brush' Color='{StaticResource color}'/> |
|||
</UserControl.Resources> |
|||
|
|||
<Border Name='border' Background='{StaticResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
[Fact] |
|||
public void StaticResource_Can_Be_Assigned_To_Resource_Property_In_Styles_File() |
|||
{ |
|||
var xaml = @"
|
|||
<Styles xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Styles.Resources> |
|||
<Color x:Key='color'>#ff506070</Color> |
|||
<SolidColorBrush x:Key='brush' Color='{StaticResource color}'/> |
|||
</Styles.Resources> |
|||
</Styles>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var styles = (Styles)loader.Load(xaml); |
|||
var brush = (SolidColorBrush)styles.Resources["brush"]; |
|||
|
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
[Fact(Skip = "Not yet supported by Portable.Xaml")] |
|||
public void StaticResource_Can_Be_Assigned_To_Property_In_ControlTemplate_In_Styles_File() |
|||
{ |
|||
var styleXaml = @"
|
|||
<Styles xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Styles.Resources> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</Styles.Resources> |
|||
|
|||
<Style Selector='Button'> |
|||
<Setter Property='Template'> |
|||
<ControlTemplate> |
|||
<Border Name='border' Background='{StaticResource brush}'/> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
</Styles>";
|
|||
|
|||
using (StyledWindow(assets: ("test:style.xaml", styleXaml))) |
|||
{ |
|||
var xaml = @"
|
|||
<Window xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<Window.Styles> |
|||
<StyleInclude Source='test:style.xaml'/> |
|||
</Window.Styles> |
|||
<Button Name='button'/> |
|||
</Window>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var window = (Window)loader.Load(xaml); |
|||
var button = window.FindControl<Button>("button"); |
|||
|
|||
window.Show(); |
|||
|
|||
var border = (Border)button.GetVisualChildren().Single(); |
|||
var brush = (SolidColorBrush)border.Background; |
|||
|
|||
// To make this work we somehow need to be able to get hold of the parent ambient
|
|||
// context from Portable.Xaml. See TODO in StaticResourceExtension.
|
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void StaticResource_Can_Be_Assigned_To_ItemTemplate_Property() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Resources> |
|||
<DataTemplate x:Key='PurpleData'> |
|||
<TextBlock Text='{Binding Name}' Background='Purple'/> |
|||
</DataTemplate> |
|||
</UserControl.Resources> |
|||
|
|||
<ListBox Name='listBox' ItemTemplate='{StaticResource PurpleData}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var listBox = userControl.FindControl<ListBox>("listBox"); |
|||
|
|||
Assert.NotNull(listBox.ItemTemplate); |
|||
} |
|||
|
|||
[Fact] |
|||
public void StaticResource_Can_Be_Assigned_To_Converter() |
|||
{ |
|||
using (StyledWindow()) |
|||
{ |
|||
var xaml = @"
|
|||
<Window xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
|
|||
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'> |
|||
<Window.Resources> |
|||
<local:TestValueConverter x:Key='converter' Append='bar'/> |
|||
</Window.Resources> |
|||
|
|||
<TextBlock Name='textBlock' Text='{Binding Converter={StaticResource converter}}'/> |
|||
</Window>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var window = (Window)loader.Load(xaml); |
|||
var textBlock = window.FindControl<TextBlock>("textBlock"); |
|||
|
|||
window.DataContext = "foo"; |
|||
window.ApplyTemplate(); |
|||
|
|||
Assert.Equal("foobar", textBlock.Text); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Control_Property_Is_Not_Updated_When_Parent_Is_Changed() |
|||
{ |
|||
var xaml = @"
|
|||
<UserControl xmlns='https://github.com/avaloniaui'
|
|||
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
|
|||
<UserControl.Resources> |
|||
<SolidColorBrush x:Key='brush'>#ff506070</SolidColorBrush> |
|||
</UserControl.Resources> |
|||
|
|||
<Border Name='border' Background='{StaticResource brush}'/> |
|||
</UserControl>";
|
|||
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var userControl = (UserControl)loader.Load(xaml); |
|||
var border = userControl.FindControl<Border>("border"); |
|||
|
|||
var brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
|
|||
userControl.Content = null; |
|||
|
|||
brush = (SolidColorBrush)border.Background; |
|||
Assert.Equal(0xff506070, brush.Color.ToUint32()); |
|||
} |
|||
|
|||
private IDisposable StyledWindow(params (string, string)[] assets) |
|||
{ |
|||
var services = TestServices.StyledWindow.With( |
|||
assetLoader: new MockAssetLoader(assets), |
|||
theme: () => new Styles |
|||
{ |
|||
WindowStyle(), |
|||
}); |
|||
|
|||
return UnitTestApplication.Start(services); |
|||
} |
|||
|
|||
private Style WindowStyle() |
|||
{ |
|||
return new Style(x => x.OfType<Window>()) |
|||
{ |
|||
Setters = |
|||
{ |
|||
new Setter( |
|||
Window.TemplateProperty, |
|||
new FuncControlTemplate<Window>(x => |
|||
new ContentPresenter |
|||
{ |
|||
Name = "PART_ContentPresenter", |
|||
[!ContentPresenter.ContentProperty] = x[!Window.ContentProperty], |
|||
})) |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using System.Globalization; |
|||
|
|||
namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions |
|||
{ |
|||
public class TestValueConverter : IValueConverter |
|||
{ |
|||
public string Append { get; set; } |
|||
|
|||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) |
|||
{ |
|||
return value.ToString() + Append; |
|||
} |
|||
|
|||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,175 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Avalonia.Controls; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Styling.UnitTests |
|||
{ |
|||
public class ResourceDictionaryTests |
|||
{ |
|||
[Fact] |
|||
public void TryGetResource_Should_Find_Resource() |
|||
{ |
|||
var target = new ResourceDictionary |
|||
{ |
|||
{ "foo", "bar" }, |
|||
}; |
|||
|
|||
Assert.True(target.TryGetResource("foo", out var result)); |
|||
Assert.Equal("bar", result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void TryGetResource_Should_Find_Resource_From_Merged_Dictionary() |
|||
{ |
|||
var target = new ResourceDictionary |
|||
{ |
|||
MergedDictionaries = |
|||
{ |
|||
new ResourceDictionary |
|||
{ |
|||
{ "foo", "bar" }, |
|||
} |
|||
} |
|||
}; |
|||
|
|||
Assert.True(target.TryGetResource("foo", out var result)); |
|||
Assert.Equal("bar", result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void TryGetResource_Should_Find_Resource_From_Itself_Before_Merged_Dictionary() |
|||
{ |
|||
var target = new ResourceDictionary |
|||
{ |
|||
{ "foo", "bar" }, |
|||
}; |
|||
|
|||
target.MergedDictionaries.Add(new ResourceDictionary |
|||
{ |
|||
{ "foo", "baz" }, |
|||
}); |
|||
|
|||
Assert.True(target.TryGetResource("foo", out var result)); |
|||
Assert.Equal("bar", result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void TryGetResource_Should_Find_Resource_From_Later_Merged_Dictionary() |
|||
{ |
|||
var target = new ResourceDictionary |
|||
{ |
|||
MergedDictionaries = |
|||
{ |
|||
new ResourceDictionary |
|||
{ |
|||
{ "foo", "bar" }, |
|||
}, |
|||
new ResourceDictionary |
|||
{ |
|||
{ "foo", "baz" }, |
|||
} |
|||
} |
|||
}; |
|||
|
|||
Assert.True(target.TryGetResource("foo", out var result)); |
|||
Assert.Equal("baz", result); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ResourcesChanged_Should_Be_Raised_On_Resource_Add() |
|||
{ |
|||
var target = new ResourceDictionary(); |
|||
var raised = false; |
|||
|
|||
target.ResourcesChanged += (_, __) => raised = true; |
|||
target.Add("foo", "bar"); |
|||
|
|||
Assert.True(raised); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ResourcesChanged_Should_Be_Raised_On_MergedDictionary_Add() |
|||
{ |
|||
var target = new ResourceDictionary(); |
|||
var raised = false; |
|||
|
|||
target.ResourcesChanged += (_, __) => raised = true; |
|||
target.MergedDictionaries.Add(new ResourceDictionary |
|||
{ |
|||
{ "foo", "bar" }, |
|||
}); |
|||
|
|||
Assert.True(raised); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ResourcesChanged_Should_Not_Be_Raised_On_Empty_MergedDictionary_Add() |
|||
{ |
|||
var target = new ResourceDictionary(); |
|||
var raised = false; |
|||
|
|||
target.ResourcesChanged += (_, __) => raised = true; |
|||
target.MergedDictionaries.Add(new ResourceDictionary()); |
|||
|
|||
Assert.False(raised); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ResourcesChanged_Should_Be_Raised_On_MergedDictionary_Remove() |
|||
{ |
|||
var target = new ResourceDictionary |
|||
{ |
|||
MergedDictionaries = |
|||
{ |
|||
new ResourceDictionary { { "foo", "bar" } }, |
|||
} |
|||
}; |
|||
var raised = false; |
|||
|
|||
target.ResourcesChanged += (_, __) => raised = true; |
|||
target.MergedDictionaries.RemoveAt(0); |
|||
|
|||
Assert.True(raised); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ResourcesChanged_Should_Not_Be_Raised_On_Empty_MergedDictionary_Remove() |
|||
{ |
|||
var target = new ResourceDictionary |
|||
{ |
|||
MergedDictionaries = |
|||
{ |
|||
new ResourceDictionary(), |
|||
} |
|||
}; |
|||
var raised = false; |
|||
|
|||
target.ResourcesChanged += (_, __) => raised = true; |
|||
target.MergedDictionaries.RemoveAt(0); |
|||
|
|||
Assert.False(raised); |
|||
} |
|||
|
|||
[Fact] |
|||
public void ResourcesChanged_Should_Be_Raised_On_MergedDictionary_Resource_Add() |
|||
{ |
|||
var target = new ResourceDictionary |
|||
{ |
|||
MergedDictionaries = |
|||
{ |
|||
new ResourceDictionary(), |
|||
} |
|||
}; |
|||
|
|||
var raised = false; |
|||
|
|||
target.ResourcesChanged += (_, __) => raised = true; |
|||
((IResourceDictionary)target.MergedDictionaries[0]).Add("foo", "bar"); |
|||
|
|||
Assert.True(raised); |
|||
} |
|||
} |
|||
} |
|||
@ -1,78 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System.Collections.Generic; |
|||
using Avalonia.Controls; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Styling.UnitTests |
|||
{ |
|||
public class ResourceTests |
|||
{ |
|||
[Fact] |
|||
public void FindStyleResource_Should_Find_Correct_Resource() |
|||
{ |
|||
Border target; |
|||
|
|||
var tree = new Decorator |
|||
{ |
|||
Styles = |
|||
{ |
|||
new Style |
|||
{ |
|||
Resources = new StyleResources |
|||
{ |
|||
{ "Foo", "foo resource" }, |
|||
{ "Bar", "overridden" }, |
|||
} |
|||
} |
|||
}, |
|||
Child = target = new Border |
|||
{ |
|||
Styles = |
|||
{ |
|||
new Style |
|||
{ |
|||
Resources = new StyleResources |
|||
{ |
|||
{ "Bar", "again overridden" }, |
|||
} |
|||
}, |
|||
new Style |
|||
{ |
|||
Resources = new StyleResources |
|||
{ |
|||
{ "Bar", "bar resource" }, |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
Assert.Equal("foo resource", target.FindStyleResource("Foo")); |
|||
Assert.Equal("bar resource", target.FindStyleResource("Bar")); |
|||
} |
|||
|
|||
[Fact] |
|||
public void FindStyleResource_Should_Return_UnsetValue_For_Not_Found() |
|||
{ |
|||
Border target; |
|||
|
|||
var tree = target = new Border |
|||
{ |
|||
Styles = |
|||
{ |
|||
new Style |
|||
{ |
|||
Resources = new StyleResources |
|||
{ |
|||
{ "Foo", "foo" }, |
|||
} |
|||
}, |
|||
} |
|||
}; |
|||
|
|||
Assert.Equal(AvaloniaProperty.UnsetValue, target.FindStyleResource("Baz")); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,115 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Styling.UnitTests |
|||
{ |
|||
public class StylesTests |
|||
{ |
|||
[Fact] |
|||
public void Adding_Style_With_Resources_Should_Raise_ResourceChanged() |
|||
{ |
|||
var style = new Style |
|||
{ |
|||
Resources = { { "foo", "bar" } }, |
|||
}; |
|||
|
|||
var target = new Styles(); |
|||
var raised = false; |
|||
|
|||
target.ResourcesChanged += (_, __) => raised = true; |
|||
target.Add(style); |
|||
|
|||
Assert.True(raised); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Removing_Style_With_Resources_Should_Raise_ResourceChanged() |
|||
{ |
|||
var target = new Styles |
|||
{ |
|||
new Style |
|||
{ |
|||
Resources = { { "foo", "bar" } }, |
|||
} |
|||
}; |
|||
|
|||
var raised = false; |
|||
|
|||
target.ResourcesChanged += (_, __) => raised = true; |
|||
target.Clear(); |
|||
|
|||
Assert.True(raised); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Adding_Style_Without_Resources_Should_Not_Raise_ResourceChanged() |
|||
{ |
|||
var style = new Style(); |
|||
var target = new Styles(); |
|||
var raised = false; |
|||
|
|||
target.ResourcesChanged += (_, __) => raised = true; |
|||
target.Add(style); |
|||
|
|||
Assert.False(raised); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Adding_Resource_Should_Raise_Child_ResourceChanged() |
|||
{ |
|||
Style child; |
|||
var target = new Styles |
|||
{ |
|||
(child = new Style()), |
|||
}; |
|||
|
|||
var raised = false; |
|||
|
|||
child.ResourcesChanged += (_, __) => raised = true; |
|||
target.Resources.Add("foo", "bar"); |
|||
|
|||
Assert.True(raised); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Adding_Resource_To_Younger_Sibling_Style_Should_Raise_ResourceChanged() |
|||
{ |
|||
Style style1; |
|||
Style style2; |
|||
var target = new Styles |
|||
{ |
|||
(style1 = new Style()), |
|||
(style2 = new Style()), |
|||
}; |
|||
|
|||
var raised = false; |
|||
|
|||
style2.ResourcesChanged += (_, __) => raised = true; |
|||
style1.Resources.Add("foo", "bar"); |
|||
|
|||
Assert.True(raised); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Adding_Resource_To_Older_Sibling_Style_Should_Raise_ResourceChanged() |
|||
{ |
|||
Style style1; |
|||
Style style2; |
|||
var target = new Styles |
|||
{ |
|||
(style1 = new Style()), |
|||
(style2 = new Style()), |
|||
}; |
|||
|
|||
var raised = false; |
|||
|
|||
style1.ResourcesChanged += (_, __) => raised = true; |
|||
style2.Resources.Add("foo", "bar"); |
|||
|
|||
Assert.False(raised); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.UnitTests |
|||
{ |
|||
public class MockAssetLoader : IAssetLoader |
|||
{ |
|||
private Dictionary<Uri, string> _assets; |
|||
|
|||
public MockAssetLoader(params (string, string)[] assets) |
|||
{ |
|||
_assets = assets.ToDictionary(x => new Uri(x.Item1, UriKind.RelativeOrAbsolute), x => x.Item2); |
|||
} |
|||
|
|||
public bool Exists(Uri uri, Uri baseUri = null) |
|||
{ |
|||
return _assets.ContainsKey(uri); |
|||
} |
|||
|
|||
public Stream Open(Uri uri, Uri baseUri = null) |
|||
{ |
|||
return new MemoryStream(Encoding.UTF8.GetBytes(_assets[uri])); |
|||
} |
|||
|
|||
public void SetDefaultAssembly(Assembly asm) |
|||
{ |
|||
throw new NotImplementedException(); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue