Skip to content

Commit

Permalink
Fix ICommand bindings in style setters (#16122)
Browse files Browse the repository at this point in the history
* Add failing test for #16113.

* Convert delegate to ICommand in style setter.

When compiling a binding to e.g. `Button.Command` in a style `Setter`, we were not converting `XamlIlClrMethodPathElementNode` to `XamlIlClrMethodAsCommandPathElementNode` as we were only testing whether the property that the binding was being assigned to is an `ICommand`.

If we detect that we're assigning the binding to a `Setter.Value` then we need to look in the `Setter.Property` to see check whether the property is an `ICommand` too.

Fixes #16113
  • Loading branch information
grokys committed Jun 28, 2024
1 parent 17cc674 commit daaf054
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ class AvaloniaXamlIlWellKnownTypes
public IXamlType TextDecorations { get; }
public IXamlType TextTrimming { get; }
public IXamlType SetterBase { get; }
public IXamlType Setter { get; }
public IXamlType IStyle { get; }
public IXamlType StyleInclude { get; }
public IXamlType ResourceInclude { get; }
Expand Down Expand Up @@ -254,6 +255,7 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
TextDecorations = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorations");
TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming");
SetterBase = cfg.TypeSystem.GetType("Avalonia.Styling.SetterBase");
Setter = cfg.TypeSystem.GetType("Avalonia.Styling.Setter");
IStyle = cfg.TypeSystem.GetType("Avalonia.Styling.IStyle");
StyleInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.StyleInclude");
ResourceInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.ResourceInclude");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ private static XamlIlBindingPathNode TransformForTargetTyping(XamlIlBindingPathN
{
return transformed;
}

var lastElement = transformed.Elements.LastOrDefault();

if (parentNode.Property?.Getter?.ReturnType == context.GetAvaloniaTypes().ICommand && lastElement is XamlIlClrMethodPathElementNode methodPathElement)
if (GetPropertyType(context, parentNode) == context.GetAvaloniaTypes().ICommand && lastElement is XamlIlClrMethodPathElementNode methodPathElement)
{
IXamlMethod executeMethod = methodPathElement.Method;
IXamlMethod canExecuteMethod = executeMethod.DeclaringType.FindMethod(new FindMethodMethodSignature($"Can{executeMethod.Name}", context.Configuration.WellKnownTypes.Boolean, context.Configuration.WellKnownTypes.Object));
Expand All @@ -110,6 +110,29 @@ private static XamlIlBindingPathNode TransformForTargetTyping(XamlIlBindingPathN
return transformed;
}

private static IXamlType GetPropertyType(AstTransformationContext context, XamlPropertyAssignmentNode node)
{
var setterType = context.GetAvaloniaTypes().Setter;

if (node.Property.DeclaringType == setterType && node.Property.Name == "Value")
{
// The property is a Setter.Value property. We need to get the type of the property that the Setter.Value property is setting.
var setter = context.ParentNodes()
.SkipWhile(x => x != node)
.OfType<XamlAstConstructableObjectNode>()
.Take(1)
.FirstOrDefault(x => x.Type.GetClrType() == setterType);
var propertyAssignment = setter?.Children.OfType<XamlPropertyAssignmentNode>()
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "Property");
var property = propertyAssignment?.Values.FirstOrDefault() as IXamlIlAvaloniaPropertyNode;

if (property.AvaloniaPropertyType is { } propertyType)
return propertyType;
}

return node.Property?.Getter?.ReturnType;
}

private static XamlIlBindingPathNode TransformBindingPath(AstTransformationContext context, IXamlLineInfo lineInfo, Func<IXamlType> startTypeResolver, IXamlType selfType, IEnumerable<BindingExpressionGrammar.INode> bindingExpression)
{
List<IXamlIlBindingPathElementNode> transformNodes = new List<IXamlIlBindingPathElementNode>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1928,6 +1928,36 @@ public void Binding_Method_With_Parameter_To_Command_CanExecute_DependsOn()
}
}

[Fact]
public void Binding_Method_To_Command_In_Style_Works()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:MethodAsCommandDataContext'>
<Window.Styles>
<Style Selector='Button'>
<Setter Property='Command' Value='{CompiledBinding Method}'/>
</Style>
</Window.Styles>
<Button Name='button'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.GetControl<Button>("button");
var vm = new MethodAsCommandDataContext();

button.DataContext = vm;
window.ApplyTemplate();

Assert.NotNull(button.Command);
PerformClick(button);
Assert.Equal("Called", vm.Value);
}
}

[Fact]
public void ResolvesDataTypeForAssignBinding()
{
Expand Down

0 comments on commit daaf054

Please sign in to comment.