Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,28 @@
before any bootstrapper has run.
-->
<PropertyGroup>
<WindowsAppSDKVersion>2.1.3</WindowsAppSDKVersion>
<!--
BLOCKED / PLACEHOLDER VERSIONS — see the pull request description.

This branch consumes the code-based DataTemplate constructor
(`new DataTemplate(DataTemplateElementFactory)`), which at time of writing
is still gated behind `[feature(Feature_ExperimentalApi)]` in
microsoft-ui-xaml and has not shipped in a public Windows App SDK release.
The versions below are the internal experimental build the change was
validated against; they do NOT resolve from public feeds, so CI cannot
restore this branch as-is.

When the API ships publicly, replace both values with the released
versions and delete this comment — no other change should be required.
-->
<WindowsAppSDKVersion>3.0.0-dev.experimental11</WindowsAppSDKVersion>
<!--
Windows App SDK 2.0 split the monolithic Microsoft.WindowsAppSDK metapackage
into independently-versioned sub-packages. Framework-dependent projects
reference only Microsoft.WindowsAppSDK.WinUI (see the injection rule in
Directory.Build.targets); the WinUI sub-package tracks its own version, which
currently trails the aggregate metapackage version (2.1.0 vs 2.1.3).
Directory.Build.targets); the WinUI sub-package tracks its own version.
-->
<WindowsAppSDKWinUIVersion>2.1.0</WindowsAppSDKWinUIVersion>
<WindowsAppSDKWinUIVersion>3.0.0-experimental.260818.21</WindowsAppSDKWinUIVersion>
<Win2DVersion>1.4.0</Win2DVersion>
<WindowsAppSDKSelfContained>false</WindowsAppSDKSelfContained>

