Skip to content

Commit 1739752

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

File tree

6 files changed

+129
-5
lines changed

6 files changed

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

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)