Yarik
Yarik

Reputation: 1578

How to bind to attached property in UWP?

I need to bind a property of a control to an attached property in XAML (so that the attached property becomes a source of the binding), and I can't figure out how to do it -- VS2015 gives me "Value does not fall within the expected range" error, and when I run the app, I get an exception.

The technique shown below worked perfectly in WPF.

Here is the sample app demonstrating the problem.

AttachedPropertyTest.cs:

namespace App7
{
    public static class AttachedPropertyTest
    {
        public static readonly DependencyProperty FooProperty = DependencyProperty.RegisterAttached(
            "Foo", typeof(string), typeof(AttachedPropertyTest), new PropertyMetadata("Hello world!"));

        public static void SetFoo(DependencyObject element, string value)
        {
            element.SetValue(FooProperty, value);
        }

        public static string GetFoo(DependencyObject element)
        {
            return (string) element.GetValue(FooProperty);
        }
    }
}

MainPage.xaml:

<!-- Based on the default MainPage.xaml template from VS2015.
     The only thing added is the TextBlock inside the Grid. -->
<Page x:Class="App7.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App7"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <TextBlock Text="{Binding Path=(local:AttachedPropertyTest.Foo), RelativeSource={RelativeSource Self}}"
                   HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Grid>
</Page>

Instead of displaying "Hello world!" (which is a default value of the Foo attached property) on the TextBlock above, I get XamlParseException, thrown from InitializeComponent. As usual, the exception object does not contain any useful information.

Interestingly enough, this doesn't happen if I try to bind to any standard (built into the framework) attached property like (Grid.Row), so it seems that the XAML parser just doesn't let me use a custom attached property provider, which is ridiculous...

So what is the correct way of doing this?

Upvotes: 6

Views: 4937

Answers (1)

user5606561
user5606561

Reputation: 139

Try using the x:Bind declaration instead of the Binding declaration.

<Grid>
    <TextBlock Text="{x:Bind Path=(local:AttachedPropertyTest.Foo) }"
               HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>

However, using x:Bind works by generating code to support the binding in the code behind.

After some experimenting, what appears to be happening in an issue with the tool that generated the XamlTypeInfo.g.cs file which is a compiled look up of declared Xaml elements, and unfortunately the AttachedPropertyTest registration is missing from the InitTypeTables() method.

An interesting article on the generation of XamlTypeInfo class describes your issue makes few suggestions including using a custom IXamlMetadataProvider

I have found the following changes will also work:

Change the declaration of the AttachedPropertyTest class from static and add the Bindable attribute which will make it detectable by the tool generating the XamlTypeInfo class.

[Bindable]
public class AttachedPropertyTest
{
    public static readonly DependencyProperty FooProperty = 
        DependencyProperty.RegisterAttached(
        "Foo", typeof(string), typeof(AttachedPropertyTest), new PropertyMetadata("Hello world!"));

    public static void SetFoo(DependencyObject element, string value)
    {
        element.SetValue(FooProperty, value);
    }

    public static string GetFoo(DependencyObject element)
    {
        return (string)element.GetValue(FooProperty);
    }
}

Then modify the xaml declaration to include the RelativeSource

    <TextBlock Text="{Binding Path=(local:AttachedPropertyTest.Foo), RelativeSource={RelativeSource Self}, Mode=OneWay}"
               HorizontalAlignment="Center" VerticalAlignment="Center" 
         />

Upvotes: 7

Related Questions