Browse Source
* Reminder for future me. * Move compiled bindings to Avalonia base. * Update suppressions. * Update API suppressions --------- Co-authored-by: Julien Lebosquain <julien@lebosquain.net>pull/20480/head
committed by
GitHub
11 changed files with 258 additions and 230 deletions
@ -0,0 +1,164 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel; |
||||
|
using System.Globalization; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Data.Converters; |
||||
|
using Avalonia.Data.Core; |
||||
|
using Avalonia.Data.Core.ExpressionNodes; |
||||
|
using Avalonia.Data.Core.Parsers; |
||||
|
|
||||
|
namespace Avalonia.Data; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// A binding which does not use reflection to access members.
|
||||
|
/// </summary>
|
||||
|
public class CompiledBinding : BindingBase |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="CompiledBinding"/> class.
|
||||
|
/// </summary>
|
||||
|
public CompiledBinding() { } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="CompiledBinding"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="path">The binding path.</param>
|
||||
|
public CompiledBinding(CompiledBindingPath path) => Path = path; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the amount of time, in milliseconds, to wait before updating the binding
|
||||
|
/// source after the value on the target changes.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// There is no delay when the source is updated via <see cref="UpdateSourceTrigger.LostFocus"/>
|
||||
|
/// or <see cref="BindingExpressionBase.UpdateSource"/>. Nor is there a delay when
|
||||
|
/// <see cref="BindingMode.OneWayToSource"/> is active and a new source object is provided.
|
||||
|
/// </remarks>
|
||||
|
public int Delay { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the <see cref="IValueConverter"/> to use.
|
||||
|
/// </summary>
|
||||
|
public IValueConverter? Converter { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the culture in which to evaluate the converter.
|
||||
|
/// </summary>
|
||||
|
/// <value>The default value is null.</value>
|
||||
|
/// <remarks>
|
||||
|
/// If this property is not set then <see cref="CultureInfo.CurrentCulture"/> will be used.
|
||||
|
/// </remarks>
|
||||
|
[TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))] |
||||
|
public CultureInfo? ConverterCulture { 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 value to use when the binding is unable to produce a value.
|
||||
|
/// </summary>
|
||||
|
public object? FallbackValue { get; set; } = AvaloniaProperty.UnsetValue; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the binding mode.
|
||||
|
/// </summary>
|
||||
|
public BindingMode Mode { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the binding path.
|
||||
|
/// </summary>
|
||||
|
public CompiledBindingPath? Path { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the binding priority.
|
||||
|
/// </summary>
|
||||
|
public BindingPriority Priority { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the source for the binding.
|
||||
|
/// </summary>
|
||||
|
public object? Source { get; set; } = AvaloniaProperty.UnsetValue; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the string format.
|
||||
|
/// </summary>
|
||||
|
public string? StringFormat { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the value to use when the binding result is null.
|
||||
|
/// </summary>
|
||||
|
public object? TargetNullValue { get; set; } = AvaloniaProperty.UnsetValue; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a value that determines the timing of binding source updates for
|
||||
|
/// <see cref="BindingMode.TwoWay"/> and <see cref="BindingMode.OneWayToSource"/> bindings.
|
||||
|
/// </summary>
|
||||
|
public UpdateSourceTrigger UpdateSourceTrigger { get; set; } |
||||
|
|
||||
|
internal WeakReference? DefaultAnchor { get; set; } |
||||
|
internal WeakReference<INameScope?>? NameScope { get; set; } |
||||
|
|
||||
|
internal override BindingExpressionBase CreateInstance( |
||||
|
AvaloniaObject target, |
||||
|
AvaloniaProperty? targetProperty, |
||||
|
object? anchor) |
||||
|
{ |
||||
|
var enableDataValidation = targetProperty?.GetMetadata(target).EnableDataValidation ?? false; |
||||
|
var nodes = new List<ExpressionNode>(); |
||||
|
var isRooted = false; |
||||
|
|
||||
|
Path?.BuildExpression(nodes, out isRooted); |
||||
|
|
||||
|
// If the binding isn't rooted (i.e. doesn't have a Source or start with $parent, $self,
|
||||
|
// #elementName etc.) then we need to add a data context source node.
|
||||
|
if (Source == AvaloniaProperty.UnsetValue && !isRooted) |
||||
|
nodes.Insert(0, ExpressionNodeFactory.CreateDataContext(targetProperty)); |
||||
|
|
||||
|
// If the first node is an ISourceNode then allow it to select the source; otherwise
|
||||
|
// use the binding source if specified, falling back to the target.
|
||||
|
var source = nodes?.Count > 0 && nodes[0] is SourceNode sn |
||||
|
? sn.SelectSource(Source, target, anchor ?? DefaultAnchor?.Target) |
||||
|
: Source != AvaloniaProperty.UnsetValue ? Source : target; |
||||
|
|
||||
|
var (mode, trigger) = ResolveDefaultsFromMetadata(target, targetProperty); |
||||
|
|
||||
|
return new BindingExpression( |
||||
|
source, |
||||
|
nodes, |
||||
|
FallbackValue, |
||||
|
delay: TimeSpan.FromMilliseconds(Delay), |
||||
|
converter: Converter, |
||||
|
converterCulture: ConverterCulture, |
||||
|
converterParameter: ConverterParameter, |
||||
|
enableDataValidation: enableDataValidation, |
||||
|
mode: mode, |
||||
|
priority: Priority, |
||||
|
stringFormat: StringFormat, |
||||
|
targetNullValue: TargetNullValue, |
||||
|
targetProperty: targetProperty, |
||||
|
targetTypeConverter: TargetTypeConverter.GetDefaultConverter(), |
||||
|
updateSourceTrigger: trigger); |
||||
|
} |
||||
|
|
||||
|
private (BindingMode, UpdateSourceTrigger) ResolveDefaultsFromMetadata( |
||||
|
AvaloniaObject target, |
||||
|
AvaloniaProperty? targetProperty) |
||||
|
{ |
||||
|
var mode = Mode; |
||||
|
var trigger = UpdateSourceTrigger == UpdateSourceTrigger.Default ? |
||||
|
UpdateSourceTrigger.PropertyChanged : UpdateSourceTrigger; |
||||
|
|
||||
|
if (mode == BindingMode.Default) |
||||
|
{ |
||||
|
if (targetProperty?.GetMetadata(target) is { } metadata) |
||||
|
mode = metadata.DefaultBindingMode; |
||||
|
else |
||||
|
mode = BindingMode.OneWay; |
||||
|
} |
||||
|
|
||||
|
return (mode, trigger); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue