diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index 731cb97161..5881efce1e 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -78,7 +78,7 @@ namespace Avalonia private static readonly ControlTheme s_invalidTheme = new ControlTheme(); private int _initCount; private string? _name; - private readonly Classes _classes = new Classes(); + private Classes? _classes; private ILogicalRoot? _logicalRoot; private IAvaloniaList? _logicalChildren; private IResourceDictionary? _resources; @@ -183,21 +183,7 @@ namespace Avalonia /// collection. /// /// - public Classes Classes - { - get - { - return _classes; - } - - set - { - if (_classes != value) - { - _classes.Replace(value); - } - } - } + public Classes Classes => _classes ??= new(); /// /// Gets or sets the control's data context. diff --git a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs index 9774b603e8..69b0ffe9a6 100644 --- a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs +++ b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs @@ -454,7 +454,7 @@ namespace Avalonia.Controls.Primitives children.Add(new ListBoxItem { Height = ItemHeight, - Classes = new Classes($"{PanelType}Item"), + Classes = { $"{PanelType}Item" }, VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Center, Focusable = false }); diff --git a/src/Avalonia.Controls/Flyouts/Flyout.cs b/src/Avalonia.Controls/Flyouts/Flyout.cs index cebcbb6562..8ec5cfa50a 100644 --- a/src/Avalonia.Controls/Flyouts/Flyout.cs +++ b/src/Avalonia.Controls/Flyouts/Flyout.cs @@ -18,17 +18,7 @@ namespace Avalonia.Controls /// /// Gets the Classes collection to apply to the FlyoutPresenter this Flyout is hosting /// - public Classes FlyoutPresenterClasses - { - get => _classes ??= new Classes(); - set - { - if (_classes is null) - _classes = value; - else if (_classes != value) - _classes.Replace(value); - } - } + public Classes FlyoutPresenterClasses => _classes ??= new Classes(); /// /// Defines the property. diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 197815f9a0..5ca2b09eba 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -43,7 +43,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions new AvaloniaXamlIlTransformSyntheticCompiledBindingMembers()); InsertAfter( new AvaloniaXamlIlAvaloniaPropertyResolver(), - new AvaloniaXamlIlReorderClassesPropertiesTransformer() + new AvaloniaXamlIlReorderClassesPropertiesTransformer(), + new AvaloniaXamlIlClassesTransformer() ); InsertBefore( diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index 4068caac21..d8524cfd88 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -221,15 +221,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions return ConvertDefinitionList(node, text, types, types.RowDefinitions, types.RowDefinition, "row definitions", out result); } - if (type.Equals(types.Classes)) - { - var classes = text.Split(' '); - var classNodes = classes.Select(c => new XamlAstTextNode(node, c, type: types.XamlIlTypes.String)).ToArray(); - - result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, types.Classes, types.XamlIlTypes.String, classNodes); - return true; - } - if (types.IBrush.IsAssignableFrom(type)) { if (Color.TryParse(text, out Color color)) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXAmlIlClassesTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXAmlIlClassesTransformer.cs new file mode 100644 index 0000000000..ed42247d29 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXAmlIlClassesTransformer.cs @@ -0,0 +1,46 @@ +using System.Linq; +using XamlX.Ast; +using XamlX.Transform; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers +{ + /// + /// Converts an attribute syntax property value assignment to a collection syntax property + /// assignment. + /// + /// + /// Converts the property assignment `Classes="foo bar"` to: + /// + /// + /// + /// foo + /// bar + /// + /// + /// + class AvaloniaXamlIlClassesTransformer : IXamlAstTransformer + { + public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) + { + var types = context.GetAvaloniaTypes(); + + if (node is XamlAstXamlPropertyValueNode propertyValue && + propertyValue.IsAttributeSyntax && + propertyValue.Property is XamlAstClrProperty property && + property.Getter?.ReturnType.Equals(types.Classes) == true && + propertyValue.Values.Count == 1 && + propertyValue.Values[0] is XamlAstTextNode value) + { + var classes = value.Text.Split(' '); + var stringType = context.Configuration.WellKnownTypes.String; + return new XamlAstXamlPropertyValueNode( + node, + property, + classes.Select(x => new XamlAstTextNode(node, x, type: stringType)), + false); + } + + return node; + } + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 25c0516744..5e30198d00 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -1,24 +1,20 @@ -using System; +using System.Collections; +using System.ComponentModel; +using System.Linq; +using System.Xml; using Avalonia.Collections; using Avalonia.Controls; +using Avalonia.Controls.Documents; using Avalonia.Controls.Presenters; using Avalonia.Data; using Avalonia.Data.Converters; -using Avalonia.Markup.Data; -using Avalonia.Markup.Xaml.Styling; using Avalonia.Markup.Xaml.Templates; using Avalonia.Media; using Avalonia.Media.Immutable; +using Avalonia.Metadata; using Avalonia.Styling; using Avalonia.UnitTests; -using System.Collections; -using System.ComponentModel; -using System.Linq; -using System.Xml; using Xunit; -using Avalonia.Controls.Documents; -using Avalonia.Metadata; -using Avalonia.Themes.Simple; namespace Avalonia.Markup.Xaml.UnitTests.Xaml { @@ -920,6 +916,22 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal(new[] { "foo", "bar" }, target.Classes); } + [Fact] + public void Can_Specify_Button_Classes_Longform() + { + var xaml = @" +"; + var target = (Button)AvaloniaRuntimeXamlLoader.Load(xaml); + + Assert.Equal(new[] { "foo", "bar" }, target.Classes); + } + [Fact] public void Can_Specify_Flyout_FlyoutPresenterClasses() {