Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Binding is hard to do in C++ #6411

Closed
asklar opened this issue Nov 30, 2021 · 5 comments
Closed

Binding is hard to do in C++ #6411

asklar opened this issue Nov 30, 2021 · 5 comments
Labels
area-Binding needs-winui-3 Indicates that feature can only be done in WinUI 3.0 or beyond. (needs winui 3) no-issue-activity team-Markup Issue for the Markup team

Comments

@asklar
Copy link
Member

asklar commented Nov 30, 2021

x:Bind is a lot easier but the two mechanisms fulfill different scenarios (runtime vs. build time). For C++, one has to implement ICustomPropertyProvider: https://docs.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/binding-property#using-the-binding-markup-extension-with-cwinrt

@ghost ghost added the needs-triage Issue needs to be triaged by the area owners label Nov 30, 2021
@StephenLPeters StephenLPeters added area-Binding team-Markup Issue for the Markup team needs-winui-3 Indicates that feature can only be done in WinUI 3.0 or beyond. (needs winui 3) labels Feb 17, 2022
@StephenLPeters
Copy link
Contributor

@RealTommyKlein FYI

@RealTommyKlein
Copy link
Contributor

We've actually been having some internal discussions around this and larger improvements to the C++ experience - will put the blurb about Binding vs. x:Bind, but the current thinking is there are scenarios only possible with {Binding} so we aren't able to remove it.

X:Bind vs. Binding Overview

