Skip to content

Commit 500233c

Browse files
committed
Fixed Classes="{Binding StringValue}" overwriting Classes.Foo="True" and Classes.Foo="{Binding}" (#18068)
1 parent 1739752 commit 500233c

File tree

5 files changed

+53
-13
lines changed

5 files changed

+53
-13
lines changed

src/Avalonia.Base/ClassBindingManager.cs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ internal static class ClassBindingManager
1717
AvaloniaProperty.RegisterAttached<StyledElement, string>(
1818
"Classes", typeof(ClassBindingManager), "");
1919

20+
public static readonly AttachedProperty<HashSet<string>?> BoundClassesProperty =
21+
AvaloniaProperty.RegisterAttached<StyledElement, HashSet<string>?>(
22+
"BoundClasses", typeof(ClassBindingManager));
23+
2024
public static void SetClasses(StyledElement element, string value)
2125
{
2226
_ = element ?? throw new ArgumentNullException(nameof(element));
@@ -29,29 +33,63 @@ public static string GetClasses(StyledElement element)
2933
return element.GetValue(ClassesProperty);
3034
}
3135

36+
public static void SetBoundClasses(StyledElement element, HashSet<string>? value)
37+
{
38+
_ = element ?? throw new ArgumentNullException(nameof(element));
39+
element.SetValue(BoundClassesProperty, value);
40+
}
41+
42+
public static HashSet<string>? GetBoundClasses(StyledElement element)
43+
{
44+
_ = element ?? throw new ArgumentNullException(nameof(element));
45+
return element.GetValue(BoundClassesProperty);
46+
}
47+
3248
static ClassBindingManager()
3349
{
3450
ClassesProperty.Changed.AddClassHandler<StyledElement, string>(ClassesPropertyChanged);
3551
}
3652

3753
private static void ClassesPropertyChanged(StyledElement sender, AvaloniaPropertyChangedEventArgs<string> e)
3854
{
55+
var boundClasses = GetBoundClasses(sender);
56+
3957
var newValue = e.GetNewValue<string?>() ?? "";
4058
var newValues = newValue.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
41-
var currentValues = sender.Classes.Where(c => !c.StartsWith(":", StringComparison.Ordinal));
59+
var currentValues = sender.Classes
60+
.Where(c => !c.StartsWith(":", StringComparison.Ordinal) && boundClasses?.Contains(c) != true)
61+
.ToList();
4262
if (currentValues.SequenceEqual(newValues))
4363
return;
4464

45-
sender.Classes.Replace(newValues);
65+
sender.Classes.Replace(currentValues, newValues);
66+
}
67+
68+
private static void AddBoundClass(StyledElement target, string className)
69+
{
70+
var boundClasses = GetBoundClasses(target);
71+
if (boundClasses == null)
72+
{
73+
boundClasses = [];
74+
SetBoundClasses(target, boundClasses);
75+
}
76+
boundClasses.Add(className);
4677
}
4778

4879
public static IDisposable BindClasses(StyledElement target, IBinding source, object anchor)
4980
{
5081
return target.Bind(ClassesProperty, source);
5182
}
5283

84+
public static void SetClass(StyledElement target, string className, bool value)
85+
{
86+
AddBoundClass(target, className);
87+
target.Classes.Set(className, value);
88+
}
89+
5390
public static IDisposable BindClass(StyledElement target, string className, IBinding source, object anchor)
5491
{
92+
AddBoundClass(target, className);
5593
var prop = GetClassProperty(className);
5694
return target.Bind(prop, source);
5795
}
@@ -63,8 +101,8 @@ private static AvaloniaProperty RegisterClassProxyProperty(string className)
63101
var prop = AvaloniaProperty.Register<StyledElement, bool>(ClassPropertyPrefix + className);
64102
prop.Changed.Subscribe(args =>
65103
{
66-
var classes = ((StyledElement)args.Sender).Classes;
67-
classes.Set(className, args.NewValue.GetValueOrDefault());
104+
var sender = (StyledElement)args.Sender;
105+
SetClass(sender, className, args.NewValue.GetValueOrDefault());
68106
});
69107

70108
return prop;

src/Avalonia.Base/StyledElementExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ public static void SetClasses(this StyledElement target, string classNames) =>
1515
public static IDisposable BindClass(this StyledElement target, string className, IBinding source, object anchor) =>
1616
ClassBindingManager.BindClass(target, className, source, anchor);
1717

18+
public static void SetClass(this StyledElement target, string className, bool value) =>
19+
ClassBindingManager.SetClass(target, className, value);
20+
1821
public static AvaloniaProperty GetClassProperty(string className) =>
1922
ClassBindingManager.GetClassProperty(className);
2023

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,11 @@ public ClassValueSetter(AvaloniaXamlIlWellKnownTypes types, string className)
4646
public void Emit(IXamlILEmitter emitter)
4747
{
4848
using (var value = emitter.LocalsPool.GetLocal(_types.XamlIlTypes.Boolean))
49-
{
5049
emitter
5150
.Stloc(value.Local)
52-
.EmitCall(_types.StyledElementClassesProperty.Getter!)
5351
.Ldstr(_className)
54-
.Ldloc(value.Local)
55-
.EmitCall(_types.Classes.GetMethod(new FindMethodMethodSignature("Set",
56-
_types.XamlIlTypes.Void, _types.XamlIlTypes.String, _types.XamlIlTypes.Boolean)));
57-
}
52+
.Ldloc(value.Local);
53+
emitter.EmitCall(_types.SetClassMethod);
5854
}
5955

6056
public IXamlType TargetType => _types.StyledElement;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public override void Emit(IXamlILEmitter emitter)
4949
emitter
5050
.Stloc(value.Local)
5151
.Ldloc(value.Local);
52-
emitter.EmitCall(Types.SetClassesMethod, true);
52+
emitter.EmitCall(Types.SetClassesMethod);
5353
}
5454
}
5555

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ sealed class AvaloniaXamlIlWellKnownTypes
101101
public IXamlType ColumnDefinitions { get; }
102102
public IXamlType Classes { get; }
103103
public IXamlMethod BindClassMethod { get; }
104+
public IXamlMethod SetClassMethod { get; }
104105
public IXamlMethod BindClassesMethod { get; }
105106
public IXamlMethod SetClassesMethod { get; }
106107
public IXamlProperty StyledElementClassesProperty { get; }
@@ -300,8 +301,10 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
300301
StyledElement.Properties.First(x => x.Name == "Classes" && x.PropertyType.Equals(Classes));
301302
BindClassMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions")
302303
.GetMethod("BindClass", IDisposable, false, StyledElement,
303-
cfg.WellKnownTypes.String,
304-
IBinding, cfg.WellKnownTypes.Object);
304+
cfg.WellKnownTypes.String, IBinding, cfg.WellKnownTypes.Object);
305+
SetClassMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions")
306+
.GetMethod("SetClass", cfg.WellKnownTypes.Void, false, StyledElement,
307+
cfg.WellKnownTypes.String, cfg.WellKnownTypes.Boolean);
305308
BindClassesMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions")
306309
.GetMethod("BindClasses", IDisposable, false, StyledElement,
307310
IBinding, cfg.WellKnownTypes.Object);

0 commit comments

Comments
 (0)