Skip to content

Commit 2935419

Browse files
committed
Added support for Classes="{Binding StringValue}" (#18068)
1 parent 5198744 commit 2935419

File tree

6 files changed

+134
-5
lines changed

6 files changed

+134
-5
lines changed

src/Avalonia.Base/ClassBindingManager.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics.CodeAnalysis;
4+
using System.Linq;
45
using Avalonia.Data;
56
using Avalonia.Reactive;
67

@@ -12,7 +13,44 @@ internal static class ClassBindingManager
1213
private static readonly Dictionary<string, AvaloniaProperty> s_RegisteredProperties =
1314
new Dictionary<string, AvaloniaProperty>();
1415

15-
public static IDisposable Bind(StyledElement target, string className, IBinding source, object anchor)
16+
public static readonly AttachedProperty<string> ClassesProperty =
17+
AvaloniaProperty.RegisterAttached<StyledElement, string>(
18+
"Classes", typeof(ClassBindingManager), "");
19+
20+
public static void SetClasses(StyledElement element, string value)
21+
{
22+
_ = element ?? throw new ArgumentNullException(nameof(element));
23+
element.SetValue(ClassesProperty, value);
24+
}
25+
26+
public static string GetClasses(StyledElement element)
27+
{
28+
_ = element ?? throw new ArgumentNullException(nameof(element));
29+
return element.GetValue(ClassesProperty);
30+
}
31+
32+
static ClassBindingManager()
33+
{
34+
ClassesProperty.Changed.AddClassHandler<StyledElement, string>(ClassesPropertyChanged);
35+
}
36+
37+
private static void ClassesPropertyChanged(StyledElement sender, AvaloniaPropertyChangedEventArgs<string> e)
38+
{
39+
var newValue = e.GetNewValue<string?>() ?? "";
40+
var newValues = newValue.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
41+
var currentValues = sender.Classes.Where(c => !c.StartsWith(":", StringComparison.Ordinal));
42+
if (currentValues.SequenceEqual(newValues))
43+
return;
44+
45+
sender.Classes.Replace(newValues);
46+
}
47+
48+
public static IDisposable BindClasses(StyledElement target, IBinding source, object anchor)
49+
{
50+
return target.Bind(ClassesProperty, source);
51+
}
52+
53+
public static IDisposable BindClass(StyledElement target, string className, IBinding source, object anchor)
1654
{
1755
var prop = GetClassProperty(className);
1856
return target.Bind(prop, source);

src/Avalonia.Base/StyledElementExtensions.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@ namespace Avalonia
66
{
77
public static class StyledElementExtensions
88
{
9+
public static IDisposable BindClasses(this StyledElement target, IBinding source, object anchor) =>
10+
ClassBindingManager.BindClasses(target, source, anchor);
11+
12+
public static void SetClasses(this StyledElement target, string classNames) =>
13+
ClassBindingManager.SetClasses(target, classNames);
14+
915
public static IDisposable BindClass(this StyledElement target, string className, IBinding source, object anchor) =>
10-
ClassBindingManager.Bind(target, className, source, anchor);
16+
ClassBindingManager.BindClass(target, className, source, anchor);
1117

1218
public static AvaloniaProperty GetClassProperty(string className) =>
1319
ClassBindingManager.GetClassProperty(className);

src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ void InsertBeforeMany(Type[] types, params IXamlAstTransformer[] t)
4545
// Targeted
4646
InsertBefore<PropertyReferenceResolver>(
4747
new AvaloniaXamlIlResolveClassesPropertiesTransformer(),
48+
new AvaloniaXamlIlResolveClassesPropertyTransformer(),
4849
new AvaloniaXamlIlTransformInstanceAttachedProperties(),
4950
new AvaloniaXamlIlTransformSyntheticCompiledBindingMembers());
5051
InsertAfter<PropertyReferenceResolver>(

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public void Emit(IXamlILEmitter emitter)
8686
.Ldloc(bloc.Local)
8787
// TODO: provide anchor?
8888
.Ldnull();
89-
emitter.EmitCall(_types.ClassesBindMethod, true);
89+
emitter.EmitCall(_types.BindClassMethod, true);
9090
}
9191

9292
public IXamlType TargetType => _types.StyledElement;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using XamlX.Ast;
4+
using XamlX.Emit;
5+
using XamlX.IL;
6+
using XamlX.Transform;
7+
using XamlX.TypeSystem;
8+
9+
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
10+
{
11+
class AvaloniaXamlIlResolveClassesPropertyTransformer : IXamlAstTransformer
12+
{
13+
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
14+
{
15+
var types = context.GetAvaloniaTypes();
16+
if (node is XamlAstNamePropertyReference prop &&
17+
prop.Name == "Classes" &&
18+
prop.TargetType is XamlAstClrTypeReference targetRef &&
19+
prop.DeclaringType is XamlAstClrTypeReference declaringRef &&
20+
types.StyledElement.IsAssignableFrom(targetRef.Type) &&
21+
types.StyledElement.IsAssignableFrom(declaringRef.Type)
22+
)
23+
{
24+
return new XamlAstClrProperty(node, prop.Name, types.StyledElement, null)
25+
{
26+
Setters = { new ClassesValueSetter(types), new ClassesBindingSetter(types) }
27+
};
28+
}
29+
return node;
30+
}
31+
32+
abstract class ClassesSetter : IXamlEmitablePropertySetter<IXamlILEmitter>
33+
{
34+
protected readonly AvaloniaXamlIlWellKnownTypes _types;
35+
36+
protected ClassesSetter(AvaloniaXamlIlWellKnownTypes types, IXamlType parameter)
37+
{
38+
_types = types;
39+
Parameters = [parameter];
40+
}
41+
42+
public abstract void Emit(IXamlILEmitter emitter);
43+
44+
public IXamlType TargetType => _types.StyledElement;
45+
public PropertySetterBinderParameters BinderParameters { get; } = new() { AllowXNull = false };
46+
public IReadOnlyList<IXamlType> Parameters { get; }
47+
public IReadOnlyList<IXamlCustomAttribute> CustomAttributes => Array.Empty<IXamlCustomAttribute>();
48+
}
49+
50+
class ClassesValueSetter(AvaloniaXamlIlWellKnownTypes types) : ClassesSetter(types, types.XamlIlTypes.String)
51+
{
52+
public override void Emit(IXamlILEmitter emitter)
53+
{
54+
using (var value = emitter.LocalsPool.GetLocal(Parameters[0]))
55+
emitter
56+
.Stloc(value.Local)
57+
.Ldloc(value.Local);
58+
emitter.EmitCall(_types.SetClassesMethod, true);
59+
}
60+
}
61+
62+
class ClassesBindingSetter(AvaloniaXamlIlWellKnownTypes types) : ClassesSetter(types, types.IBinding)
63+
{
64+
public override void Emit(IXamlILEmitter emitter)
65+
{
66+
using (var value = emitter.LocalsPool.GetLocal(Parameters[0]))
67+
emitter
68+
.Stloc(value.Local)
69+
.Ldloc(value.Local)
70+
// TODO: provide anchor?
71+
.Ldnull();
72+
emitter.EmitCall(_types.BindClassesMethod, true);
73+
}
74+
}
75+
}
76+
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,9 @@ sealed class AvaloniaXamlIlWellKnownTypes
100100
public IXamlType ColumnDefinition { get; }
101101
public IXamlType ColumnDefinitions { get; }
102102
public IXamlType Classes { get; }
103-
public IXamlMethod ClassesBindMethod { get; }
103+
public IXamlMethod BindClassMethod { get; }
104+
public IXamlMethod BindClassesMethod { get; }
105+
public IXamlMethod SetClassesMethod { get; }
104106
public IXamlProperty StyledElementClassesProperty { get; }
105107
public IXamlType IBrush { get; }
106108
public IXamlType ImmutableSolidColorBrush { get; }
@@ -296,10 +298,16 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
296298
Classes = cfg.TypeSystem.GetType("Avalonia.Controls.Classes");
297299
StyledElementClassesProperty =
298300
StyledElement.Properties.First(x => x.Name == "Classes" && x.PropertyType.Equals(Classes));
299-
ClassesBindMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions")
301+
BindClassMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions")
300302
.GetMethod("BindClass", IDisposable, false, StyledElement,
301303
cfg.WellKnownTypes.String,
302304
IBinding, cfg.WellKnownTypes.Object);
305+
BindClassesMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions")
306+
.GetMethod("BindClasses", IDisposable, false, StyledElement,
307+
IBinding, cfg.WellKnownTypes.Object);
308+
SetClassesMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions")
309+
.GetMethod("SetClasses", cfg.WellKnownTypes.Void, false, StyledElement,
310+
cfg.WellKnownTypes.String);
303311

304312
IBrush = cfg.TypeSystem.GetType("Avalonia.Media.IBrush");
305313
ImmutableSolidColorBrush = cfg.TypeSystem.GetType("Avalonia.Media.Immutable.ImmutableSolidColorBrush");

0 commit comments

Comments
 (0)