Browse Source

Merge pull request #1594 from jkoritzinsky/refactor/binding-support

Refactor Binding Support classes and Styling
pull/1626/head
Steven Kirk 8 years ago
committed by GitHub
parent
commit
e7df62ab2c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      Avalonia.sln
  2. 8
      build/Binding.props
  3. 4
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
  4. 20
      samples/RenderTest/Pages/AnimationsPage.xaml
  5. 2
      samples/RenderTest/Pages/ClippingPage.xaml
  6. 7
      src/Android/Avalonia.Android/Resources/Resource.Designer.cs
  7. 33
      src/Avalonia.Animation/Animation.cs
  8. 17
      src/Avalonia.Animation/AnimationSetter.cs
  9. 18
      src/Avalonia.Animation/AnimatorAttribute.cs
  10. 21
      src/Avalonia.Animation/DoubleSetter.cs
  11. 1
      src/Avalonia.Base/Avalonia.Base.csproj
  12. 2
      src/Avalonia.Base/AvaloniaObject.cs
  13. 5
      src/Avalonia.Base/Collections/AvaloniaDictionary.cs
  14. 3
      src/Avalonia.Base/Data/BindingNotification.cs
  15. 2
      src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs
  16. 2
      src/Avalonia.Base/Data/Converters/BoolConverters.cs
  17. 2
      src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
  18. 2
      src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs
  19. 2
      src/Avalonia.Base/Data/Converters/FuncValueConverter.cs
  20. 2
      src/Avalonia.Base/Data/Converters/IMultiValueConverter.cs
  21. 2
      src/Avalonia.Base/Data/Converters/IValueConverter.cs
  22. 2
      src/Avalonia.Base/Data/Converters/StringConverters.cs
  23. 4
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  24. 2
      src/Avalonia.Base/Data/Core/CommonPropertyNames.cs
  25. 2
      src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs
  26. 2
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  27. 4
      src/Avalonia.Base/Data/Core/ExpressionNodeBuilder.cs
  28. 45
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  29. 4
      src/Avalonia.Base/Data/Core/ExpressionParseException.cs
  30. 2
      src/Avalonia.Base/Data/Core/ISettableNode.cs
  31. 2
      src/Avalonia.Base/Data/Core/ITransformNode.cs
  32. 2
      src/Avalonia.Base/Data/Core/IndexerNode.cs
  33. 2
      src/Avalonia.Base/Data/Core/LogicalNotNode.cs
  34. 3
      src/Avalonia.Base/Data/Core/MarkupBindingChainException.cs
  35. 2
      src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs
  36. 2
      src/Avalonia.Base/Data/Core/Parsers/ExpressionParser.cs
  37. 2
      src/Avalonia.Base/Data/Core/Parsers/IdentifierParser.cs
  38. 2
      src/Avalonia.Base/Data/Core/Parsers/Reader.cs
  39. 2
      src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
  40. 2
      src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs
  41. 2
      src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs
  42. 2
      src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
  43. 2
      src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs
  44. 2
      src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs
  45. 2
      src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs
  46. 2
      src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs
  47. 2
      src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
  48. 2
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  49. 2
      src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
  50. 2
      src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
  51. 2
      src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs
  52. 2
      src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs
  53. 2
      src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs
  54. 4
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  55. 2
      src/Avalonia.Base/Data/Core/StreamNode.cs
  56. 3
      src/Avalonia.Base/Properties/AssemblyInfo.cs
  57. 764
      src/Avalonia.Controls/Control.cs
  58. 15
      src/Avalonia.Controls/ControlExtensions.cs
  59. 6
      src/Avalonia.Controls/HotkeyManager.cs
  60. 30
      src/Avalonia.Controls/IControl.cs
  61. 24
      src/Avalonia.Controls/Utils/AncestorFinder.cs
  62. 4
      src/Avalonia.Input/InputElement.cs
  63. 3
      src/Avalonia.Styling/Avalonia.Styling.csproj
  64. 16
      src/Avalonia.Styling/Controls/Classes.cs
  65. 0
      src/Avalonia.Styling/Controls/IPseudoClasses.cs
  66. 2
      src/Avalonia.Styling/Controls/ISetInheritanceParent.cs
  67. 2
      src/Avalonia.Styling/Controls/ISetLogicalParent.cs
  68. 36
      src/Avalonia.Styling/Controls/NameScope.cs
  69. 12
      src/Avalonia.Styling/Controls/NameScopeExtensions.cs
  70. 0
      src/Avalonia.Styling/INamed.cs
  71. 42
      src/Avalonia.Styling/IStyledElement.cs
  72. 97
      src/Avalonia.Styling/LogicalTree/ControlLocator.cs
  73. 783
      src/Avalonia.Styling/StyledElement.cs
  74. 14
      src/Avalonia.Styling/Styling/IRequiresTemplateInSetter.cs
  75. 1
      src/Avalonia.Styling/Styling/IStyleable.cs
  76. 9
      src/Avalonia.Styling/Styling/Setter.cs
  77. 15
      src/Avalonia.Styling/app.config
  78. 22
      src/Avalonia.Visuals/Animation/TransformSetter.cs
  79. 3
      src/Avalonia.Visuals/Avalonia.Visuals.csproj
  80. 5
      src/Avalonia.Visuals/Media/Transform.cs
  81. 2
      src/Avalonia.Visuals/Visual.cs
  82. 46
      src/Avalonia.Visuals/VisualTree/VisualLocator.cs
  83. 9
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  84. 2
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  85. 2
      src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs
  86. 4
      src/Markup/Avalonia.Markup.Xaml/Converters/SelectorTypeConverter.cs
  87. 3
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  88. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs
  89. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs
  90. 4
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  91. 5
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
  92. 4
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaRuntimeTypeProvider.cs
  93. 2
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs
  94. 2
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs
  95. 2
      src/Markup/Avalonia.Markup.Xaml/Properties/AssemblyInfo.cs
  96. 2
      src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs
  97. 7
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  98. 9
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  99. 157
      src/Markup/Avalonia.Markup/ControlLocator.cs
  100. 70
      src/Markup/Avalonia.Markup/Data/Binding.cs

2
Avalonia.sln

@ -134,6 +134,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.NetCore", "s
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1-27F5-4255-9AFC-04ABFD11683A}"
ProjectSection(SolutionItems) = preProject
build\Base.props = build\Base.props
build\Binding.props = build\Binding.props
build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props

8
build/Binding.props

@ -0,0 +1,8 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="System.ComponentModel.Annotations" Version="4.3.0" />
<PackageReference Include="System.ComponentModel.TypeConverter" Version="4.3.0" />
<PackageReference Include="System.ComponentModel.Primitives" Version="4.3.0" />
<PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" />
</ItemGroup>
</Project>

4
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs

