Question

my problem is the following: In my program I let the user place shapes (class DrawingShape) on a Canvas. The Drawing Shape encapsulates a stacked path and label:

<UserControl x:Class="HandballTrainerFluent.Graphics.DrawingShape"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d"
             d:DesignHeight="60"
             d:DesignWidth="60"
             DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid x:Name="container" Width="Auto" Height="Auto">
        <Grid.RowDefinitions>
            <RowDefinition Height="38"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Canvas x:Name="geometryCanvas" HorizontalAlignment="Center" Grid.Row="0" Width="38" Height="38">
            <Path x:Name="Path"
                  Width="35.8774"
                  Height="31.2047"
                  Canvas.Left="1.0613"
                  Canvas.Top="3.29528"
                  Stretch="Fill"
                  StrokeLineJoin="Round"
                  Stroke="{Binding OutlineBrush,Mode=OneWay}"
                  StrokeThickness="{Binding OutlineWidth,Mode=OneWay}"
                  StrokeDashArray="{Binding OutlinePattern,Mode=OneWay}"
                  Fill="{Binding FillBrush,Mode=OneWay}"
                  Data="F1 M 19,3.79528L 1.5613,34L 36.4387,34L 19,3.79528 Z "/>
        </Canvas>
        <TextBlock x:Name="TextBox" HorizontalAlignment="Center" Grid.Row="1" Text="{Binding LabelText,Mode=OneWay}"></TextBlock>
    </Grid>
</UserControl>

So some visual setting and the label text are bound to Properties of the code-behind file.

After deserializing a Canvas with these Drawing shapes, I need to restore the binding between the XAML and the code-behind file. I've tried this, but it does not seem to work:

private void RepairBindingsAfterLoading()
{
    foreach (UIElement element in this.drawingCanvas.Children)
    {
        if (element.GetType() == typeof(DrawingShape))
        {
            DrawingShape shape = (DrawingShape)element;
            BindingOperations.ClearAllBindings(shape.Path);
            BindingOperations.ClearAllBindings(shape.TextBox);
            BindingOperations.ClearAllBindings(shape);

            shape.BeginInit();
            Binding dataContextBinding = new Binding();
            dataContextBinding.RelativeSource = RelativeSource.Self;
            shape.SetBinding(DrawingShape.DataContextProperty, dataContextBinding);

            Binding fillBinding = new Binding("FillBrush");
            shape.Path.SetBinding(Path.FillProperty, fillBinding);
            Binding outlineBinding = new Binding("OutlineBrush");
            shape.Path.SetBinding(Path.StrokeProperty, outlineBinding);
            Binding widthBinding = new Binding("OutlineWidth");
            shape.Path.SetBinding(Path.StrokeThicknessProperty, widthBinding);
            Binding patternBinding = new Binding("OutlinePattern");
            shape.Path.SetBinding(Path.StrokeDashArrayProperty, patternBinding);

            Binding labelTextBinding = new Binding("LabelText");
            shape.TextBox.SetBinding(TextBlock.TextProperty, labelTextBinding);
            shape.EndInit();
            shape.UpdateLayout();
        }
    }
}

No matter what I do to the code-behind Properties (e.g. change FillBrush), the visuals of the displayed DrawingShape won't update. Am I missing an important step here?

I've added shape.BeginUpdate() and shape.EndUpdate() after seeing this question: Bindings not applied to dynamically-loaded xaml

Thanks a lot for any insights

Edit 2012-09-25

Looking at another piece of code which does not depend on any bindings makes me wonder, if I can actually reference any elements from the Xaml-Definition via their x:Name after de-serialization. The following callback does not do anything on a shape:

private void rotateClockwiseMenuItem_Click(object sender, RoutedEventArgs e)
{
    if(this.drawingCanvas.SelectedItem.GetType() == typeof(DrawingShape))
    {
        DrawingShape shape = (DrawingShape)this.drawingCanvas.SelectedItem;
        TransformGroup transformStack = new TransformGroup();
        transformStack.Children.Add(shape.geometryCanvas.LayoutTransform);
        transformStack.Children.Add(new RotateTransform(90));
        shape.geometryCanvas.LayoutTransform = transformStack;
    }
}

Debugging tells me that the contents of shape seem just right. When I execute the command once, shape.geometryCanvas.LayoutTransformis the identity matrix. When executing it a second time, shape.geometryCanvas.LayoutTransform is a TransformGroup of two elements.

It somehow looks like the reference for geometryCanvas (declared in the Xaml) is no the one used on screen.

Was it helpful?

Solution

Got it!

I didn't know that you can't successfully reference x:Name'd XAML elements from outside the code-behind file after de-serialization (that at least seems to be the problem at hand).

A solution is to use FindName() on the UserControl, e.g.:

TextBlock textBox = shape.FindName("TextBox") as TextBlock;

The complete and correct RepairBindingsAfterLoading() looks like this:

private void RepairBindingsAfterLoading()
{
    foreach (UIElement element in this.drawingCanvas.Children)
    {
        if (element.GetType() == typeof(DrawingShape))
        {
            DrawingShape shape = (DrawingShape)element;
            shape.DataContext = shape;

            Path path = shape.FindName("Path") as Path;
            Binding fillBinding = new Binding("FillBrush");
            path.SetBinding(Path.FillProperty, fillBinding);
            Binding outlineBinding = new Binding("OutlineBrush");
            path.SetBinding(Path.StrokeProperty, outlineBinding);
            Binding widthBinding = new Binding("OutlineWidth");
            path.SetBinding(Path.StrokeThicknessProperty, widthBinding);
            Binding patternBinding = new Binding("OutlinePattern");
            path.SetBinding(Path.StrokeDashArrayProperty, patternBinding);

            TextBlock textBox = shape.FindName("TextBox") as TextBlock;
            Binding labelTextBinding = new Binding("LabelText");
            textBox.SetBinding(TextBlock.TextProperty, labelTextBinding);
        }
    }
}

Just for the record, my clumsy

BindingOperations.ClearAllBindings(shape.Path);
BindingOperations.ClearAllBindings(shape.TextBox);
BindingOperations.ClearAllBindings(shape);

works just like the much more simple and elegant solution suggested by dbaseman with:

shape.DataContext = this;

Hope this helps someone else to avoid my mistake :-)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top