UWP Custom control gotchas– Attached property

I’ve been having fun with a custom list. In this case I wanted to provide render in the 3 dimensional space instead of 2d, i.e. a 3d canvas.

TL;DR Stackoverflow answer.

Initially there were two problems. Firstly that it isn’t quite possible to add X and Y, as it seems canvas does. So here’s how I posted the problem on StackOverflow

In Universal Windows App flavoured Xaml, I want to create a custom panel that has a very similar attached property such as Canvas.Left (or Top, Right or Bottom). So I can simply create one according to the guide

public static readonly DependencyProperty XProperty = 
    DependencyProperty.RegisterAttached("X", typeof(double), typeof(Track), null);

public static double GetX(DependencyObject d)
{
   return (double)d.GetValue(XProperty);
}
// SetX and property wrapper omitted for brevity here

Now I can write

<c:Track>
  <TextBlock Text="1" c:Track.X="1"/>
  <TextBlock Text="2" c:Track.X="2"/>
  <TextBlock Text="3" c:Track.X="3"/>
</c:Track>

And I can then use my attached values in

public override void ArrangeOverride(Size finalSize)
{
  foreach (var child in Children)
  { 
     var x = GetX(child);
     child.Arrange(CalculateArrange(x, finalSize));
  }
}

And everything is working perfectly so far.

However when we come to an ItemsControl I can do this,

<ItemsControl ItemsSource="{Binding ListOfInts}">
  <ItemsControl.ItemPanel>
    <ItemPanelTemplate>
       <Canvas/>
    </ItemPanelTemplate>
  </ItemsControl.ItemPanel>

  <ItemsControl.ItemTemplate>
    <DataTemplate x:DatatType="Int">
      <TextBlock Canvas.Left="{Binding}" Text="{Binding}"/>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

But if I want to do it for my own custom panel, I have to do this

<ItemsControl ItemsSource="{Binding ListOfInts}">
  <ItemsControl.ItemPanel>
    <ItemPanelTemplate>
       <Track/>   <!--Change to my panel-->
    </ItemPanelTemplate>
  </ItemsControl.ItemPanel>

 <ItemsControl.ItemContainerStyle>  <!-- need to add the attached property here -->
   <Style TargetType="ContentPresenter">
     <Setter Property="c:Track.X" Value="{Binding}"/>
   </Style>
 </ItemsControl.ItemContainerStyle>

  <ItemsControl.ItemTemplate>
    <DataTemplate x:DatatType="Int">
      <TextBlock Text="{Binding}"/>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

Because I can’t do

public override void ArrangeOverride(Size finalSize)
{
  //Children are now contentPresenters
  foreach (ContentPresenter child in Children) 
  {
    var controlWithAttribute = child.????
    //child.Content is the same as DataContext i.e. an Int

    var x = GetX(controlWithAttribute);
    child.Arrange(CalculateArrange(, finalSize));
  }
}

This question already has an answer here:

And the best answer there was

As noted in the section Migration notes on the Setter.Value property page on MSDN, UWP/Windows Runtime does not support bindings in Style Setters.

Windows Presentation Foundation (WPF) and Microsoft Silverlight supported the ability to use a Binding expression to supply the Value for a Setter in a Style. The Windows Runtime doesn’t support a Binding usage for Setter.Value (the Binding won’t evaluate and the Setter has no effect, you won’t get errors, but you won’t get the desired result either). When you convert XAML styles from WPF or Silverlight XAML, replace any Binding expression usages with strings or objects that set values, or refactor the values as shared {StaticResource} markup extension values rather than Binding-obtained values.

A workaround could be a helper class with attached properties for the source paths of the bindings. It creates the bindings in code behind in a PropertyChangedCallback of the helper property:

public class BindingHelper
{
    public static readonly DependencyProperty GridColumnBindingPathProperty =
        DependencyProperty.RegisterAttached(
            "GridColumnBindingPath", typeof(string), typeof(BindingHelper),
            new PropertyMetadata(null, GridBindingPathPropertyChanged));

    public static readonly DependencyProperty GridRowBindingPathProperty =
        DependencyProperty.RegisterAttached(
            "GridRowBindingPath", typeof(string), typeof(BindingHelper),
            new PropertyMetadata(null, GridBindingPathPropertyChanged));

    public static string GetGridColumnBindingPath(DependencyObject obj)
    {
        return (string)obj.GetValue(GridColumnBindingPathProperty);
    }

    public static void SetGridColumnBindingPath(DependencyObject obj, string value)
    {
        obj.SetValue(GridColumnBindingPathProperty, value);
    }

    public static string GetGridRowBindingPath(DependencyObject obj)
    {
        return (string)obj.GetValue(GridRowBindingPathProperty);
    }

    public static void SetGridRowBindingPath(DependencyObject obj, string value)
    {
        obj.SetValue(GridRowBindingPathProperty, value);
    }

    private static void GridBindingPathPropertyChanged(
        DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var propertyPath = e.NewValue as string;

        if (propertyPath != null)
        {
            var gridProperty =
                e.Property == GridColumnBindingPathProperty
                ? Grid.ColumnProperty
                : Grid.RowProperty;

            BindingOperations.SetBinding(
                obj,
                gridProperty,
                new Binding { Path = new PropertyPath(propertyPath) });
        }
    }
}

You would use them in XAML like this:

<ItemsControl.ItemContainerStyle>
    <Style TargetType="ContentPresenter">
        <Setter Property="local:BindingHelper.GridColumnBindingPath" Value="Level"/>
        <Setter Property="local:BindingHelper.GridRowBindingPath" Value="Row"/>
    </Style>
</ItemsControl.ItemContainerStyle>
Advertisements