/// <summary> /// Checks whether a <see cref="PerspexProperty"/> is set on this object. /// </summary> /// <param name="property">The property.</param> /// <returns>True if the property is set, otherwise false.</returns> public bool IsSet(PerspexProperty property) { Contract.Requires <ArgumentNullException>(property != null); PriorityValue value; if (_values.TryGetValue(property, out value)) { return(value.Value != PerspexProperty.UnsetValue); } return(false); }
/// <summary> /// Gets a <see cref="PerspexProperty"/> value. /// </summary> /// <typeparam name="T">The type of the property.</typeparam> /// <param name="property">The property.</param> /// <returns>The value.</returns> public T GetValue <T>(PerspexProperty <T> property) { Contract.Requires <ArgumentNullException>(property != null); if (property.IsDirect) { return(((PerspexProperty <T>)GetRegistered(property)).Getter(this)); } else { return((T)GetValue((PerspexProperty)property)); } }
/// <summary> /// Binds a <see cref="PerspexProperty"/> to an observable. /// </summary> /// <param name="property">The property.</param> /// <param name="source">The observable.</param> /// <param name="priority">The priority of the binding.</param> /// <returns> /// A disposable which can be used to terminate the binding. /// </returns> public IDisposable Bind( PerspexProperty property, IObservable <object> source, BindingPriority priority = BindingPriority.LocalValue) { Contract.Requires <ArgumentNullException>(property != null); VerifyAccess(); if (property.IsDirect) { property = GetRegistered(property); if (property.Setter == null) { throw new ArgumentException($"The property {property.Name} is readonly."); } _propertyLog.Verbose( "Bound {Property} to {Binding} with priority LocalValue", property, GetDescription(source)); return(source .Select(x => TypeUtilities.CastOrDefault(x, property.PropertyType)) .Subscribe(x => SetValue(property, x))); } else { PriorityValue v; if (!PerspexPropertyRegistry.Instance.IsRegistered(this, property)) { ThrowNotRegistered(property); } if (!_values.TryGetValue(property, out v)) { v = CreatePriorityValue(property); _values.Add(property, v); } _propertyLog.Verbose( "Bound {Property} to {Binding} with priority {Priority}", property, GetDescription(source), priority); return(v.Add(source, (int)priority)); } }
/// <inheritdoc/> public override void Merge(PropertyMetadata baseMetadata, PerspexProperty property) { base.Merge(baseMetadata, property); var src = baseMetadata as DirectPropertyMetadata <TValue>; if (src != null) { if (UnsetValue == null) { UnsetValue = src.UnsetValue; } } }
/// <summary> /// Sets the backing field for a direct perspex property, raising the /// <see cref="PropertyChanged"/> event if the value has changed. /// </summary> /// <typeparam name="T">The type of the property.</typeparam> /// <param name="property">The property.</param> /// <param name="field">The backing field.</param> /// <param name="value">The value.</param> /// <returns> /// True if the value changed, otherwise false. /// </returns> protected bool SetAndRaise <T>(PerspexProperty <T> property, ref T field, T value) { VerifyAccess(); if (!object.Equals(field, value)) { var old = field; field = value; RaisePropertyChanged(property, old, value, BindingPriority.LocalValue); return(true); } else { return(false); } }
/// <summary> /// Sets a <see cref="PerspexProperty"/> value. /// </summary> /// <param name="property">The property.</param> /// <param name="value">The value.</param> /// <param name="priority">The priority of the value.</param> public void SetValue( PerspexProperty property, object value, BindingPriority priority = BindingPriority.LocalValue) { Contract.Requires <ArgumentNullException>(property != null); VerifyAccess(); if (property.IsDirect) { var accessor = (IDirectPropertyAccessor)GetRegistered(property); LogPropertySet(property, value, priority); accessor.SetValue(this, DirectUnsetToDefault(value, property)); } else { PriorityValue v; var originalValue = value; if (!PerspexPropertyRegistry.Instance.IsRegistered(this, property)) { ThrowNotRegistered(property); } if (!TypeUtilities.TryCast(property.PropertyType, value, out value)) { throw new ArgumentException(string.Format( "Invalid value for Property '{0}': '{1}' ({2})", property.Name, originalValue, originalValue?.GetType().FullName ?? "(null)")); } if (!_values.TryGetValue(property, out v)) { if (value == PerspexProperty.UnsetValue) { return; } v = CreatePriorityValue(property); _values.Add(property, v); } LogPropertySet(property, value, priority); v.SetValue(value, (int)priority); } }
/// <summary> /// Initiates a two-way binding between a <see cref="PerspexProperty"/> and an /// <see cref="ISubject{Object}"/>. /// </summary> /// <param name="property">The property on this object.</param> /// <param name="source">The subject to bind to.</param> /// <param name="priority">The priority of the binding.</param> /// <returns> /// A disposable which can be used to terminate the binding. /// </returns> /// <remarks> /// The binding is first carried out from <paramref name="source"/> to this. /// </remarks> public IDisposable BindTwoWay( PerspexProperty property, ISubject <object> source, BindingPriority priority = BindingPriority.LocalValue) { VerifyAccess(); _propertyLog.Verbose( "Bound two way {Property} to {Binding} with priority {Priority}", property, GetDescription(source), priority); return(new CompositeDisposable( Bind(property, source), GetObservable(property).Subscribe(source))); }
/// <summary> /// Raises the <see cref="PropertyChanged"/> event. /// </summary> /// <param name="property">The property that has changed.</param> /// <param name="oldValue">The old property value.</param> /// <param name="newValue">The new property value.</param> private void RaisePropertyChanged(PerspexProperty property, object oldValue, object newValue) { Contract.Requires <NullReferenceException>(property != null); if (this.PropertyChanged != null) { PerspexPropertyChangedEventArgs e = new PerspexPropertyChangedEventArgs(this, property, oldValue, newValue); property.NotifyChanged(e); this.PropertyChanged(this, e); } if (this.inpcChanged != null) { PropertyChangedEventArgs e = new PropertyChangedEventArgs(property.Name); this.inpcChanged(this, e); } }
/// <summary> /// Initiates a two-way binding between <see cref="PerspexProperty"/>s. /// </summary> /// <param name="property">The property on this object.</param> /// <param name="source">The source object.</param> /// <param name="sourceProperty">The property on the source object.</param> /// <param name="priority">The priority of the binding.</param> /// <returns> /// A disposable which can be used to terminate the binding. /// </returns> /// <remarks> /// The binding is first carried out from <paramref name="source"/> to this. /// </remarks> public IDisposable BindTwoWay( PerspexProperty property, PerspexObject source, PerspexProperty sourceProperty, BindingPriority priority = BindingPriority.LocalValue) { VerifyAccess(); _propertyLog.Verbose( "Bound two way {Property} to {Binding} with priority {Priority}", property, source, priority); return(new CompositeDisposable( Bind(property, source.GetObservable(sourceProperty)), source.Bind(sourceProperty, GetObservable(property)))); }
/// <summary> /// Raises the <see cref="PropertyChanged"/> event. /// </summary> /// <param name="property">The property that has changed.</param> /// <param name="oldValue">The old property value.</param> /// <param name="newValue">The new property value.</param> /// <param name="priority">The priority of the binding that produced the value.</param> protected void RaisePropertyChanged( PerspexProperty property, object oldValue, object newValue, BindingPriority priority) { Contract.Requires <ArgumentNullException>(property != null); VerifyAccess(); PerspexPropertyChangedEventArgs e = new PerspexPropertyChangedEventArgs( this, property, oldValue, newValue, priority); if (property.Notifying != null) { property.Notifying(this, true); } try { OnPropertyChanged(e); property.NotifyChanged(e); if (PropertyChanged != null) { PropertyChanged(this, e); } if (_inpcChanged != null) { PropertyChangedEventArgs e2 = new PropertyChangedEventArgs(property.Name); _inpcChanged(this, e2); } } finally { if (property.Notifying != null) { property.Notifying(this, false); } } }
/// <summary> /// Gets a subject for a <see cref="PerspexProperty"/>. /// </summary> /// <typeparam name="T">The property type.</typeparam> /// <param name="o">The object.</param> /// <param name="property">The property.</param> /// <param name="priority"> /// The priority with which binding values are written to the object. /// </param> /// <returns> /// An <see cref="ISubject{T}"/> which can be used for two-way binding to/from the /// property. /// </returns> public static ISubject <T> GetSubject <T>( this IPerspexObject o, PerspexProperty <T> property, BindingPriority priority = BindingPriority.LocalValue) { // TODO: Subject.Create<T> is not yet in stable Rx : once it is, remove the // AnonymousSubject classes from this file and use Subject.Create<T>. var output = new Subject <T>(); var result = new AnonymousSubject <T>( Observer.Create <T>( x => output.OnNext(x), e => output.OnError(e), () => output.OnCompleted()), o.GetObservable(property)); o.Bind(property, output, priority); return(result); }
/// <summary> /// Creates a <see cref="PriorityValue"/> for a <see cref="PerspexProperty"/>. /// </summary> /// <param name="property">The property.</param> /// <returns>The <see cref="PriorityValue"/>.</returns> private PriorityValue CreatePriorityValue(PerspexProperty property) { var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(GetType()); Func <object, object> validate2 = null; if (validate != null) { validate2 = v => validate(this, v); } PriorityValue result = new PriorityValue( this, property, property.PropertyType, validate2); return(result); }
/// <summary> /// Registers a <see cref="PerspexProperty"/> on a type. /// </summary> /// <param name="type">The type.</param> /// <param name="property">The property.</param> /// <remarks> /// You won't usually want to call this method directly, instead use the /// <see cref="PerspexProperty.Register"/> method. /// </remarks> public static void Register(Type type, PerspexProperty property) { Contract.Requires <NullReferenceException>(type != null); Contract.Requires <NullReferenceException>(property != null); List <PerspexProperty> list; if (!registered.TryGetValue(type, out list)) { list = new List <PerspexProperty>(); registered.Add(type, list); } if (!list.Contains(property)) { list.Add(property); } }
/// <summary> /// Sets a <see cref="PerspexProperty"/> value. /// </summary> /// <param name="property">The property.</param> /// <param name="value">The value.</param> public void SetValue(PerspexProperty property, object value) { Contract.Requires <NullReferenceException>(property != null); const int Priority = (int)BindingPriority.LocalValue; PriorityValue v; if (!this.IsRegistered(property)) { throw new InvalidOperationException(string.Format( "Property '{0}' not registered on '{1}'", property.Name, this.GetType())); } if (!PriorityValue.IsValidValue(value, property.PropertyType)) { throw new InvalidOperationException(string.Format( "Invalid value for Property '{0}': {1} ({2})", property.Name, value, value.GetType().FullName)); } if (!this.values.TryGetValue(property, out v)) { if (value == PerspexProperty.UnsetValue) { return; } v = this.CreatePriorityValue(property); this.values.Add(property, v); } this.Log().Debug( "Set local value of {0}.{1} (#{2:x8}) to {3}", this.GetType().Name, property.Name, this.GetHashCode(), value); v.Replace(Observable.Never <object>().StartWith(value), Priority); }
/// <summary> /// Sets a <see cref="PerspexProperty"/> value. /// </summary> /// <param name="property">The property.</param> /// <param name="value">The value.</param> /// <param name="priority">The priority of the value.</param> public void SetValue( PerspexProperty property, object value, BindingPriority priority = BindingPriority.LocalValue) { Contract.Requires <NullReferenceException>(property != null); PriorityValue v; if (!IsRegistered(property)) { throw new InvalidOperationException(string.Format( "Property '{0}' not registered on '{1}'", property.Name, GetType())); } if (!TypeUtilities.TryCast(property.PropertyType, value, out value)) { throw new InvalidOperationException(string.Format( "Invalid value for Property '{0}': {1} ({2})", property.Name, value, value?.GetType().FullName ?? "(null)")); } if (!_values.TryGetValue(property, out v)) { if (value == PerspexProperty.UnsetValue) { return; } v = CreatePriorityValue(property); _values.Add(property, v); } _propertyLog.Verbose( "Set {Property} to {$Value} with priority {Priority}", property, value, priority); v.SetDirectValue(value, (int)priority); }
/// <inheritdoc/> public override void Merge(PropertyMetadata baseMetadata, PerspexProperty property) { base.Merge(baseMetadata, property); var src = baseMetadata as StyledPropertyMetadata <TValue>; if (src != null) { if (DefaultValue == null) { DefaultValue = src.DefaultValue; } if (Validate == null) { Validate = src.Validate; } } }
/// <summary> /// Creates a <see cref="PriorityValue"/> for a <see cref="PerspexProperty"/>. /// </summary> /// <param name="property">The property.</param> /// <returns>The <see cref="PriorityValue"/>.</returns> private PriorityValue CreatePriorityValue(PerspexProperty property) { var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(GetType()); Func <object, object> validate2 = null; if (validate != null) { validate2 = v => validate(this, v); } PriorityValue result = new PriorityValue( this, property.Name, property.PropertyType, validate2); result.Changed.Subscribe(x => { object oldValue = (x.Item1 == PerspexProperty.UnsetValue) ? GetDefaultValue(property) : x.Item1; object newValue = (x.Item2 == PerspexProperty.UnsetValue) ? GetDefaultValue(property) : x.Item2; if (!Equals(oldValue, newValue)) { RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)result.ValuePriority); Logger.Verbose( LogArea.Property, this, "{Property} changed from {$Old} to {$Value} with priority {Priority}", property, oldValue, newValue, (BindingPriority)result.ValuePriority); } }); return(result); }
/// <summary> /// Registers the direct property on another type. /// </summary> /// <typeparam name="TOwner">The type of the additional owner.</typeparam> /// <returns>The property.</returns> public PerspexProperty <TValue> AddOwner <TOwner>( Func <TOwner, TValue> getter, Action <TOwner, TValue> setter = null) where TOwner : PerspexObject { if (!IsDirect) { throw new InvalidOperationException( "This overload of AddOwner is for direct PerspexProperties."); } var result = new PerspexProperty <TValue>( this, typeof(TOwner), CastReturn(getter), CastParam1(setter)); PerspexPropertyRegistry.Instance.Register(typeof(TOwner), result); return(result); }
/// <summary> /// Registers an attached <see cref="PerspexProperty"/>. /// </summary> /// <typeparam name="TOwner">The type of the class that is registering the property.</typeparam> /// <typeparam name="THost">The type of the class that the property is to be registered on.</typeparam> /// <typeparam name="TValue">The type of the property's value.</typeparam> /// <param name="name">The name of the property.</param> /// <param name="defaultValue">The default value of the property.</param> /// <param name="inherits">Whether the property inherits its value.</param> /// <param name="defaultBindingMode">The default binding mode for the property.</param> /// <returns>A <see cref="PerspexProperty{TValue}"/></returns> public static PerspexProperty <TValue> RegisterAttached <TOwner, THost, TValue>( string name, TValue defaultValue = default(TValue), bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay) where TOwner : PerspexObject { Contract.Requires <NullReferenceException>(name != null); PerspexProperty <TValue> result = new PerspexProperty <TValue>( typeof(TOwner) + "." + name, typeof(TOwner), defaultValue, inherits, defaultBindingMode); PerspexObject.Register(typeof(THost), result); return(result); }
/// <summary> /// Gets an observable for a <see cref="PerspexProperty"/>. /// </summary> /// <typeparam name="T"></typeparam> /// <param name="property"></param> /// <returns></returns> public IObservable <Tuple <T, T> > GetObservableWithHistory <T>(PerspexProperty <T> property) { return(Observable.Create <Tuple <T, T> >(observer => { EventHandler <PerspexPropertyChangedEventArgs> handler = (s, e) => { if (e.Property == property) { observer.OnNext(Tuple.Create((T)e.OldValue, (T)e.NewValue)); } }; this.PropertyChanged += handler; return () => { this.PropertyChanged -= handler; }; })); }
/// <summary> /// Checks whether a <see cref="PerspexProperty"/> is registered on this class. /// </summary> /// <param name="property">The property.</param> /// <returns>True if the property is registered, otherwise false.</returns> private bool IsRegistered(PerspexProperty property) { Type type = this.GetType(); while (type != null) { List <PerspexProperty> list; if (registered.TryGetValue(type, out list)) { if (list.Contains(property)) { return(true); } } type = type.GetTypeInfo().BaseType; } return(false); }
/// <summary> /// Finds a <see cref="PerspexProperty"/> registered on a type. /// </summary> /// <param name="type">The type.</param> /// <param name="property">The property.</param> /// <returns>The registered property or null if not found.</returns> /// <remarks> /// Calling AddOwner on a PerspexProperty creates a new PerspexProperty that is a /// different object but is equal according to <see cref="object.Equals(object)"/>. /// </remarks> public PerspexProperty FindRegistered(Type type, PerspexProperty property) { while (type != null) { Dictionary <int, PerspexProperty> inner; if (_registered.TryGetValue(type, out inner)) { PerspexProperty result; if (inner.TryGetValue(property.Id, out result)) { return(result); } } type = type.GetTypeInfo().BaseType; } return(null); }
/// <summary> /// Binds a property on an <see cref="IPerspexObject"/> to an <see cref="IBinding"/>. /// </summary> /// <param name="target">The object.</param> /// <param name="property">The property to bind.</param> /// <param name="binding">The binding.</param> /// <param name="anchor"> /// An optional anchor from which to locate required context. When binding to objects that /// are not in the logical tree, certain types of binding need an anchor into the tree in /// order to locate named controls or resources. The <paramref name="anchor"/> parameter /// can be used to provice this context. /// </param> /// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns> public static IDisposable Bind( this IPerspexObject target, PerspexProperty property, IBinding binding, object anchor = null) { Contract.Requires <ArgumentNullException>(target != null); Contract.Requires <ArgumentNullException>(property != null); Contract.Requires <ArgumentNullException>(binding != null); var result = binding.Initiate(target, property, anchor); if (result != null) { return(BindingOperations.Apply(target, property, result, anchor)); } else { return(Disposable.Empty); } }
/// <summary> /// Finds a <see cref="PerspexProperty"/> registered on a type. /// </summary> /// <param name="type">The type.</param> /// <param name="property">The property.</param> /// <returns>The registered property or null if not found.</returns> /// <remarks> /// Calling AddOwner on a PerspexProperty creates a new PerspexProperty that is a /// different object but is equal according to <see cref="object.Equals(object)"/>. /// </remarks> public PerspexProperty FindRegistered(Type type, PerspexProperty property) { while (type != null) { List <PerspexProperty> list; if (_registered.TryGetValue(type, out list)) { var index = list.IndexOf(property); if (index != -1) { return(list[index]); } } type = type.GetTypeInfo().BaseType; } return(null); }
/// <summary> /// Gets an observable for a <see cref="PerspexProperty"/>. /// </summary> /// <typeparam name="T">The type of the property.</typeparam> /// <param name="property">The property.</param> /// <returns>An observable which when subscribed pushes the old and new values of the /// property each time it is changed.</returns> public IObservable <Tuple <T, T> > GetObservableWithHistory <T>(PerspexProperty <T> property) { return(new PerspexObservable <Tuple <T, T> >( observer => { EventHandler <PerspexPropertyChangedEventArgs> handler = (s, e) => { if (e.Property == property) { observer.OnNext(Tuple.Create((T)e.OldValue, (T)e.NewValue)); } }; PropertyChanged += handler; return Disposable.Create(() => { PropertyChanged -= handler; }); }, GetObservableDescription(property))); }
/// <summary> /// Binds a property on an <see cref="IPerspexObject"/> to an <see cref="IBinding"/>. /// </summary> /// <param name="o">The object.</param> /// <param name="property">The property to bind.</param> /// <param name="binding">The binding.</param> /// <returns>An <see cref="IDisposable"/> which can be used to cancel the binding.</returns> public static IDisposable Bind( this IPerspexObject o, PerspexProperty property, IBinding binding) { Contract.Requires <ArgumentNullException>(o != null); Contract.Requires <ArgumentNullException>(property != null); Contract.Requires <ArgumentNullException>(binding != null); var mode = binding.Mode; if (mode == BindingMode.Default) { mode = property.GetMetadata(o.GetType()).DefaultBindingMode; } return(o.Bind( property, binding.CreateSubject(o, property), mode, binding.Priority)); }
/// <summary> /// Registers an attached <see cref="PerspexProperty"/>. /// </summary> /// <typeparam name="THost">The type of the class that the property is to be registered on.</typeparam> /// <typeparam name="TValue">The type of the property's value.</typeparam> /// <param name="name">The name of the property.</param> /// <param name="ownerType">The type of the class that is registering the property.</param> /// <param name="defaultValue">The default value of the property.</param> /// <param name="inherits">Whether the property inherits its value.</param> /// <param name="defaultBindingMode">The default binding mode for the property.</param> /// <param name="validate">A validation function.</param> /// <returns>A <see cref="PerspexProperty{TValue}"/></returns> public static PerspexProperty <TValue> RegisterAttached <THost, TValue>( string name, Type ownerType, TValue defaultValue = default(TValue), bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, Func <PerspexObject, TValue, TValue> validate = null) { Contract.Requires <NullReferenceException>(name != null); PerspexProperty <TValue> result = new PerspexProperty <TValue>( name, ownerType, defaultValue, inherits, defaultBindingMode, validate, true); PerspexObject.Register(typeof(THost), result); return(result); }
/// <summary> /// Gets an observable for a <see cref="PerspexProperty"/>. /// </summary> /// <param name="property">The property.</param> /// <returns>An observable.</returns> public IObservable <object> GetObservable(PerspexProperty property) { Contract.Requires <NullReferenceException>(property != null); return(Observable.Create <object>(observer => { EventHandler <PerspexPropertyChangedEventArgs> handler = (s, e) => { if (e.Property == property) { observer.OnNext(e.NewValue); } }; this.PropertyChanged += handler; observer.OnNext(this.GetValue(property)); return () => { this.PropertyChanged -= handler; }; }).StartWith(this.GetValue(property))); }
/// <summary> /// Binds a <see cref="PerspexProperty"/> to an observable. /// </summary> /// <typeparam name="T">The type of the property.</typeparam> /// <param name="property">The property.</param> /// <param name="source">The observable.</param> /// <param name="priority">The priority of the binding.</param> /// <returns> /// A disposable which can be used to terminate the binding. /// </returns> public IDisposable Bind <T>( PerspexProperty <T> property, IObservable <T> source, BindingPriority priority = BindingPriority.LocalValue) { Contract.Requires <ArgumentNullException>(property != null); VerifyAccess(); if (property.IsDirect) { property = (PerspexProperty <T>)GetRegistered(property); if (property.Setter == null) { throw new ArgumentException($"The property {property.Name} is readonly."); } return(source.Subscribe(x => SetValue(property, x))); } else { return(Bind((PerspexProperty)property, source.Select(x => (object)x), priority)); } }
/// <summary> /// Registers a <see cref="PerspexProperty"/>. /// </summary> /// <typeparam name="TOwner">The type of the class that is registering the property.</typeparam> /// <typeparam name="TValue">The type of the property's value.</typeparam> /// <param name="name">The name of the property.</param> /// <param name="defaultValue">The default value of the property.</param> /// <param name="inherits">Whether the property inherits its value.</param> /// <param name="defaultBindingMode">The default binding mode for the property.</param> /// <param name="validate">A validation function.</param> /// <returns>A <see cref="PerspexProperty{TValue}"/></returns> public static PerspexProperty <TValue> Register <TOwner, TValue>( string name, TValue defaultValue = default(TValue), bool inherits = false, BindingMode defaultBindingMode = BindingMode.OneWay, Func <TOwner, TValue, TValue> validate = null) where TOwner : PerspexObject { Contract.Requires <NullReferenceException>(name != null); PerspexProperty <TValue> result = new PerspexProperty <TValue>( name, typeof(TOwner), defaultValue, inherits, defaultBindingMode, Cast(validate), false); PerspexObject.Register(typeof(TOwner), result); return(result); }