views:

636

answers:

2

How do I bind an element of a derived <UserControl> to an element of the base <UserControl>?

I have defined a UserControl called Tile as my base class. This would be an abstract base class, if XAML wouldn't balk when I tried to instantiate an object derived from it ...

Anyway, so far Tile only contains a single dependency property: OuterShape. It is a System.Windows.Shapes.Shape object (also abstract). Thus: all tiles will have some kind of visual shape associated with them, for rendering. But there are many different types of Tiles --- some with System.Windows.Shapes.Paths, some with System.Windows.Shapes.Polygons, etc.

So now I am working on the first derived UserControl: PolyTile. As the name implies, this is a Tile that uses a System.Windows.Shapes.Polygon as its "OuterShape" property.

The problem I am running into is that I can't figure out how to successfully bind the XAML <Polygon> element to the Shape property in the base class.

/************
** Tile.cs **
************/
// This class contains a generic shape, and that's all.
// This class does NOT have a XAML file to go with it. It is purely code implemented.
public class Tile : System.Windows.Controls.UserControl
{
    public static readonly System.Windows.DependencyProperty OuterShapeProperty
        = System.Windows.DependencyProperty.Register(   "OuterShape",
                                                        typeof(System.Windows.Shapes.Shape),
                                                        typeof(Tile));
    public System.Windows.Shapes.Shape OuterShape
    {   get { return (System.Windows.Shapes.Shape)GetValue(OuterShapeProperty); }
        set { SetValue(OuterShapeProperty, (System.Windows.Shapes.Shape)value); }
    }
}

........................

<!--*****************
*** PolyTile.xaml ***
******************-->
<local:Tile x:Name="__PolyTile__" x:Class="WPFApplication1.PolyTile"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WPFApplication1">
    <Grid Name="grdMain">
        <Polygon Name="OuterPoly" />
            <!--OuterPoly needs to be data-bound (OneWay)
            to the OuterShape property of the Tile UserControl.
            That way, when my derived class sets OuterShape to a new <Polygon> element,
            OuterShape will show up on the screen as a <Polygon>-->
    </Grid>
</local:Tile>

........................

/****************
** PolyTile.cs **
****************/
// This class encapsulates a tile that is in the shape of a specified <Polygon> element.
/// <summary>
/// Interaction logic for PolyTile.xaml
/// </summary>
public partial class PolyTile : Tile // Derived from Tile
{
    public PolyTile()
    {
        InitializeComponent();

        System.Windows.Data.BindingExpressionBase BindExp;
        PolyConverter polyConverter = new PolyConverter();

        System.Windows.Data.Binding PolyBinding = new System.Windows.Data.Binding("OuterPoly");
        PolyBinding.Source = __PolyTile__;
        PolyBinding.Mode = System.Windows.Data.BindingMode.OneWayToSource;
        PolyBinding.Converter = polyConverter;
        BindExp = __PolyTile__.SetBinding(Tile.OuterShapeProperty, PolyBinding);
    }



    // The framework won't convert a base object into a derived object without an explicit cast ...
    // ... in this case, that means I have to make a custom DataConverter.
    [System.Windows.Data.ValueConversion(typeof(System.Windows.Shapes.Polygon), typeof(System.Windows.Shapes.Shape))]
    protected class PolyConverter : System.Windows.Data.IValueConverter
    {
        public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {    return value;    }
        // OneWayToSource binding: Thus the Convert method is never used.
        // Rather, ConverBack is used to cast the base <Shape> into a derived <Polygon>

        public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (value.GetType() == typeof(System.Windows.Shapes.Polygon))
                return value;
            else
                return System.Windows.DependencyProperty.UnsetValue;
        }
    }
}

I don't see any possible way to set this binding in XAML. (Although I don't understand DataContexts well enough to say that I've thought of everything ...)

Therefore, I tried setting the binding manually in the back-code. I've tried many different ways of organizing the binding path and source and target elements. Every time I get a fatal exception or a PathError or an UpdateSourceError something similar.

Does anyone know what I am doing wrong?
Any advice is greatly appreciated!

+1  A: 

you can do this a couple of ways

you can use relative source: -

  <TextBlock
     Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ParentUserControl}}, Path=MyProperty}" />

or you can use element name bindings, which i think are easier to read. (you have to use the x:Name syntax on the user control to name the whole control in the xaml file.

  <TextBlock
     Text="{Binding ElementName=childUserControl, Path=MyProperty}" />

here is a little test project i wrote to test this

Aran Mulholland
This almost answers my question. Could you rewrite this code a little bit, so that you are binding the child TextBox control to a TextBox property of the parent? I'm trying to bind the control itself - not just a property of the control, such as "Text"
Giffyguy
A: 

It can be done using XAML object-syntax.

It is important to note that the default data context works perfectly, and problems will arise if you attempt to get very explicit. For example: The binding will fail if you add ElementName="__PolyTile__" to the binding.

It also appears that the data-converter is not required in this solution. That makes the code much easier to follow, though perhaps at the cost of a little type-saftey.

<!--*****************
*** PolyTile.xaml ***
******************-->
<local:Tile x:Name="__PolyTile__" x:Class="WPFApplication1.PolyTile"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WPFApplication1">

    <local:Tile.OuterShape>
        <Binding Path="OuterPoly" Mode="OneWayToSource" />
    </local:Tile.OuterShape>

    <Grid Name="grdMain">
        <Polygon Name="OuterPoly" />
    </Grid>

</local:Tile>
Giffyguy