committed by
GitHub
173 changed files with 2052 additions and 1975 deletions
@ -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> |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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,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 |
|||
{ |
|||
@ -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 |
|||
{ |
|||
@ -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")] |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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,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> |
|||
@ -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 |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue