// -----------------------------------------------------------------------
//
// Copyright 2013 Tricycle. All rights reserved.
//
// -----------------------------------------------------------------------
namespace Perspex
{
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
using System.Reflection;
///
/// An object with support.
///
///
/// This class is analogous to DependencyObject in WPF.
///
public class PerspexObject
{
///
/// The registered properties by type.
///
private static Dictionary> registered =
new Dictionary>();
///
/// The parent object that inherited values are inherited from.
///
private PerspexObject inheritanceParent;
///
/// The set values on this object.
///
private Dictionary values =
new Dictionary();
///
/// The current bindings on this object.
///
private Dictionary bindings =
new Dictionary();
///
/// Raised when a value changes on this object/
///
public event EventHandler PropertyChanged;
///
/// Gets or sets the parent object that inherited values
/// are inherited from.
///
protected PerspexObject InheritanceParent
{
get
{
return this.inheritanceParent;
}
set
{
if (this.inheritanceParent != value)
{
if (this.inheritanceParent != null)
{
this.inheritanceParent.PropertyChanged -= this.ParentPropertyChanged;
}
var inherited = (from property in GetProperties(this.GetType())
where property.Inherits
select new
{
Property = property,
Value = this.GetValue(property),
}).ToList();
this.inheritanceParent = value;
foreach (var i in inherited)
{
object newValue = this.GetValue(i.Property);
if (!object.Equals(i.Value, newValue))
{
this.RaisePropertyChanged(i.Property, i.Value, newValue);
}
}
if (this.inheritanceParent != null)
{
this.inheritanceParent.PropertyChanged += this.ParentPropertyChanged;
}
}
}
}
///
/// Gets all s registered on a type.
///
/// The type.
/// A collection of definitions.
public static IEnumerable GetProperties(Type type)
{
Contract.Requires(type != null);
TypeInfo i = type.GetTypeInfo();
while (type != null)
{
List list;
if (registered.TryGetValue(type, out list))
{
foreach (PerspexProperty p in list)
{
yield return p;
}
}
type = type.GetTypeInfo().BaseType;
}
}
///
/// Registers a on a type.
///
/// The type.
/// The property.
///
/// You won't usually want to call this method directly, instead use the
/// method.
///
public static void Register(Type type, PerspexProperty property)
{
Contract.Requires(type != null);
Contract.Requires(property != null);
List list;
if (!registered.TryGetValue(type, out list))
{
list = new List();
registered.Add(type, list);
}
if (!list.Contains(property))
{
list.Add(property);
}
}
///
/// Binds a property on this object to an observable.
///
/// The type of the property.
/// The target property.
/// The observable.
/// A disposable binding.
public void Bind(PerspexProperty target, IObservable source)
{
Contract.Requires(target != null);
Contract.Requires(source != null);
this.ClearBinding(target);
IDisposable binding = source.Subscribe(Observer.Create(value =>
{
this.SetValue(target, value);
}));
this.bindings.Add(target, binding);
}
///
/// Clears a binding on a , leaving the last bound value in
/// place.
///
/// The property.
public void ClearBinding(PerspexProperty property)
{
Contract.Requires(property != null);
IDisposable binding;
if (this.bindings.TryGetValue(property, out binding))
{
binding.Dispose();
this.bindings.Remove(property);
}
}
///
/// Clears a value, including its bindings.
///
/// The property.
public void ClearValue(PerspexProperty property)
{
Contract.Requires(property != null);
this.ClearBinding(property);
this.values.Remove(property);
}
///
/// Gets an observable for a .
///
///
///
///
public IObservable GetObservable(PerspexProperty property)
{
return Observable.Create(observer =>
{
EventHandler handler = (s, e) =>
{
if (e.Property == property)
{
observer.OnNext((T)e.NewValue);
}
};
this.PropertyChanged += handler;
observer.OnNext(this.GetValue(property));
return () =>
{
this.PropertyChanged -= handler;
};
});
}
///
/// Gets a value.
///
/// The property.
/// The value.
public object GetValue(PerspexProperty property)
{
Contract.Requires(property != null);
object value;
if (!this.values.TryGetValue(property, out value))
{
if (property.Inherits && this.inheritanceParent != null)
{
value = this.inheritanceParent.GetValue(property);
}
else
{
value = property.GetDefaultValue(this.GetType());
}
}
return value;
}
///
/// Gets a value.
///
/// The property.
/// The value.
public T GetValue(PerspexProperty property)
{
Contract.Requires(property != null);
return (T)this.GetValue((PerspexProperty)property);
}
///
/// Gets a value.
///
/// The type of the property.
/// The property.
/// The value.
public T GetValue(ReadOnlyPerspexProperty property)
{
Contract.Requires(property != null);
return (T)this.GetValue(property.Property);
}
///
/// Checks whether a is set on this object.
///
///
///
public bool IsSet(PerspexProperty property)
{
Contract.Requires(property != null);
return this.values.ContainsKey(property);
}
///
/// Sets a value.
///
/// The type of the property.
/// The property.
/// The value.
public void SetValue(PerspexProperty property, T value)
{
Contract.Requires(property != null);
this.ClearBinding(property);
T oldValue = this.GetValue(property);
if (!object.Equals(oldValue, value))
{
this.values[property] = value;
this.RaisePropertyChanged(property, oldValue, value);
}
}
///
/// Called when a property is changed on the current .
///
/// The event sender.
/// The event args.
///
/// Checks for changes in an inherited property value.
///
private void ParentPropertyChanged(object sender, PerspexPropertyChangedEventArgs e)
{
if (e.Property.Inherits && !this.IsSet(e.Property))
{
this.RaisePropertyChanged(e.Property, e.OldValue, e.NewValue);
}
}
///
/// Raises the event.
///
/// The property that has changed.
/// The old property value.
/// The new property value.
private void RaisePropertyChanged(PerspexProperty property, object oldValue, object newValue)
{
Contract.Requires(property != null);
if (this.PropertyChanged != null)
{
this.PropertyChanged(
this,
new PerspexPropertyChangedEventArgs(property, oldValue, newValue));
}
}
}
}