Browse Source

Make DynamicResource use a BindingExpression.

refactor/bindingexpressions-in-valuestore
Steven Kirk 3 years ago
parent
commit
629160a936
  1. 23
      src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs
  2. 2
      src/Avalonia.Base/Styling/Setter.cs
  3. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  4. 149
      src/Markup/Avalonia.Markup.Xaml/Data/DynamicResourceExpression.cs
  5. 36
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

23
src/Avalonia.Base/Data/Core/UntypedBindingExpressionBase.cs

@ -282,7 +282,7 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase,
/// <returns>
/// True if the value could be written to the binding source; otherwise false.
/// </returns>
internal abstract bool WriteValueToSource(object? value);
internal virtual bool WriteValueToSource(object? value) => false;
/// <summary>
/// Converts a value using a value converter, logging a warning if necessary.
@ -368,6 +368,27 @@ public abstract class UntypedBindingExpressionBase : BindingExpressionBase,
}
}
/// <summary>
/// Logs a binding error.
/// </summary>
/// <param name="error">The error message.</param>
/// <param name="level">The log level.</param>
protected void Log(string error, LogEventLevel level = LogEventLevel.Warning)
{
if (!TryGetTarget(out var target))
return;
if (!Logger.TryGet(level, LogArea.Binding, out var log))
return;
log.Log(
target,
"An error occurred binding {Property} to {Expression}: {Message}",
(object?)TargetProperty ?? "(unknown)",
Description,
error);
}
/// <summary>
/// Logs a binding error.
/// </summary>

2
src/Avalonia.Base/Styling/Setter.cs

