@ -1905,31 +1905,172 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
[Theory]
[InlineData("5")]
[InlineData("hello")]
[InlineData(null)]
public void Binding_Method_With_Parameter_To_Command_Works ( string? parameter )
[InlineData("ObjectMethod", "<x:String>hello</x:String>", "Called ObjectMethod with hello")]
[InlineData("StringMethod", "<x:String>hello</x:String>", "Called StringMethod with hello")]
[InlineData("StringMethod", "<x:Null />", "Called StringMethod with ")]
[InlineData("Int32Method", "<x:Int32>42</x:Int32>", "Called Int32Method with 42")]
[InlineData("VirtualObjectMethod", "<x:String>hello</x:String>", "Called VirtualObjectMethod with hello")]
[InlineData("VirtualStringMethod", "<x:String>hello</x:String>", "Called VirtualStringMethod with hello")]
[InlineData("VirtualStringMethod", "<x:Null />", "Called VirtualStringMethod with ")]
[InlineData("VirtualInt32Method", "<x:Int32>42</x:Int32>", "Called VirtualInt32Method with 42")]
[InlineData("MethodWithNewSlot", "<x:Int32>42</x:Int32>", "Called MethodWithNewSlot with 42")]
public void Binding_Method_With_Parameter_To_Command_Uses_Single_Parameter_Overload (
string methodName ,
string xamlParameter ,
string expected )
{
using var app = UnitTestApplication . Start ( TestServices . StyledWindow ) ;
var window = ( Window ) AvaloniaRuntimeXamlLoader . Load (
$ $"" "
< 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 ' >
< Button Name = ' button ' Command = ' { CompiledBinding { { methodName } } } ' >
< Button . CommandParameter >
{ { xamlParameter } }
< / Button . CommandParameter >
< / Button >
< / Window >
"" ");
var button = window . GetControl < Button > ( "button" ) ;
var vm = new MethodAsCommandDataContext ( ) ;
button . DataContext = vm ;
window . ApplyTemplate ( ) ;
Assert . NotNull ( button . Command ) ;
PerformClick ( button ) ;
Assert . Equal ( expected , vm . Value ) ;
}
[Theory]
[InlineData("Int32Method", "<x:String>hello</x:String>", typeof(InvalidCastException))]
[InlineData("Int32Method", "<x:Null />", typeof(NullReferenceException))]
[InlineData("StringMethod", "<x:Int32>42</x:Int32>", typeof(InvalidCastException))]
public void Binding_Method_With_Parameter_To_Command_With_Single_Parameter_Overload_Throws_At_Runtime_If_Mismatched_Types (
string methodName ,
string xamlParameter ,
Type exceptionType )
{
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 ' >
< Button Name = ' button ' Command = ' { { CompiledBinding Method1 } } ' CommandParameter = ' { parameter ? ? "{x:Null}" } ' / >
< / Window > ";
var window = ( Window ) AvaloniaRuntimeXamlLoader . Load ( xaml ) ;
var button = window . GetControl < Button > ( "button" ) ;
var vm = new MethodAsCommandDataContext ( ) ;
using var app = UnitTestApplication . Start ( TestServices . StyledWindow ) ;
button . DataContext = vm ;
window . ApplyTemplate ( ) ;
var window = ( Window ) AvaloniaRuntimeXamlLoader . Load (
$ $"" "
< 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 ' >
< Button Name = ' button ' Command = ' { CompiledBinding { { methodName } } } ' >
< Button . CommandParameter >
{ { xamlParameter } }
< / Button . CommandParameter >
< / Button >
< / Window >
"" ");
var button = window . GetControl < Button > ( "button" ) ;
var vm = new MethodAsCommandDataContext ( ) ;
Assert . NotNull ( button . Command ) ;
PerformClick ( button ) ;
Assert . Equal ( "Called " + parameter , vm . Value ) ;
}
button . DataContext = vm ;
window . ApplyTemplate ( ) ;
Assert . NotNull ( button . Command ) ;
Assert . Throws ( exceptionType , ( ) = > PerformClick ( button ) ) ;
}
[Fact]
public void Binding_Method_With_Parameter_To_Command_Prefers_Object_Overload ( )
{
using var app = UnitTestApplication . Start ( TestServices . StyledWindow ) ;
var window = ( Window ) AvaloniaRuntimeXamlLoader . Load (
"" "
< 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 ' >
< Button Name = ' button ' Command = ' { CompiledBinding MethodWithOverloads } ' CommandParameter = "foo" / >
< / Window >
"" ");
var button = window . GetControl < Button > ( "button" ) ;
var vm = new MethodAsCommandDataContext ( ) ;
button . DataContext = vm ;
window . ApplyTemplate ( ) ;
Assert . NotNull ( button . Command ) ;
PerformClick ( button ) ;
Assert . Equal ( "Called MethodWithOverloads with Object foo" , vm . Value ) ;
}
[Fact]
public void Binding_Method_With_Parameter_To_Command_Fails_With_Multiple_Single_Parameter_Overloads_Without_Object ( )
{
using var app = UnitTestApplication . Start ( TestServices . StyledWindow ) ;
var exception = Assert . ThrowsAny < XmlException > ( ( ) = > ( Window ) AvaloniaRuntimeXamlLoader . Load (
"" "
< 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 ' >
< Button Name = ' button ' Command = ' { CompiledBinding MethodWithOverloads2 } ' CommandParameter = "foo" / >
< / Window >
"" "));
Assert . StartsWith (
"Unable to resolve method of name 'MethodWithOverloads2' on type 'Avalonia.Markup.Xaml.UnitTests.MarkupExtensions.MethodAsCommandDataContext'. " +
"Found 2 overloads accepting one parameter: 'System.Int32', 'System.String'. " +
"Expected either a single overload with one parameter, or an overload accepting System.Object." ,
exception . Message ) ;
}
[Fact]
public void Binding_Method_With_Parameter_To_Command_Uses_Parameterless_Overload_When_No_Overloads_With_Parameter_Exist ( )
{
using var app = UnitTestApplication . Start ( TestServices . StyledWindow ) ;
var window = ( Window ) AvaloniaRuntimeXamlLoader . Load (
"" "
< 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 ' >
< Button Name = ' button ' Command = ' { CompiledBinding MethodWithOverloads3 } ' CommandParameter = "foo" / >
< / Window >
"" ");
var button = window . GetControl < Button > ( "button" ) ;
var vm = new MethodAsCommandDataContext ( ) ;
button . DataContext = vm ;
window . ApplyTemplate ( ) ;
Assert . NotNull ( button . Command ) ;
PerformClick ( button ) ;
Assert . Equal ( "Called MethodWithOverloads3 without parameter" , vm . Value ) ;
}
[Fact]
public void Binding_Method_With_Parameter_To_Command_Fails_Without_Valid_Overloads ( )
{
using var app = UnitTestApplication . Start ( TestServices . StyledWindow ) ;
var exception = Assert . ThrowsAny < XmlException > ( ( ) = > ( Window ) AvaloniaRuntimeXamlLoader . Load (
"" "
< 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 ' >
< Button Name = ' button ' Command = ' { CompiledBinding MethodWithOverloads4 } ' CommandParameter = "foo" / >
< / Window >
"" "));
Assert . StartsWith (
"Unable to resolve method of name 'MethodWithOverloads4' on type 'Avalonia.Markup.Xaml.UnitTests.MarkupExtensions.MethodAsCommandDataContext'. " +
"Found 2 overloads accepting more than one parameter. " +
"Expected a method with zero or one parameter. " ,
exception . Message ) ;
}
[Fact]
@ -2541,15 +2682,57 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
public object CustomDelegateTypeInt ( object i ) = > i ;
}
public class MethodAsCommandDataContext : INotifyPropertyChanged
public class MethodAsCommandDataContextBase
{
public virtual void VirtualObjectMethod ( object? i ) { }
public virtual void VirtualInt32Method ( int i ) { }
public virtual void VirtualStringMethod ( string i ) { }
public void MethodWithNewSlot ( int i ) { }
}
public class MethodAsCommandDataContext : MethodAsCommandDataContextBase , INotifyPropertyChanged
{
public event PropertyChangedEventHandler ? PropertyChanged ;
public string Method ( ) = > Value = "Called" ;
public string Method1 ( ) = > Value = "Called" ;
public string Method1 ( int i ) = > throw new InvalidOperationException ( "Binding to method with typed parameters is not supported" ) ;
public string Method1 ( object i ) = > Value = $"Called {i}" ;
public string Method2 ( int i , int j ) = > Value = $"Called {i},{j}" ;
public void Method ( ) = > Value = "Called" ;
public void ObjectMethod ( object i ) = > Value = $"Called ObjectMethod with {i}" ;
public void Int32Method ( int i ) = > Value = $"Called Int32Method with {i}" ;
public void StringMethod ( string i ) = > Value = $"Called StringMethod with {i}" ;
public void MethodWithOverloads ( ) = > Value = "Called MethodWithOverloads without parameter" ;
public void MethodWithOverloads ( int i ) = > Value = $"Called MethodWithOverloads with Int32 {i}" ;
public void MethodWithOverloads ( string i ) = > Value = $"Called MethodWithOverloads with String {i}" ;
public void MethodWithOverloads ( object i ) = > Value = $"Called MethodWithOverloads with Object {i}" ;
public void MethodWithOverloads2 ( ) = > Value = "Called MethodWithOverloads2 without parameter" ;
public void MethodWithOverloads2 ( int i ) = > Value = $"Called MethodWithOverloads2 with Int32 {i}" ;
public void MethodWithOverloads2 ( string i ) = > Value = $"Called MethodWithOverloads2 with String {i}" ;
public void MethodWithOverloads3 ( ) = > Value = "Called MethodWithOverloads3 without parameter" ;
public void MethodWithOverloads3 ( int a , int b ) = > throw new InvalidOperationException ( "MethodWithOverloads3 should not be called" ) ;
public void MethodWithOverloads3 ( string a , string b ) = > throw new InvalidOperationException ( "MethodWithOverloads3 should not be called" ) ;
public void MethodWithOverloads4 ( int a , int b ) = > throw new InvalidOperationException ( "MethodWithOverloads4 should not be called" ) ;
public void MethodWithOverloads4 ( string a , string b ) = > throw new InvalidOperationException ( "MethodWithOverloads4 should not be called" ) ;
public override void VirtualObjectMethod ( object? i )
= > Value = $"Called VirtualObjectMethod with {i}" ;
public override void VirtualInt32Method ( int i )
= > Value = $"Called VirtualInt32Method with {i}" ;
public override void VirtualStringMethod ( string i )
= > Value = $"Called VirtualStringMethod with {i}" ;
public new void MethodWithNewSlot ( int i )
= > Value = $"Called MethodWithNewSlot with {i}" ;
public string Value { get ; private set ; } = "Not called" ;
private object? _ parameter ;