Skip to content

Commit abfbace

Browse files
authored
Merge pull request #22006 from unoplatform/copilot/mark-uno-foundation-types-bindable
feat: Mark Windows.Foundation value types as bindable for NAOT trimmer support
2 parents 61b36b0 + 9e1b33c commit abfbace

File tree

10 files changed

+168
-1
lines changed

10 files changed

+168
-1
lines changed

src/SourceGenerators/Uno.UI.SourceGenerators/BindableTypeProviders/BindableTypeProvidersGenerationTask.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,10 +364,15 @@ where field.IsStatic
364364
continue;
365365
}
366366

367+
// For value types (structs), we cannot generate setters because modifying an unboxed value type
368+
// creates a temporary copy that is immediately discarded. Only generate getters for structs.
369+
var isValueType = ownerType.TypeKind == TypeKind.Struct;
370+
367371
if (
368372
property.SetMethod != null
369373
&& !property.SetMethod.IsInitOnly
370374
&& property.SetMethod.IsLocallyPublic(_currentModule!)
375+
&& !isValueType
371376
)
372377
{
373378
writer.AppendLineIndented($@"bindableType.AddProperty(""{propertyName}"", typeof({propertyTypeName}), Get{propertyName}, Set{propertyName});");

src/SourceGenerators/Uno.UI.SourceGenerators/Uno.UI.SourceGenerators.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
</ItemGroup>
2424

2525
<ItemGroup>
26+
<Compile Include="..\..\Uno.Foundation\Internals\BindableAttribute.cs">
27+
<Link>XamlGenerator\XamlPathParser\BindableAttribute.cs</Link>
28+
</Compile>
2629
<Compile Include="..\..\Uno.Foundation\Point.cs">
2730
<Link>XamlGenerator\XamlPathParser\Point.cs</Link>
2831
</Compile>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#nullable enable
2+
3+
using System;
4+
5+
namespace Uno.Foundation.Internals
6+
{
7+
/// <summary>
8+
/// Marks a type as bindable for the NAOT trimmer.
9+
/// This attribute is used by the BindableTypeProvidersSourceGenerator to identify types
10+
/// that should have binding metadata generated, preventing them from being trimmed.
11+
/// </summary>
12+
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
13+
internal sealed class BindableAttribute : Attribute
14+
{
15+
}
16+
}

src/Uno.Foundation/Point.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
namespace Windows.Foundation;
1212

1313
[DebuggerDisplay("{DebugDisplay,nq}")]
14+
[Uno.Foundation.Internals.Bindable]
1415
public partial struct Point
1516
{
1617
// These are public in WinUI (with the underscore!), but we don't want to expose it for now at least.

src/Uno.Foundation/Rect.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
namespace Windows.Foundation;
1010

1111
[DebuggerDisplay("[Rect {Size}@{Location}]")]
12+
[Uno.Foundation.Internals.Bindable]
1213
public partial struct Rect
1314
{
1415
// These are public in WinUI (with the underscore!), but we don't want to expose it for now at least.

src/Uno.Foundation/Size.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace Windows.Foundation;
99

1010
[DebuggerDisplay("{DebugDisplay,nq}")]
1111
[TypeConverter(typeof(SizeConverter))]
12+
[Uno.Foundation.Internals.Bindable]
1213
public partial struct Size
1314
{
1415
// These are public in WinUI (with the underscore!), but we don't want to expose it for now at least.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Page
2+
x:Class="Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Data.BindableFoundationStructsTestPage"
3+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5+
xmlns:local="using:Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Data"
6+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
7+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
8+
mc:Ignorable="d"
9+
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
10+
11+
<Grid>
12+
<StackPanel>
13+
<!-- Test binding to Rect.Width property -->
14+
<TextBlock Tag="{Binding TestRect.Width}" x:FieldModifier="public" x:Name="RectTextBlock" />
15+
16+
<!-- Test binding to Size.Height property -->
17+
<TextBlock Tag="{Binding TestSize.Height}" x:FieldModifier="public" x:Name="SizeTextBlock" />
18+
19+
<!-- Test binding to Point.X property -->
20+
<TextBlock Tag="{Binding TestPoint.X}" x:FieldModifier="public" x:Name="PointTextBlock" />
21+
</StackPanel>
22+
</Grid>
23+
</Page>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#nullable enable
2+
3+
using System.ComponentModel;
4+
using Microsoft.UI.Xaml.Controls;
5+
using Windows.Foundation;
6+
7+
namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Data;
8+
9+
public sealed partial class BindableFoundationStructsTestPage : Page, INotifyPropertyChanged
10+
{
11+
private Rect _testRect;
12+
private Size _testSize;
13+
private Point _testPoint;
14+
15+
public BindableFoundationStructsTestPage()
16+
{
17+
this.InitializeComponent();
18+
this.DataContext = this;
19+
}
20+
21+
public event PropertyChangedEventHandler? PropertyChanged;
22+
23+
public Rect TestRect
24+
{
25+
get => _testRect;
26+
set
27+
{
28+
if (_testRect != value)
29+
{
30+
_testRect = value;
31+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TestRect)));
32+
}
33+
}
34+
}
35+
36+
public Size TestSize
37+
{
38+
get => _testSize;
39+
set
40+
{
41+
if (_testSize != value)
42+
{
43+
_testSize = value;
44+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TestSize)));
45+
}
46+
}
47+
}
48+
49+
public Point TestPoint
50+
{
51+
get => _testPoint;
52+
set
53+
{
54+
if (_testPoint != value)
55+
{
56+
_testPoint = value;
57+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TestPoint)));
58+
}
59+
}
60+
}
61+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using Microsoft.VisualStudio.TestTools.UnitTesting;
2+
using Windows.Foundation;
3+
4+
namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Data;
5+
6+
[TestClass]
7+
public class Given_BindableFoundationStructs
8+
{
9+
[TestMethod]
10+
[RunsOnUIThread]
11+
public void When_Binding_To_Rect_Property()
12+
{
13+
var page = new BindableFoundationStructsTestPage();
14+
var rectTextBlock = page.RectTextBlock;
15+
16+
// Verify binding to Rect.Width works
17+
page.TestRect = new Rect(10, 20, 100, 200);
18+
Assert.AreEqual(100.0, rectTextBlock.Tag);
19+
20+
// Verify binding updates when property changes
21+
page.TestRect = new Rect(0, 0, 250, 300);
22+
Assert.AreEqual(250.0, rectTextBlock.Tag);
23+
}
24+
25+
[TestMethod]
26+
[RunsOnUIThread]
27+
public void When_Binding_To_Size_Property()
28+
{
29+
var page = new BindableFoundationStructsTestPage();
30+
var sizeTextBlock = page.SizeTextBlock;
31+
32+
// Verify binding to Size.Height works
33+
page.TestSize = new Size(100, 200);
34+
Assert.AreEqual(200.0, sizeTextBlock.Tag);
35+
36+
// Verify binding updates when property changes
37+
page.TestSize = new Size(50, 150);
38+
Assert.AreEqual(150.0, sizeTextBlock.Tag);
39+
}
40+
41+
[TestMethod]
42+
[RunsOnUIThread]
43+
public void When_Binding_To_Point_Property()
44+
{
45+
var page = new BindableFoundationStructsTestPage();
46+
var pointTextBlock = page.PointTextBlock;
47+
48+
// Verify binding to Point.X works
49+
page.TestPoint = new Point(10, 20);
50+
Assert.AreEqual(10.0, pointTextBlock.Tag);
51+
52+
// Verify binding updates when property changes
53+
page.TestPoint = new Point(50, 60);
54+
Assert.AreEqual(50.0, pointTextBlock.Tag);
55+
}
56+
}

src/Uno.UI/DataBinding/BindingPropertyHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1358,7 +1358,7 @@ private static void UnsetValueUnsetter(object unused) { }
13581358
/// </summary>
13591359
/// <remarks>This method needs to be aligned with the symbols query in BindableTypeProvidersSourceGenerator.</remarks>
13601360
private static bool IsValidMetadataProviderType(Type type)
1361-
=> type.IsPublic && type.IsClass;
1361+
=> type.IsPublic && (type.IsClass || type.IsValueType);
13621362
}
13631363
}
13641364
#endif

0 commit comments

Comments
 (0)