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>( AvaloniaProperty.RegisterAttached<StyledElement, string>(
"Classes", typeof(ClassBindingManager), ""); "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) public static void SetClasses(StyledElement element, string value)
{ {
_ = element ?? throw new ArgumentNullException(nameof(element)); _ = element ?? throw new ArgumentNullException(nameof(element));
@ -29,6 +33,18 @@ namespace Avalonia
return element.GetValue(ClassesProperty); 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() static ClassBindingManager()
{ {
ClassesProperty.Changed.AddClassHandler<StyledElement, string>(ClassesPropertyChanged); ClassesProperty.Changed.AddClassHandler<StyledElement, string>(ClassesPropertyChanged);
@ -36,13 +52,28 @@ namespace Avalonia
private static void ClassesPropertyChanged(StyledElement sender, AvaloniaPropertyChangedEventArgs<string> e) private static void ClassesPropertyChanged(StyledElement sender, AvaloniaPropertyChangedEventArgs<string> e)
{ {
var boundClasses = GetBoundClasses(sender);
var newValue = e.GetNewValue<string?>() ?? ""; var newValue = e.GetNewValue<string?>() ?? "";
var newValues = newValue.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); 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)) if (currentValues.SequenceEqual(newValues))
return; 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) public static IDisposable BindClasses(StyledElement target, BindingBase source, object anchor)
@ -50,8 +81,15 @@ namespace Avalonia
return target.Bind(ClassesProperty, source); 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) public static IDisposable BindClass(StyledElement target, string className, BindingBase source, object anchor)
{ {
AddBoundClass(target, className);
var prop = GetClassProperty(className); var prop = GetClassProperty(className);
return target.Bind(prop, source); return target.Bind(prop, source);
} }
@ -63,8 +101,8 @@ namespace Avalonia
var prop = AvaloniaProperty.Register<StyledElement, bool>(ClassPropertyPrefix + className); var prop = AvaloniaProperty.Register<StyledElement, bool>(ClassPropertyPrefix + className);
prop.Changed.Subscribe(args => prop.Changed.Subscribe(args =>
{ {
var classes = ((StyledElement)args.Sender).Classes; var sender = (StyledElement)args.Sender;
classes.Set(className, args.NewValue.GetValueOrDefault()); SetClass(sender, className, args.NewValue.GetValueOrDefault());
}); });
return prop; return prop;

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

@ -280,6 +280,26 @@ namespace Avalonia.Controls
NotifyChanged(); 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/> /// <inheritdoc/>
void IPseudoClasses.Add(string name) 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) => public static IDisposable BindClass(this StyledElement target, string className, BindingBase source, object anchor) =>
ClassBindingManager.BindClass(target, className, source, 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) => public static AvaloniaProperty GetClassProperty(string className) =>
ClassBindingManager.GetClassProperty(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) public void Emit(IXamlILEmitter emitter)
{ {
using (var value = emitter.LocalsPool.GetLocal(_types.XamlIlTypes.Boolean)) using (var value = emitter.LocalsPool.GetLocal(_types.XamlIlTypes.Boolean))
{
emitter emitter
.Stloc(value.Local) .Stloc(value.Local)
.EmitCall(_types.StyledElementClassesProperty.Getter!)
.Ldstr(_className) .Ldstr(_className)
.Ldloc(value.Local) .Ldloc(value.Local);
.EmitCall(_types.Classes.GetMethod(new FindMethodMethodSignature("Set", emitter.EmitCall(_types.SetClassMethod);
_types.XamlIlTypes.Void, _types.XamlIlTypes.String, _types.XamlIlTypes.Boolean)));
}
} }
public IXamlType TargetType => _types.StyledElement; 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 emitter
.Stloc(value.Local) .Stloc(value.Local)
.Ldloc(value.Local); .Ldloc(value.Local);
emitter.EmitCall(Types.SetClassesMethod, true); emitter.EmitCall(Types.SetClassesMethod);
} }
} }
class ClassesBindingSetter(AvaloniaXamlIlWellKnownTypes types) class ClassesBindingSetter(AvaloniaXamlIlWellKnownTypes types)
: ClassesSetter(types, types.IBinding) : ClassesSetter(types, types.BindingBase)
{ {
public override void Emit(IXamlILEmitter emitter) 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 ColumnDefinitions { get; }
public IXamlType Classes { get; } public IXamlType Classes { get; }
public IXamlMethod BindClassMethod { get; } public IXamlMethod BindClassMethod { get; }
public IXamlMethod SetClassMethod { get; }
public IXamlMethod BindClassesMethod { get; } public IXamlMethod BindClassesMethod { get; }
public IXamlMethod SetClassesMethod { get; } public IXamlMethod SetClassesMethod { get; }
public IXamlProperty StyledElementClassesProperty { 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)); StyledElement.Properties.First(x => x.Name == "Classes" && x.PropertyType.Equals(Classes));
BindClassMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions") BindClassMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions")
.GetMethod("BindClass", IDisposable, false, StyledElement, .GetMethod("BindClass", IDisposable, false, StyledElement,
cfg.WellKnownTypes.String, cfg.WellKnownTypes.String, BindingBase, cfg.WellKnownTypes.Object);
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") BindClassesMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions")
.GetMethod("BindClasses", IDisposable, false, StyledElement, .GetMethod("BindClasses", IDisposable, false, StyledElement,
BindingBase, cfg.WellKnownTypes.Object); BindingBase, cfg.WellKnownTypes.Object);

Loading…
Cancel
Save