@ -2,12 +2,14 @@ using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Markup;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Data;
using Avalonia.Markup.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Data.Converters;
using Avalonia.Data;
namespace ControlCatalog.Pages
{

20
samples/RenderTest/Pages/AnimationsPage.xaml

@ -48,13 +48,13 @@
PlaybackDirection="AlternateReverse"
Easing="SineEaseInOut">
<KeyFrame Cue="20%">
<TransformSetter Property="RotateTransform.Angle" Value="45"/>
<Setter Property="RotateTransform.Angle" Value="45"/>
</KeyFrame>
<KeyFrame Cue="50%">
<TransformSetter Property="ScaleTransform.ScaleX" Value="1.5"/>
<Setter Property="ScaleTransform.ScaleX" Value="1.5"/>
</KeyFrame>
<KeyFrame Cue="80%">
<TransformSetter Property="RotateTransform.Angle" Value="120"/>
<Setter Property="RotateTransform.Angle" Value="120"/>
</KeyFrame>
</Animation>
</Style.Animations>
@ -63,8 +63,8 @@
<Style.Animations>
<Animation Duration="0:0:0.5" Easing="SineEaseInOut">
<KeyFrame Cue="50%">
<TransformSetter Property="ScaleTransform.ScaleX" Value="0.8"/>
<TransformSetter Property="ScaleTransform.ScaleY" Value="0.8"/>
<Setter Property="ScaleTransform.ScaleX" Value="0.8"/>
<Setter Property="ScaleTransform.ScaleY" Value="0.8"/>
</KeyFrame>
</Animation>
</Style.Animations>
@ -76,8 +76,8 @@
Easing="QuadraticEaseInOut"
RepeatCount="Loop">
<KeyFrame Cue="50%">
<TransformSetter Property="ScaleTransform.ScaleX" Value="0.8"/>
<TransformSetter Property="ScaleTransform.ScaleY" Value="0.8"/>
<Setter Property="ScaleTransform.ScaleX" Value="0.8"/>
<Setter Property="ScaleTransform.ScaleY" Value="0.8"/>
</KeyFrame>
</Animation>
</Style.Animations>
@ -86,7 +86,7 @@
<Style.Animations>
<Animation Duration="0:0:3" Easing="BounceEaseInOut">
<KeyFrame Cue="48%">
<TransformSetter Property="TranslateTransform.Y" Value="-100"/>
<Setter Property="TranslateTransform.Y" Value="-100"/>
</KeyFrame>
</Animation>
</Style.Animations>
@ -95,10 +95,10 @@
<Style.Animations>
<Animation Duration="0:0:3" Easing="CircularEaseInOut">
<KeyFrame Cue="25%">
<TransformSetter Property="SkewTransform.AngleX" Value="-20"/>
<Setter Property="SkewTransform.AngleX" Value="-20"/>
</KeyFrame>
<KeyFrame Cue="75%">
<TransformSetter Property="SkewTransform.AngleX" Value="20"/>
<Setter Property="SkewTransform.AngleX" Value="20"/>
</KeyFrame>
</Animation>
</Style.Animations>

2
samples/RenderTest/Pages/ClippingPage.xaml

@ -10,7 +10,7 @@
<Style.Animations>
<Animation Duration="0:0:2" RepeatCount="Loop">
<KeyFrame Cue="100%">
<TransformSetter Property="RotateTransform.Angle" Value="360"/>
<Setter Property="RotateTransform.Angle" Value="360"/>
</KeyFrame>
</Animation>
</Style.Animations>

7
src/Android/Avalonia.Android/Resources/Resource.Designer.cs

@ -40,11 +40,14 @@ namespace Avalonia.Android
public partial class String
{
// aapt resource value: 0x7f020002
public static int ApplicationName = 2130837506;
// aapt resource value: 0x7f020001
public static int ApplicationName = 2130837505;
public static int Hello = 2130837505;
// aapt resource value: 0x7f020000
public static int Hello = 2130837504;
public static int library_name = 2130837504;
static String()
{

33
src/Avalonia.Animation/Animation.cs

@ -18,6 +18,28 @@ namespace Avalonia.Animation
/// </summary>
public class Animation : AvaloniaList<KeyFrame>, IDisposable, IAnimation
{
private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator)> Animators = new List<(Func<AvaloniaProperty, bool>, Type)>
{
( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) )
};
public static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition)
where TAnimator: IAnimator
{
Animators.Insert(0, (condition, typeof(TAnimator)));
}
private static Type GetAnimatorType(AvaloniaProperty property)
{
foreach (var (condition, type) in Animators)
{
if (condition(property))
{
return type;
}
}
return null;
}
private bool _isChildrenChanged = false;
private List<IDisposable> _subscription = new List<IDisposable>();
@ -67,14 +89,13 @@ namespace Avalonia.Animation
{
foreach (var setter in keyframe)
{
var custAttr = setter.GetType()
.GetCustomAttributes()
.Where(p => p.GetType() == typeof(AnimatorAttribute));
var handler = GetAnimatorType(setter.Property);
if (!custAttr.Any())
throw new InvalidProgramException($"Type {setter.GetType()} doesn't have Animator attribute.");
if (handler == null)
{
throw new InvalidOperationException($"No animator registered for the property {setter.Property}. Add an animator to the Animation.Animators collection that matches this property to animate it.");
}
var handler = ((AnimatorAttribute)custAttr.First()).HandlerType;
if (!handlerList.Contains((handler, setter.Property)))
handlerList.Add((handler, setter.Property));

17
src/Avalonia.Animation/AnimationSetter.cs

@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Reactive.Linq;
using System.Diagnostics;
using Avalonia.Animation.Utils;
using Avalonia.Data;
namespace Avalonia.Animation
{
public abstract class AnimationSetter : IAnimationSetter
{
public AvaloniaProperty Property { get; set; }
public object Value { get; set; }
}
}

18
src/Avalonia.Animation/AnimatorAttribute.cs

@ -1,18 +0,0 @@
using System;
namespace Avalonia.Animation
{
/// <summary>
/// Attribute for <see cref="IAnimationSetter"/> objects
/// that maps the setter to it's <see cref="Animator{T}"/>.
/// </summary>
public class AnimatorAttribute : Attribute
{
public Type HandlerType;
public AnimatorAttribute(Type handler)
{
this.HandlerType = handler;
}
}
}

21
src/Avalonia.Animation/DoubleSetter.cs

@ -1,21 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Reactive.Linq;
using System.Diagnostics;
using Avalonia.Animation.Utils;
using Avalonia.Data;
namespace Avalonia.Animation
{
/// <summary>
/// Setter that handles <see cref="double"/> properties
/// in the target.
/// </summary>
[Animator(typeof(DoubleAnimator))]
public class DoubleSetter : AnimationSetter
{
}
}

1
src/Avalonia.Base/Avalonia.Base.csproj

@ -5,6 +5,7 @@
<RootNamespace>Avalonia</RootNamespace>
</PropertyGroup>
<Import Project="..\..\build\Base.props" />
<Import Project="..\..\build\Binding.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\JetBrains.Annotations.props" />
</Project>

2
src/Avalonia.Base/AvaloniaObject.cs

@ -703,7 +703,7 @@ namespace Avalonia
/// <returns>The default value.</returns>
private object GetDefaultValue(AvaloniaProperty property)
{
if (property.Inherits && _inheritanceParent is AvaloniaObject aobj)
if (property.Inherits && InheritanceParent is AvaloniaObject aobj)
return aobj.GetValueInternal(property);
return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType());
}

5
src/Avalonia.Base/Collections/AvaloniaDictionary.cs

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using Avalonia.Data.Core;
namespace Avalonia.Collections
{
@ -116,8 +117,8 @@ namespace Avalonia.Collections
_inner = new Dictionary<TKey, TValue>();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(CommonPropertyNames.IndexerName));
if (CollectionChanged != null)

3
src/Avalonia.Base/Data/BindingNotification.cs

@ -171,8 +171,7 @@ namespace Avalonia.Data
/// </remarks>
public static object ExtractError(object o)
{
var notification = o as BindingNotification;
return notification != null ? notification.Error : o;
return o is BindingNotification notification ? notification.Error : o;
}
/// <summary>

2
src/Markup/Avalonia.Markup/AlwaysEnabledDelegateCommand.cs → src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs

@ -6,7 +6,7 @@ using System.Reflection;
using System.Text;
using System.Windows.Input;
namespace Avalonia.Markup
namespace Avalonia.Data.Converters
{
class AlwaysEnabledDelegateCommand : ICommand
{

2
src/Markup/Avalonia.Markup/BoolConverters.cs → src/Avalonia.Base/Data/Converters/BoolConverters.cs

@ -3,7 +3,7 @@
using System.Linq;
namespace Avalonia.Markup
namespace Avalonia.Data.Converters
{
/// <summary>
/// Provides a set of useful <see cref="IValueConverter"/>s for working with string values.

2
src/Markup/Avalonia.Markup/DefaultValueConverter.cs → src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs

@ -7,7 +7,7 @@ using Avalonia.Data;
using Avalonia.Utilities;
using System.Windows.Input;
namespace Avalonia.Markup
namespace Avalonia.Data.Converters
{
/// <summary>
/// Provides a default set of value conversions for bindings that do not specify a value

2
src/Markup/Avalonia.Markup/FuncMultiValueConverter.cs → src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs

@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Avalonia.Markup
namespace Avalonia.Data.Converters
{
/// <summary>
/// A general purpose <see cref="IValueConverter"/> that uses a <see cref="Func{T1, TResult}"/>

2
src/Markup/Avalonia.Markup/FuncValueConverter.cs → src/Avalonia.Base/Data/Converters/FuncValueConverter.cs

@ -5,7 +5,7 @@ using System;
using System.Globalization;
using Avalonia.Utilities;
namespace Avalonia.Markup
namespace Avalonia.Data.Converters
{
/// <summary>
/// A general purpose <see cref="IValueConverter"/> that uses a <see cref="Func{T1, TResult}"/>

2
src/Markup/Avalonia.Markup/IMultiValueConverter.cs → src/Avalonia.Base/Data/Converters/IMultiValueConverter.cs

@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
namespace Avalonia.Markup
namespace Avalonia.Data.Converters
{
/// <summary>
/// Converts multi-binding inputs to a final value.

2
src/Markup/Avalonia.Markup/IValueConverter.cs → src/Avalonia.Base/Data/Converters/IValueConverter.cs

@ -5,7 +5,7 @@ using System;
using System.Globalization;
using Avalonia.Data;
namespace Avalonia.Markup
namespace Avalonia.Data.Converters
{
/// <summary>
/// Converts a binding value.

2
src/Markup/Avalonia.Markup/StringConverters.cs → src/Avalonia.Base/Data/Converters/StringConverters.cs

@ -5,7 +5,7 @@ using System;
using System.Globalization;
using Avalonia.Utilities;
namespace Avalonia.Markup
namespace Avalonia.Data.Converters
{
/// <summary>
/// Provides a set of useful <see cref="IValueConverter"/>s for working with string values.

4
src/Markup/Avalonia.Markup/Data/BindingExpression.cs → src/Avalonia.Base/Data/Core/BindingExpression.cs

@ -5,11 +5,11 @@ using System;
using System.Globalization;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Logging;
using Avalonia.Utilities;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
/// <summary>
/// Binds to an expression on an object using a type value converter to convert the values

2
src/Markup/Avalonia.Markup/Data/CommonPropertyNames.cs → src/Avalonia.Base/Data/Core/CommonPropertyNames.cs

@ -1,7 +1,7 @@
// 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.
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
public static class CommonPropertyNames
{

2
src/Markup/Avalonia.Markup/Data/EmptyExpressionNode.cs → src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs

@ -4,7 +4,7 @@
using System;
using System.Reactive.Linq;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
internal class EmptyExpressionNode : ExpressionNode
{

2
src/Markup/Avalonia.Markup/Data/ExpressionNode.cs → src/Avalonia.Base/Data/Core/ExpressionNode.cs

@ -7,7 +7,7 @@ using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Data;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
internal abstract class ExpressionNode : ISubject<object>
{

4
src/Markup/Avalonia.Markup/Data/ExpressionNodeBuilder.cs → src/Avalonia.Base/Data/Core/ExpressionNodeBuilder.cs

@ -2,9 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Markup.Data.Parsers;
using Avalonia.Data.Core.Parsers;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
internal static class ExpressionNodeBuilder
{

45
src/Markup/Avalonia.Markup/Data/ExpressionObserver.cs → src/Avalonia.Base/Data/Core/ExpressionObserver.cs

@ -8,9 +8,9 @@ using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.Markup.Data.Plugins;
using Avalonia.Data.Core.Plugins;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
/// <summary>
/// Observes and sets the value of an expression on an object.
@ -245,40 +245,35 @@ namespace Avalonia.Markup.Data
private object Translate(object o)
{
var weak = o as WeakReference;
if (weak != null)
if (o is WeakReference weak)
{
return weak.Target;
}
else
else if (BindingNotification.ExtractError(o) is MarkupBindingChainException broken)
{
var broken = BindingNotification.ExtractError(o) as MarkupBindingChainException;
if (broken != null)
{
broken.Commit(Description);
}
return o;
broken.Commit(Description);
}
return o;
}
private IDisposable StartRoot()
{
var observable = _root as IObservable<object>;
if (observable != null)
switch (_root)
{
return observable.Subscribe(
x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null),
_ => _finished.OnNext(Unit.Default),
() => _finished.OnNext(Unit.Default));
}
else
{
_node.Target = (WeakReference)_root;
return Disposable.Empty;
case IObservable<object> observable:
return observable.Subscribe(
x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null),
_ => _finished.OnNext(Unit.Default),
() => _finished.OnNext(Unit.Default));
case WeakReference weak:
_node.Target = weak;
break;
default:
throw new AvaloniaInternalException("The ExpressionObserver._root member should only be either an observable or WeakReference.");
}
return Disposable.Empty;
}
}
}

4
src/Markup/Avalonia.Markup/Data/ExpressionParseException.cs → src/Avalonia.Base/Data/Core/ExpressionParseException.cs

@ -2,9 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Markup.Data.Parsers;
using Avalonia.Data.Core.Parsers;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
/// <summary>
/// Exception thrown when <see cref="ExpressionObserver"/> could not parse the provided

2
src/Markup/Avalonia.Markup/Data/ISettableNode.cs → src/Avalonia.Base/Data/Core/ISettableNode.cs

@ -5,7 +5,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
interface ISettableNode
{

2
src/Markup/Avalonia.Markup/Data/ITransformNode.cs → src/Avalonia.Base/Data/Core/ITransformNode.cs

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
interface ITransformNode
{

2
src/Markup/Avalonia.Markup/Data/IndexerNode.cs → src/Avalonia.Base/Data/Core/IndexerNode.cs

@ -13,7 +13,7 @@ using System.Reflection;
using System.Reactive.Linq;
using Avalonia.Data;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
internal class IndexerNode : ExpressionNode, ISettableNode
{

2
src/Markup/Avalonia.Markup/Data/LogicalNotNode.cs → src/Avalonia.Base/Data/Core/LogicalNotNode.cs

@ -5,7 +5,7 @@ using System;
using System.Globalization;
using Avalonia.Data;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
internal class LogicalNotNode : ExpressionNode, ITransformNode
{

3
src/Markup/Avalonia.Markup/Data/MarkupBindingChainException.cs → src/Avalonia.Base/Data/Core/MarkupBindingChainException.cs

@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Data;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
internal class MarkupBindingChainException : BindingChainException
{

2
src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs → src/Avalonia.Base/Data/Core/Parsers/ArgumentListParser.cs

@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Markup.Data.Parsers
namespace Avalonia.Data.Core.Parsers
{
internal static class ArgumentListParser
{

2
src/Markup/Avalonia.Markup/Data/Parsers/ExpressionParser.cs → src/Avalonia.Base/Data/Core/Parsers/ExpressionParser.cs

@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Markup.Data.Parsers
namespace Avalonia.Data.Core.Parsers
{
internal class ExpressionParser
{

2
src/Markup/Avalonia.Markup/Data/Parsers/IdentifierParser.cs → src/Avalonia.Base/Data/Core/Parsers/IdentifierParser.cs

@ -4,7 +4,7 @@
using System.Globalization;
using System.Text;
namespace Avalonia.Markup.Data.Parsers
namespace Avalonia.Data.Core.Parsers
{
internal static class IdentifierParser
{

2
src/Markup/Avalonia.Markup/Data/Parsers/Reader.cs → src/Avalonia.Base/Data/Core/Parsers/Reader.cs

@ -3,7 +3,7 @@
using System;
namespace Avalonia.Markup.Data.Parsers
namespace Avalonia.Data.Core.Parsers
{
internal class Reader
{

2
src/Markup/Avalonia.Markup/Data/Plugins/AvaloniaPropertyAccessorPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs

@ -6,7 +6,7 @@ using System.Linq;
using System.Reactive.Linq;
using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Reads a property from a <see cref="AvaloniaObject"/>.

2
src/Markup/Avalonia.Markup/Data/Plugins/DataAnnotationsValidationPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs

@ -8,7 +8,7 @@ using System.Linq;
using System.Reflection;
using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Validates properties on that have <see cref="ValidationAttribute"/>s.

2
src/Markup/Avalonia.Markup/Data/Plugins/DataValidatiorBase.cs → src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs

@ -4,7 +4,7 @@
using System;
using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Base class for data validators.

2
src/Markup/Avalonia.Markup/Data/Plugins/ExceptionValidationPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs

@ -5,7 +5,7 @@ using Avalonia.Data;
using System;
using System.Reflection;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Validates properties that report errors by throwing exceptions.

2
src/Markup/Avalonia.Markup/Data/Plugins/IDataValidationPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/IDataValidationPlugin.cs

@ -4,7 +4,7 @@
using System;
using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Defines how data validation is observed by an <see cref="ExpressionObserver"/>.

2
src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessor.cs → src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs

@ -4,7 +4,7 @@
using System;
using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Defines an accessor to a property on an object returned by a

2
src/Markup/Avalonia.Markup/Data/Plugins/IPropertyAccessorPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessorPlugin.cs

@ -3,7 +3,7 @@
using System;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Defines how a member is read, written and observed by an

2
src/Markup/Avalonia.Markup/Data/Plugins/IStreamPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/IStreamPlugin.cs

@ -3,7 +3,7 @@
using System;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Defines a plugin that handles the '^' stream binding operator.

2
src/Markup/Avalonia.Markup/Data/Plugins/IndeiValidationPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs

@ -8,7 +8,7 @@ using System.Linq;
using Avalonia.Data;
using Avalonia.Utilities;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Validates properties on objects that implement <see cref="INotifyDataErrorInfo"/>.

2
src/Markup/Avalonia.Markup/Data/Plugins/InpcPropertyAccessorPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -10,7 +10,7 @@ using Avalonia.Data;
using Avalonia.Logging;
using Avalonia.Utilities;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Reads a property from a standard C# object that optionally supports the

2
src/Markup/Avalonia.Markup/Data/Plugins/MethodAccessorPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs

@ -5,7 +5,7 @@ using Avalonia.Data;
using System.Reflection;
using System.Linq;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
class MethodAccessorPlugin : IPropertyAccessorPlugin
{

2
src/Markup/Avalonia.Markup/Data/Plugins/ObservableStreamPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs

@ -3,7 +3,7 @@
using System;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Handles binding to <see cref="IObservable{T}"/>s for the '^' stream binding operator.

2
src/Markup/Avalonia.Markup/Data/Plugins/PropertyAccessorBase.cs → src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs

@ -4,7 +4,7 @@
using System;
using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Defines a default base implementation for a <see cref="IPropertyAccessor"/>.

2
src/Markup/Avalonia.Markup/Data/Plugins/PropertyError.cs → src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs

@ -2,7 +2,7 @@ using System;
using System.Reactive.Disposables;
using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// An <see cref="IPropertyAccessor"/> that represents an error.

2
src/Markup/Avalonia.Markup/Data/Plugins/TaskStreamPlugin.cs → src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs

@ -9,7 +9,7 @@ using System.Reflection;
using System.Threading.Tasks;
using Avalonia.Data;
namespace Avalonia.Markup.Data.Plugins
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Handles binding to <see cref="Task"/>s for the '^' stream binding operator.

4
src/Markup/Avalonia.Markup/Data/PropertyAccessorNode.cs → src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@ -6,9 +6,9 @@ using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia.Data;
using Avalonia.Markup.Data.Plugins;
using Avalonia.Data.Core.Plugins;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
internal class PropertyAccessorNode : ExpressionNode, ISettableNode
{

2
src/Markup/Avalonia.Markup/Data/StreamNode.cs → src/Avalonia.Base/Data/Core/StreamNode.cs

@ -6,7 +6,7 @@ using System.Globalization;
using Avalonia.Data;
using System.Reactive.Linq;
namespace Avalonia.Markup.Data
namespace Avalonia.Data.Core
{
internal class StreamNode : ExpressionNode
{

3
src/Avalonia.Base/Properties/AssemblyInfo.cs

@ -1,9 +1,10 @@
// 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.Reflection;
using System.Runtime.CompilerServices;
using Avalonia.Metadata;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Data.Converters")]
[assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.UnitTests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

764
src/Avalonia.Controls/Control.cs

@ -29,53 +29,22 @@ namespace Avalonia.Controls
/// <remarks>
/// The control class extends <see cref="InputElement"/> and adds the following features:
///
/// - An inherited <see cref="DataContext"/>.
/// - A <see cref="Tag"/> property to allow user-defined data to be attached to the control.
/// - A collection of class strings for custom styling.
/// - Implements <see cref="IStyleable"/> to allow styling to work on the control.
/// - Implements <see cref="ILogical"/> to form part of a logical tree.
/// </remarks>
public class Control : InputElement, IControl, INamed, ISetInheritanceParent, ISetLogicalParent, ISupportInitialize, IVisualBrushInitialize
public class Control : InputElement, IControl, INamed, ISupportInitialize, IVisualBrushInitialize, IRequiresTemplateInSetter
{
/// <summary>
/// Defines the <see cref="DataContext"/> property.
/// </summary>
public static readonly StyledProperty<object> DataContextProperty =
AvaloniaProperty.Register<Control, object>(
nameof(DataContext),
inherits: true,
notifying: DataContextNotifying);
/// <summary>
/// Defines the <see cref="FocusAdorner"/> property.
/// </summary>
public static readonly StyledProperty<ITemplate<IControl>> FocusAdornerProperty =
AvaloniaProperty.Register<Control, ITemplate<IControl>>(nameof(FocusAdorner));
/// <summary>
/// Defines the <see cref="Name"/> property.
/// </summary>
public static readonly DirectProperty<Control, string> NameProperty =
AvaloniaProperty.RegisterDirect<Control, string>(nameof(Name), o => o.Name, (o, v) => o.Name = v);
/// <summary>
/// Defines the <see cref="Parent"/> property.
/// </summary>
public static readonly DirectProperty<Control, IControl> ParentProperty =
AvaloniaProperty.RegisterDirect<Control, IControl>(nameof(Parent), o => o.Parent);
/// <summary>
/// Defines the <see cref="Tag"/> property.
/// </summary>
public static readonly StyledProperty<object> TagProperty =
AvaloniaProperty.Register<Control, object>(nameof(Tag));
/// <summary>
/// Defines the <see cref="TemplatedParent"/> property.
/// </summary>
public static readonly StyledProperty<ITemplatedControl> TemplatedParentProperty =
AvaloniaProperty.Register<Control, ITemplatedControl>(nameof(TemplatedParent), inherits: true);
/// <summary>
/// Defines the <see cref="ContextMenu"/> property.
/// </summary>
@ -88,152 +57,8 @@ namespace Avalonia.Controls
public static readonly RoutedEvent<RequestBringIntoViewEventArgs> RequestBringIntoViewEvent =
RoutedEvent.Register<Control, RequestBringIntoViewEventArgs>("RequestBringIntoView", RoutingStrategies.Bubble);
private int _initCount;
private string _name;
private IControl _parent;
private readonly Classes _classes = new Classes();
private DataTemplates _dataTemplates;
private IControl _focusAdorner;
private bool _isAttachedToLogicalTree;
private IAvaloniaList<ILogical> _logicalChildren;
private INameScope _nameScope;
private IResourceDictionary _resources;
private Styles _styles;
private bool _styled;
private Subject<IStyleable> _styleDetach = new Subject<IStyleable>();
private bool _dataContextUpdating;
/// <summary>
/// Initializes static members of the <see cref="Control"/> class.
/// </summary>
static Control()
{
AffectsMeasure(IsVisibleProperty);
PseudoClass(IsEnabledCoreProperty, x => !x, ":disabled");
PseudoClass(IsFocusedProperty, ":focus");
PseudoClass(IsPointerOverProperty, ":pointerover");
DataContextProperty.Changed.AddClassHandler<Control>(x => x.OnDataContextChangedCore);
}
/// <summary>
/// Initializes a new instance of the <see cref="Control"/> class.
/// </summary>
public Control()
{
_nameScope = this as INameScope;
_isAttachedToLogicalTree = this is IStyleRoot;
}
/// <summary>
/// Raised when the control is attached to a rooted logical tree.
/// </summary>
public event EventHandler<LogicalTreeAttachmentEventArgs> AttachedToLogicalTree;
/// <summary>
/// Raised when the control is detached from a rooted logical tree.
/// </summary>
public event EventHandler<LogicalTreeAttachmentEventArgs> DetachedFromLogicalTree;
/// <summary>
/// Occurs when the <see cref="DataContext"/> property changes.
/// </summary>
/// <remarks>
/// This event will be raised when the <see cref="DataContext"/> property has changed and
/// all subscribers to that change have been notified.
/// </remarks>
public event EventHandler DataContextChanged;
/// <summary>
/// Occurs when the control has finished initialization.
/// </summary>
/// <remarks>
/// The Initialized event indicates that all property values on the control have been set.
/// When loading the control from markup, it occurs when
/// <see cref="ISupportInitialize.EndInit"/> is called *and* the control
/// is attached to a rooted logical tree. When the control is created by code and
/// <see cref="ISupportInitialize"/> is not used, it is called when the control is attached
/// to the visual tree.
/// </remarks>
public event EventHandler Initialized;
/// <summary>
/// Occurs when a resource in this control or a parent control has changed.
/// </summary>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary>
/// Gets or sets the name of the control.
/// </summary>
/// <remarks>
/// An element's name is used to uniquely identify a control within the control's name
/// scope. Once the element is added to a logical tree, its name cannot be changed.
/// </remarks>
public string Name
{
get
{
return _name;
}
set
{
if (String.IsNullOrWhiteSpace(value))
{
throw new InvalidOperationException("Cannot set Name to null or empty string.");
}
if (_styled)
{
throw new InvalidOperationException("Cannot set Name : control already styled.");
}
_name = value;
}
}
/// <summary>
/// Gets or sets the control's classes.
/// </summary>
/// <remarks>
/// <para>
/// Classes can be used to apply user-defined styling to controls, or to allow controls
/// that share a common purpose to be easily selected.
/// </para>
/// <para>
/// Even though this property can be set, the setter is only intended for use in object
/// initializers. Assigning to this property does not change the underlying collection,
/// it simply clears the existing collection and addds the contents of the assigned
/// collection.
/// </para>
/// </remarks>
public Classes Classes
{
get
{
return _classes;
}
set
{
if (_classes != value)
{
_classes.Replace(value);
}
}
}
/// <summary>
/// Gets or sets the control's data context.
/// </summary>
/// <remarks>
/// The data context is an inherited property that specifies the default object that will
/// be used for data binding.
/// </remarks>
public object DataContext
{
get { return GetValue(DataContextProperty); }
set { SetValue(DataContextProperty, value); }
}
/// <summary>
/// Gets or sets the control's focus adorner.
@ -253,55 +78,6 @@ namespace Avalonia.Controls
/// </remarks>
public DataTemplates DataTemplates => _dataTemplates ?? (_dataTemplates = new DataTemplates());
/// <summary>
/// Gets a value that indicates whether the element has finished initialization.
/// </summary>
/// <remarks>
/// For more information about when IsInitialized is set, see the <see cref="Initialized"/>
/// event.
/// </remarks>
public bool IsInitialized { get; private set; }
/// <summary>
/// Gets the styles for the control.
/// </summary>
/// <remarks>
/// Styles for the entire application are added to the Application.Styles collection, but
/// each control may in addition define its own styles which are applied to the control
/// itself and its children.
/// </remarks>
public Styles Styles
{
get { return _styles ?? (Styles = new Styles()); }
set
{
Contract.Requires<ArgumentNullException>(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;
}
}
}
/// <summary>
/// Gets the control's logical parent.
/// </summary>
public IControl Parent => _parent;
/// <summary>
/// Gets or sets a context menu to the control.
/// </summary>
@ -311,34 +87,6 @@ namespace Avalonia.Controls
set { SetValue(ContextMenuProperty, value); }
}
/// <summary>
/// Gets or sets the control's resource dictionary.
/// </summary>
public IResourceDictionary Resources
{
get => _resources ?? (Resources = new ResourceDictionary());
set
{
Contract.Requires<ArgumentNullException>(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());
}
}
}
/// <summary>
/// Gets or sets a user-defined object attached to the control.
/// </summary>
@ -348,226 +96,11 @@ namespace Avalonia.Controls
set { SetValue(TagProperty, value); }
}
/// <summary>
/// Gets the control whose lookless template this control is part of.
/// </summary>
public ITemplatedControl TemplatedParent
{
get { return GetValue(TemplatedParentProperty); }
internal set { SetValue(TemplatedParentProperty, value); }
}
/// <summary>
/// Gets the control's logical children.
/// </summary>
protected IAvaloniaList<ILogical> LogicalChildren
{
get
{
if (_logicalChildren == null)
{
var list = new AvaloniaList<ILogical>();
list.ResetBehavior = ResetBehavior.Remove;
list.Validate = ValidateLogicalChild;
list.CollectionChanged += LogicalChildrenCollectionChanged;
_logicalChildren = list;
}
return _logicalChildren;
}
}
public new IControl Parent => (IControl)base.Parent;
/// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
/// <summary>
/// Gets the <see cref="Classes"/> collection in a form that allows adding and removing
/// pseudoclasses.
/// </summary>
protected IPseudoClasses PseudoClasses => Classes;
/// <summary>
/// Gets a value indicating whether the element is attached to a rooted logical tree.
/// </summary>
bool ILogical.IsAttachedToLogicalTree => _isAttachedToLogicalTree;
/// <summary>
/// Gets the control's logical parent.
/// </summary>
ILogical ILogical.LogicalParent => Parent;
/// <summary>
/// Gets the control's logical children.
/// </summary>
IAvaloniaReadOnlyList<ILogical> ILogical.LogicalChildren => LogicalChildren;
/// <inheritdoc/>
bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources;
/// <inheritdoc/>
IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode;
/// <inheritdoc/>
IAvaloniaReadOnlyList<string> IStyleable.Classes => Classes;
/// <summary>
/// Gets the type by which the control is styled.
/// </summary>
/// <remarks>
/// Usually controls are styled by their own type, but there are instances where you want
/// a control to be styled by its base type, e.g. creating SpecialButton that
/// derives from Button and adds extra functionality but is still styled as a regular
/// Button.
/// </remarks>
Type IStyleable.StyleKey => GetType();
/// <inheritdoc/>
IObservable<IStyleable> IStyleable.StyleDetach => _styleDetach;
/// <inheritdoc/>
bool IStyleHost.IsStylesInitialized => _styles != null;
/// <inheritdoc/>
IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent;
/// <inheritdoc/>
public virtual void BeginInit()
{
++_initCount;
}
/// <inheritdoc/>
public virtual void EndInit()
{
if (_initCount == 0)
{
throw new InvalidOperationException("BeginInit was not called.");
}
if (--_initCount == 0 && _isAttachedToLogicalTree)
{
InitializeStylesIfNeeded();
InitializeIfNeeded();
}
}
private void InitializeStylesIfNeeded(bool force = false)
{
if (_initCount == 0 && (!_styled || force))
{
RegisterWithNameScope();
ApplyStyling();
_styled = true;
}
}
private void InitializeIfNeeded()
{
if (_initCount == 0 && !IsInitialized)
{
IsInitialized = true;
Initialized?.Invoke(this, EventArgs.Empty);
}
}
/// <inheritdoc/>
void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
this.OnAttachedToLogicalTreeCore(e);
}
/// <inheritdoc/>
void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
this.OnDetachedFromLogicalTreeCore(e);
}
/// <inheritdoc/>
void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
/// <inheritdoc/>
bool IResourceProvider.TryGetResource(string key, out object value)
{
value = null;
return (_resources?.TryGetResource(key, out value) ?? false) ||
(_styles?.TryGetResource(key, out value) ?? false);
}
/// <summary>
/// Sets the control's logical parent.
/// </summary>
/// <param name="parent">The parent.</param>
void ISetLogicalParent.SetParent(ILogical parent)
{
var old = Parent;
if (parent != old)
{
if (old != null && parent != null)
{
throw new InvalidOperationException("The Control already has a parent.");
}
if (_isAttachedToLogicalTree)
{
var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot;
if (oldRoot == null)
{
throw new AvaloniaInternalException("Was attached to logical tree but cannot find root.");
}
var e = new LogicalTreeAttachmentEventArgs(oldRoot);
OnDetachedFromLogicalTreeCore(e);
}
if (InheritanceParent == null || parent == null)
{
InheritanceParent = parent as AvaloniaObject;
}
_parent = (IControl)parent;
if (old != null)
{
old.ResourcesChanged -= ThisResourcesChanged;
}
if (_parent != null)
{
_parent.ResourcesChanged += ThisResourcesChanged;
}
((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)
{
var newRoot = FindStyleRoot(this);
if (newRoot == null)
{
throw new AvaloniaInternalException("Parent is atttached to logical tree but cannot find root.");
}
var e = new LogicalTreeAttachmentEventArgs(newRoot);
OnAttachedToLogicalTreeCore(e);
}
RaisePropertyChanged(ParentProperty, old, _parent, BindingPriority.LocalValue);
}
}
/// <summary>
/// Sets the control's inheritance parent.
/// </summary>
/// <param name="parent">The parent.</param>
void ISetInheritanceParent.SetParent(IAvaloniaObject parent)
{
InheritanceParent = parent;
}
/// <inheritdoc/>
void IVisualBrushInitialize.EnsureInitialized()
{
@ -600,52 +133,6 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Adds a pseudo-class to be set when a property is true.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="className">The pseudo-class.</param>
protected static void PseudoClass(AvaloniaProperty<bool> property, string className)
{
PseudoClass(property, x => x, className);
}
/// <summary>
/// Adds a pseudo-class to be set when a property equals a certain value.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="selector">Returns a boolean value based on the property value.</param>
/// <param name="className">The pseudo-class.</param>
protected static void PseudoClass<T>(
AvaloniaProperty<T> property,
Func<T, bool> selector,
string className)
{
Contract.Requires<ArgumentNullException>(property != null);
Contract.Requires<ArgumentNullException>(selector != null);
Contract.Requires<ArgumentNullException>(className != null);
if (string.IsNullOrWhiteSpace(className))
{
throw new ArgumentException("Cannot supply an empty className.");
}
property.Changed.Merge(property.Initialized)
.Where(e => e.Sender is Control)
.Subscribe(e =>
{
if (selector((T)e.NewValue))
{
((Control)e.Sender).PseudoClasses.Add(className);
}
else
{
((Control)e.Sender).PseudoClasses.Remove(className);
}
});
}
/// <summary>
/// Gets the element that recieves the focus adorner.
/// </summary>
@ -655,22 +142,6 @@ namespace Avalonia.Controls
return this;
}
/// <summary>
/// Called when the control is added to a rooted logical tree.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
}
/// <summary>
/// Called when the control is removed from a rooted logical tree.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
}
/// <inheritdoc/>
protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
@ -685,29 +156,6 @@ namespace Avalonia.Controls
base.OnDetachedFromVisualTreeCore(e);
}
/// <summary>
/// Called when the <see cref="DataContext"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnDataContextChanged(EventArgs e)
{
DataContextChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Called when the <see cref="DataContext"/> begins updating.
/// </summary>
protected virtual void OnDataContextBeginUpdate()
{
}
/// <summary>
/// Called when the <see cref="DataContext"/> finishes updating.
/// </summary>
protected virtual void OnDataContextEndUpdate()
{
}
/// <inheritdoc/>
protected override void OnGotFocus(GotFocusEventArgs e)
{
@ -757,211 +205,5 @@ namespace Avalonia.Controls
_focusAdorner = null;
}
}
private static void DataContextNotifying(IAvaloniaObject o, bool notifying)
{
if (o is Control control)
{
DataContextNotifying(control, notifying);
}
}
private static void DataContextNotifying(Control control, bool notifying)
{
if (notifying)
{
if (!control._dataContextUpdating)
{
control._dataContextUpdating = true;
control.OnDataContextBeginUpdate();
foreach (var child in control.LogicalChildren)
{
if (child is Control c &&
c.InheritanceParent == control &&
!c.IsSet(DataContextProperty))
{
DataContextNotifying(c, notifying);
}
}
}
}
else
{
if (control._dataContextUpdating)
{
control.OnDataContextEndUpdate();
control._dataContextUpdating = false;
}
}
}
private static IStyleRoot FindStyleRoot(IStyleHost e)
{
while (e != null)
{
if (e is IRenderRoot root)
{
return root as IStyleRoot;
}
e = e.StylingParent;
}
return null;
}
private void ApplyStyling()
{
AvaloniaLocator.Current.GetService<IStyler>()?.ApplyStyles(this);
}
private void RegisterWithNameScope()
{
if (_nameScope == null)
{
_nameScope = NameScope.GetNameScope(this) ?? ((Control)Parent)?._nameScope;
}
if (Name != null)
{
_nameScope?.Register(Name, this);
var visualParent = Parent as Visual;
if (this is INameScope && visualParent != null)
{
// If we have e.g. a named UserControl in a window then we want that control
// to be findable by name from the Window, so register with both name scopes.
// This differs from WPF's behavior in that XAML manually registers controls
// with name scopes based on the XAML file in which the name attribute appears,
// but we're trying to avoid XAML magic in Avalonia in order to made code-
// created UIs easy. This will cause problems if a UserControl declares a name
// in its XAML and that control is included multiple times in a parent control
// (as the name will be duplicated), however at the moment I'm fine with saying
// "don't do that".
var parentNameScope = NameScope.FindNameScope(visualParent);
parentNameScope?.Register(Name, this);
}
}
}
private static void ValidateLogicalChild(ILogical c)
{
if (c == null)
{
throw new ArgumentException("Cannot add null to LogicalChildren.");
}
}
private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
{
// This method can be called when a control is already attached to the logical tree
// in the following scenario:
// - ListBox gets assigned Items containing ListBoxItem
// - ListBox makes ListBoxItem a logical child
// - ListBox template gets applied; making its Panel get attached to logical tree
// - That AttachedToLogicalTree signal travels down to the ListBoxItem
if (!_isAttachedToLogicalTree)
{
_isAttachedToLogicalTree = true;
InitializeStylesIfNeeded(true);
OnAttachedToLogicalTree(e);
AttachedToLogicalTree?.Invoke(this, e);
}
foreach (var child in LogicalChildren.OfType<Control>())
{
child.OnAttachedToLogicalTreeCore(e);
}
}
private void OnDetachedFromLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
{
if (_isAttachedToLogicalTree)
{
if (Name != null)
{
_nameScope?.Unregister(Name);
}
_isAttachedToLogicalTree = false;
_styleDetach.OnNext(this);
OnDetachedFromLogicalTree(e);
DetachedFromLogicalTree?.Invoke(this, e);
foreach (var child in LogicalChildren.OfType<Control>())
{
child.OnDetachedFromLogicalTreeCore(e);
}
#if DEBUG
if (((INotifyCollectionChangedDebug)_classes).GetCollectionChangedSubscribers()?.Length > 0)
{
Logger.Warning(
LogArea.Control,
this,
"{Type} detached from logical tree but still has class listeners",
this.GetType());
}
#endif
}
}
private void OnDataContextChangedCore(AvaloniaPropertyChangedEventArgs e)
{
OnDataContextChanged(EventArgs.Empty);
}
private void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
SetLogicalParent(e.NewItems.Cast<ILogical>());
break;
case NotifyCollectionChangedAction.Remove:
ClearLogicalParent(e.OldItems.Cast<ILogical>());
break;
case NotifyCollectionChangedAction.Replace:
ClearLogicalParent(e.OldItems.Cast<ILogical>());
SetLogicalParent(e.NewItems.Cast<ILogical>());
break;
case NotifyCollectionChangedAction.Reset:
throw new NotSupportedException("Reset should not be signalled on LogicalChildren collection");
}
}
private void SetLogicalParent(IEnumerable<ILogical> children)
{
foreach (var i in children)
{
if (i.LogicalParent == null)
{
((ISetLogicalParent)i).SetParent(this);
}
}
}
private void ClearLogicalParent(IEnumerable<ILogical> children)
{
foreach (var i in children)
{
if (i.LogicalParent == this)
{
((ISetLogicalParent)i).SetParent(null);
}
}
}
private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e)
{
((ILogical)this).NotifyResourcesChanged(e);
}
}
}

15
src/Avalonia.Controls/ControlExtensions.cs

@ -66,21 +66,6 @@ namespace Avalonia.Controls
return nameScope.Find<T>(name);
}
/// <summary>
/// Finds the name scope for a control by searching up the logical tree.
/// </summary>
/// <param name="control">The control.</param>
/// <returns>The control's name scope, or null if not found.</returns>
public static INameScope FindNameScope(this IControl control)
{
Contract.Requires<ArgumentNullException>(control != null);
return control.GetSelfAndLogicalAncestors()
.OfType<Control>()
.Select(x => (x as INameScope) ?? NameScope.GetNameScope(x))
.FirstOrDefault(x => x != null);
}
/// <summary>
/// Adds or removes a pseudoclass depending on a boolean value.
/// </summary>

6
src/Avalonia.Controls/HotkeyManager.cs

@ -55,13 +55,13 @@ namespace Avalonia.Controls
public void Init()
{
_hotkeySub = _control.GetObservable(HotKeyProperty).Subscribe(OnHotkeyChanged);
_parentSub = AncestorFinder.Create(_control, typeof (TopLevel)).Subscribe(OnParentChanged);
_parentSub = AncestorFinder.Create<TopLevel>(_control).Subscribe(OnParentChanged);
}
private void OnParentChanged(IControl control)
private void OnParentChanged(TopLevel control)
{
Unregister();
_root = (TopLevel) control;
_root = control;
Register();
}

30
src/Avalonia.Controls/IControl.cs

@ -16,37 +16,11 @@ namespace Avalonia.Controls
/// </summary>
public interface IControl : IVisual,
IDataTemplateHost,
ILogical,
ILayoutable,
IInputElement,
INamed,
IResourceNode,
IStyleable,
IStyleHost
IStyledElement
{
/// <summary>
/// Occurs when the control has finished initialization.
/// </summary>
event EventHandler Initialized;
/// <summary>
/// Gets or sets the control's styling classes.
/// </summary>
new Classes Classes { get; set; }
/// <summary>
/// Gets or sets the control's data context.
/// </summary>
object DataContext { get; set; }
/// <summary>
/// Gets a value that indicates whether the element has finished initialization.
/// </summary>
bool IsInitialized { get; }
/// <summary>
/// Gets the control's logical parent.
/// </summary>
IControl Parent { get; }
new IControl Parent { get; }
}
}

24
src/Avalonia.Controls/Utils/AncestorFinder.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Reflection;
using System.Text;
@ -14,15 +15,15 @@ namespace Avalonia.Controls.Utils
{
class FinderNode : IDisposable
{
private readonly IControl _control;
private readonly IStyledElement _control;
private readonly TypeInfo _ancestorType;
public IObservable<IControl> Observable => _subject;
private readonly Subject<IControl> _subject = new Subject<IControl>();
public IObservable<IStyledElement> Observable => _subject;
private readonly Subject<IStyledElement> _subject = new Subject<IStyledElement>();
private FinderNode _child;
private IDisposable _disposable;
public FinderNode(IControl control, TypeInfo ancestorType)
public FinderNode(IStyledElement control, TypeInfo ancestorType)
{
_control = control;
_ancestorType = ancestorType;
@ -33,7 +34,7 @@ namespace Avalonia.Controls.Utils
_disposable = _control.GetObservable(Control.ParentProperty).Subscribe(OnValueChanged);
}
private void OnValueChanged(IControl next)
private void OnValueChanged(IStyledElement next)
{
if (next == null || _ancestorType.IsAssignableFrom(next.GetType().GetTypeInfo()))
_subject.OnNext(next);
@ -46,7 +47,7 @@ namespace Avalonia.Controls.Utils
}
}
private void OnChildValueChanged(IControl control) => _subject.OnNext(control);
private void OnChildValueChanged(IStyledElement control) => _subject.OnNext(control);
public void Dispose()
@ -55,10 +56,15 @@ namespace Avalonia.Controls.Utils
}
}
public static IObservable<T> Create<T>(IStyledElement control)
where T : IStyledElement
{
return Create(control, typeof(T)).Cast<T>();
}
public static IObservable<IControl> Create(IControl control, Type ancestorType)
public static IObservable<IStyledElement> Create(IStyledElement control, Type ancestorType)
{
return new AnonymousObservable<IControl>(observer =>
return new AnonymousObservable<IStyledElement>(observer =>
{
var finder = new FinderNode(control, ancestorType.GetTypeInfo());
var subscription = finder.Observable.Subscribe(observer);
@ -70,8 +76,6 @@ namespace Avalonia.Controls.Utils
finder.Dispose();
});
});
}
}
}

4
src/Avalonia.Input/InputElement.cs

@ -168,6 +168,10 @@ namespace Avalonia.Input
PointerPressedEvent.AddClassHandler<InputElement>(x => x.OnPointerPressed);
PointerReleasedEvent.AddClassHandler<InputElement>(x => x.OnPointerReleased);
PointerWheelChangedEvent.AddClassHandler<InputElement>(x => x.OnPointerWheelChanged);
PseudoClass(IsEnabledCoreProperty, x => !x, ":disabled");
PseudoClass(IsFocusedProperty, ":focus");
PseudoClass(IsPointerOverProperty, ":pointerover");
}
/// <summary>

3
src/Avalonia.Styling/Avalonia.Styling.csproj

@ -5,9 +5,8 @@
<RootNamespace>Avalonia</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
</ItemGroup>
<Import Project="..\..\build\Rx.props" />
</Project>

16
src/Avalonia.Controls/Classes.cs → src/Avalonia.Styling/Controls/Classes.cs

@ -9,7 +9,7 @@ using Avalonia.Collections;
namespace Avalonia.Controls
{
/// <summary>
/// Holds a collection of style classes for an <see cref="IControl"/>.
/// Holds a collection of style classes for an <see cref="IStyledElement"/>.
/// </summary>
/// <remarks>
/// Similar to CSS, each control may have any number of styling classes applied.
@ -54,7 +54,7 @@ namespace Avalonia.Controls
/// <param name="name">The class name.</param>
/// <remarks>
/// Only standard classes may be added via this method. To add pseudoclasses (classes
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
/// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
/// property.
/// </remarks>
public override void Add(string name)
@ -73,7 +73,7 @@ namespace Avalonia.Controls
/// <param name="names">The class names.</param>
/// <remarks>
/// Only standard classes may be added via this method. To add pseudoclasses (classes
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
/// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
/// property.
/// </remarks>
public override void AddRange(IEnumerable<string> names)
@ -114,7 +114,7 @@ namespace Avalonia.Controls
/// <param name="name">The class name.</param>
/// <remarks>
/// Only standard classes may be added via this method. To add pseudoclasses (classes
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
/// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
/// property.
/// </remarks>
public override void Insert(int index, string name)
@ -134,7 +134,7 @@ namespace Avalonia.Controls
/// <param name="names">The class names.</param>
/// <remarks>
/// Only standard classes may be added via this method. To add pseudoclasses (classes
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
/// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
/// property.
/// </remarks>
public override void InsertRange(int index, IEnumerable<string> names)
@ -160,7 +160,7 @@ namespace Avalonia.Controls
/// <param name="name">The class name.</param>
/// <remarks>
/// Only standard classes may be removed via this method. To remove pseudoclasses (classes
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
/// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
/// property.
/// </remarks>
public override bool Remove(string name)
@ -175,7 +175,7 @@ namespace Avalonia.Controls
/// <param name="names">The class name.</param>
/// <remarks>
/// Only standard classes may be removed via this method. To remove pseudoclasses (classes
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
/// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
/// property.
/// </remarks>
public override void RemoveAll(IEnumerable<string> names)
@ -201,7 +201,7 @@ namespace Avalonia.Controls
/// <param name="index">The index of the class in the collection.</param>
/// <remarks>
/// Only standard classes may be removed via this method. To remove pseudoclasses (classes
/// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
/// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
/// property.
/// </remarks>
public override void RemoveAt(int index)

0
src/Avalonia.Controls/IPseudoClasses.cs → src/Avalonia.Styling/Controls/IPseudoClasses.cs

2
src/Avalonia.Controls/ISetInheritanceParent.cs → src/Avalonia.Styling/Controls/ISetInheritanceParent.cs

@ -4,7 +4,7 @@
namespace Avalonia.Controls
{
/// <summary>
/// Defines an interface through which a <see cref="Control"/>'s inheritance parent can be set.
/// Defines an interface through which a <see cref="StyledElement"/>'s inheritance parent can be set.
/// </summary>
/// <remarks>
/// You should not usually need to use this interface - it is for advanced scenarios only.

2
src/Avalonia.Controls/ISetLogicalParent.cs → src/Avalonia.Styling/Controls/ISetLogicalParent.cs

@ -6,7 +6,7 @@ using Avalonia.LogicalTree;
namespace Avalonia.Controls
{
/// <summary>
/// Defines an interface through which a <see cref="Control"/>'s logical parent can be set.
/// Defines an interface through which a <see cref="StyledElement"/>'s logical parent can be set.
/// </summary>
/// <remarks>
/// You should not usually need to use this interface - it is for advanced scenarios only.

36
src/Avalonia.Styling/Controls/NameScope.cs

@ -16,7 +16,7 @@ namespace Avalonia.Controls
/// Defines the NameScope attached property.
/// </summary>
public static readonly AttachedProperty<INameScope> NameScopeProperty =
AvaloniaProperty.RegisterAttached<NameScope, Visual, INameScope>("NameScope");
AvaloniaProperty.RegisterAttached<NameScope, StyledElement, INameScope>("NameScope");
private readonly Dictionary<string, object> _inner = new Dictionary<string, object>();
@ -31,53 +31,53 @@ namespace Avalonia.Controls
public event EventHandler<NameScopeEventArgs> Unregistered;
/// <summary>
/// Finds the containing name scope for a visual.
/// Finds the containing name scope for a styled element.
/// </summary>
/// <param name="visual">The visual.</param>
/// <param name="styled">The styled element.</param>
/// <returns>The containing name scope.</returns>
public static INameScope FindNameScope(Visual visual)
public static INameScope FindNameScope(StyledElement styled)
{
Contract.Requires<ArgumentNullException>(visual != null);
Contract.Requires<ArgumentNullException>(styled != null);
INameScope result;
while (visual != null)
while (styled != null)
{
result = visual as INameScope ?? GetNameScope(visual);
result = styled as INameScope ?? GetNameScope(styled);
if (result != null)
{
return result;
}
visual = (visual as ILogical)?.LogicalParent as Visual;
styled = (styled as ILogical)?.LogicalParent as StyledElement;
}
return null;
}
/// <summary>
/// Gets the value of the attached <see cref="NameScopeProperty"/> on a visual.
/// Gets the value of the attached <see cref="NameScopeProperty"/> on a styled element.
/// </summary>
/// <param name="visual">The visual.</param>
/// <param name="styled">The styled element.</param>
/// <returns>The value of the NameScope attached property.</returns>
public static INameScope GetNameScope(Visual visual)
public static INameScope GetNameScope(StyledElement styled)
{
Contract.Requires<ArgumentNullException>(visual != null);
Contract.Requires<ArgumentNullException>(styled != null);
return visual.GetValue(NameScopeProperty);
return styled.GetValue(NameScopeProperty);
}
/// <summary>
/// Sets the value of the attached <see cref="NameScopeProperty"/> on a visual.
/// Sets the value of the attached <see cref="NameScopeProperty"/> on a styled element.
/// </summary>
/// <param name="visual">The visual.</param>
/// <param name="styled">The styled element.</param>
/// <param name="value">The value to set.</param>
public static void SetNameScope(Visual visual, INameScope value)
public static void SetNameScope(StyledElement styled, INameScope value)
{
Contract.Requires<ArgumentNullException>(visual != null);
Contract.Requires<ArgumentNullException>(styled != null);
visual.SetValue(NameScopeProperty, value);
styled.SetValue(NameScopeProperty, value);
}
/// <summary>

12
src/Avalonia.Styling/Controls/NameScopeExtensions.cs

@ -3,6 +3,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.LogicalTree;
namespace Avalonia.Controls
{
@ -64,5 +66,15 @@ namespace Avalonia.Controls
return (T)result;
}
public static INameScope FindNameScope(this ILogical control)
{
Contract.Requires<ArgumentNullException>(control != null);
return control.GetSelfAndLogicalAncestors()
.OfType<StyledElement>()
.Select(x => (x as INameScope) ?? NameScope.GetNameScope(x))
.FirstOrDefault(x => x != null);
}
}
}

0
src/Avalonia.Visuals/INamed.cs → src/Avalonia.Styling/INamed.cs

42
src/Avalonia.Styling/IStyledElement.cs

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Styling;
namespace Avalonia
{
public interface IStyledElement :
IStyleable,
IStyleHost,
ILogical,
IResourceProvider,
IResourceNode
{
/// <summary>
/// Occurs when the control has finished initialization.
/// </summary>
event EventHandler Initialized;
/// <summary>
/// Gets a value that indicates whether the element has finished initialization.
/// </summary>
bool IsInitialized { get; }
/// <summary>
/// Gets or sets the control's styling classes.
/// </summary>
new Classes Classes { get; set; }
/// <summary>
/// Gets or sets the control's data context.
/// </summary>
object DataContext { get; set; }
/// <summary>
/// Gets the control's logical parent.
/// </summary>
IStyledElement Parent { get; }
}
}

97
src/Avalonia.Styling/LogicalTree/ControlLocator.cs

@ -0,0 +1,97 @@
// 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 System.Reactive.Linq;
using System.Reflection;
using Avalonia.Controls;
namespace Avalonia.LogicalTree
{
/// <summary>
/// Locates controls relative to other controls.
/// </summary>
public static class ControlLocator
{
/// <summary>
/// Tracks a named control relative to another control.
/// </summary>
/// <param name="relativeTo">
/// The control relative from which the other control should be found.
/// </param>
/// <param name="name">The name of the control to find.</param>
public static IObservable<ILogical> Track(ILogical relativeTo, string name)
{
var attached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
x => relativeTo.AttachedToLogicalTree += x,
x => relativeTo.AttachedToLogicalTree -= x)
.Select(x => ((ILogical)x.Sender).FindNameScope())
.StartWith(relativeTo.FindNameScope());
var detached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
x => relativeTo.DetachedFromLogicalTree += x,
x => relativeTo.DetachedFromLogicalTree -= x)
.Select(x => (INameScope)null);
return attached.Merge(detached).Select(nameScope =>
{
if (nameScope != null)
{
var registered = Observable.FromEventPattern<NameScopeEventArgs>(
x => nameScope.Registered += x,
x => nameScope.Registered -= x)
.Where(x => x.EventArgs.Name == name)
.Select(x => x.EventArgs.Element)
.OfType<ILogical>();
var unregistered = Observable.FromEventPattern<NameScopeEventArgs>(
x => nameScope.Unregistered += x,
x => nameScope.Unregistered -= x)
.Where(x => x.EventArgs.Name == name)
.Select(_ => (ILogical)null);
return registered
.StartWith(nameScope.Find<ILogical>(name))
.Merge(unregistered);
}
else
{
return Observable.Return<ILogical>(null);
}
}).Switch();
}
public static IObservable<ILogical> Track(ILogical relativeTo, int ancestorLevel, Type ancestorType = null)
{
return TrackAttachmentToTree(relativeTo).Select(isAttachedToTree =>
{
if (isAttachedToTree)
{
return relativeTo.GetLogicalAncestors()
.Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
.ElementAtOrDefault(ancestorLevel);
}
else
{
return null;
}
});
}
private static IObservable<bool> TrackAttachmentToTree(ILogical relativeTo)
{
var attached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
x => relativeTo.AttachedToLogicalTree += x,
x => relativeTo.AttachedToLogicalTree -= x)
.Select(x => true)
.StartWith(relativeTo.IsAttachedToLogicalTree);
var detached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
x => relativeTo.DetachedFromLogicalTree += x,
x => relativeTo.DetachedFromLogicalTree -= x)
.Select(x => false);
var attachmentStatus = attached.Merge(detached);
return attachmentStatus;
}
}
}

783
src/Avalonia.Styling/StyledElement.cs

@ -0,0 +1,783 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Text;
using Avalonia.Animation;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.Logging;
using Avalonia.LogicalTree;
using Avalonia.Styling;
namespace Avalonia
{
/// <summary>
/// Extends an <see cref="Animatable"/> with the following features:
///
/// - An inherited <see cref="DataContext"/>.
/// - Implements <see cref="IStyleable"/> to allow styling to work on the styled element.
/// - Implements <see cref="ILogical"/> to form part of a logical tree.
/// - A collection of class strings for custom styling.
/// </summary>
public class StyledElement : Animatable, IStyledElement, ISetLogicalParent, ISetInheritanceParent
{
/// <summary>
/// Defines the <see cref="DataContext"/> property.
/// </summary>
public static readonly StyledProperty<object> DataContextProperty =
AvaloniaProperty.Register<StyledElement, object>(
nameof(DataContext),
inherits: true,
notifying: DataContextNotifying);
/// <summary>
/// Defines the <see cref="Name"/> property.
/// </summary>
public static readonly DirectProperty<StyledElement, string> NameProperty =
AvaloniaProperty.RegisterDirect<StyledElement, string>(nameof(Name), o => o.Name, (o, v) => o.Name = v);
/// <summary>
/// Defines the <see cref="Parent"/> property.
/// </summary>
public static readonly DirectProperty<StyledElement, IStyledElement> ParentProperty =
AvaloniaProperty.RegisterDirect<StyledElement, IStyledElement>(nameof(Parent), o => o.Parent);
/// <summary>
/// Defines the <see cref="TemplatedParent"/> property.
/// </summary>
public static readonly StyledProperty<ITemplatedControl> TemplatedParentProperty =
AvaloniaProperty.Register<StyledElement, ITemplatedControl>(nameof(TemplatedParent), inherits: true);
private int _initCount;
private string _name;
private readonly Classes _classes = new Classes();
private bool _isAttachedToLogicalTree;
private IAvaloniaList<ILogical> _logicalChildren;
private INameScope _nameScope;
private IResourceDictionary _resources;
private Styles _styles;
private bool _styled;
private Subject<IStyleable> _styleDetach = new Subject<IStyleable>();
private bool _dataContextUpdating;
/// <summary>
/// Initializes static members of the <see cref="StyledElement"/> class.
/// </summary>
static StyledElement()
{
DataContextProperty.Changed.AddClassHandler<StyledElement>(x => x.OnDataContextChangedCore);
}
/// <summary>
/// Initializes a new instance of the <see cref="StyledElement"/> class.
/// </summary>
public StyledElement()
{
_nameScope = this as INameScope;
_isAttachedToLogicalTree = this is IStyleRoot;
}
/// <summary>
/// Raised when the styled element is attached to a rooted logical tree.
/// </summary>
public event EventHandler<LogicalTreeAttachmentEventArgs> AttachedToLogicalTree;
/// <summary>
/// Raised when the styled element is detached from a rooted logical tree.
/// </summary>
public event EventHandler<LogicalTreeAttachmentEventArgs> DetachedFromLogicalTree;
/// <summary>
/// Occurs when the <see cref="DataContext"/> property changes.
/// </summary>
/// <remarks>
/// This event will be raised when the <see cref="DataContext"/> property has changed and
/// all subscribers to that change have been notified.
/// </remarks>
public event EventHandler DataContextChanged;
/// <summary>
/// Occurs when the styled element has finished initialization.
/// </summary>
/// <remarks>
/// The Initialized event indicates that all property values on the styled element have been set.
/// When loading the styled element from markup, it occurs when
/// <see cref="ISupportInitialize.EndInit"/> is called *and* the styled element
/// is attached to a rooted logical tree. When the styled element is created by code and
/// <see cref="ISupportInitialize"/> is not used, it is called when the styled element is attached
/// to the visual tree.
/// </remarks>
public event EventHandler Initialized;
/// <summary>
/// Occurs when a resource in this styled element or a parent styled element has changed.
/// </summary>
public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
/// <summary>
/// Gets or sets the name of the styled element.
/// </summary>
/// <remarks>
/// An element's name is used to uniquely identify an element within the element's name
/// scope. Once the element is added to a logical tree, its name cannot be changed.
/// </remarks>
public string Name
{
get
{
return _name;
}
set
{
if (String.IsNullOrWhiteSpace(value))
{
throw new InvalidOperationException("Cannot set Name to null or empty string.");
}
if (_styled)
{
throw new InvalidOperationException("Cannot set Name : styled element already styled.");
}
_name = value;
}
}
/// <summary>
/// Gets or sets the styled element's classes.
/// </summary>
/// <remarks>
/// <para>
/// Classes can be used to apply user-defined styling to styled elements, or to allow styled elements
/// that share a common purpose to be easily selected.
/// </para>
/// <para>
/// Even though this property can be set, the setter is only intended for use in object
/// initializers. Assigning to this property does not change the underlying collection,
/// it simply clears the existing collection and addds the contents of the assigned
/// collection.
/// </para>
/// </remarks>
public Classes Classes
{
get
{
return _classes;
}
set
{
if (_classes != value)
{
_classes.Replace(value);
}
}
}
/// <summary>
/// Gets or sets the control's data context.
/// </summary>
/// <remarks>
/// The data context is an inherited property that specifies the default object that will
/// be used for data binding.
/// </remarks>
public object DataContext
{
get { return GetValue(DataContextProperty); }
set { SetValue(DataContextProperty, value); }
}
/// <summary>
/// Gets a value that indicates whether the element has finished initialization.
/// </summary>
/// <remarks>
/// For more information about when IsInitialized is set, see the <see cref="Initialized"/>
/// event.
/// </remarks>
public bool IsInitialized { get; private set; }
/// <summary>
/// Gets the styles for the styled element.
/// </summary>
/// <remarks>
/// Styles for the entire application are added to the Application.Styles collection, but
/// each styled element may in addition define its own styles which are applied to the styled element
/// itself and its children.
/// </remarks>
public Styles Styles
{
get { return _styles ?? (Styles = new Styles()); }
set
{
Contract.Requires<ArgumentNullException>(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;
}
}
}
/// <summary>
/// Gets or sets the styled element's resource dictionary.
/// </summary>
public IResourceDictionary Resources
{
get => _resources ?? (Resources = new ResourceDictionary());
set
{
Contract.Requires<ArgumentNullException>(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());
}
}
}
/// <summary>
/// Gets the styled element whose lookless template this styled element is part of.
/// </summary>
public ITemplatedControl TemplatedParent
{
get { return GetValue(TemplatedParentProperty); }
internal set { SetValue(TemplatedParentProperty, value); }
}
/// <summary>
/// Gets the styled element's logical children.
/// </summary>
protected IAvaloniaList<ILogical> LogicalChildren
{
get
{
if (_logicalChildren == null)
{
var list = new AvaloniaList<ILogical>
{
ResetBehavior = ResetBehavior.Remove,
Validate = ValidateLogicalChild
};
list.CollectionChanged += LogicalChildrenCollectionChanged;
_logicalChildren = list;
}
return _logicalChildren;
}
}
/// <summary>
/// Gets the <see cref="Classes"/> collection in a form that allows adding and removing
/// pseudoclasses.
/// </summary>
protected IPseudoClasses PseudoClasses => Classes;
/// <summary>
/// Gets a value indicating whether the element is attached to a rooted logical tree.
/// </summary>
bool ILogical.IsAttachedToLogicalTree => _isAttachedToLogicalTree;
/// <summary>
/// Gets the styled element's logical parent.
/// </summary>
public IStyledElement Parent { get; private set; }
/// <summary>
/// Gets the styled element's logical parent.
/// </summary>
ILogical ILogical.LogicalParent => Parent;
/// <summary>
/// Gets the styled element's logical children.
/// </summary>
IAvaloniaReadOnlyList<ILogical> ILogical.LogicalChildren => LogicalChildren;
/// <inheritdoc/>
bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources;
/// <inheritdoc/>
IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode;
/// <inheritdoc/>
IAvaloniaReadOnlyList<string> IStyleable.Classes => Classes;
/// <summary>
/// Gets the type by which the styled element is styled.
/// </summary>
/// <remarks>
/// Usually controls are styled by their own type, but there are instances where you want
/// a styled element to be styled by its base type, e.g. creating SpecialButton that
/// derives from Button and adds extra functionality but is still styled as a regular
/// Button.
/// </remarks>
Type IStyleable.StyleKey => GetType();
/// <inheritdoc/>
IObservable<IStyleable> IStyleable.StyleDetach => _styleDetach;
/// <inheritdoc/>
bool IStyleHost.IsStylesInitialized => _styles != null;
/// <inheritdoc/>
IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent;
/// <inheritdoc/>
public virtual void BeginInit()
{
++_initCount;
}
/// <inheritdoc/>
public virtual void EndInit()
{
if (_initCount == 0)
{
throw new InvalidOperationException("BeginInit was not called.");
}
if (--_initCount == 0 && _isAttachedToLogicalTree)
{
InitializeStylesIfNeeded();
InitializeIfNeeded();
}
}
private void InitializeStylesIfNeeded(bool force = false)
{
if (_initCount == 0 && (!_styled || force))
{
RegisterWithNameScope();
ApplyStyling();
_styled = true;
}
}
protected void InitializeIfNeeded()
{
if (_initCount == 0 && !IsInitialized)
{
IsInitialized = true;
Initialized?.Invoke(this, EventArgs.Empty);
}
}
/// <inheritdoc/>
void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
this.OnAttachedToLogicalTreeCore(e);
}
/// <inheritdoc/>
void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
this.OnDetachedFromLogicalTreeCore(e);
}
/// <inheritdoc/>
void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
/// <inheritdoc/>
bool IResourceProvider.TryGetResource(string key, out object value)
{
value = null;
return (_resources?.TryGetResource(key, out value) ?? false) ||
(_styles?.TryGetResource(key, out value) ?? false);
}
/// <summary>
/// Sets the styled element's logical parent.
/// </summary>
/// <param name="parent">The parent.</param>
void ISetLogicalParent.SetParent(ILogical parent)
{
var old = Parent;
if (parent != old)
{
if (old != null && parent != null)
{
throw new InvalidOperationException("The Control already has a parent.");
}
if (_isAttachedToLogicalTree)
{
var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot;
if (oldRoot == null)
{
throw new AvaloniaInternalException("Was attached to logical tree but cannot find root.");
}
var e = new LogicalTreeAttachmentEventArgs(oldRoot);
OnDetachedFromLogicalTreeCore(e);
}
if (InheritanceParent == null || parent == null)
{
InheritanceParent = parent as AvaloniaObject;
}
Parent = (IStyledElement)parent;
if (old != null)
{
old.ResourcesChanged -= ThisResourcesChanged;
}
if (Parent != null)
{
Parent.ResourcesChanged += ThisResourcesChanged;
}
((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
if (Parent is IStyleRoot || Parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)
{
var newRoot = FindStyleRoot(this);
if (newRoot == null)
{
throw new AvaloniaInternalException("Parent is atttached to logical tree but cannot find root.");
}
var e = new LogicalTreeAttachmentEventArgs(newRoot);
OnAttachedToLogicalTreeCore(e);
}
RaisePropertyChanged(ParentProperty, old, Parent, BindingPriority.LocalValue);
}
}
/// <summary>
/// Sets the styled element's inheritance parent.
/// </summary>
/// <param name="parent">The parent.</param>
void ISetInheritanceParent.SetParent(IAvaloniaObject parent)
{
InheritanceParent = parent;
}
/// <summary>
/// Adds a pseudo-class to be set when a property is true.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="className">The pseudo-class.</param>
protected static void PseudoClass(AvaloniaProperty<bool> property, string className)
{
PseudoClass(property, x => x, className);
}
/// <summary>
/// Adds a pseudo-class to be set when a property equals a certain value.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="selector">Returns a boolean value based on the property value.</param>
/// <param name="className">The pseudo-class.</param>
protected static void PseudoClass<T>(
AvaloniaProperty<T> property,
Func<T, bool> selector,
string className)
{
Contract.Requires<ArgumentNullException>(property != null);
Contract.Requires<ArgumentNullException>(selector != null);
Contract.Requires<ArgumentNullException>(className != null);
if (string.IsNullOrWhiteSpace(className))
{
throw new ArgumentException("Cannot supply an empty className.");
}
property.Changed.Merge(property.Initialized)
.Where(e => e.Sender is StyledElement)
.Subscribe(e =>
{
if (selector((T)e.NewValue))
{
((StyledElement)e.Sender).PseudoClasses.Add(className);
}
else
{
((StyledElement)e.Sender).PseudoClasses.Remove(className);
}
});
}
/// <summary>
/// Called when the styled element is added to a rooted logical tree.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
}
/// <summary>
/// Called when the styled element is removed from a rooted logical tree.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
}
/// <summary>
/// Called when the <see cref="DataContext"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnDataContextChanged(EventArgs e)
{
DataContextChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Called when the <see cref="DataContext"/> begins updating.
/// </summary>
protected virtual void OnDataContextBeginUpdate()
{
}
/// <summary>
/// Called when the <see cref="DataContext"/> finishes updating.
/// </summary>
protected virtual void OnDataContextEndUpdate()
{
}
private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted)
{
if (o is StyledElement element)
{
DataContextNotifying(element, updateStarted);
}
}
private static void DataContextNotifying(StyledElement element, bool updateStarted)
{
if (updateStarted)
{
if (!element._dataContextUpdating)
{
element._dataContextUpdating = true;
element.OnDataContextBeginUpdate();
foreach (var child in element.LogicalChildren)
{
if (child is StyledElement s &&
s.InheritanceParent == element &&
!s.IsSet(DataContextProperty))
{
DataContextNotifying(s, updateStarted);
}
}
}
}
else
{
if (element._dataContextUpdating)
{
element.OnDataContextEndUpdate();
element._dataContextUpdating = false;
}
}
}
private static IStyleRoot FindStyleRoot(IStyleHost e)
{
while (e != null)
{
if (e is IStyleRoot root)
{
return root;
}
e = e.StylingParent;
}
return null;
}
private void ApplyStyling()
{
AvaloniaLocator.Current.GetService<IStyler>()?.ApplyStyles(this);
}
private void RegisterWithNameScope()
{
if (_nameScope == null)
{
_nameScope = NameScope.GetNameScope(this) ?? ((StyledElement)Parent)?._nameScope;
}
if (Name != null)
{
_nameScope?.Register(Name, this);
var visualParent = Parent as StyledElement;
if (this is INameScope && visualParent != null)
{
// If we have e.g. a named UserControl in a window then we want that control
// to be findable by name from the Window, so register with both name scopes.
// This differs from WPF's behavior in that XAML manually registers controls
// with name scopes based on the XAML file in which the name attribute appears,
// but we're trying to avoid XAML magic in Avalonia in order to made code-
// created UIs easy. This will cause problems if a UserControl declares a name
// in its XAML and that control is included multiple times in a parent control
// (as the name will be duplicated), however at the moment I'm fine with saying
// "don't do that".
var parentNameScope = NameScope.FindNameScope(visualParent);
parentNameScope?.Register(Name, this);
}
}
}
private static void ValidateLogicalChild(ILogical c)
{
if (c == null)
{
throw new ArgumentException("Cannot add null to LogicalChildren.");
}
}
private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
{
// This method can be called when a control is already attached to the logical tree
// in the following scenario:
// - ListBox gets assigned Items containing ListBoxItem
// - ListBox makes ListBoxItem a logical child
// - ListBox template gets applied; making its Panel get attached to logical tree
// - That AttachedToLogicalTree signal travels down to the ListBoxItem
if (!_isAttachedToLogicalTree)
{
_isAttachedToLogicalTree = true;
InitializeStylesIfNeeded(true);
OnAttachedToLogicalTree(e);
AttachedToLogicalTree?.Invoke(this, e);
}
foreach (var child in LogicalChildren.OfType<StyledElement>())
{
child.OnAttachedToLogicalTreeCore(e);
}
}
private void OnDetachedFromLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
{
if (_isAttachedToLogicalTree)
{
if (Name != null)
{
_nameScope?.Unregister(Name);
}
_isAttachedToLogicalTree = false;
_styleDetach.OnNext(this);
OnDetachedFromLogicalTree(e);
DetachedFromLogicalTree?.Invoke(this, e);
foreach (var child in LogicalChildren.OfType<StyledElement>())
{
child.OnDetachedFromLogicalTreeCore(e);
}
#if DEBUG
if (((INotifyCollectionChangedDebug)_classes).GetCollectionChangedSubscribers()?.Length > 0)
{
Logger.Warning(
LogArea.Control,
this,
"{Type} detached from logical tree but still has class listeners",
this.GetType());
}
#endif
}
}
private void OnDataContextChangedCore(AvaloniaPropertyChangedEventArgs e)
{
OnDataContextChanged(EventArgs.Empty);
}
private void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
SetLogicalParent(e.NewItems.Cast<ILogical>());
break;
case NotifyCollectionChangedAction.Remove:
ClearLogicalParent(e.OldItems.Cast<ILogical>());
break;
case NotifyCollectionChangedAction.Replace:
ClearLogicalParent(e.OldItems.Cast<ILogical>());
SetLogicalParent(e.NewItems.Cast<ILogical>());
break;
case NotifyCollectionChangedAction.Reset:
throw new NotSupportedException("Reset should not be signalled on LogicalChildren collection");
}
}
private void SetLogicalParent(IEnumerable<ILogical> children)
{
foreach (var i in children)
{
if (i.LogicalParent == null)
{
((ISetLogicalParent)i).SetParent(this);
}
}
}
private void ClearLogicalParent(IEnumerable<ILogical> children)
{
foreach (var i in children)
{
if (i.LogicalParent == this)
{
((ISetLogicalParent)i).SetParent(null);
}
}
}
private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e)
{
((ILogical)this).NotifyResourcesChanged(e);
}
}
}

14
src/Avalonia.Styling/Styling/IRequiresTemplateInSetter.cs

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Styling
{
/// <summary>
/// This is an interface for advanced scenarios to assist users in correct style development.
/// You as a user will not need to use this interface directly.
/// </summary>
public interface IRequiresTemplateInSetter
{
}
}

1
src/Avalonia.Styling/Styling/IStyleable.cs

@ -3,6 +3,7 @@
using System;
using Avalonia.Collections;
using Avalonia.LogicalTree;
namespace Avalonia.Styling
{

9
src/Avalonia.Styling/Styling/Setter.cs

@ -5,6 +5,7 @@ using System;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using System.Reflection;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Metadata;
@ -19,7 +20,7 @@ namespace Avalonia.Styling
/// A <see cref="Setter"/> is used to set a <see cref="AvaloniaProperty"/> value on a
/// <see cref="AvaloniaObject"/> depending on a condition.
/// </remarks>
public class Setter : ISetter
public class Setter : ISetter, IAnimationSetter
{
private object _value;
@ -65,10 +66,10 @@ namespace Avalonia.Styling
set
{
if (value is IStyleable)
if (value is IRequiresTemplateInSetter)
{
throw new ArgumentException(
"Cannot assign a control to Style.Value. Wrap the control in a <Template>.",
"Cannot assign a control to Setter.Value. Wrap the control in a <Template>.",
"value");
}
@ -105,7 +106,7 @@ namespace Avalonia.Styling
if (template != null && !isPropertyOfTypeITemplate)
{
var materialized = template.Build();
NameScope.SetNameScope((Visual)materialized, new NameScope());
NameScope.SetNameScope((StyledElement)materialized, new NameScope());
value = materialized;
}

15
src/Avalonia.Styling/app.config

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Reactive.Interfaces" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.2.5.0" newVersion="2.2.5.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Reactive.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.2.5.0" newVersion="2.2.5.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

22
src/Avalonia.Visuals/Animation/TransformSetter.cs

@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Reactive.Linq;
using System.Diagnostics;
using Avalonia.Animation.Utils;
using Avalonia.Data;
using Avalonia.Media;
namespace Avalonia.Animation
{
/// <summary>
/// Setter that handles <see cref="Transform"/> objects
/// in the target.
/// </summary>
[Animator(typeof(TransformAnimator))]
public class TransformSetter : AnimationSetter
{
}
}

3
src/Avalonia.Visuals/Avalonia.Visuals.csproj

@ -5,6 +5,7 @@
<ItemGroup>
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
</ItemGroup>
<ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
</ItemGroup>
<Import Project="..\..\build\Rx.props" />
</Project>

5
src/Avalonia.Visuals/Media/Transform.cs

@ -12,6 +12,11 @@ namespace Avalonia.Media
/// </summary>
public abstract class Transform : Animatable
{
static Transform()
{
Animation.Animation.RegisterAnimator<TransformAnimator>(prop => typeof(Transform).IsAssignableFrom(prop.OwnerType));
}
/// <summary>
/// Raised when the transform changes.
/// </summary>

2
src/Avalonia.Visuals/Visual.cs

@ -24,7 +24,7 @@ namespace Avalonia
/// <see cref="IRenderer"/> to render the control. To traverse the visual tree, use the
/// extension methods defined in <see cref="VisualExtensions"/>.
/// </remarks>
public class Visual : Animatable, IVisual
public class Visual : StyledElement, IVisual
{
/// <summary>
/// Defines the <see cref="Bounds"/> property.

46
src/Avalonia.Visuals/VisualTree/VisualLocator.cs

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
using System.Text;
namespace Avalonia.VisualTree
{
public class VisualLocator
{
public static IObservable<IVisual> Track(IVisual relativeTo, int ancestorLevel, Type ancestorType = null)
{
return TrackAttachmentToTree(relativeTo).Select(isAttachedToTree =>
{
if (isAttachedToTree)
{
return relativeTo.GetVisualAncestors()
.Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
.ElementAtOrDefault(ancestorLevel);
}
else
{
return null;
}
});
}
private static IObservable<bool> TrackAttachmentToTree(IVisual relativeTo)
{
var attached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
x => relativeTo.AttachedToVisualTree += x,
x => relativeTo.AttachedToVisualTree -= x)
.Select(x => true)
.StartWith(relativeTo.IsAttachedToVisualTree);
var detached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
x => relativeTo.DetachedFromVisualTree += x,
x => relativeTo.DetachedFromVisualTree -= x)
.Select(x => false);
var attachmentStatus = attached.Merge(detached);
return attachmentStatus;
}
}
}

9
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -30,8 +30,8 @@
<Compile Include="Converters\ParseTypeConverter.cs" />
<Compile Include="Converters\SetterValueTypeConverter.cs" />
<Compile Include="Converters\TimeSpanTypeConverter.cs" />
<Compile Include="Data\ResourceInclude.cs" />
<Compile Include="MarkupExtensions\DynamicResourceExtension.cs" />
<Compile Include="MarkupExtensions\ResourceInclude.cs" />
<Compile Include="MarkupExtensions\StaticResourceExtension.cs" />
<Compile Include="MarkupExtensions\StyleIncludeExtension.cs" />
<Compile Include="PortableXaml\AvaloniaXamlContext.cs" />
@ -47,15 +47,9 @@
<Compile Include="Converters\AvaloniaPropertyTypeConverter.cs" />
<Compile Include="Converters\PointsListTypeConverter.cs" />
<Compile Include="Converters\SelectorTypeConverter.cs" />
<Compile Include="Data\Binding.cs" />
<Compile Include="Data\DelayedBinding.cs" />
<Compile Include="Data\MultiBinding.cs" />
<Compile Include="Data\RelativeSource.cs" />
<Compile Include="MarkupExtensions\BindingExtension.cs" />
<Compile Include="MarkupExtensions\RelativeSourceExtension.cs" />
<Compile Include="MarkupExtensions\TemplateBindingExtension.cs" />
<Compile Include="Parsers\SelectorGrammar.cs" />
<Compile Include="Parsers\SelectorParser.cs" />
<Compile Include="PortableXaml\AvaloniaTypeAttributeProvider.cs" />
<Compile Include="PortableXaml\AvaloniaXamlType.cs" />
<Compile Include="PortableXaml\TypeDescriptorExtensions.cs" />
@ -86,6 +80,5 @@
<ProjectReference Include="..\Avalonia.Markup\Avalonia.Markup.csproj" />
</ItemGroup>
<Import Project="..\..\..\build\Markup.props" />
<Import Project="..\..\..\build\Sprache.props" />
<Import Project="..\..\..\build\Rx.props" />
</Project>

2
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@ -2,7 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls;
using Avalonia.Markup.Xaml.Data;
using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.PortableXaml;
using Avalonia.Platform;
using Portable.Xaml;

2
src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs

@ -10,7 +10,7 @@ namespace Avalonia.Markup.Xaml.Converters
using Avalonia.Styling;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using System.ComponentModel;
using System.ComponentModel;
using Portable.Xaml.Markup;
public class AvaloniaPropertyTypeConverter : TypeConverter

4
src/Markup/Avalonia.Markup.Xaml/Converters/SelectorTypeConverter.cs

@ -3,12 +3,12 @@
using System;
using System.Globalization;
using Avalonia.Markup.Xaml.Parsers;
using Avalonia.Markup.Parsers;
namespace Avalonia.Markup.Xaml.Converters
{
using Portable.Xaml.ComponentModel;
using System.ComponentModel;
using System.ComponentModel;
public class SelectorTypeConverter : TypeConverter
{

3
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -2,12 +2,13 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Data;
using Avalonia.Markup.Xaml.Data;
using System;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Markup.Data;
using Avalonia.Styling;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;

2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/RelativeSourceExtension.cs

@ -1,7 +1,7 @@
// 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 Avalonia.Markup.Xaml.Data;
using Avalonia.Data;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{

2
src/Markup/Avalonia.Markup.Xaml/Data/ResourceInclude.cs → src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs

@ -4,7 +4,7 @@ using Avalonia.Controls;
using Portable.Xaml.ComponentModel;
using Portable.Xaml.Markup;
namespace Avalonia.Markup.Xaml.Data
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
/// <summary>
/// Loads a resource dictionary from a specified URL.

4
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@ -6,7 +6,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Markup.Xaml.Data;
using Avalonia.Markup.Data;
using Portable.Xaml;
using Portable.Xaml.ComponentModel;
using Portable.Xaml.Markup;
@ -69,7 +69,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
throw new KeyNotFoundException($"Static resource '{ResourceKey}' not found.");
}
private object GetValue(IControl control)
private object GetValue(IStyledElement control)
{
return control.FindResource(ResourceKey);
}

5
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs

@ -2,14 +2,15 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Data;
using Avalonia.Markup.Xaml.Data;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
using System;
using Avalonia.Data.Converters;
using Avalonia.Markup.Data;
using Portable.Xaml.Markup;
[MarkupExtensionReturnType(typeof(Binding))]
[MarkupExtensionReturnType(typeof(IBinding))]
public class TemplateBindingExtension : MarkupExtension
{
public TemplateBindingExtension()

4
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaRuntimeTypeProvider.cs

@ -6,6 +6,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media;
using Avalonia.Metadata;
@ -35,7 +37,7 @@ namespace Avalonia.Markup.Xaml.Context
typeof(Style).GetTypeInfo().Assembly,
typeof(DataTemplate).GetTypeInfo().Assembly,
typeof(SolidColorBrush).GetTypeInfo().Assembly,
typeof(IValueConverter).GetTypeInfo().Assembly,
typeof(Binding).GetTypeInfo().Assembly,
};
private Dictionary<string, HashSet<ClrNamespaceInfo>> _namespaces = new Dictionary<string, HashSet<ClrNamespaceInfo>>();

2
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs

@ -1,6 +1,6 @@
using Avalonia.Data;
using Avalonia.Markup.Xaml.Context;
using Avalonia.Markup.Xaml.Data;
using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Markup.Xaml.Styling;
using Portable.Xaml;

2
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs

@ -1,6 +1,6 @@
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Markup.Xaml.Data;
using Avalonia.Markup.Data;
using Avalonia.Metadata;
using Avalonia.Styling;
using Portable.Xaml;

2
src/Markup/Avalonia.Markup.Xaml/Properties/AssemblyInfo.cs

@ -5,10 +5,8 @@ using System.Reflection;
using Avalonia.Metadata;
using System.Runtime.CompilerServices;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Xaml.Data")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Xaml.MarkupExtensions")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Xaml.Styling")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup.Xaml.Templates")]
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml", "Avalonia.Markup.Xaml.MarkupExtensions.Standard")]
[assembly: InternalsVisibleTo("Avalonia.Markup.Xaml.UnitTests")]

2
src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs

@ -3,7 +3,7 @@
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Markup.Data;
using Avalonia.Data.Core;
using System;
using System.Reactive.Linq;

7
src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

@ -6,8 +6,8 @@ using System.Reflection;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Markup.Data;
using Avalonia.Markup.Xaml.Data;
using Avalonia.Metadata;
namespace Avalonia.Markup.Xaml.Templates
@ -48,11 +48,6 @@ namespace Avalonia.Markup.Xaml.Templates
return null;
}
public bool IsExpanded(object item)
{
return true;
}
public IControl Build(object data)
{
var visualTreeForItem = TemplateContent.Load(Content);

9
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@ -1,16 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Avalonia</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj" />
<ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
<ProjectReference Include="..\..\Avalonia.Layout\Avalonia.Layout.csproj" />
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj" />
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
</ItemGroup>
<Import Project="..\..\..\build\Markup.props" />
<Import Project="..\..\..\build\Rx.props" />
<Import Project="..\..\..\build\Sprache.props" />
</Project>

157
src/Markup/Avalonia.Markup/ControlLocator.cs

@ -1,157 +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;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
namespace Avalonia.Markup
{
/// <summary>
/// The type of tree via which to track a control.
/// </summary>
public enum TreeType
{
/// <summary>
/// The visual tree.
/// </summary>
Visual,
/// <summary>
/// The logical tree.
/// </summary>
Logical,
}
/// <summary>
/// Locates controls relative to other controls.
/// </summary>
public static class ControlLocator
{
/// <summary>
/// Tracks a named control relative to another control.
/// </summary>
/// <param name="relativeTo">
/// The control relative from which the other control should be found.
/// </param>
/// <param name="name">The name of the control to find.</param>
public static IObservable<IControl> Track(IControl relativeTo, string name)
{
var attached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
x => relativeTo.AttachedToLogicalTree += x,
x => relativeTo.AttachedToLogicalTree -= x)
.Select(x => ((IControl)x.Sender).FindNameScope())
.StartWith(relativeTo.FindNameScope());
var detached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
x => relativeTo.DetachedFromLogicalTree += x,
x => relativeTo.DetachedFromLogicalTree -= x)
.Select(x => (INameScope)null);
return attached.Merge(detached).Select(nameScope =>
{
if (nameScope != null)
{
var registered = Observable.FromEventPattern<NameScopeEventArgs>(
x => nameScope.Registered += x,
x => nameScope.Registered -= x)
.Where(x => x.EventArgs.Name == name)
.Select(x => x.EventArgs.Element)
.OfType<IControl>();
var unregistered = Observable.FromEventPattern<NameScopeEventArgs>(
x => nameScope.Unregistered += x,
x => nameScope.Unregistered -= x)
.Where(x => x.EventArgs.Name == name)
.Select(_ => (IControl)null);
return registered
.StartWith(nameScope.Find<IControl>(name))
.Merge(unregistered);
}
else
{
return Observable.Return<IControl>(null);
}
}).Switch();
}
/// <summary>
/// Tracks a typed visual ancestor control.
/// </summary>
/// <param name="relativeTo">
/// The control relative from which the other control should be found.
/// </param>
/// <param name="tree">The tree via which to track the control.</param>
/// <param name="ancestorLevel">
/// The level of ancestor control to look for. Use 0 for the first ancestor of the
/// requested type.
/// </param>
/// <param name="ancestorType">The type of the ancestor to find.</param>
public static IObservable<IControl> Track(IControl relativeTo, TreeType tree, int ancestorLevel, Type ancestorType = null)
{
return TrackAttachmentToTree(relativeTo, tree).Select(isAttachedToTree =>
{
if (isAttachedToTree)
{
if (tree == TreeType.Visual)
{
return relativeTo.GetVisualAncestors()
.Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
.ElementAtOrDefault(ancestorLevel) as IControl;
}
else
{
return relativeTo.GetLogicalAncestors()
.Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
.ElementAtOrDefault(ancestorLevel) as IControl;
}
}
else
{
return null;
}
});
}
private static IObservable<bool> TrackAttachmentToTree(IControl relativeTo, TreeType tree)
{
return tree == TreeType.Visual ? TrackAttachmentToVisualTree(relativeTo) : TrackAttachmentToLogicalTree(relativeTo);
}
private static IObservable<bool> TrackAttachmentToVisualTree(IControl relativeTo)
{
var attached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
x => relativeTo.AttachedToVisualTree += x,
x => relativeTo.AttachedToVisualTree -= x)
.Select(x => true)
.StartWith(relativeTo.IsAttachedToVisualTree);
var detached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
x => relativeTo.DetachedFromVisualTree += x,
x => relativeTo.DetachedFromVisualTree -= x)
.Select(x => false);
var attachmentStatus = attached.Merge(detached);
return attachmentStatus;
}
private static IObservable<bool> TrackAttachmentToLogicalTree(IControl relativeTo)
{
var attached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
x => relativeTo.AttachedToLogicalTree += x,
x => relativeTo.AttachedToLogicalTree -= x)
.Select(x => true)
.StartWith(relativeTo.IsAttachedToLogicalTree);
var detached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
x => relativeTo.DetachedFromLogicalTree += x,
x => relativeTo.DetachedFromLogicalTree -= x)
.Select(x => false);
var attachmentStatus = attached.Merge(detached);
return attachmentStatus;
}
}
}

