// 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.Disposables;
using System.Reactive.Linq;
using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.Logging;
using Avalonia.Threading;
using Avalonia.Utilities;
using System.Reactive.Concurrency;
namespace Avalonia
{
///
/// An object with support.
///
///
/// This class is analogous to DependencyObject in WPF.
///
public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged, IPriorityValueOwner
{
///
/// The parent object that inherited values are inherited from.
///
private IAvaloniaObject _inheritanceParent;
///
/// The set values/bindings on this object.
///
private readonly Dictionary _values =
new Dictionary();
///
/// Maintains a list of direct property binding subscriptions so that the binding source
/// doesn't get collected.
///
private List _directBindings;
///
/// Event handler for implementation.
///
private PropertyChangedEventHandler _inpcChanged;
///
/// Event handler for implementation.
///
private EventHandler _propertyChanged;
///
/// Initializes a new instance of the class.
///
public AvaloniaObject()
{
foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegistered(this))
{
object value = property.IsDirect ?
((IDirectPropertyAccessor)property).GetValue(this) :
((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
var e = new AvaloniaPropertyChangedEventArgs(
this,
property,
AvaloniaProperty.UnsetValue,
value,
BindingPriority.Unset);
property.NotifyInitialized(e);
}
}
///
/// 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 IAvaloniaObject InheritanceParent
{
get
{
return _inheritanceParent;
}
set
{
if (_inheritanceParent != value)
{
if (_inheritanceParent != null)
{
_inheritanceParent.PropertyChanged -= ParentPropertyChanged;
}
var inherited = (from property in AvaloniaPropertyRegistry.Instance.GetRegistered(this)
where property.Inherits
select new
{
Property = property,
Value = GetValue(property),
}).ToList();
_inheritanceParent = value;
foreach (var i in inherited)
{
object newValue = GetValue(i.Property);
if (!Equals(i.Value, newValue))
{
RaisePropertyChanged(i.Property, i.Value, newValue, BindingPriority.LocalValue);
}
}
if (_inheritanceParent != null)
{
_inheritanceParent.PropertyChanged += 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);
}
///
/// 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
{
if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
{
ThrowNotRegistered(property);
}
return GetValueInternal(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 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();
PriorityValue value;
if (_values.TryGetValue(property, out value))
{
return value.Value != AvaloniaProperty.UnsetValue;
}
return 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