Browse Source

Fixed Classes="{Binding StringValue}" overwriting Classes.Foo="True" and Classes.Foo="{Binding}" (#18068)

pull/20182/head
Athari 9 months ago
parent
commit
ea4e15eb6e
  1. 46
      src/Avalonia.Base/ClassBindingManager.cs
  2. 20
      src/Avalonia.Base/Controls/Classes.cs
  3. 3
      src/Avalonia.Base/StyledElementExtensions.cs
  4. 8
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlClassesPropertyResolver.cs
  5. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResolveClassesPropertyTransformer.cs
  6. 7
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

46
src/Avalonia.Base/ClassBindingManager.cs

@ -17,6 +17,10 @@ namespace Avalonia
AvaloniaProperty.RegisterAttached<StyledElement, string>(
"Classes", typeof(ClassBindingManager), "");
public static readonly AttachedProperty<HashSet<string>?> BoundClassesProperty =
AvaloniaProperty.RegisterAttached<StyledElement, HashSet<string>?>(
"BoundClasses", typeof(ClassBindingManager));
public static void SetClasses(StyledElement element, string value)
{
_ = element ?? throw new ArgumentNullException(nameof(element));
@ -29,6 +33,18 @@ namespace Avalonia
return element.GetValue(ClassesProperty);
}
public static void SetBoundClasses(StyledElement element, HashSet<string>? value)
{
_ = element ?? throw new ArgumentNullException(nameof(element));
element.SetValue(BoundClassesProperty, value);
}
public static HashSet<string>? GetBoundClasses(StyledElement element)
{
_ = element ?? throw new ArgumentNullException(nameof(element));
return element.GetValue(BoundClassesProperty);
}
static ClassBindingManager()
{
ClassesProperty.Changed.AddClassHandler<StyledElement, string>(ClassesPropertyChanged);
@ -36,13 +52,28 @@ namespace Avalonia
private static void ClassesPropertyChanged(StyledElement sender, AvaloniaPropertyChangedEventArgs<string> e)
{
var boundClasses = GetBoundClasses(sender);
var newValue = e.GetNewValue<string?>() ?? "";
var newValues = newValue.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var currentValues = sender.Classes.Where(c => !c.StartsWith(":", StringComparison.Ordinal));
var currentValues = sender.Classes
.Where(c => !c.StartsWith(":", StringComparison.Ordinal) && boundClasses?.Contains(c) != true)
.ToList();
if (currentValues.SequenceEqual(newValues))
return;
sender.Classes.Replace(newValues);
sender.Classes.Replace(currentValues, newValues);
}
private static void AddBoundClass(StyledElement target, string className)
{
var boundClasses = GetBoundClasses(target);
if (boundClasses == null)
{
boundClasses = [];
SetBoundClasses(target, boundClasses);
}
boundClasses.Add(className);
}
public static IDisposable BindClasses(StyledElement target, BindingBase source, object anchor)
@ -50,8 +81,15 @@ namespace Avalonia
return target.Bind(ClassesProperty, source);
}
public static void SetClass(StyledElement target, string className, bool value)
{
AddBoundClass(target, className);
target.Classes.Set(className, value);
}
public static IDisposable BindClass(StyledElement target, string className, BindingBase source, object anchor)
{
AddBoundClass(target, className);
var prop = GetClassProperty(className);
return target.Bind(prop, source);
}
@ -63,8 +101,8 @@ namespace Avalonia
var prop = AvaloniaProperty.Register<StyledElement, bool>(ClassPropertyPrefix + className);
prop.Changed.Subscribe(args =>
{
var classes = ((StyledElement)args.Sender).Classes;
classes.Set(className, args.NewValue.GetValueOrDefault());
var sender = (StyledElement)args.Sender;
SetClass(sender, className, args.NewValue.GetValueOrDefault());
});
return prop;

20
src/Avalonia.Base/Controls/Classes.cs

@ -280,6 +280,26 @@ namespace Avalonia.Controls
NotifyChanged();
}
internal void Replace(IList<string>? toRemove, IList<string> toAdd)
{
foreach (var name in toAdd)
{
ThrowIfPseudoclass(name, "added");
}
if (toRemove != null)
{
foreach (var name in toRemove)
{
ThrowIfPseudoclass(name, "removed");
}
base.RemoveAll(toRemove);
}
base.AddRange(toAdd);
NotifyChanged();
}
/// <inheritdoc/>
void IPseudoClasses.Add(string name)
{

3
src/Avalonia.Base/StyledElementExtensions.cs

@ -15,6 +15,9 @@ namespace Avalonia
public static IDisposable BindClass(this StyledElement target, string className, BindingBase source, object anchor) =>
ClassBindingManager.BindClass(target, className, source, anchor);
public static void SetClass(this StyledElement target, string className, bool value) =>
ClassBindingManager.SetClass(target, className, value);
public static AvaloniaProperty GetClassProperty(string className) =>
ClassBindingManager.GetClassProperty(className);

8
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlClassesPropertyResolver.cs

@ -46,15 +46,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public void Emit(IXamlILEmitter emitter)
{
using (var value = emitter.LocalsPool.GetLocal(_types.XamlIlTypes.Boolean))
{
emitter
.Stloc(value.Local)
.EmitCall(_types.StyledElementClassesProperty.Getter!)
.Ldstr(_className)
.Ldloc(value.Local)
.EmitCall(_types.Classes.GetMethod(new FindMethodMethodSignature("Set",
_types.XamlIlTypes.Void, _types.XamlIlTypes.String, _types.XamlIlTypes.Boolean)));
}
.Ldloc(value.Local);
emitter.EmitCall(_types.SetClassMethod);
}
public IXamlType TargetType => _types.StyledElement;

4
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlResolveClassesPropertyTransformer.cs

@ -49,12 +49,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
emitter
.Stloc(value.Local)
.Ldloc(value.Local);
emitter.EmitCall(Types.SetClassesMethod, true);
emitter.EmitCall(Types.SetClassesMethod);
}
}
class ClassesBindingSetter(AvaloniaXamlIlWellKnownTypes types)
: ClassesSetter(types, types.IBinding)
: ClassesSetter(types, types.BindingBase)
{
public override void Emit(IXamlILEmitter emitter)
{

7
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -102,6 +102,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType ColumnDefinitions { get; }
public IXamlType Classes { get; }
public IXamlMethod BindClassMethod { get; }
public IXamlMethod SetClassMethod { get; }
public IXamlMethod BindClassesMethod { get; }
public IXamlMethod SetClassesMethod { get; }
public IXamlProperty StyledElementClassesProperty { get; }
@ -302,8 +303,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
StyledElement.Properties.First(x => x.Name == "Classes" && x.PropertyType.Equals(Classes));
BindClassMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions")
.GetMethod("BindClass", IDisposable, false, StyledElement,
cfg.WellKnownTypes.String,
BindingBase, cfg.WellKnownTypes.Object);
cfg.WellKnownTypes.String, BindingBase, cfg.WellKnownTypes.Object);
SetClassMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions")
.GetMethod("SetClass", cfg.WellKnownTypes.Void, false, StyledElement,
cfg.WellKnownTypes.String, cfg.WellKnownTypes.Boolean);
BindClassesMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions")
.GetMethod("BindClasses", IDisposable, false, StyledElement,
BindingBase, cfg.WellKnownTypes.Object);

Loading…
Cancel
Save