// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.Logging;
using Avalonia.Threading;
using Avalonia.Utilities;
namespace Avalonia
{
///
/// An object with support.
///
///
/// This class is analogous to DependencyObject in WPF.
///
public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged
{
private IAvaloniaObject _inheritanceParent;
private List _directBindings;
private PropertyChangedEventHandler _inpcChanged;
private EventHandler _propertyChanged;
private EventHandler _inheritablePropertyChanged;
private ValueStore _values;
private ValueStore Values => _values ?? (_values = new ValueStore(this));
///
/// Initializes a new instance of the class.
///
public AvaloniaObject()
{
VerifyAccess();
AvaloniaPropertyRegistry.Instance.NotifyInitialized(this);
}
///
/// 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; }
}
///
/// Raised when an inheritable value changes on this object.
///
event EventHandler IAvaloniaObject.InheritablePropertyChanged
{
add { _inheritablePropertyChanged += value; }
remove { _inheritablePropertyChanged -= value; }
}
///
/// Gets or sets the parent object that inherited values
/// are inherited from.
///
///
/// The inheritance parent.
///
protected IAvaloniaObject InheritanceParent
{
get
{
return _inheritanceParent;
}
set
{
VerifyAccess();
if (_inheritanceParent != value)
{
if (_inheritanceParent != null)
{
_inheritanceParent.InheritablePropertyChanged -= ParentPropertyChanged;
}
var oldInheritanceParent = _inheritanceParent;
_inheritanceParent = value;
var valuestore = _values;
foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegisteredInherited(GetType()))
{
if (valuestore != null && valuestore.GetValue(property) != AvaloniaProperty.UnsetValue)
{
// if local value set there can be no change
continue;
}
// get the value as it would have been with the previous InheritanceParent
object oldValue;
if (oldInheritanceParent is AvaloniaObject aobj)
{
oldValue = aobj.GetValueOrDefaultUnchecked(property);
}
else
{
oldValue = ((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
}
object newValue = GetDefaultValue(property);
if (!Equals(oldValue, newValue))
{
RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue);
}
}
if (_inheritanceParent != null)
{
_inheritanceParent.InheritablePropertyChanged += ParentPropertyChanged;
}
}
}
}
///
/// 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
{
var sourceBinding = value as IBinding;
this.Bind(binding.Property, sourceBinding);
}
}
public bool CheckAccess() => Dispatcher.UIThread.CheckAccess();
public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess();
///
/// Clears a 's local value.
///
/// The property.
public void ClearValue(AvaloniaProperty property)
{
Contract.Requires(property != null);
VerifyAccess();
SetValue(property, AvaloniaProperty.UnsetValue);
}
///
/// 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)
{
Contract.Requires(property != null);
VerifyAccess();
if (property.IsDirect)
{
return ((IDirectPropertyAccessor)GetRegistered(property)).GetValue(this);
}
else
{
return GetValueOrDefaultUnchecked(property);
}
}
///
/// Gets a value.
///
/// The type of the property.
/// The property.
/// The value.
public T GetValue(AvaloniaProperty property)
{
Contract.Requires(property != null);
return (T)GetValue((AvaloniaProperty)property);
}
///
/// Checks whether a is animating.
///
/// The property.
/// True if the property is animating, otherwise false.
public bool IsAnimating(AvaloniaProperty property)
{
Contract.Requires(property != null);
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)
{
Contract.Requires(property != null);
VerifyAccess();
return _values?.IsSet(property) ?? false;
}
///
/// Sets a value.
///
/// The property.
/// The value.
/// The priority of the value.
public void SetValue(
AvaloniaProperty property,
object value,
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires(property != null);
VerifyAccess();
if (property.IsDirect)
{
SetDirectValue(property, value);
}
else
{
SetStyledValue(property, value, priority);
}
}
///
/// Sets a value.
///
/// The type of the property.
/// The property.
/// The value.
/// The priority of the value.
public void SetValue(
AvaloniaProperty property,
T value,
BindingPriority priority = BindingPriority.LocalValue)
{
Contract.Requires(property != null);
SetValue((AvaloniaProperty)property, value, priority);
}
///
/// 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