Browse Source

Support complex types in datatype (#18379)

* Fix XamlTypeExtensionNode not being handled on the x:DataType transformer

* Add testt with complex DataType

* Make vm:MainWindowViewModel+TestItem nested type generic on BindingDemo
release/11.2.6
Maxwell Katz 11 months ago
committed by Julien Lebosquain
parent
commit
4df24bc593
No known key found for this signature in database GPG Key ID: 1833CAD10ACC46FD
  1. 1
      samples/BindingDemo/BindingDemo.csproj
  2. 13
      samples/BindingDemo/MainWindow.xaml
  3. 2
      samples/BindingDemo/MainWindow.xaml.cs
  4. 4
      samples/BindingDemo/TestItemView.xaml
  5. 22
      samples/BindingDemo/ViewModels/MainWindowViewModel.cs
  6. 6
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
  7. 66
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

1
samples/BindingDemo/BindingDemo.csproj

@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>$(AvsCurrentTargetFramework)</TargetFramework> <TargetFramework>$(AvsCurrentTargetFramework)</TargetFramework>
<!-- <AvaloniaXamlIlDebuggerLaunch>true</AvaloniaXamlIlDebuggerLaunch>-->
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />

13
samples/BindingDemo/MainWindow.xaml

@ -3,6 +3,7 @@
x:Class="BindingDemo.MainWindow" x:Class="BindingDemo.MainWindow"
xmlns:vm="using:BindingDemo.ViewModels" xmlns:vm="using:BindingDemo.ViewModels"
xmlns:local="using:BindingDemo" xmlns:local="using:BindingDemo"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
Title="AvaloniaUI Bindings Test" Title="AvaloniaUI Bindings Test"
Width="800" Width="800"
Height="600" Height="600"
@ -29,7 +30,7 @@
</StackPanel> </StackPanel>
<StackPanel Margin="18" Spacing="4" Width="200"> <StackPanel Margin="18" Spacing="4" Width="200">
<TextBlock FontSize="16" Text="Collection Bindings"/> <TextBlock FontSize="16" Text="Collection Bindings"/>
<TextBox Watermark="Items[1].StringValue" UseFloatingWatermark="True" Text="{Binding Path=Items[1].StringValue}"/> <TextBox Watermark="Items[1].Value" UseFloatingWatermark="True" Text="{Binding Path=Items[1].Value}"/>
<Button Command="{Binding ShuffleItems}">Shuffle</Button> <Button Command="{Binding ShuffleItems}">Shuffle</Button>
</StackPanel> </StackPanel>
<StackPanel Margin="18" Spacing="4" Width="200"> <StackPanel Margin="18" Spacing="4" Width="200">
@ -51,9 +52,9 @@
<TextBox Watermark="Value of first TextBox" UseFloatingWatermark="True" <TextBox Watermark="Value of first TextBox" UseFloatingWatermark="True"
Text="{Binding #first.Text, Mode=TwoWay}"/> Text="{Binding #first.Text, Mode=TwoWay}"/>
<TextBox Watermark="Value of SharedItem.StringValue" UseFloatingWatermark="True" <TextBox Watermark="Value of SharedItem.StringValue" UseFloatingWatermark="True"
Text="{Binding StringValue, Source={StaticResource SharedItem}, Mode=TwoWay, DataType=vm:MainWindowViewModel+TestItem}"/> Text="{Binding Value, Source={StaticResource SharedItem}, Mode=TwoWay, DataType={x:Type vm:MainWindowViewModel+TestItem, x:TypeArguments=x:String}}"/>
<TextBox Watermark="Value of SharedItem.StringValue (duplicate)" UseFloatingWatermark="True" <TextBox Watermark="Value of SharedItem.StringValue (duplicate)" UseFloatingWatermark="True"
Text="{Binding StringValue, Source={StaticResource SharedItem}, Mode=TwoWay, DataType=vm:MainWindowViewModel+TestItem}"/> Text="{Binding Value, Source={StaticResource SharedItem}, Mode=TwoWay, DataType={x:Type vm:MainWindowViewModel+TestItem, x:TypeArguments=x:String}}"/>
</StackPanel> </StackPanel>
<StackPanel Margin="18" Spacing="4" Width="200" HorizontalAlignment="Left"> <StackPanel Margin="18" Spacing="4" Width="200" HorizontalAlignment="Left">
<TextBlock FontSize="16" Text="Scheduler"/> <TextBlock FontSize="16" Text="Scheduler"/>
@ -67,8 +68,8 @@
<TabItem Header="ListBox"> <TabItem Header="ListBox">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<StackPanel.DataTemplates> <StackPanel.DataTemplates>
<DataTemplate DataType="vm:MainWindowViewModel+TestItem"> <DataTemplate x:DataType="{x:Type vm:MainWindowViewModel+TestItem, x:TypeArguments=x:String}">
<TextBlock Text="{Binding StringValue}"/> <TextBlock Text="{Binding Value}"/>
</DataTemplate> </DataTemplate>
</StackPanel.DataTemplates> </StackPanel.DataTemplates>
<StackPanel Margin="18" Spacing="4" Width="200"> <StackPanel Margin="18" Spacing="4" Width="200">
@ -81,7 +82,7 @@
</StackPanel> </StackPanel>
<ContentControl Content="{ReflectionBinding Selection.SelectedItems[0]}"> <ContentControl Content="{ReflectionBinding Selection.SelectedItems[0]}">
<ContentControl.DataTemplates> <ContentControl.DataTemplates>
<DataTemplate DataType="vm:MainWindowViewModel+TestItem"> <DataTemplate x:DataType="{x:Type vm:MainWindowViewModel+TestItem, x:TypeArguments=x:String}">
<local:TestItemView></local:TestItemView> <local:TestItemView></local:TestItemView>
</DataTemplate> </DataTemplate>
</ContentControl.DataTemplates> </ContentControl.DataTemplates>

2
samples/BindingDemo/MainWindow.xaml.cs

@ -9,7 +9,7 @@ namespace BindingDemo
{ {
public MainWindow() public MainWindow()
{ {
Resources["SharedItem"] = new MainWindowViewModel.TestItem() { StringValue = "shared" }; Resources["SharedItem"] = new MainWindowViewModel.TestItem<string>() { Value = "shared" };
this.InitializeComponent(); this.InitializeComponent();
this.DataContext = new MainWindowViewModel(); this.DataContext = new MainWindowViewModel();
this.AttachDevTools(); this.AttachDevTools();

4
samples/BindingDemo/TestItemView.xaml

@ -2,9 +2,9 @@
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:viewModels="using:BindingDemo.ViewModels" xmlns:viewModels="using:BindingDemo.ViewModels"
x:Class="BindingDemo.TestItemView" x:Class="BindingDemo.TestItemView"
x:DataType="viewModels:MainWindowViewModel+TestItem"> x:DataType="{x:Type viewModels:MainWindowViewModel+TestItem, x:TypeArguments=x:String}">
<StackPanel> <StackPanel>
<TextBlock Classes="h1" Text="{Binding StringValue}"/> <TextBlock Classes="h1" Text="{Binding Value}"/>
<TextBox Text="{Binding Detail}" AcceptsReturn="True"/> <TextBox Text="{Binding Detail}" AcceptsReturn="True"/>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

22
samples/BindingDemo/ViewModels/MainWindowViewModel.cs

@ -23,14 +23,14 @@ namespace BindingDemo.ViewModels
public MainWindowViewModel() public MainWindowViewModel()
{ {
Items = new ObservableCollection<TestItem>( Items = new ObservableCollection<TestItem<string>>(
Enumerable.Range(0, 20).Select(x => new TestItem Enumerable.Range(0, 20).Select(x => new TestItem<string>
{ {
StringValue = "Item " + x, Value = "Item " + x,
Detail = "Item " + x + " details", Detail = "Item " + x + " details",
})); }));
Selection = new SelectionModel<TestItem> { SingleSelect = false }; Selection = new SelectionModel<TestItem<string>> { SingleSelect = false };
ShuffleItems = MiniCommand.Create(() => ShuffleItems = MiniCommand.Create(() =>
{ {
@ -58,8 +58,8 @@ namespace BindingDemo.ViewModels
.Select(x => DateTimeOffset.Now); .Select(x => DateTimeOffset.Now);
} }
public ObservableCollection<TestItem> Items { get; } public ObservableCollection<TestItem<string>> Items { get; }
public SelectionModel<TestItem> Selection { get; } public SelectionModel<TestItem<string>> Selection { get; }
public MiniCommand ShuffleItems { get; } public MiniCommand ShuffleItems { get; }
public string BooleanString public string BooleanString
@ -117,15 +117,15 @@ namespace BindingDemo.ViewModels
} }
// Nested class, jsut so we can test it in XAML // Nested class, jsut so we can test it in XAML
public class TestItem : ViewModelBase public class TestItem<T> : ViewModelBase
{ {
private string _stringValue = "String Value"; private T _value;
private string _detail; private string _detail;
public string StringValue public T Value
{ {
get { return _stringValue; } get { return _value; }
set { this.RaiseAndSetIfChanged(ref this._stringValue, value); } set { this.RaiseAndSetIfChanged(ref this._value, value); }
} }
public string Detail public string Detail

6
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs

@ -44,7 +44,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{ {
on.Children.RemoveAt(i); on.Children.RemoveAt(i);
i--; i--;
if (directive.Values[0] is XamlAstTextNode text) if (directive.Values[0] is XamlTypeExtensionNode typeNode)
{
directiveDataContextTypeNode = new AvaloniaXamlIlDataContextTypeMetadataNode(on, typeNode.Value.GetClrType());
}
else if (directive.Values[0] is XamlAstTextNode text)
{ {
directiveDataContextTypeNode = new AvaloniaXamlIlDataContextTypeMetadataNode(on, directiveDataContextTypeNode = new AvaloniaXamlIlDataContextTypeMetadataNode(on,
TypeReferenceResolver.ResolveType(context, text.Text, isMarkupExtension: false, text, strict: true).Type); TypeReferenceResolver.ResolveType(context, text.Text, isMarkupExtension: false, text, strict: true).Type);

66
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@ -244,7 +244,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
window.DataContext = dataContext; window.DataContext = dataContext;
Assert.Equal(dataContext.TaskProperty.Result, textBlock.Text); Assert.Equal("foobar", textBlock.Text);
} }
} }
@ -1320,6 +1320,28 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
} }
} }
[Fact]
public void SupportsParentInPathWithTypeAndLevelFilter()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = (Window)AvaloniaRuntimeXamlLoader.Load(@"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Border x:Name='p2'>
<Border x:Name='p1'>
<Button x:Name='p0'>
<TextBlock x:Name='textBlock' Text='{CompiledBinding $parent[Control;1].Name}' />
</Button>
</Border>
</Border>
</Window>");
var textBlock = window.GetControl<TextBlock>("textBlock");
Assert.Equal("p1", textBlock.Text);
}
}
[Fact] [Fact]
public void SupportConverterWithParameter() public void SupportConverterWithParameter()
{ {
@ -1936,7 +1958,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
[Theory] [Theory]
[InlineData(null, "Not called")] [InlineData(null, "Not called")]
[InlineData("A", "Do A")] [InlineData("A", "Do A")]
public void Binding_Method_With_Parameter_To_Command_CanExecute(object commandParameter, string result) public void Binding_Method_With_Parameter_To_Command_CanExecute(object? commandParameter, string result)
{ {
using (UnitTestApplication.Start(TestServices.StyledWindow)) using (UnitTestApplication.Start(TestServices.StyledWindow))
{ {
@ -2213,6 +2235,37 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
} }
} }
[Fact]
public void Resolves_Nested_Generic_DataTypes()
{
using (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='{x:Type local:TestDataContext+NestedGeneric, x:TypeArguments=x:String}'
x:Name='MyWindow'>
<Panel>
<TextBlock Text='{CompiledBinding Value}' Name='textBlock' />
</Panel>
</Window>");
var textBlock = window.GetControl<TextBlock>("textBlock");
var dataContext = new TestDataContext
{
NestedGenericString = new TestDataContext.NestedGeneric<string>
{
Value = "10"
}
};
window.DataContext = dataContext.NestedGenericString;
Assert.Equal(dataContext.NestedGenericString.Value, textBlock.Text);
}
}
static void Throws(string type, Action cb) static void Throws(string type, Action cb)
{ {
try try
@ -2303,7 +2356,9 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
public INonIntegerIndexerDerived NonIntegerIndexerInterfaceProperty => NonIntegerIndexerProperty; public INonIntegerIndexerDerived NonIntegerIndexerInterfaceProperty => NonIntegerIndexerProperty;
string IHasExplicitProperty.ExplicitProperty => "Hello"; public NestedGeneric<string>? NestedGenericString { get; init; }
string IHasExplicitProperty.ExplicitProperty => "Hello";
public string ExplicitProperty => "Bye"; public string ExplicitProperty => "Bye";
@ -2328,6 +2383,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
} }
} }
} }
public class NestedGeneric<T>
{
public T Value { get; set; }
}
} }
public class ListItemCollectionView<T> : List<T> public class ListItemCollectionView<T> : List<T>

Loading…
Cancel
Save