CompiledBindings

repository·master·Indexed 43 days ago

https://github.com/levitali/compiledbindings

Brings the high-performance {x:Bind} markup extension to WPF, MAUI, and Xamarin.Forms by generating C# code from XAML bindings during compilation. It supports complex expressions, operators, asynchronous tasks, and tuple binding, providing a compiled alternative to traditional data binding.

Tokens
3.2K
Snippets
11
Records
14
Agent score
6%

What's inside CompiledBindings

  1. Use the x:Set Markup Extension

    master

    The {x:Set} markup extension allows you to set a property once using an expression. It is useful for one-time assignments that do not require change tracking.

    Key differences between x:Set and x:Bind:

    • Evaluation Timing: x:Set expressions are evaluated at the end of the constructor. x:Bind expressions (when x:DataType is specified) are evaluated when the DataContext/BindingContext is set.
    • Change Tracking: x:Set does not generate code to listen for property changes. For x:Bind, you must set Mode=OneTime if you do not want change tracking.
    • Data Root: The data root for x:Set is always the view itself. The data root for x:Bind depends on the x:DataType attribute. Both support using static members in expressions.
  2. Observe Property Changes with \. and /. Operators

    master

    CompiledBindings generates code to observe changes in properties. You can fine-tune this behavior using special operators:

    • \. (Backslash Dot): Forces the compiler to check for INotifyPropertyChanged at runtime instead of compile time. This is useful when binding to properties of an interface (like IList.Count) where the specific implementation type is unknown.
    • /. (Slash Dot): Tells the compiler to stop listening for changes on that property. Use this for optimization when a property (like a collection instance) is returned but the instance itself never changes, even if its contents do.

    Forcing Notifications on Get-Only Properties

    By default, no change-listening code is generated for auto-generated get-only properties. To force the compiler to generate listening code for a property that changes (even if it's get-only), use the [ReadOnly(false)] attribute in your C# code.

    <!-- Force runtime INPC check for IList.Count -->
    <TextBlock Text="{x:Bind serialNumbersList.SelectedItems\.Count}"/>
    
    <!-- Stop listening to changes on the SelectedItems instance itself -->
    <TextBlock Text="{x:Bind serialNumbersList/.SelectedItems\.Count}"/>
  3. Bind to Tuples

    master

    You can bind multiple UI properties to a single property or function that returns a tuple. This is efficient because the property is only called once, and its values are cached locally in the generated code to set the various target properties.

    <!-- C# Property -->
    public (Brush foreground, Brush background) Colors => ...;
    
    <!-- XAML Usage -->
    <TextBlock Foreground="{x:Bind Colors.foreground}" Background="{x:Bind Colors.background}"/>
  4. Expressions and Operators in {x:Bind}

    master

    You can use various operators within a {x:Bind} expression to perform logic at compile time:

    • Binary Operators: +, -, *, /, = (eq), != (ne), < (lt), > (gt), <= (le), >= (ge)
    • Unary Operators: -, +, ! (not)
    • Logical Operators: && (and), || (or)
    • Coalesce Operator: ? : (e.g., IsChange ? Collapsed : Visible)
    • Null Check Operator: ?? (e.g., Movie.Title ?? 'no title')
    • Cast Operator: (namespace:Type)expression (e.g., ((local:Movie)itemsList.SelectedItem).Title)
    • As Operator: expression as namespace:Type (e.g., (itemsList.SelectedItem as local:Movie).Title)
    • New Operator: new namespace:Type(args)
    • Interpolated Strings: Starts with $ followed by a string literal. Supports {} for expressions and optional formatting (e.g., $'Value: {Prop:0.###}')
    • Is Operator: Used for multi-expression matching (e.g., IntProp1 is 0 or 1 or IntProp1 is ge 0 and le 10). It also supports type checking (e.g., ObjProp is system:String).
    <!-- Example of is-operator with comparison -->
    <Label IsVisible="{x:Bind IntProp1 is ge 0 and le 10}"/>
    
    <!-- Example of interpolated string -->
    <Label Text="{x:Bind $'Decimal value: {DecimalProp:0.###}, Boolean value: {BooleanProp}'}" />
  5. Install CompiledBindings for WPF, MAUI, Xamarin.Forms, or WinUI

    master

    CompiledBindings provides the {x:Bind} markup extension for platforms that do not natively support it (WPF, MAUI, and Xamarin.Forms). It works by converting {x:Bind} expressions into C# code at XAML compile time.

    Note: Because it relies on XAML compilation to generate C# code, it cannot be used in Visual Basic projects.

    // Install via NuGet depending on your target platform:
    // WPF:
    # Install-Package CompiledBindings.WPF
    
    // MAUI:
    # Install-Package CompiledBindings.MAUI
    
    // Xamarin.Forms:
    # Install-Package CompiledBindings.XF
    
    // WinUI:
    # Install-Package CompiledBindings.WinUI
  6. Use m: Namespace to bypass markup extension errors

    master

    If a valid {x:Bind} or {x:Set} expression causes build errors (for example, due to quotes in the expression which some XAML parsers like WPF cannot handle), you can use the m: namespace to set the property directly. This bypasses the standard markup extension parsing logic while still using the compiled binding expression.

    Example (WPF workaround): Instead of: <TextBlock Text="{x:Bind system:String.Format('{0:0.###} {1}', Quantity, Unit)}" />

    Use: <TextBlock m:Text="{x:Bind system:String.Format('{0:0.###} {1}', Quantity, Unit)}" />

    <TextBlock m:Text="{x:Bind system:String.Format('{0:0.###} {1}', Quantity, Unit)}" />
  7. Configure the Data Source for {x:Bind}

    master

    By default, the data source is the root control/page/window.

    Setting DataType

    • Xamarin Forms: Use the x:DataType attribute.
    • WPF: Since x:DataType is not natively available, declare the http://compiledbindings.com/x namespace (e.g., xmlns:mx="http://compiledbindings.com/x"), add it to mc:Ignorable="d mx", and use mx:DataType="local:MainViewModel".
    • DataTemplates: You must specify the data type in a DataTemplate. You can use x:DataType (or mx:DataType for WPF) or set DataType={x:Null} to reset it.

    Automatic Type Detection with IsItemsSource

    When binding to the ItemsSource of a collection control (e.g., CollectionView, ListBox), set IsItemsSource=true. This allows the compiler to automatically detect the element type for the ItemTemplate, provided the template is defined as a direct XAML child of the collection control.

    <!-- WPF Data Source Setup -->
    <Window ...
          xmlns:mx="http://compiledbindings.com/x"
          mc:Ignorable="d mx"
          mx:DataType="local:MainViewModel">
    
    <!-- Automatic type detection for ItemsSource -->
    <CollectionView ItemsSource="{x:Bind Movies, IsItemsSource=true}">
        <CollectionView.ItemTemplate>
            <DataTemplate>
                <Label Text="{x:Bind Title}"/>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
  8. Bind to methods and extension methods

    master

    You can use a method or an extension method as a target for {x:Bind} or {x:Set} using OneTime or OneWay modes.

    Requirements for Instance Methods

    • Must have exactly one parameter.

    Requirements for Extension Methods

    • Must have exactly two parameters: the first must be the this parameter of the control, and the second is the parameter to which the expression is set.

    Setup Steps

    1. Declare the http://compiledbindings.com/ namespace (e.g., with prefix m).
    2. Mark the namespace as ignorable in mc:Ignorable.
    3. For extension methods, declare the namespace of the extension class using using:Namespace.

    Example

    C# Extension Method:

    public static void SetFocused(this VisualElement visualElement, bool focused)
    {
        if (focused)
        {
        	visualElement.Focus();
        }
    }

    XAML Usage:

    <ContentPage 
        xmlns="http://schemas.microsoft.com/dotnet/2021/xaml"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:m="http://compiledbindings.com/"
        xmlns:extension="using:CompiledBindingsDemo.Extensions"
        mc:Ignorable="d m mx">
    
      <Entry m:SetFocused="{x:Bind IsNameFocused}" />
    </ContentPage>
    <Entry m:SetFocused="{x:Bind IsNameFocused}" />
  9. Bind to Asynchronous Tasks

    master

    If a property or function returns a Task<T>, {x:Bind} will automatically await the value.

    • While waiting, the binding will display the FallbackValue (if provided) or the default value of the target property.
    • If the asynchronous function throws an exception, the exception is ignored and the target property remains unchanged.
    <Image Source="{x:Bind LoadImageAsync(), FallbackValue={StaticResource defaultImage}}" />
  10. Debug x:Bind and x:Set with breakpoints

    master

    You can set breakpoints directly in XAML on lines containing x:Bind or x:Set extensions. A breakpoint will trigger whenever the property is set.

    Limitations & Tips:

    • Only one breakpoint per line is supported.
    • To inspect the values of the binding expression during debugging, use the dataRoot variable in the Watch window to refer to the binding root object.
  11. Use the {x:Bind} Markup Extension

    master

    The {x:Bind} markup extension allows for compiled data binding in XAML. It accepts an expression as its first parameter or a Path parameter. It supports property paths, function calls (both instance and static), and a wide range of operators for complex logic directly within the XAML.

    <!-- Property paths -->
    <TextBlock Text="{x:Bind Title}"/>
    <TextBlock Text="{x:Bind Movie.Year}"/>
    
    <!-- Function calls -->
    <TextBlock Text="{x:Bind Movie.Description.Trim()}"/>
    <TextBlock Text="{x:Bind local:MainPage.GenerateSongTitle(Title, Description)}"/>
  12. Bind to Events as Targets

    master

    You can bind a control's event to a function.

    1. Direct Event Parameters: If the function signature matches the event handler (e.g., receiving object sender and EventArgs e), the parameters are passed automatically. The function in the XAML must be called without parameters.
    2. Custom Parameters: You can pass specific arguments to a function by including them in the expression.
    <!-- Passing event parameters automatically -->
    <TextBox Drop="{x:Bind _viewModel.OnDrop}"/>
    
    <!-- Passing custom parameters -->
    <Button Click="{x:Bind _viewModel.Save(true)}"/>