70
src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs → src/Markup/Avalonia.Markup/Data/Binding.cs

@ -7,11 +7,12 @@ using System.Reactive;
using System.Reactive.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Markup.Data;
using Avalonia.Data.Converters;
using Avalonia.Data.Core;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
namespace Avalonia.Markup.Xaml.Data
namespace Avalonia.Data
{
/// <summary>
/// A XAML binding.
@ -83,7 +84,7 @@ namespace Avalonia.Markup.Xaml.Data
/// </summary>
public object Source { get; set; }
internal WeakReference DefaultAnchor { get; set; }
public WeakReference DefaultAnchor { get; set; }
/// <inheritdoc/>
public InstancedBinding Initiate(
@ -102,7 +103,7 @@ namespace Avalonia.Markup.Xaml.Data
if (ElementName != null)
{
observer = CreateElementObserver(
(target as IControl) ?? (anchor as IControl),
(target as IStyledElement) ?? (anchor as IStyledElement),
ElementName,
Path,
enableDataValidation);
@ -113,10 +114,10 @@ namespace Avalonia.Markup.Xaml.Data
}
else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext)
{
observer = CreateDataContexObserver(
observer = CreateDataContextObserver(
target,
Path,
targetProperty == Control.DataContextProperty,
targetProperty == StyledElement.DataContextProperty,
anchor,
enableDataValidation);
}
@ -136,7 +137,7 @@ namespace Avalonia.Markup.Xaml.Data
}
observer = CreateFindAncestorObserver(
(target as IControl) ?? (anchor as IControl),
(target as IStyledElement) ?? (anchor as IStyledElement),
RelativeSource,
Path,
enableDataValidation);
@ -151,8 +152,8 @@ namespace Avalonia.Markup.Xaml.Data
// If we're binding to DataContext and our fallback is UnsetValue then override
// the fallback value to null, as broken bindings to DataContext must reset the
// DataContext in order to not propagate incorrect DataContexts to child controls.
// See Avalonia.Markup.Xaml.UnitTests.Data.DataContext_Binding_Should_Produce_Correct_Results.
if (targetProperty == Control.DataContextProperty && fallback == AvaloniaProperty.UnsetValue)
// See Avalonia.Markup.UnitTests.Data.DataContext_Binding_Should_Produce_Correct_Results.
if (targetProperty == StyledElement.DataContextProperty && fallback == AvaloniaProperty.UnsetValue)
{
fallback = null;
}
@ -168,7 +169,7 @@ namespace Avalonia.Markup.Xaml.Data
return new InstancedBinding(subject, Mode, Priority);
}
private ExpressionObserver CreateDataContexObserver(
private ExpressionObserver CreateDataContextObserver(
IAvaloniaObject target,
string path,
bool targetIsDataContext,
@ -177,9 +178,9 @@ namespace Avalonia.Markup.Xaml.Data
{
Contract.Requires<ArgumentNullException>(target != null);
if (!(target is IControl))
if (!(target is IStyledElement))
{
target = anchor as IControl;
target = anchor as IStyledElement;
if (target == null)
{
@ -189,11 +190,11 @@ namespace Avalonia.Markup.Xaml.Data
if (!targetIsDataContext)
{
var update = target.GetObservable(Control.DataContextProperty)
var update = target.GetObservable(StyledElement.DataContextProperty)
.Skip(1)
.Select(_ => Unit.Default);
var result = new ExpressionObserver(
() => target.GetValue(Control.DataContextProperty),
() => target.GetValue(StyledElement.DataContextProperty),
path,
update,
enableDataValidation);
@ -210,7 +211,7 @@ namespace Avalonia.Markup.Xaml.Data
}
private ExpressionObserver CreateElementObserver(
IControl target,
IStyledElement target,
string elementName,
string path,
bool enableDataValidation)
@ -227,15 +228,35 @@ namespace Avalonia.Markup.Xaml.Data
}
private ExpressionObserver CreateFindAncestorObserver(
IControl target,
IStyledElement target,
RelativeSource relativeSource,
string path,
bool enableDataValidation)
{
Contract.Requires<ArgumentNullException>(target != null);
IObservable<object> controlLocator;
switch (relativeSource.Tree)
{
case TreeType.Logical:
controlLocator = ControlLocator.Track(
(ILogical)target,
relativeSource.AncestorLevel - 1,
relativeSource.AncestorType);
break;
case TreeType.Visual:
controlLocator = VisualLocator.Track(
(IVisual)target,
relativeSource.AncestorLevel - 1,
relativeSource.AncestorType);
break;
default:
throw new InvalidOperationException("Invalid tree to traverse.");
}
return new ExpressionObserver(
ControlLocator.Track(target, relativeSource.Tree, relativeSource.AncestorLevel - 1, relativeSource.AncestorType),
controlLocator,
path,
enableDataValidation);
}
@ -257,12 +278,12 @@ namespace Avalonia.Markup.Xaml.Data
{
Contract.Requires<ArgumentNullException>(target != null);
var update = target.GetObservable(Control.TemplatedParentProperty)
var update = target.GetObservable(StyledElement.TemplatedParentProperty)
.Skip(1)
.Select(_ => Unit.Default);
var result = new ExpressionObserver(
() => target.GetValue(Control.TemplatedParentProperty),
() => target.GetValue(StyledElement.TemplatedParentProperty),
path,
update,
enableDataValidation);
@ -281,16 +302,9 @@ namespace Avalonia.Markup.Xaml.Data
return target.GetObservable(Visual.VisualParentProperty)
.Select(x =>
{
return (x as IAvaloniaObject)?.GetObservable(Control.DataContextProperty) ??
return (x as IAvaloniaObject)?.GetObservable(StyledElement.DataContextProperty) ??
Observable.Return((object)null);
}).Switch();
}
private class PathInfo
{
public string Path { get; set; }
public string ElementName { get; set; }
public RelativeSource RelativeSource { get; set; }
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save