3

Does WinUI 3 support binding in a Style Setter? I've defined a Style for a NavigationView and the third line is:

<Setter Property="CompactPaneLength" Value="{Binding CurrentCompactPaneLength}" />

This produces a Specified cast is not valid. exception at run time. The DataContext for the page containing the NavigationView is the ViewModel for the page. Both NavigationView.CompactPaneLength and CurrentCompactPaneLength are double and public and CurrentCompactPaneLength is an ObservableObject (from CommunityToolkit.Mvvm.ComponentModel).

The source code for the WinUI 3 (SDK 1.1.2) includes various Setters, such as

<Setter Target="PaneContentGrid.Width" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=CompactPaneLength}" />

Doing the bindings in code works, if that's what's necessary. But shouldn't the XAML work, too?

aturnbul
  • 347
  • 2
  • 12
  • You actually found the source code for WinUI 3? Might I ask where? All Microsoft repos are mazes. – Emperor Eto Jun 12 '23 at 13:21
  • I think you might be looking at the WinUI2 code. AFAIK the Winui3 code is still not open source (and at this rate never will be). – Emperor Eto Jun 12 '23 at 13:23

1 Answers1

0

Apparently, general bindings in Setters are not supported yet in WinUI 3, although it is a much-requested feature. A workaround is to create a helper class with a DependencyProperty in it that calls a change handler whenever the property is changed/set. The change handler can then create the required binding in code. Kudos to clemens who suggested something like this ages ago for UWP. Here is an example helper class:

internal class BindingHelper
{
    #region CompactPaneLengthBindingPath
    public static readonly DependencyProperty CompactPaneLengthBindingPathProperty = DependencyProperty.RegisterAttached(
            "CompactPaneLengthBindingPath", typeof(string), typeof(BindingHelper), new PropertyMetadata(null, BindingPathChanged));
            
    public static string GetCompactPaneLengthBindingPath(DependencyObject obj)
    {
        return (string)obj.GetValue(CompactPaneLengthBindingPathProperty);
    }
    
    public static void SetCompactPaneLengthBindingPath(DependencyObject obj, string value)
    {
        obj.SetValue(CompactPaneLengthBindingPathProperty, value);
    }
    #endregion
        
    #region HeightBindingPath
    // another DP is defined here (all of them are strings)
        
    #region ForegroundBindingPath
    // and a third one, etc.
        

    // ===================== Change Handler: Creates the actual binding

    private static void BindingPathChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue is string source)                                                                // source property (could add DataContext by setting Value="source@datacontext" for example)
        {
            DependencyProperty target;                                                                  // which property is the target of the binding?
            if (e.Property == CompactPaneLengthBindingPathProperty) target = NavigationView.CompactPaneLengthProperty;
            else if (e.Property == HeightBindingPathProperty) target = FrameworkElement.HeightProperty;
            else if (e.Property == ForegroundBindingPathProperty) target = Control.ForegroundProperty;
            else throw new System.Exception($"BindingHelper: Unknown target ({nameof(e.Property)}");    // don't know this property
        
            obj.ClearValue(target);                                                                     // clear previous bindings (and value)
            BindingOperations.SetBinding(obj, target,                                                   // set new binding (and value)
               new Binding { Path = new PropertyPath(source), Mode = BindingMode.OneWay });
        }
    }

Note that all of the DependencyProperties are of type string and that the target type can be any ancestor type of the control you are working with. For example, the HeightBindingPathProperty binding can be used with any FrameworkElement.

Use the helper in a Style just as you would any Setter, like this:

<Style x:Key="MyNavigationView" TargetType="controls:NavigationView" >
    <Setter Property="local:BindingHelper.CompactPaneLengthBindingPath" Value="CurrentCompactPaneLength" />
</Style>

I hope this helps.

aturnbul
  • 347
  • 2
  • 12