committed by
GitHub
28 changed files with 508 additions and 262 deletions
@ -0,0 +1,172 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Data; |
||||
|
|
||||
|
namespace Avalonia |
||||
|
{ |
||||
|
internal class ValueStore : IPriorityValueOwner |
||||
|
{ |
||||
|
private readonly AvaloniaObject _owner; |
||||
|
private readonly Dictionary<AvaloniaProperty, object> _values = |
||||
|
new Dictionary<AvaloniaProperty, object>(); |
||||
|
|
||||
|
public ValueStore(AvaloniaObject owner) |
||||
|
{ |
||||
|
_owner = owner; |
||||
|
} |
||||
|
|
||||
|
public IDisposable AddBinding( |
||||
|
AvaloniaProperty property, |
||||
|
IObservable<object> source, |
||||
|
BindingPriority priority) |
||||
|
{ |
||||
|
PriorityValue priorityValue; |
||||
|
|
||||
|
if (_values.TryGetValue(property, out var v)) |
||||
|
{ |
||||
|
priorityValue = v as PriorityValue; |
||||
|
|
||||
|
if (priorityValue == null) |
||||
|
{ |
||||
|
priorityValue = CreatePriorityValue(property); |
||||
|
priorityValue.SetValue(v, (int)BindingPriority.LocalValue); |
||||
|
_values[property] = priorityValue; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
priorityValue = CreatePriorityValue(property); |
||||
|
_values.Add(property, priorityValue); |
||||
|
} |
||||
|
|
||||
|
return priorityValue.Add(source, (int)priority); |
||||
|
} |
||||
|
|
||||
|
public void AddValue(AvaloniaProperty property, object value, int priority) |
||||
|
{ |
||||
|
PriorityValue priorityValue; |
||||
|
|
||||
|
if (_values.TryGetValue(property, out var v)) |
||||
|
{ |
||||
|
priorityValue = v as PriorityValue; |
||||
|
|
||||
|
if (priorityValue == null) |
||||
|
{ |
||||
|
if (priority == (int)BindingPriority.LocalValue) |
||||
|
{ |
||||
|
_values[property] = Validate(property, value); |
||||
|
Changed(property, priority, v, value); |
||||
|
return; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
priorityValue = CreatePriorityValue(property); |
||||
|
priorityValue.SetValue(v, (int)BindingPriority.LocalValue); |
||||
|
_values[property] = priorityValue; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (value == AvaloniaProperty.UnsetValue) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (priority == (int)BindingPriority.LocalValue) |
||||
|
{ |
||||
|
_values.Add(property, Validate(property, value)); |
||||
|
Changed(property, priority, AvaloniaProperty.UnsetValue, value); |
||||
|
return; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
priorityValue = CreatePriorityValue(property); |
||||
|
_values.Add(property, priorityValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
priorityValue.SetValue(value, priority); |
||||
|
} |
||||
|
|
||||
|
public void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification) |
||||
|
{ |
||||
|
((IPriorityValueOwner)_owner).BindingNotificationReceived(property, notification); |
||||
|
} |
||||
|
|
||||
|
public void Changed(AvaloniaProperty property, int priority, object oldValue, object newValue) |
||||
|
{ |
||||
|
((IPriorityValueOwner)_owner).Changed(property, priority, oldValue, newValue); |
||||
|
} |
||||
|
|
||||
|
public IDictionary<AvaloniaProperty, PriorityValue> GetSetValues() => throw new NotImplementedException(); |
||||
|
|
||||
|
public object GetValue(AvaloniaProperty property) |
||||
|
{ |
||||
|
var result = AvaloniaProperty.UnsetValue; |
||||
|
|
||||
|
if (_values.TryGetValue(property, out var value)) |
||||
|
{ |
||||
|
result = (value is PriorityValue priorityValue) ? priorityValue.Value : value; |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
public bool IsAnimating(AvaloniaProperty property) |
||||
|
{ |
||||
|
return _values.TryGetValue(property, out var value) ? (value as PriorityValue)?.IsAnimating ?? false : false; |
||||
|
} |
||||
|
|
||||
|
public bool IsSet(AvaloniaProperty property) |
||||
|
{ |
||||
|
if (_values.TryGetValue(property, out var value)) |
||||
|
{ |
||||
|
return ((value as PriorityValue)?.Value ?? value) != AvaloniaProperty.UnsetValue; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
public void Revalidate(AvaloniaProperty property) |
||||
|
{ |
||||
|
if (_values.TryGetValue(property, out var value)) |
||||
|
{ |
||||
|
(value as PriorityValue)?.Revalidate(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void VerifyAccess() => _owner.VerifyAccess(); |
||||
|
|
||||
|
private PriorityValue CreatePriorityValue(AvaloniaProperty property) |
||||
|
{ |
||||
|
var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(_owner.GetType()); |
||||
|
Func<object, object> validate2 = null; |
||||
|
|
||||
|
if (validate != null) |
||||
|
{ |
||||
|
validate2 = v => validate(_owner, v); |
||||
|
} |
||||
|
|
||||
|
PriorityValue result = new PriorityValue( |
||||
|
this, |
||||
|
property, |
||||
|
property.PropertyType, |
||||
|
validate2); |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
private object Validate(AvaloniaProperty property, object value) |
||||
|
{ |
||||
|
var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(_owner.GetType()); |
||||
|
|
||||
|
if (validate != null && value != AvaloniaProperty.UnsetValue) |
||||
|
{ |
||||
|
return validate(_owner, value); |
||||
|
} |
||||
|
|
||||
|
return value; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,51 +0,0 @@ |
|||||
// 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 Avalonia.Data; |
|
||||
|
|
||||
namespace Avalonia.Markup.Xaml.MarkupExtensions |
|
||||
{ |
|
||||
using System; |
|
||||
using Avalonia.Data.Converters; |
|
||||
using Avalonia.Markup.Data; |
|
||||
using Portable.Xaml.Markup; |
|
||||
|
|
||||
[MarkupExtensionReturnType(typeof(IBinding))] |
|
||||
public class TemplateBindingExtension : MarkupExtension |
|
||||
{ |
|
||||
public TemplateBindingExtension() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public TemplateBindingExtension(string path) |
|
||||
{ |
|
||||
Path = path; |
|
||||
} |
|
||||
|
|
||||
public override object ProvideValue(IServiceProvider serviceProvider) |
|
||||
{ |
|
||||
return new Binding |
|
||||
{ |
|
||||
Converter = Converter, |
|
||||
ElementName = ElementName, |
|
||||
Mode = Mode, |
|
||||
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), |
|
||||
Path = Path ?? string.Empty, |
|
||||
Priority = Priority, |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
public IValueConverter Converter { get; set; } |
|
||||
|
|
||||
public string ElementName { get; set; } |
|
||||
|
|
||||
public object FallbackValue { get; set; } |
|
||||
|
|
||||
public BindingMode Mode { get; set; } |
|
||||
|
|
||||
[ConstructorArgument("path")] |
|
||||
public string Path { get; set; } |
|
||||
|
|
||||
public BindingPriority Priority { get; set; } = BindingPriority.TemplatedParent; |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,178 @@ |
|||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
using System.Reactive.Subjects; |
||||
|
using Avalonia.Data.Converters; |
||||
|
using Avalonia.Reactive; |
||||
|
|
||||
|
namespace Avalonia.Data |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A XAML binding to a property on a control's templated parent.
|
||||
|
/// </summary>
|
||||
|
public class TemplateBinding : SingleSubscriberObservableBase<object>, |
||||
|
IBinding, |
||||
|
IDescription, |
||||
|
ISubject<object> |
||||
|
{ |
||||
|
private IStyledElement _target; |
||||
|
private Type _targetType; |
||||
|
|
||||
|
public TemplateBinding() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public TemplateBinding(AvaloniaProperty property) |
||||
|
{ |
||||
|
Property = property; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public InstancedBinding Initiate( |
||||
|
IAvaloniaObject target, |
||||
|
AvaloniaProperty targetProperty, |
||||
|
object anchor = null, |
||||
|
bool enableDataValidation = false) |
||||
|
{ |
||||
|
// Usually each `TemplateBinding` will only be instantiated once; in this case we can
|
||||
|
// use the `TemplateBinding` object itself as the instanced binding in order to save
|
||||
|
// allocating a new object. If the binding *is* instantiated more than once (which can
|
||||
|
// happen if it appears in a `Setter` for example, then just make a clone and instantiate
|
||||
|
// that.
|
||||
|
if (_target == null) |
||||
|
{ |
||||
|
_target = (IStyledElement)target; |
||||
|
_targetType = targetProperty?.PropertyType; |
||||
|
|
||||
|
return new InstancedBinding( |
||||
|
this, |
||||
|
Mode == BindingMode.Default ? BindingMode.OneWay : Mode, |
||||
|
BindingPriority.TemplatedParent); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var clone = new TemplateBinding |
||||
|
{ |
||||
|
Converter = Converter, |
||||
|
ConverterParameter = ConverterParameter, |
||||
|
Property = Property, |
||||
|
}; |
||||
|
|
||||
|
return clone.Initiate(target, targetProperty, anchor, enableDataValidation); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the <see cref="IValueConverter"/> to use.
|
||||
|
/// </summary>
|
||||
|
public IValueConverter Converter { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a parameter to pass to <see cref="Converter"/>.
|
||||
|
/// </summary>
|
||||
|
public object ConverterParameter { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the binding mode.
|
||||
|
/// </summary>
|
||||
|
public BindingMode Mode { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the name of the source property on the templated parent.
|
||||
|
/// </summary>
|
||||
|
public AvaloniaProperty Property { get; set; } |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public string Description => "TemplateBinding: " + Property; |
||||
|
|
||||
|
void IObserver<object>.OnCompleted() => throw new NotImplementedException(); |
||||
|
void IObserver<object>.OnError(Exception error) => throw new NotImplementedException(); |
||||
|
|
||||
|
void IObserver<object>.OnNext(object value) |
||||
|
{ |
||||
|
if (_target.TemplatedParent != null && Property != null) |
||||
|
{ |
||||
|
if (Converter != null) |
||||
|
{ |
||||
|
value = Converter.ConvertBack( |
||||
|
value, |
||||
|
Property.PropertyType, |
||||
|
ConverterParameter, |
||||
|
CultureInfo.CurrentCulture); |
||||
|
} |
||||
|
|
||||
|
_target.TemplatedParent.SetValue(Property, value, BindingPriority.TemplatedParent); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override void Subscribed() |
||||
|
{ |
||||
|
TemplatedParentChanged(); |
||||
|
_target.PropertyChanged += TargetPropertyChanged; |
||||
|
} |
||||
|
|
||||
|
protected override void Unsubscribed() |
||||
|
{ |
||||
|
if (_target.TemplatedParent != null) |
||||
|
{ |
||||
|
_target.TemplatedParent.PropertyChanged -= TemplatedParentPropertyChanged; |
||||
|
} |
||||
|
|
||||
|
_target.PropertyChanged -= TargetPropertyChanged; |
||||
|
} |
||||
|
|
||||
|
private void PublishValue() |
||||
|
{ |
||||
|
if (_target.TemplatedParent != null) |
||||
|
{ |
||||
|
var value = Property != null ? |
||||
|
_target.TemplatedParent.GetValue(Property) : |
||||
|
_target.TemplatedParent; |
||||
|
|
||||
|
if (Converter != null) |
||||
|
{ |
||||
|
value = Converter.Convert(value, _targetType, ConverterParameter, CultureInfo.CurrentCulture); |
||||
|
} |
||||
|
|
||||
|
PublishNext(value); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
PublishNext(AvaloniaProperty.UnsetValue); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void TemplatedParentChanged() |
||||
|
{ |
||||
|
if (_target.TemplatedParent != null) |
||||
|
{ |
||||
|
_target.TemplatedParent.PropertyChanged += TemplatedParentPropertyChanged; |
||||
|
} |
||||
|
|
||||
|
PublishValue(); |
||||
|
} |
||||
|
|
||||
|
private void TargetPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) |
||||
|
{ |
||||
|
if (e.Property == StyledElement.TemplatedParentProperty) |
||||
|
{ |
||||
|
var oldValue = (IAvaloniaObject)e.OldValue; |
||||
|
var newValue = (IAvaloniaObject)e.OldValue; |
||||
|
|
||||
|
if (oldValue != null) |
||||
|
{ |
||||
|
oldValue.PropertyChanged -= TemplatedParentPropertyChanged; |
||||
|
} |
||||
|
|
||||
|
TemplatedParentChanged(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void TemplatedParentPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) |
||||
|
{ |
||||
|
if (e.Property == Property) |
||||
|
{ |
||||
|
PublishNext(_target.TemplatedParent.GetValue(Property)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue