TemplateBinding with Converter - what is wrong?

Martin Vseticka picture Martin Vseticka · Apr 6, 2010 · Viewed 10.1k times · Source

I'm creating a game desk. I wanted to specify field size (one field is a square) as a attached property and with this data set value of ViewPort which would draw 2x2 matrix (and tile mode would do the rest of game desk).

I'm quite at loss what is wrong because the binding doesn't work.

Testing line in XAML for the behaviour I would like to have:

<DrawingBrush Viewport="0,0,100,100" ViewportUnits="Absolute" TileMode="None">

The game desk is based on this sample of DrawingPaint: http://msdn.microsoft.com/en-us/library/aa970904.aspx (an image is here)

XAML:

<Window x:Class="Sokoban.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Sokoban"
    Title="Window1" Height="559" Width="419">
    <Window.Resources>
        <local:FieldSizeToRectConverter x:Key="fieldSizeConverter" />
        <Style x:Key="GameDesk" TargetType="{x:Type Rectangle}">
            <Setter Property="local:GameDeskProperties.FieldSize" Value="50" />
            <Setter Property="Fill">
                <Setter.Value>
                    <!--<DrawingBrush Viewport="0,0,100,100" ViewportUnits="Absolute" TileMode="None">-->
                    <DrawingBrush Viewport="{TemplateBinding local:GameDeskProperties.FieldSize, Converter={StaticResource fieldSizeConverter}}" ViewportUnits="Absolute" TileMode="None">
                        <DrawingBrush.Drawing>
                            <DrawingGroup>
                                <GeometryDrawing Brush="CornflowerBlue">
                                    <GeometryDrawing.Geometry>
                                        <RectangleGeometry Rect="0,0,100,100" />
                                    </GeometryDrawing.Geometry>
                                </GeometryDrawing>

                                <GeometryDrawing Brush="Azure">
                                    <GeometryDrawing.Geometry>
                                        <GeometryGroup>
                                            <RectangleGeometry Rect="0,0,50,50" />
                                            <RectangleGeometry Rect="50,50,50,50" />
                                        </GeometryGroup>
                                    </GeometryDrawing.Geometry>
                                </GeometryDrawing>
                            </DrawingGroup>
                        </DrawingBrush.Drawing>
                    </DrawingBrush>
               </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>

    <StackPanel>
        <Rectangle Style="{StaticResource GameDesk}" Width="300" Height="150" />        
    </StackPanel>
</Window>

Converter and property definition:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Diagnostics;
using System.Windows.Data;

namespace Sokoban
{
    public class GameDeskProperties : Panel
    {

        public static readonly DependencyProperty FieldSizeProperty;

        static GameDeskProperties()
        {
            PropertyChangedCallback fieldSizeChanged =
                new PropertyChangedCallback(OnFieldSizeChanged);
            PropertyMetadata fieldSizeMetadata =
                new PropertyMetadata(50, fieldSizeChanged);

            FieldSizeProperty = DependencyProperty.RegisterAttached("FieldSize",
                typeof(int), typeof(GameDeskProperties), fieldSizeMetadata);
        }

        public static int GetFieldSize(DependencyObject target)
        {
            return (int)target.GetValue(FieldSizeProperty);
        }

        public static void SetFieldSize(DependencyObject target, int value)
        {
            target.SetValue(FieldSizeProperty, value);
        }


        static void OnFieldSizeChanged(DependencyObject target,
                              DependencyPropertyChangedEventArgs e)
        {
            Debug.WriteLine("FieldSize just changed: " + e.NewValue);
        }
    }

    [ValueConversion(/* sourceType */ typeof(int), /* targetType */ typeof(Rect))]
    public class FieldSizeToRectConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            Debug.Assert(targetType == typeof(int));

            int fieldSize = int.Parse(value.ToString());
            return new Rect(0, 0, 2 * fieldSize, 2 * fieldSize);
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            // should not be called in our example
            throw new NotImplementedException();
        }
    }
}

Answer

Abe Heidebrecht picture Abe Heidebrecht · Apr 6, 2010

TemplateBindings only work for dependency properties defined on the control being templated (in a ControlTemplate). You just need to switch this around to be a Binding with a RelativeSource of AncestorType (also, attached properties require parenthesis to be used in a binding):

...
<DrawingBrush Viewport="{Binding Path=(local:GameDeskProperties.FieldSize), Converter={StaticResource fieldSizeConverter}, RelativeSource={RelativeSource AncestorType={x:Type Rectangle}}}"
...

Edit Updated the RelativeSource binding, as it was not defined inside of a ControlTemplate.