How to bind to attached property in UWP?
Asked Answered
R

1

6

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?

Resh answered 10/1, 2016 at 2:9 Comment(0)
M
7

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" 
         />
Magnetism answered 10/1, 2016 at 5:53 Comment(7)
x:Bind fails to compile as soon as i set Mode=OneWay. One-time binding is not what I need...Resh
Wasn't aware you needed to use a Mode=OneWay. Updated the answer to be more comprehensive and allow for using other modes.Magnetism
1. Mode=OneWay is the default mode for Bindings (in contrast with x:Bind you mentioned, which has Mode=OneTime by default). 2. What do you mean by ReferenceSource?Resh
3. The [Bindable] attribute completely solved my issue! Even though VS still yelling about "Value does not fall within the expected range". It seems that WinRT XAML engine is not smart enough to infer that I'm using that type when it is simply a source of binding, but not a target. The lack of documentation is a real problem when it comes to UWP (the MSDN simply says that this attribute "Specifies that a type defined in C++ can be used for binding [...] all types defined in C# are bindable by default", ugh....).Resh
Opps, I meant RelativeSource I will have to update that, and yes "Value does not fall within the expected range" seems to be a warning/error that can be ignored in this scenario.Magnetism
My code snippet had the RelativeSource initially, so it wasn't the cause of the issue.Resh
and if you want to pass value to the attached property how would xaml syntax look like ?Seafarer

© 2022 - 2024 — McMap. All rights reserved.