Today in Xaml, we have two different built-in ways of achieving data binding:

  • {Binding}, an implementation of data binding usable either via markup or code-behind. Can be dynamically created + used at any point, including loading markup at runtime via XamlReader.Load.

  • {x:Bind}, an implementation of data binding only usable via markup compiled when the app is built. There is no "x:Bind object" at runtime - instead, the Xaml compiler generates code in the component (C++, C#, etc.) which implements the binding logic.

{Binding} was the original data binding implementation, and was meant to be supplanted by {x:Bind} due to flaws with {Binding}. So the summary will go in the order they were designed:

  • {Binding}'s and its pros, the problem it was designed to solve

  • {Binding}'s cons, which led to the development of {x:Bind}

  • {x:Bind}'s pros, which are mostly {Binding}'s cons it addresses

  • {x:Bind}'s cons, which have become apparent as developers switched from {Binding} to {x:Bind}

{Binding} pros

{Binding} is a concept that has existed since WPF, designed to allow apps to easily show and modify data. For example, displaying information from a data model in an application, or sending user input from an application back to a data model for processing. By putting the functionality in the {Binding} markup extension, the intent in the UI is clear (what the target + source of the Binding are) while developing the markup, and the otherwise complex implementation like subscribing + handling property change notifications are handled for the developer.

{Binding} cons

However, as developers used {Binding}, they encountered several flaws which made using {Binding} unintuitive and difficult:

{Binding}s are too dynamic for accurate compile time validation. Because of their dynamic nature, tooling can't easily validate or support {Binding}. Consider the following abridged markup for a UserControl that uses a {Binding}:

BindingControl.xaml:

<UserControl x:Class="Foo.BindingControl"> 

    <TextBlock Text="{Binding Text, ElementName=SomeTextControl}" /> 

</UserControl> 

In BindingControl.xaml, there is no element named "SomeTextControl", so we might think this Binding should be invalid and raise an error at compile time. However, because Binding evaluates the visual tree at runtime, using it like this would work since now there is a context where "SomeTextControl" is a valid element:

<Page x:Class="Bar.Page"> 

  <TextBox Text="some text" x:Name="SomeTextControl" /> 

  <foo:BindingControl /> 

</Page> 

But in general, the Xaml compiler can't analyze this case - imagine BindingControl was defined in a control library, and the sample markup above was from an app consuming it. When the Xaml compiler is building the control library, it has no way of knowing how the control will be used, and when compiling the application, the Xaml compiler doesn't know the source .xaml content for BindingControl to validate its final usage.

  • {Binding}s have no type safety. As in the example above, there is no way for Binding to know what the type of the object or property it's binding to ahead of time.

  • This also means no automatic type info generation for types used by {Binding}. Type info generation is necessary at compile time by the Xaml compiler, so the Xaml framework (and by extension {Binding}) can interact with user-defined types at runtime. Because we can't determine the types used at compile time and automatically generate type info, developers have to help the compiler by manually adding the Microsoft.UI.Xaml.Data.Bindable attribute to types they know they're binding to and need to have type info for.

  • Poor error experience at runtime. When a Binding fails at runtime, the error is often vague, making {Binding} failures difficult to diagnose.

  • Subpar performance. Because {Bindings} always need to perform ICustomPropertyProvider to resolve types + properties, performance takes a hit even in well-defined/simple scenarios.

{x:Bind} pros

{x:Bind} was designed to address these issues with {Binding}. Instead of an actual {Binding} object created at runtime, when the Xaml compiler sees an {x:Bind} in markup, it generates custom code to implement the binding logic, which is compiled into the component/library being built.

{x:Bind} has great compile time validation. The DataContext and types used by x:Bind are all known at the Xaml compiler's compilation time. The Xaml compiler can verify all types/properties used by x:Bind, and raise a descriptive compile-time error instead of a difficult to diagnose runtime error. Because x:Bind is implemented in compiled code, the Xaml compiler doesn't have to worry about generating type information for the types, and developer never need to add the Xaml.Data.Bindable attribute.

{x:Bind} has better performance than {Binding}. The binding logic is implemented as compiled code, not runtime reflection, enabling more optimization.

{x:Bind} has newer features. Because development shifted to x:Bind over Bindings, x:Bind has received new features that Binding doesn't have, like the ability to use functions in x:Binds (called "function binding").

{x:Bind} cons

Since developers have used x:Bind, it has several apparent flaws:

  • To use x:Bind, the .xaml file must have a backing x:Class in code-behind. This means x:Bind can't be used in scenarios where Binding could without adding overhead. A common scenario is defining a ResourceDictionary in its own .xaml file, and being unable to use x:Bind because it doesn't have a backing x:Class. The workaround is to add a backing x:Class to the file, but it adds overhead to the development experience.

  • x:Bind relies on metadata to perform validation, leading to developer overhead in C++/WinRT. C++/WinRT relies on developer-authored IDL files to generate its metadata, which the Xaml compiler uses to perform x:Bind validation. This means any type or field used by x:Bind must be authored in the IDL file. This is especially painful when binding to an x:Name'd element. When an element has an x:Name, the Xaml compiler automatically generates a corresponding field for it. E.g. in the SomeTextControl example, the Xaml compiler would generate a "SomeTextControl" field of type TextBox on the Bar.Page class. But in C++/WinRT, a developer would also need to add a SomeTextControl property to their Bar.Page class in their IDL file. This also pollutes the API surface of the application.

  • x:Binds are unusable in markup loaded via XamlReader.Load(). Because x:Binds are implemented by code generated and built into the component when it is compiled, dynamically loaded markup via XamlReader.Load() cannot use it.

@kmgallahan
Copy link
Contributor

kmgallahan commented Feb 18, 2022

@RealTommyKlein

I'd add an {x:Bind} con. You can't easily do this using {x:bind} (or at all?):

<UserControl Name="ThisControl" ... />
    <DataTemplate x:DataType="a:b">
        ...
        <ColumnDefinition Width="{Binding Path=SomeValue, ElementName=ThisControl, Mode=OneWay}" />

@michael-hawker
Copy link
Collaborator

@kmgallahan yes, that's a big problem with x;Bind is scoping resolution doesn't seem to work as intended. There's an open issue on this: #2508

If some time to improve x:Bind could be focused on, it'd solve a lot of pain points with application development in general.

Would be nice to make it easier to use for templated control development auto-generating glue for GetTemplateChild, though wouldn't be able to make use of those improvements for a while unless they're back-ported to UWP as well (as wouldn't duplicate our XAML files to support both UWP and WinUI 3, need them to be compatible with both until we drop UWP support in the Toolkit in 1-2 years or later).

@bpulliam bpulliam removed the needs-triage Issue needs to be triaged by the area owners label Dec 6, 2022
@github-actions
Copy link

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 5 days.

@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Aug 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-Binding needs-winui-3 Indicates that feature can only be done in WinUI 3.0 or beyond. (needs winui 3) no-issue-activity team-Markup Issue for the Markup team
Projects
None yet
Development

No branches or pull requests

6 participants