|
@@ -84,6 +84,11 @@ namespace Avalonia.Data
|
|
|
/// </summary>
|
|
/// </summary>
|
|
|
public object Source { get; set; }
|
|
public object Source { get; set; }
|
|
|
|
|
|
|
|
|
|
+ /// <summary>
|
|
|
|
|
+ /// Gets or sets the string format.
|
|
|
|
|
+ /// </summary>
|
|
|
|
|
+ public string StringFormat { get; set; }
|
|
|
|
|
+
|
|
|
public WeakReference DefaultAnchor { get; set; }
|
|
public WeakReference DefaultAnchor { get; set; }
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
@@ -105,34 +110,53 @@ namespace Avalonia.Data
|
|
|
|
|
|
|
|
ExpressionObserver observer;
|
|
ExpressionObserver observer;
|
|
|
|
|
|
|
|
|
|
+ var (node, mode) = ExpressionObserverBuilder.Parse(Path, enableDataValidation, TypeResolver);
|
|
|
|
|
+
|
|
|
if (ElementName != null)
|
|
if (ElementName != null)
|
|
|
{
|
|
{
|
|
|
observer = CreateElementObserver(
|
|
observer = CreateElementObserver(
|
|
|
(target as IStyledElement) ?? (anchor as IStyledElement),
|
|
(target as IStyledElement) ?? (anchor as IStyledElement),
|
|
|
ElementName,
|
|
ElementName,
|
|
|
- Path,
|
|
|
|
|
- enableDataValidation);
|
|
|
|
|
|
|
+ node);
|
|
|
}
|
|
}
|
|
|
else if (Source != null)
|
|
else if (Source != null)
|
|
|
{
|
|
{
|
|
|
- observer = CreateSourceObserver(Source, Path, enableDataValidation);
|
|
|
|
|
|
|
+ observer = CreateSourceObserver(Source, node);
|
|
|
}
|
|
}
|
|
|
- else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext)
|
|
|
|
|
|
|
+ else if (RelativeSource == null)
|
|
|
|
|
+ {
|
|
|
|
|
+ if (mode == SourceMode.Data)
|
|
|
|
|
+ {
|
|
|
|
|
+ observer = CreateDataContextObserver(
|
|
|
|
|
+ target,
|
|
|
|
|
+ node,
|
|
|
|
|
+ targetProperty == StyledElement.DataContextProperty,
|
|
|
|
|
+ anchor);
|
|
|
|
|
+ }
|
|
|
|
|
+ else
|
|
|
|
|
+ {
|
|
|
|
|
+ observer = new ExpressionObserver(
|
|
|
|
|
+ (target as IStyledElement) ?? (anchor as IStyledElement),
|
|
|
|
|
+ node);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ else if (RelativeSource.Mode == RelativeSourceMode.DataContext)
|
|
|
{
|
|
{
|
|
|
observer = CreateDataContextObserver(
|
|
observer = CreateDataContextObserver(
|
|
|
target,
|
|
target,
|
|
|
- Path,
|
|
|
|
|
|
|
+ node,
|
|
|
targetProperty == StyledElement.DataContextProperty,
|
|
targetProperty == StyledElement.DataContextProperty,
|
|
|
- anchor,
|
|
|
|
|
- enableDataValidation);
|
|
|
|
|
|
|
+ anchor);
|
|
|
}
|
|
}
|
|
|
else if (RelativeSource.Mode == RelativeSourceMode.Self)
|
|
else if (RelativeSource.Mode == RelativeSourceMode.Self)
|
|
|
{
|
|
{
|
|
|
- observer = CreateSourceObserver(target, Path, enableDataValidation);
|
|
|
|
|
|
|
+ observer = CreateSourceObserver(target, node);
|
|
|
}
|
|
}
|
|
|
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
|
|
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
|
|
|
{
|
|
{
|
|
|
- observer = CreateTemplatedParentObserver(target, Path, enableDataValidation);
|
|
|
|
|
|
|
+ observer = CreateTemplatedParentObserver(
|
|
|
|
|
+ (target as IStyledElement) ?? (anchor as IStyledElement),
|
|
|
|
|
+ node);
|
|
|
}
|
|
}
|
|
|
else if (RelativeSource.Mode == RelativeSourceMode.FindAncestor)
|
|
else if (RelativeSource.Mode == RelativeSourceMode.FindAncestor)
|
|
|
{
|
|
{
|
|
@@ -144,8 +168,7 @@ namespace Avalonia.Data
|
|
|
observer = CreateFindAncestorObserver(
|
|
observer = CreateFindAncestorObserver(
|
|
|
(target as IStyledElement) ?? (anchor as IStyledElement),
|
|
(target as IStyledElement) ?? (anchor as IStyledElement),
|
|
|
RelativeSource,
|
|
RelativeSource,
|
|
|
- Path,
|
|
|
|
|
- enableDataValidation);
|
|
|
|
|
|
|
+ node);
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
@@ -163,11 +186,23 @@ namespace Avalonia.Data
|
|
|
fallback = null;
|
|
fallback = null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ var converter = Converter;
|
|
|
|
|
+ var targetType = targetProperty?.PropertyType ?? typeof(object);
|
|
|
|
|
+
|
|
|
|
|
+ // We only respect `StringFormat` if the type of the property we're assigning to will
|
|
|
|
|
+ // accept a string. Note that this is slightly different to WPF in that WPF only applies
|
|
|
|
|
+ // `StringFormat` for target type `string` (not `object`).
|
|
|
|
|
+ if (!string.IsNullOrWhiteSpace(StringFormat) &&
|
|
|
|
|
+ (targetType == typeof(string) || targetType == typeof(object)))
|
|
|
|
|
+ {
|
|
|
|
|
+ converter = new StringFormatValueConverter(StringFormat, converter);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
var subject = new BindingExpression(
|
|
var subject = new BindingExpression(
|
|
|
observer,
|
|
observer,
|
|
|
- targetProperty?.PropertyType ?? typeof(object),
|
|
|
|
|
|
|
+ targetType,
|
|
|
fallback,
|
|
fallback,
|
|
|
- Converter ?? DefaultValueConverter.Instance,
|
|
|
|
|
|
|
+ converter ?? DefaultValueConverter.Instance,
|
|
|
ConverterParameter,
|
|
ConverterParameter,
|
|
|
Priority);
|
|
Priority);
|
|
|
|
|
|
|
@@ -176,10 +211,9 @@ namespace Avalonia.Data
|
|
|
|
|
|
|
|
private ExpressionObserver CreateDataContextObserver(
|
|
private ExpressionObserver CreateDataContextObserver(
|
|
|
IAvaloniaObject target,
|
|
IAvaloniaObject target,
|
|
|
- string path,
|
|
|
|
|
|
|
+ ExpressionNode node,
|
|
|
bool targetIsDataContext,
|
|
bool targetIsDataContext,
|
|
|
- object anchor,
|
|
|
|
|
- bool enableDataValidation)
|
|
|
|
|
|
|
+ object anchor)
|
|
|
{
|
|
{
|
|
|
Contract.Requires<ArgumentNullException>(target != null);
|
|
Contract.Requires<ArgumentNullException>(target != null);
|
|
|
|
|
|
|
@@ -195,48 +229,41 @@ namespace Avalonia.Data
|
|
|
|
|
|
|
|
if (!targetIsDataContext)
|
|
if (!targetIsDataContext)
|
|
|
{
|
|
{
|
|
|
- var result = ExpressionObserverBuilder.Build(
|
|
|
|
|
|
|
+ var result = new ExpressionObserver(
|
|
|
() => target.GetValue(StyledElement.DataContextProperty),
|
|
() => target.GetValue(StyledElement.DataContextProperty),
|
|
|
- path,
|
|
|
|
|
|
|
+ node,
|
|
|
new UpdateSignal(target, StyledElement.DataContextProperty),
|
|
new UpdateSignal(target, StyledElement.DataContextProperty),
|
|
|
- enableDataValidation,
|
|
|
|
|
- typeResolver: TypeResolver);
|
|
|
|
|
|
|
+ null);
|
|
|
|
|
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
- return ExpressionObserverBuilder.Build(
|
|
|
|
|
|
|
+ return new ExpressionObserver(
|
|
|
GetParentDataContext(target),
|
|
GetParentDataContext(target),
|
|
|
- path,
|
|
|
|
|
- enableDataValidation,
|
|
|
|
|
- typeResolver: TypeResolver);
|
|
|
|
|
|
|
+ node,
|
|
|
|
|
+ null);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private ExpressionObserver CreateElementObserver(
|
|
private ExpressionObserver CreateElementObserver(
|
|
|
IStyledElement target,
|
|
IStyledElement target,
|
|
|
string elementName,
|
|
string elementName,
|
|
|
- string path,
|
|
|
|
|
- bool enableDataValidation)
|
|
|
|
|
|
|
+ ExpressionNode node)
|
|
|
{
|
|
{
|
|
|
Contract.Requires<ArgumentNullException>(target != null);
|
|
Contract.Requires<ArgumentNullException>(target != null);
|
|
|
-
|
|
|
|
|
- var description = $"#{elementName}.{path}";
|
|
|
|
|
- var result = ExpressionObserverBuilder.Build(
|
|
|
|
|
|
|
+
|
|
|
|
|
+ var result = new ExpressionObserver(
|
|
|
ControlLocator.Track(target, elementName),
|
|
ControlLocator.Track(target, elementName),
|
|
|
- path,
|
|
|
|
|
- enableDataValidation,
|
|
|
|
|
- description,
|
|
|
|
|
- typeResolver: TypeResolver);
|
|
|
|
|
|
|
+ node,
|
|
|
|
|
+ null);
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private ExpressionObserver CreateFindAncestorObserver(
|
|
private ExpressionObserver CreateFindAncestorObserver(
|
|
|
IStyledElement target,
|
|
IStyledElement target,
|
|
|
RelativeSource relativeSource,
|
|
RelativeSource relativeSource,
|
|
|
- string path,
|
|
|
|
|
- bool enableDataValidation)
|
|
|
|
|
|
|
+ ExpressionNode node)
|
|
|
{
|
|
{
|
|
|
Contract.Requires<ArgumentNullException>(target != null);
|
|
Contract.Requires<ArgumentNullException>(target != null);
|
|
|
|
|
|
|
@@ -260,36 +287,32 @@ namespace Avalonia.Data
|
|
|
throw new InvalidOperationException("Invalid tree to traverse.");
|
|
throw new InvalidOperationException("Invalid tree to traverse.");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return ExpressionObserverBuilder.Build(
|
|
|
|
|
|
|
+ return new ExpressionObserver(
|
|
|
controlLocator,
|
|
controlLocator,
|
|
|
- path,
|
|
|
|
|
- enableDataValidation,
|
|
|
|
|
- typeResolver: TypeResolver);
|
|
|
|
|
|
|
+ node,
|
|
|
|
|
+ null);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private ExpressionObserver CreateSourceObserver(
|
|
private ExpressionObserver CreateSourceObserver(
|
|
|
object source,
|
|
object source,
|
|
|
- string path,
|
|
|
|
|
- bool enableDataValidation)
|
|
|
|
|
|
|
+ ExpressionNode node)
|
|
|
{
|
|
{
|
|
|
Contract.Requires<ArgumentNullException>(source != null);
|
|
Contract.Requires<ArgumentNullException>(source != null);
|
|
|
|
|
|
|
|
- return ExpressionObserverBuilder.Build(source, path, enableDataValidation, typeResolver: TypeResolver);
|
|
|
|
|
|
|
+ return new ExpressionObserver(source, node);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private ExpressionObserver CreateTemplatedParentObserver(
|
|
private ExpressionObserver CreateTemplatedParentObserver(
|
|
|
IAvaloniaObject target,
|
|
IAvaloniaObject target,
|
|
|
- string path,
|
|
|
|
|
- bool enableDataValidation)
|
|
|
|
|
|
|
+ ExpressionNode node)
|
|
|
{
|
|
{
|
|
|
Contract.Requires<ArgumentNullException>(target != null);
|
|
Contract.Requires<ArgumentNullException>(target != null);
|
|
|
|
|
|
|
|
- var result = ExpressionObserverBuilder.Build(
|
|
|
|
|
|
|
+ var result = new ExpressionObserver(
|
|
|
() => target.GetValue(StyledElement.TemplatedParentProperty),
|
|
() => target.GetValue(StyledElement.TemplatedParentProperty),
|
|
|
- path,
|
|
|
|
|
|
|
+ node,
|
|
|
new UpdateSignal(target, StyledElement.TemplatedParentProperty),
|
|
new UpdateSignal(target, StyledElement.TemplatedParentProperty),
|
|
|
- enableDataValidation,
|
|
|
|
|
- typeResolver: TypeResolver);
|
|
|
|
|
|
|
+ null);
|
|
|
|
|
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|