@ -118,7 +118,7 @@ namespace Avalonia.Styling
if (mode == BindingMode.OneWay || mode == BindingMode.TwoWay)
{
if (i.Source is BindingExpression expression)
if (i.Expression is { } expression)
{
expression.Attach(target.GetValueStore(), target, Property, instance.Priority);
return expression;

1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -14,6 +14,7 @@
<Compile Include="Converters\ColorToBrushConverter.cs" />
<Compile Include="Converters\FontFamilyTypeConverter.cs" />
<Compile Include="Converters\TimeSpanTypeConverter.cs" />
<Compile Include="Data\DynamicResourceExpression.cs" />
<Compile Include="Extensions.cs" />
<Compile Include="MarkupExtension.cs" />
<Compile Include="MarkupExtensions\CompiledBindingExtension.cs" />

149
src/Markup/Avalonia.Markup.Xaml/Data/DynamicResourceExpression.cs

@ -0,0 +1,149 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using Avalonia.Data.Core;
using Avalonia.Logging;
using Avalonia.Markup.Xaml.Converters;
using Avalonia.Media;
using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.MarkupExtensions
{
internal class DynamicResourceExpression : UntypedBindingExpressionBase
{
private readonly object _resourceKey;
private readonly object? _anchor;
private IResourceHost? _host;
private IResourceProvider? _provider;
private bool _overrideThemeVariant;
private bool _targetTypeIsBrush;
private ThemeVariant? _themeVariant;
public DynamicResourceExpression(
object resourceKey,
object? anchor,
ThemeVariant? themeVariant)
{
_resourceKey = resourceKey;
_anchor = anchor;
_themeVariant = themeVariant;
}
public override string Description => $"DynamicResource {_resourceKey}";
protected override void StartCore()
{
if (!TryGetResourceHost(out _host))
{
// The target is not an IResourceHost, so we need to find one from the anchor.
if (_anchor is IResourceProvider provider)
{
_provider = provider;
_host = provider.Owner;
_overrideThemeVariant = _themeVariant is not null;
}
}
// If we wouldn't find a host or provider then log an error: we can't do anything.
if (_host is null && _provider is null)
{
Log("Unable to find IResourceHost or IResourceProvider from which to lookup " +
$"DynamicResource {_resourceKey}.", LogEventLevel.Error);
return;
}
// Hook up events.
if (_provider is not null)
_provider.OwnerChanged += OnResourceProviderOwnerChanged;
Subscribe(_host);
// And publish the initial value.
_targetTypeIsBrush = TargetType == typeof(IBrush);
PublishValue();
}
protected override void StopCore()
{
if (_provider is not null)
_provider.OwnerChanged -= OnResourceProviderOwnerChanged;
Unsubscribe(_host);
_host = null;
_provider = null;
}
private void OnResourceProviderOwnerChanged(object? sender, EventArgs e)
{
Unsubscribe(_host);
_host = _provider?.Owner;
Subscribe(_host);
PublishValue();
}
private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e) => PublishValue();
private void ActualThemeVariantChanged(object? sender, EventArgs e)
{
_themeVariant = ((IThemeVariantHost)sender!).ActualThemeVariant;
PublishValue();
}
private void PublishValue()
{
if (_host is not null)
{
var theme = _themeVariant;
var value = _host.FindResource(theme, _resourceKey) ?? AvaloniaProperty.UnsetValue;
var convertedValue = _targetTypeIsBrush ?
ColorToBrushConverter.Convert(value, typeof(IBrush)) :
value;
PublishValue(convertedValue);
}
else
{
PublishValue(AvaloniaProperty.UnsetValue);
}
}
private bool TryGetResourceHost([NotNullWhen(true)] out IResourceHost? host)
{
if (TryGetTarget(out var target) && target is IResourceHost targetHost)
{
host = targetHost;
return true;
}
if (_anchor is IResourceHost anchorHost)
{
host = anchorHost;
return host is not null;
}
host = null;
return false;
}
private void Subscribe(IResourceHost? host)
{
if (host is not null)
{
host.ResourcesChanged += ResourcesChanged;
if (!_overrideThemeVariant && _host is IThemeVariantHost themeVariantHost)
{
_themeVariant = themeVariantHost.ActualThemeVariant;
themeVariantHost.ActualThemeVariantChanged += ActualThemeVariantChanged;
}
}
}
private void Unsubscribe(IResourceHost? host)
{
if (host is not null)
{
host.ResourcesChanged -= ResourcesChanged;
if (!_overrideThemeVariant && _host is IThemeVariantHost themeVariantHost)
themeVariantHost.ActualThemeVariantChanged -= ActualThemeVariantChanged;
}
}
}
}

36
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@ -1,6 +1,9 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Logging;
using Avalonia.Markup.Xaml.Converters;
using Avalonia.Media;
using Avalonia.Styling;
@ -11,7 +14,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
{
private object? _anchor;
private BindingPriority _priority;
private ThemeVariant? _currentThemeVariant;
private ThemeVariant? _themeVariant;
public DynamicResourceExtension()
{
@ -38,7 +41,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
(object?)serviceProvider.GetFirstParent<IResourceHost>();
}
_currentThemeVariant = StaticResourceExtension.GetDictionaryVariant(serviceProvider);
_themeVariant = StaticResourceExtension.GetDictionaryVariant(serviceProvider);
return this;
}
@ -50,34 +53,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
bool enableDataValidation)
{
if (ResourceKey is null)
{
return null;
}
var control = target as IResourceHost ?? _anchor as IResourceHost;
if (control != null)
{
var source = control.GetResourceObservable(ResourceKey, GetConverter(targetProperty));
return InstancedBinding.OneWay(source, _priority);
}
else if (_anchor is IResourceProvider resourceProvider)
{
var source = resourceProvider.GetResourceObservable(ResourceKey, _currentThemeVariant, GetConverter(targetProperty));
return InstancedBinding.OneWay(source, _priority);
}
return null;
}
private static Func<object?, object?>? GetConverter(AvaloniaProperty? targetProperty)
{
if (targetProperty?.PropertyType == typeof(IBrush))
{
return x => ColorToBrushConverter.Convert(x, typeof(IBrush));
}
return null;
var expression = new DynamicResourceExpression(ResourceKey, _anchor, _themeVariant);
return new InstancedBinding(target, expression, BindingMode.OneWay, _priority);
}
}
}

Loading…
Cancel
Save