diff --git a/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject b/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
index 235da29767..a8c3abe8f2 100644
--- a/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
@@ -1,6 +1,7 @@
1000
+ True
True
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject b/.ncrunch/Avalonia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject
new file mode 100644
index 0000000000..15d9efad87
--- /dev/null
+++ b/.ncrunch/Avalonia.Markup.UnitTests.netcoreapp1.1.v3.ncrunchproject
@@ -0,0 +1,9 @@
+
+
+
+
+ Avalonia.Markup.UnitTests.Data.Plugins.DataAnnotationsValidationPluginTests.Produces_Aggregate_BindingNotificationsx
+
+
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject b/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject
index 235da29767..a8c3abe8f2 100644
--- a/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject
@@ -1,6 +1,7 @@
1000
+ True
True
\ No newline at end of file
diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs
index c2e8c0c082..65c37c898b 100644
--- a/samples/ControlCatalog/MainWindow.xaml.cs
+++ b/samples/ControlCatalog/MainWindow.xaml.cs
@@ -20,7 +20,7 @@ namespace ControlCatalog
// so we must refer to this resource DLL statically. For
// now I am doing that here. But we need a better solution!!
var theme = new Avalonia.Themes.Default.DefaultTheme();
- theme.FindResource("Button");
+ theme.TryGetResource("Button", out _);
AvaloniaXamlLoader.Load(this);
}
}
diff --git a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
index 190b3ee2be..ec1643427b 100644
--- a/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
+++ b/src/Avalonia.Base/AvaloniaPropertyRegistry.cs
@@ -47,6 +47,9 @@ namespace Avalonia
{
Dictionary inner;
+ // Ensure the type's static ctor has been run.
+ RuntimeHelpers.RunClassConstructor(ownerType.TypeHandle);
+
if (_attached.TryGetValue(ownerType, out inner))
{
return inner.Values;
diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs
index 1aa239180c..b90dccf74e 100644
--- a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs
@@ -16,6 +16,7 @@ namespace Avalonia.Collections
/// The type of the dictionary key.
/// The type of the dictionary value.
public class AvaloniaDictionary : IDictionary,
+ IDictionary,
INotifyCollectionChanged,
INotifyPropertyChanged
{
@@ -51,6 +52,16 @@ namespace Avalonia.Collections
///
public ICollection Values => _inner.Values;
+ bool IDictionary.IsFixedSize => ((IDictionary)_inner).IsFixedSize;
+
+ ICollection IDictionary.Keys => ((IDictionary)_inner).Keys;
+
+ ICollection IDictionary.Values => ((IDictionary)_inner).Values;
+
+ bool ICollection.IsSynchronized => ((IDictionary)_inner).IsSynchronized;
+
+ object ICollection.SyncRoot => ((IDictionary)_inner).SyncRoot;
+
///
/// Gets or sets the named resource.
///
@@ -89,6 +100,8 @@ namespace Avalonia.Collections
}
}
+ object IDictionary.this[object key] { get => ((IDictionary)_inner)[key]; set => ((IDictionary)_inner)[key] = value; }
+
///
public void Add(TKey key, TValue value)
{
@@ -118,10 +131,7 @@ namespace Avalonia.Collections
}
///
- public bool ContainsKey(TKey key)
- {
- return _inner.ContainsKey(key);
- }
+ public bool ContainsKey(TKey key) => _inner.ContainsKey(key);
///
public void CopyTo(KeyValuePair[] array, int arrayIndex)
@@ -130,21 +140,16 @@ namespace Avalonia.Collections
}
///
- public IEnumerator> GetEnumerator()
- {
- return _inner.GetEnumerator();
- }
+ public IEnumerator> GetEnumerator() => _inner.GetEnumerator();
///
public bool Remove(TKey key)
{
- TValue value;
-
- if (_inner.TryGetValue(key, out value))
+ if (_inner.TryGetValue(key, out TValue value))
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]"));
-
+
if (CollectionChanged != null)
{
var e = new NotifyCollectionChangedEventArgs(
@@ -163,16 +168,13 @@ namespace Avalonia.Collections
}
///
- public bool TryGetValue(TKey key, out TValue value)
- {
- return _inner.TryGetValue(key, out value);
- }
+ public bool TryGetValue(TKey key, out TValue value) => _inner.TryGetValue(key, out value);
///
- IEnumerator IEnumerable.GetEnumerator()
- {
- return _inner.GetEnumerator();
- }
+ IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator();
+
+ ///
+ void ICollection.CopyTo(Array array, int index) => ((ICollection)_inner).CopyTo(array, index);
///
void ICollection>.Add(KeyValuePair item)
@@ -192,6 +194,18 @@ namespace Avalonia.Collections
return Remove(item.Key);
}
+ ///
+ void IDictionary.Add(object key, object value) => Add((TKey)key, (TValue)value);
+
+ ///
+ bool IDictionary.Contains(object key) => ((IDictionary) _inner).Contains(key);
+
+ ///
+ IDictionaryEnumerator IDictionary.GetEnumerator() => ((IDictionary)_inner).GetEnumerator();
+
+ ///
+ void IDictionary.Remove(object key) => Remove((TKey)key);
+
private void NotifyAdd(TKey key, TValue value)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs
index 18eb1f7606..abae080515 100644
--- a/src/Avalonia.Controls/Application.cs
+++ b/src/Avalonia.Controls/Application.cs
@@ -29,7 +29,7 @@ namespace Avalonia
/// method.
/// - Tracks the lifetime of the application.
///
- public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IApplicationLifecycle
+ public class Application : IApplicationLifecycle, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode
{
///
/// The application-global data templates.
@@ -40,6 +40,7 @@ namespace Avalonia
new Lazy(() => (IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard)));
private readonly Styler _styler = new Styler();
private Styles _styles;
+ private IResourceDictionary _resources;
///
/// Initializes a new instance of the class.
@@ -49,6 +50,9 @@ namespace Avalonia
OnExit += OnExiting;
}
+ ///
+ public event EventHandler ResourcesChanged;
+
///
/// Gets the current instance of the class.
///
@@ -97,6 +101,34 @@ namespace Avalonia
///
public IClipboard Clipboard => _clipboard.Value;
+ ///
+ /// Gets the application's global resource dictionary.
+ ///
+ public IResourceDictionary Resources
+ {
+ get => _resources ?? (Resources = new ResourceDictionary());
+ set
+ {
+ Contract.Requires(value != null);
+
+ var hadResources = false;
+
+ if (_resources != null)
+ {
+ hadResources = _resources.Count > 0;
+ _resources.ResourcesChanged -= ResourcesChanged;
+ }
+
+ _resources = value;
+ _resources.ResourcesChanged += ResourcesChanged;
+
+ if (hadResources || _resources.Count > 0)
+ {
+ ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
+ }
+ }
+ }
+
///
/// Gets the application's global styles.
///
@@ -119,6 +151,12 @@ namespace Avalonia
///
bool IStyleHost.IsStylesInitialized => _styles != null;
+ ///
+ bool IResourceProvider.HasResources => _resources?.Count > 0;
+
+ ///
+ IResourceNode IResourceNode.ResourceParent => null;
+
///
/// Initializes the application by loading XAML etc.
///
@@ -145,13 +183,20 @@ namespace Avalonia
{
OnExit?.Invoke(this, EventArgs.Empty);
}
-
+
+ ///
+ bool IResourceProvider.TryGetResource(string key, out object value)
+ {
+ value = null;
+ return (_resources?.TryGetResource(key, out value) ?? false) ||
+ Styles.TryGetResource(key, out value);
+ }
+
///
/// Sent when the application is exiting.
///
public event EventHandler OnExit;
-
///
/// Called when the application is exiting.
///
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index 4913037ea4..f3705c9127 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -97,8 +97,9 @@ namespace Avalonia.Controls
private bool _isAttachedToLogicalTree;
private IAvaloniaList _logicalChildren;
private INameScope _nameScope;
- private bool _styled;
+ private IResourceDictionary _resources;
private Styles _styles;
+ private bool _styled;
private Subject _styleDetach = new Subject();
///
@@ -153,6 +154,11 @@ namespace Avalonia.Controls
///
public event EventHandler Initialized;
+ ///
+ /// Occurs when a resource in this control or a parent control has changed.
+ ///
+ public event EventHandler ResourcesChanged;
+
///
/// Gets or sets the name of the control.
///
@@ -262,7 +268,32 @@ namespace Avalonia.Controls
/// each control may in addition define its own styles which are applied to the control
/// itself and its children.
///
- public Styles Styles => _styles ?? (_styles = new Styles());
+ public Styles Styles
+ {
+ get { return _styles ?? (Styles = new Styles()); }
+ set
+ {
+ Contract.Requires(value != null);
+
+ if (_styles != value)
+ {
+ if (_styles != null)
+ {
+ (_styles as ISetStyleParent)?.SetParent(null);
+ _styles.ResourcesChanged -= ThisResourcesChanged;
+ }
+
+ _styles = value;
+
+ if (value is ISetStyleParent setParent && setParent.ResourceParent == null)
+ {
+ setParent.SetParent(this);
+ }
+
+ _styles.ResourcesChanged += ThisResourcesChanged;
+ }
+ }
+ }
///
/// Gets the control's logical parent.
@@ -278,6 +309,34 @@ namespace Avalonia.Controls
set { SetValue(ContextMenuProperty, value); }
}
+ ///
+ /// Gets or sets the control's resource dictionary.
+ ///
+ public IResourceDictionary Resources
+ {
+ get => _resources ?? (Resources = new ResourceDictionary());
+ set
+ {
+ Contract.Requires(value != null);
+
+ var hadResources = false;
+
+ if (_resources != null)
+ {
+ hadResources = _resources.Count > 0;
+ _resources.ResourcesChanged -= ThisResourcesChanged;
+ }
+
+ _resources = value;
+ _resources.ResourcesChanged += ThisResourcesChanged;
+
+ if (hadResources || _resources.Count > 0)
+ {
+ ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
+ }
+ }
+ }
+
///
/// Gets or sets a user-defined object attached to the control.
///
@@ -296,9 +355,35 @@ namespace Avalonia.Controls
internal set { SetValue(TemplatedParentProperty, value); }
}
+ ///
+ /// Gets the control's logical children.
+ ///
+ protected IAvaloniaList LogicalChildren
+ {
+ get
+ {
+ if (_logicalChildren == null)
+ {
+ var list = new AvaloniaList();
+ list.ResetBehavior = ResetBehavior.Remove;
+ list.Validate = ValidateLogicalChild;
+ list.CollectionChanged += LogicalChildrenCollectionChanged;
+ _logicalChildren = list;
+ }
+
+ return _logicalChildren;
+ }
+ }
+
///
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
+ ///
+ /// Gets the collection in a form that allows adding and removing
+ /// pseudoclasses.
+ ///
+ protected IPseudoClasses PseudoClasses => Classes;
+
///
/// Gets a value indicating whether the element is attached to a rooted logical tree.
///
@@ -314,6 +399,12 @@ namespace Avalonia.Controls
///
IAvaloniaReadOnlyList ILogical.LogicalChildren => LogicalChildren;
+ ///
+ bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources;
+
+ ///
+ IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode;
+
///
IAvaloniaReadOnlyList IStyleable.Classes => Classes;
@@ -390,31 +481,24 @@ namespace Avalonia.Controls
this.OnDetachedFromLogicalTreeCore(e);
}
- ///
- /// Gets the control's logical children.
- ///
- protected IAvaloniaList LogicalChildren
+ ///
+ void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e)
{
- get
- {
- if (_logicalChildren == null)
- {
- var list = new AvaloniaList();
- list.ResetBehavior = ResetBehavior.Remove;
- list.Validate = ValidateLogicalChild;
- list.CollectionChanged += LogicalChildrenCollectionChanged;
- _logicalChildren = list;
- }
+ ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
- return _logicalChildren;
+ foreach (var child in LogicalChildren)
+ {
+ child.NotifyResourcesChanged(e);
}
}
- ///
- /// Gets the collection in a form that allows adding and removing
- /// pseudoclasses.
- ///
- protected IPseudoClasses PseudoClasses => Classes;
+ ///
+ bool IResourceProvider.TryGetResource(string key, out object value)
+ {
+ value = null;
+ return (_resources?.TryGetResource(key, out value) ?? false) ||
+ (_styles?.TryGetResource(key, out value) ?? false);
+ }
///
/// Sets the control's logical parent.
@@ -450,6 +534,7 @@ namespace Avalonia.Controls
}
_parent = (IControl)parent;
+ ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)
{
@@ -841,5 +926,10 @@ namespace Avalonia.Controls
}
}
}
+
+ private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e)
+ {
+ ((ILogical)this).NotifyResourcesChanged(e);
+ }
}
}
diff --git a/src/Avalonia.Controls/IControl.cs b/src/Avalonia.Controls/IControl.cs
index 41436c6d87..36e09b2ea1 100644
--- a/src/Avalonia.Controls/IControl.cs
+++ b/src/Avalonia.Controls/IControl.cs
@@ -20,6 +20,7 @@ namespace Avalonia.Controls
ILayoutable,
IInputElement,
INamed,
+ IResourceNode,
IStyleable,
IStyleHost
{
diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs
index a2b5f3f8b4..5f6b3ad4c8 100644
--- a/src/Avalonia.Controls/Presenters/TextPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs
@@ -115,7 +115,7 @@ namespace Avalonia.Controls.Presenters
if (_highlightBrush == null)
{
- _highlightBrush = (IBrush)this.FindStyleResource("HighlightBrush");
+ _highlightBrush = (IBrush)this.FindResource("HighlightBrush");
}
foreach (var rect in rects)
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index f8db0e2a5b..1af347ab4e 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -10,9 +10,11 @@ using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.Logging;
+using Avalonia.LogicalTree;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Styling;
+using Avalonia.Utilities;
using Avalonia.VisualTree;
using JetBrains.Annotations;
@@ -26,7 +28,13 @@ namespace Avalonia.Controls
/// It handles scheduling layout, styling and rendering as well as
/// tracking the widget's .
///
- public abstract class TopLevel : ContentControl, IInputRoot, ILayoutRoot, IRenderRoot, ICloseable, IStyleRoot
+ public abstract class TopLevel : ContentControl,
+ IInputRoot,
+ ILayoutRoot,
+ IRenderRoot,
+ ICloseable,
+ IStyleRoot,
+ IWeakSubscriber
{
///
/// Defines the property.
@@ -100,7 +108,6 @@ namespace Avalonia.Controls
impl.Resized = HandleResized;
impl.ScalingChanged = HandleScalingChanged;
-
_keyboardNavigationHandler?.SetOwner(this);
_accessKeyHandler?.SetOwner(this);
styler?.ApplyStyles(this);
@@ -116,6 +123,14 @@ namespace Avalonia.Controls
{
_applicationLifecycle.OnExit += OnApplicationExiting;
}
+
+ if (((IStyleHost)this).StylingParent is IResourceProvider applicationResources)
+ {
+ WeakSubscriptionManager.Subscribe(
+ applicationResources,
+ nameof(IResourceProvider.ResourcesChanged),
+ this);
+ }
}
///
@@ -165,6 +180,11 @@ namespace Avalonia.Controls
///
IMouseDevice IInputRoot.MouseDevice => PlatformImpl?.MouseDevice;
+ void IWeakSubscriber.OnEvent(object sender, ResourcesChangedEventArgs e)
+ {
+ ((ILogical)this).NotifyResourcesChanged(e);
+ }
+
///
/// Gets or sets a value indicating whether access keys are shown in the window.
///
diff --git a/src/Avalonia.Styling/Avalonia.Styling.csproj b/src/Avalonia.Styling/Avalonia.Styling.csproj
index 965b6d87e6..6c0b957bf4 100644
--- a/src/Avalonia.Styling/Avalonia.Styling.csproj
+++ b/src/Avalonia.Styling/Avalonia.Styling.csproj
@@ -2,6 +2,7 @@
netstandard2.0
false
+ Avalonia
true
diff --git a/src/Avalonia.Styling/Controls/IResourceDictionary.cs b/src/Avalonia.Styling/Controls/IResourceDictionary.cs
new file mode 100644
index 0000000000..d7c86b7d74
--- /dev/null
+++ b/src/Avalonia.Styling/Controls/IResourceDictionary.cs
@@ -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
+{
+ ///
+ /// An indexed dictionary of resources.
+ ///
+ public interface IResourceDictionary : IResourceProvider, IDictionary