Expand Down
1 change: 1 addition & 0 deletions docs/aot-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ These subsystems compile cleanly with `IsAotCompatible=true` (the warnings are s
| **DataGrid `AutoColumns<T>`** | Reflects over `T`'s public properties via `TypeRegistry`. The `T` parameter is annotated `[DynamicallyAccessedMembers(PublicProperties)]` so the trimmer keeps the members, but `AutoColumns` ultimately funnels into `TypeRegistry.Resolve`, which is `RequiresUnreferencedCode`. Use explicit `Column<T,V>(…)` definitions instead. | Issue #70 |
| **`UseObservable` on POCOs** | `ObservableTreeTracker` walks public properties via reflection to subscribe to INPC. Observables built explicitly (`Observable<T>`, `IObservableCollection`) are fine; the implicit-INPC path is not. | Issue #70 |
| **Form validation** | `FormField`'s default editor resolution goes through `TypeRegistry`. Same caveat as PropertyGrid. | Issue #70 |
| **Classic `{Binding}` in a hand-supplied `DataTemplate`** | A `{Binding Some.Path}` resolves the path *by string* through CsWinRT's `ICustomPropertyProvider`, i.e. reflection, so the trimmer drops it and the target renders blank — with no build-time warning, because the reflection happens across the WinRT ABI where the IL analyzers cannot see it. Reactor itself contains no bindings: item templates are built from code and populated in a `DataContextChanged` handler, which is strongly typed and needs no metadata. If you install your own template through an escape hatch such as `TreeView(...).Set(tv => tv.ItemTemplate = ...)`, do the same — or annotate your data type with `[WinRT.GeneratedBindableCustomProperty]`. Note `{x:Bind}` is not an option from code: it is a XAML-compiler feature. | Issue #70 |
| **Component discovery (`ReactorApp.Run<TApp>` reflection paths)** | The instantiation of `TApp` itself is annotated and works. Devtools component enumeration is available only when the app opts into `Reactor.DevtoolsSupport` at build time and launches with `--devtools`; leave the switch off for retail/AOT builds. | Issue #70 |
| **Devtools `properties` / `setProperty` DP discovery** | The tools locate `DependencyProperty` statics by reflection, and the trimmer keeps no reflection metadata for them unless something roots the members — so under AOT the lookups find nothing. Not specific to WinUI's CsWinRT-projected static properties: a C#-authored DP *field* on a custom control is equally undiscoverable. Works under JIT, which is where the devtools inner loop lives. The tool reports the likely cause rather than an empty result. See [Devtools DependencyProperty discovery](#devtools-dependencyproperty-discovery) for the measured cost of fixing it. Affected fixture: `Devtools_PropertyToolsDpDiscovery`. | Issue #1109 |
| **Theme resource lookup (`Theme.X`, `ThemeRef.Resolve`)** | Works once the WindowsAppSDK#6394 workaround target ships the project `.pri` into the publish output (see [Required publish-time workarounds](#required-publish-time-workarounds)). Reactor's library itself is AOT-clean here. | WindowsAppSDK#6394 |
Expand Down
13 changes: 1 addition & 12 deletions src/Reactor/Core/Element.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2550,18 +2550,7 @@ public record PivotItemData(string Header, Element Content);

public record BreadcrumbBarItemData(string Label, object? Tag = null);

// The legacy node-mode TreeView renders node text through a classic {Binding}
// (Reconciler.TreeViewTextItemTemplate resolves "Content.Content"). That path
// goes through CsWinRT's ICustomPropertyProvider, which is reflection-based and
// gets trimmed under NativeAOT — the TextBlock then silently renders empty, with
// no build-time warning. This attribute makes the CsWinRT source generator emit
// strongly-typed binding metadata for this type so the hop survives trimming.
//
// Scoped to just "Content": that is the only member the template binds, and the
// parameterless overload would also emit an accessor for the [Obsolete]
// ContentElement, which fails the warnings-as-errors Release build (CS0618).
[WinRT.GeneratedBindableCustomProperty(["Content"], [])]
public partial record TreeViewNodeData(string Content, TreeViewNodeData[]? Children = null)
public record TreeViewNodeData(string Content, TreeViewNodeData[]? Children = null)
{
public bool IsExpanded { get; init; }

Expand Down
2 changes: 1 addition & 1 deletion src/Reactor/Core/IItemsRepeaterFactorySource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ internal interface IItemsRepeaterFactorySource
void ConfigureLayout(WinUI.ItemsRepeater repeater);

/// <summary>
/// Produce a fresh <see cref="IElementFactory"/> closure that knows
/// Produce a fresh <see cref="WinUI.IElementFactory"/> closure that knows
/// how to realize element index N into a UIElement subtree. Called on
/// first mount AND whenever the existing factory's type no longer
/// matches (e.g. element re-keyed to a different TItem).
Expand Down
50 changes: 38 additions & 12 deletions src/Reactor/Core/Reconciler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -334,27 +334,53 @@ internal bool TryResolveFromControlRegistry(Type elementType, out IV1HandlerEntr
/// </summary>
/// <summary>
/// A shared DataTemplate containing a ContentControl shell.
/// Parsed once via XamlReader.Load, reused across all items controls (ListView, GridView, FlipView).
/// Built once from code via <c>new DataTemplate(factory)</c> and reused across
/// all items controls (ListView, GridView, FlipView). The factory overload
/// replaces a <c>XamlReader.Load</c> of an equivalent markup string — see the
/// PR description for why parsing markup at runtime was a problem here.
/// </summary>
internal static readonly Lazy<DataTemplate> SharedContentControlTemplate = new(() =>
(DataTemplate)Microsoft.UI.Xaml.Markup.XamlReader.Load(
"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>" +
"<ContentControl HorizontalContentAlignment='Stretch' VerticalContentAlignment='Stretch'/>" +
"</DataTemplate>"));
new DataTemplate(() => new Microsoft.UI.Xaml.Controls.ContentControl
{
HorizontalContentAlignment = Microsoft.UI.Xaml.HorizontalAlignment.Stretch,
VerticalContentAlignment = Microsoft.UI.Xaml.VerticalAlignment.Stretch,
}));

/// <summary>
/// Spec 047 §14 Phase 3 finish — text-bound TreeView item template
/// shared between the legacy <c>MountTreeView</c> arm and the
/// <see cref="V1Protocol.TreeChildren{TElement,TControl}"/> strategy.
/// In node mode the template's DataContext is <c>TreeViewNode</c>, so
/// <c>{Binding Content.Content}</c> resolves <c>TreeViewNode.Content</c>
/// (a <c>TreeViewNodeData</c>) → its <c>Content</c> (the display string).
/// In node mode the template's DataContext is <c>TreeViewNode</c>, so the
/// handler reads <c>TreeViewNode.Content</c> (a <c>TreeViewNodeData</c>) →
/// its <c>Content</c> (the display string).
/// </summary>
internal static readonly Lazy<DataTemplate> TreeViewTextItemTemplate = new(() =>
(DataTemplate)Microsoft.UI.Xaml.Markup.XamlReader.Load(
"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>" +
"<TextBlock Text='{Binding Content.Content}'/>" +
"</DataTemplate>"));
new DataTemplate(() =>
{
var tb = new Microsoft.UI.Xaml.Controls.TextBlock();

// Strongly-typed replacement for {Binding Content.Content}. A classic
// Binding resolves that path by string through CsWinRT's
// ICustomPropertyProvider, i.e. by reflection — which NativeAOT trims
// unless the source type is annotated, and which costs a reflective
// lookup per realized row even when it works. Reading the same chain in
// a DataContextChanged handler is reflection-free, so it needs no
// annotation and does no lookup. The event re-fires when a virtualized
// row is recycled onto new data, so reused rows retext correctly.
tb.DataContextChanged += static (sender, args) =>
{
if (sender is not Microsoft.UI.Xaml.Controls.TextBlock text) return;
text.Text = args.NewValue switch
{
Microsoft.UI.Xaml.Controls.TreeViewNode { Content: TreeViewNodeData d } => d.Content,
Microsoft.UI.Xaml.Controls.TreeViewNode { Content: string s } => s,
TreeViewNodeData d => d.Content,
_ => string.Empty,
};
};

return tb;
}));

// ════════════════════════════════════════════════════════════════════
// ReactorAttached.StateProperty (ReactorState)
Expand Down
17 changes: 17 additions & 0 deletions tests/Reactor.AppTests.Host/Reactor.AppTests.Host.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@
<!-- Full WinUI host (selftest runner); bundle the WAS runtime so the
exe launches without depending on the machine-wide install. -->
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<!-- BLOCKED / PLACEHOLDER — see the pull request description and the version
comment in Directory.Build.props. Self-contained projects normally take
the Microsoft.WindowsAppSDK metapackage (see the injection rule in
Directory.Build.targets), but the experimental aggregate caps WinUI below
3.0.0 and wants a newer Foundation, so it cannot resolve alongside the
WinUI slice carrying the code-based DataTemplate constructor. Opt out of
the injection and take the self-consistent split packages instead.
Revert this block once the API ships in a public Windows App SDK. -->
<ReactorSkipWinAppSDKInjection>true</ReactorSkipWinAppSDKInjection>
<!-- CsWinRT1030: generic WinRT-implementing collection use (e.g. List<Border>
in SplitterMatrixFixtures) requires the CsWinRT-generated marshaling
code to use unsafe blocks. This flag enables that for the generated
Expand Down Expand Up @@ -57,6 +66,14 @@
<PackageReference Include="MessageFormat" />
</ItemGroup>

<!-- BLOCKED / PLACEHOLDER — the explicit split-package references that replace
the metapackage injection skipped above. Revert together with
ReactorSkipWinAppSDKInjection once the API ships publicly. -->
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK.WinUI" />
<PackageReference Include="Microsoft.WindowsAppSDK.Runtime" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\_shared\BuiltInHandlerBootstrap.cs" Link="BuiltInHandlerBootstrap.cs" />
</ItemGroup>
Expand Down
Loading