using System;
using System.Collections.Generic;
using System.ComponentModel;
using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.Logging;
using Avalonia.PropertyStore;
using Avalonia.Reactive;
using Avalonia.Threading;
namespace Avalonia
{
///
/// An object with support.
///
///
/// This class is analogous to DependencyObject in WPF.
///
public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged
{
private AvaloniaObject? _inheritanceParent;
private List? _directBindings;
private PropertyChangedEventHandler? _inpcChanged;
private EventHandler? _propertyChanged;
private List? _inheritanceChildren;
private ValueStore? _values;
private bool _batchUpdate;
///
/// Initializes a new instance of the class.
///
public AvaloniaObject()
{
VerifyAccess();
}
///
/// Raised when a value changes on this object.
///
public event EventHandler? PropertyChanged
{
add { _propertyChanged += value; }
remove { _propertyChanged -= value; }
}
///
/// Raised when a value changes on this object.
///
event PropertyChangedEventHandler? INotifyPropertyChanged.PropertyChanged
{
add { _inpcChanged += value; }
remove { _inpcChanged -= value; }
}
///
/// Gets or sets the parent object that inherited values
/// are inherited from.
///
///
/// The inheritance parent.
///
protected AvaloniaObject? InheritanceParent
{
get
{
return _inheritanceParent;
}
set
{
VerifyAccess();
if (_inheritanceParent != value)
{
var oldParent = _inheritanceParent;
var valuestore = _values;
_inheritanceParent?.RemoveInheritanceChild(this);
_inheritanceParent = value;
var properties = AvaloniaPropertyRegistry.Instance.GetRegisteredInherited(GetType());
var propertiesCount = properties.Count;
for (var i = 0; i < propertiesCount; i++)
{
var property = properties[i];
if (valuestore?.IsSet(property) == true)
{
// If local value set there can be no change.
continue;
}
property.RouteInheritanceParentChanged(this, oldParent);
}
_inheritanceParent?.AddInheritanceChild(this);
}
}
}
///
/// Gets or sets the value of a .
///
/// The property.
public object? this[AvaloniaProperty property]
{
get { return GetValue(property); }
set { SetValue(property, value); }
}
///
/// Gets or sets a binding for a .
///
/// The binding information.
public IBinding this[IndexerDescriptor binding]
{
get { return new IndexerBinding(this, binding.Property!, binding.Mode); }
set { this.Bind(binding.Property!, value); }
}
private ValueStore Values
{
get
{
if (_values is null)
{
_values = new ValueStore(this);
if (_batchUpdate)
_values.BeginBatchUpdate();
}
return _values;
}
}
public bool CheckAccess() => Dispatcher.UIThread.CheckAccess();
public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess();
///
/// Clears a 's local value.
///
/// The property.
public void ClearValue(AvaloniaProperty property)
{
property = property ?? throw new ArgumentNullException(nameof(property));
property.RouteClearValue(this);
}
///
/// Clears a 's local value.
///
/// The property.
public void ClearValue(AvaloniaProperty property)
{
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
switch (property)
{
case StyledPropertyBase styled:
ClearValue(styled);
break;
case DirectPropertyBase direct:
ClearValue(direct);
break;
default:
throw new NotSupportedException("Unsupported AvaloniaProperty type.");
}
}
///
/// Clears a 's local value.
///
/// The property.
public void ClearValue(StyledPropertyBase property)
{
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
_values?.ClearLocalValue(property);
}
///
/// Clears a 's local value.
///
/// The property.
public void ClearValue(DirectPropertyBase property)
{
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
var p = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
p.InvokeSetter(this, p.GetUnsetValue(GetType()));
}
///
/// Compares two objects using reference equality.
///
/// The object to compare.
///
/// Overriding Equals and GetHashCode on an AvaloniaObject is disallowed for two reasons:
///
/// - AvaloniaObjects are by their nature mutable
/// - The presence of attached properties means that the semantics of equality are
/// difficult to define
///
/// See https://github.com/AvaloniaUI/Avalonia/pull/2747 for the discussion that prompted
/// this.
///
public sealed override bool Equals(object? obj) => base.Equals(obj);
///
/// Gets the hash code for the object.
///
///
/// Overriding Equals and GetHashCode on an AvaloniaObject is disallowed for two reasons:
///
/// - AvaloniaObjects are by their nature mutable
/// - The presence of attached properties means that the semantics of equality are
/// difficult to define
///
/// See https://github.com/AvaloniaUI/Avalonia/pull/2747 for the discussion that prompted
/// this.
///
public sealed override int GetHashCode() => base.GetHashCode();
///
/// Gets a value.
///
/// The property.
/// The value.
public object? GetValue(AvaloniaProperty property)
{
property = property ?? throw new ArgumentNullException(nameof(property));
return property.RouteGetValue(this);
}
///
/// Gets a value.
///
/// The type of the property.
/// The property.
/// The value.
public T GetValue(StyledPropertyBase property)
{
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
return GetValueOrInheritedOrDefault(property);
}
///
/// Gets a value.
///
/// The type of the property.
/// The property.
/// The value.
public T GetValue(DirectPropertyBase property)
{
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
var registered = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
return registered.InvokeGetter(this);
}
///
public Optional GetBaseValue(StyledPropertyBase property, BindingPriority maxPriority)
{
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
if (_values is object &&
_values.TryGetValue(property, maxPriority, out var value))
{
return value;
}
return default;
}
///
/// Checks whether a is animating.
///
/// The property.
/// True if the property is animating, otherwise false.
public bool IsAnimating(AvaloniaProperty property)
{
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
return _values?.IsAnimating(property) ?? false;
}
///
/// Checks whether a is set on this object.
///
/// The property.
/// True if the property is set, otherwise false.
///
/// Checks whether a value is assigned to the property, or that there is a binding to the
/// property that is producing a value other than .
///
public bool IsSet(AvaloniaProperty property)
{
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
return _values?.IsSet(property) ?? false;
}
///
/// Sets a value.
///
/// The property.
/// The value.
/// The priority of the value.
public IDisposable? SetValue(
AvaloniaProperty property,
object? value,
BindingPriority priority = BindingPriority.LocalValue)
{
property = property ?? throw new ArgumentNullException(nameof(property));
return property.RouteSetValue(this, value, priority);
}
///
/// Sets a value.
///
/// The type of the property.
/// The property.
/// The value.
/// The priority of the value.
///
/// An if setting the property can be undone, otherwise null.
///
public IDisposable? SetValue(
StyledPropertyBase property,
T value,
BindingPriority priority = BindingPriority.LocalValue)
{
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
LogPropertySet(property, value, priority);
if (value is UnsetValueType)
{
if (priority == BindingPriority.LocalValue)
{
Values.ClearLocalValue(property);
}
else
{
throw new NotSupportedException(
"Cannot set property to Unset at non-local value priority.");
}
}
else if (!(value is DoNothingType))
{
return Values.SetValue(property, value, priority);
}
return null;
}
///
/// Sets a value.
///
/// The type of the property.
/// The property.
/// The value.
public void SetValue(DirectPropertyBase property, T value)
{
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
LogPropertySet(property, value, BindingPriority.LocalValue);
SetDirectValueUnchecked(property, value);
}
///
/// Binds a to an observable.
///
/// The property.
/// The observable.
/// The priority of the binding.
///
/// A disposable which can be used to terminate the binding.
///
public IDisposable Bind(
AvaloniaProperty property,
IObservable