using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq.Expressions;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Data.Core;
using Avalonia.Data.Core.ExpressionNodes;
using Avalonia.Data.Core.Parsers;
using Avalonia.Metadata;
namespace Avalonia.Data;
///
/// A binding which does not use reflection to access members.
///
public class CompiledBinding : BindingBase
{
///
/// Initializes a new instance of the class.
///
public CompiledBinding() { }
///
/// Initializes a new instance of the class.
///
/// The binding path.
public CompiledBinding(CompiledBindingPath path) => Path = path;
///
/// Creates a from a lambda expression.
///
/// The input type of the binding expression.
/// The output type of the binding expression.
///
/// The lambda expression representing the binding path
/// (e.g., vm => vm.PropertyName).
///
/// The source object for the binding. If null, uses the target's DataContext.
///
///
/// Optional value converter to transform values between source and target.
///
///
/// The binding mode. Default is which resolves to the
/// property's default binding mode.
///
/// The binding priority.
/// The culture in which to evaluate the converter.
/// A parameter to pass to the converter.
///
/// The value to use when the binding is unable to produce a value.
///
/// The string format for the binding result.
/// The value to use when the binding result is null.
///
/// The timing of binding source updates for TwoWay/OneWayToSource bindings.
///
///
/// The amount of time, in milliseconds, to wait before updating the binding source.
///
///
/// A configured instance ready to be applied to a property.
///
///
/// Thrown when the expression contains unsupported operations or invalid syntax for binding
/// expressions.
///
///
/// This builds a with a path described by a lambda expression.
/// The resulting binding avoids reflection for property access, providing better performance
/// than reflection-based bindings.
///
/// Supported expressions include:
///
/// - Property access: x => x.Property
/// - Nested properties: x => x.Property.Nested
/// - Indexers: x => x.Items[0]
/// - Type casts: x => ((DerivedType)x).Property
/// - Logical NOT: x => !x.BoolProperty
/// - AvaloniaProperty access: x => x[MyProperty]
///
///
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Expression statically preserves members used in binding expressions.")]
public static CompiledBinding Create(
Expression> expression,
object? source = null,
IValueConverter? converter = null,
BindingMode mode = BindingMode.Default,
BindingPriority priority = BindingPriority.LocalValue,
CultureInfo? converterCulture = null,
object? converterParameter = null,
object? fallbackValue = null,
string? stringFormat = null,
object? targetNullValue = null,
UpdateSourceTrigger updateSourceTrigger = UpdateSourceTrigger.Default,
int delay = 0)
{
var path = BindingExpressionVisitor.BuildPath(expression);
return new CompiledBinding(path)
{
Source = source ?? AvaloniaProperty.UnsetValue,
Converter = converter,
ConverterCulture = converterCulture,
ConverterParameter = converterParameter,
FallbackValue = fallbackValue ?? AvaloniaProperty.UnsetValue,
Mode = mode,
Priority = priority,
StringFormat = stringFormat,
TargetNullValue = targetNullValue ?? AvaloniaProperty.UnsetValue,
UpdateSourceTrigger = updateSourceTrigger,
Delay = delay
};
}
///
/// Gets or sets the amount of time, in milliseconds, to wait before updating the binding
/// source after the value on the target changes.
///
///
/// There is no delay when the source is updated via
/// or . Nor is there a delay when
/// is active and a new source object is provided.
///
public int Delay { get; set; }
///
/// Gets or sets the to use.
///
public IValueConverter? Converter { get; set; }
///
/// Gets or sets the culture in which to evaluate the converter.
///
/// The default value is null.
///
/// If this property is not set then will be used.
///
[TypeConverter(typeof(CultureInfoIetfLanguageTagConverter))]
public CultureInfo? ConverterCulture { get; set; }
///
/// Gets or sets a parameter to pass to .
///
public object? ConverterParameter { get; set; }
///
/// Gets or sets the value to use when the binding is unable to produce a value.
///
public object? FallbackValue { get; set; } = AvaloniaProperty.UnsetValue;
///
/// Gets or sets the binding mode.
///
public BindingMode Mode { get; set; }
///
/// Gets or sets the binding path.
///
[ConstructorArgument("path")]
public CompiledBindingPath? Path { get; set; }
///
/// Gets or sets the binding priority.
///
public BindingPriority Priority { get; set; }
///
/// Gets or sets the source for the binding.
///
public object? Source { get; set; } = AvaloniaProperty.UnsetValue;
///
/// Gets or sets the string format.
///
public string? StringFormat { get; set; }
///
/// Gets or sets the value to use when the binding result is null.
///
public object? TargetNullValue { get; set; } = AvaloniaProperty.UnsetValue;
///
/// Gets or sets a value that determines the timing of binding source updates for
/// and bindings.
///
public UpdateSourceTrigger UpdateSourceTrigger { get; set; }
internal WeakReference? DefaultAnchor { get; set; }
internal WeakReference? NameScope { get; set; }
internal override BindingExpressionBase CreateInstance(
AvaloniaObject target,
AvaloniaProperty? targetProperty,
object? anchor)
{
var enableDataValidation = targetProperty?.GetMetadata(target).EnableDataValidation ?? false;
var nodes = new List();
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);
}
}