Skip to content

Experiment: Using compiled bindings to make TableView AOT compatible #349

Draft
w-ahmad wants to merge 17 commits intomainfrom
feat/native-aot
Draft

Experiment: Using compiled bindings to make TableView AOT compatible #349
w-ahmad wants to merge 17 commits intomainfrom
feat/native-aot

Conversation

@w-ahmad
Copy link
Copy Markdown
Owner

@w-ahmad w-ahmad commented Apr 21, 2026

Summary

This PR extends the AOT-safe TableView binding pipeline by generating strongly typed getter and setter logic in the source generator and using that generated setter path from TableView column bindings. The change removes binding dependencies and adds typed two-way editing support for numeric, date, and time inputs while preserving type safety.

Diagnostics codes

These diagnostics are required to fail fast when source-generated TableView wiring cannot be produced safely, and they are emitted during source generation when required XAML metadata, binding type resolution, connector hookup, or member-path resolution is invalid/missing.

  • TV0001: TableView requires a name for source generation (x:Name/Name is required).
  • TV0002: Unable to resolve ItemsSource item type from x:Bind to a typed generic collection.
  • TV0003: A Window containing TableView must call ConnectTableViews() in code-behind after InitializeComponent().
  • TV0004: Unable to resolve a member path for the resolved item type.

Generated Wiring (Connector + Provider)

ITableViewConnector (src/ITableViewConnector.cs)

  • The source generator emits a partial implementation of this interface on the XAML root class that owns TableView controls.
  • ConnectTableView(TableView tableView) connects one specific TableView instance by reference and assigns its generated ICellValueProvider.
  • ConnectTableViews() connects all generated/named TableView fields in that XAML class in one call.

Generated shape (simplified):

partial class BlankPage1 : ITableViewConnector
{
    void ITableViewConnector.ConnectTableView(TableView tableView)
    {
        if (ReferenceEquals(tableView, this.TableView1))
        {
            tableView.CellValueProvider = new TableView_TableView1_MemberValueProvider();
        }
    }

    void ITableViewConnector.ConnectTableViews()
    {
        this.TableView1.CellValueProvider = new TableView_TableView1_MemberValueProvider();
    }
}

ICellValueProvider (src/ICellValueProvider.cs)

  • Defines the generated contract used by TableView and columns:
    • TryGetBindingValue / TrySetBindingValue
    • TryGetSortMemberValue
    • TryGetClipboardContentBindingValue
    • TryGetContentBindingValue
    • TryGetDisplayMemberValue
  • The generator emits a typed nested provider class per named TableView to implement these methods without reflection.

Xaml definition

<tv:TableView x:Name="TableView1"
              AutoGenerateColumns="False"
              ItemsSource="{x:Bind ViewModel.Items}"> 
        <tv:TableViewTextColumn Header="Address"
                                Binding="{Binding Address}" />
    </tv:TableView.Columns>
</tv:TableView>

Generated shape (simplified):

private sealed class TableView_TableView1_MemberValueProvider : ICellValueProvider
{
    bool ICellValueProvider.TryGetBindingValue(string? path, object? item, out object? value)
    {
        var typedItem = item as global::AotTestApp.ExampleModel;

        switch (path)
        {
            case "Address":
                value = typedItem?.Address;
                return true;
            default:
                value = null;
                return false;
        }
    }

    bool ICellValueProvider.TrySetBindingValue(string? path, object? item, object? value)
    {
        if (item is not global::AotTestApp.ExampleModel typedItem)
        {
            return false;
        }

        switch (path)
        {
            case "Address":
                return TrySet_Address(typedItem, value);
            default:
                return false;
        }

        static bool TrySet_Address(global::AotTestApp.ExampleModel typedItem, object? value)
        {
            if (value is global::System.String typedValue)
            {
                typedItem.Address = typedValue;
                return true;
            }

            if (value is null)
            {
                typedItem.Address = null;
                return true;
            }

            return false;
        }
    }
}

Runtime connection flow

  1. TableView.OnApplyTemplate checks CellValueProvider.
  2. If null, it finds the nearest ITableViewConnector ancestor and calls ConnectTableView(this).
  3. After connection, columns use TableView.CellValueProvider for typed get/set path resolution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant