Yesterday a friend of mine showed me some WPF code: he defined a transformation in XAML and was trying to alter it at runtime changing part of its transformation matrix (he was doing that substituting the scale transform matrix as a whole); he kept trying but he never saw any result (the object was always rendered with the same dimensions).

I looked at the code and at first I was surprised too to not see it working as expected, I gave him a quick explanation (which, in all honesty, I have to admit it was partially wrong :D) and suggested another way to alter the things that worked.

Later on I had time to double check the things and found what was my wrong assumption...but let’s start from the beginning.

Here’s a snippet of the XAML he posed me:

...
<Ellipse x:Name="spinningWheel" Width="128" Height="128" Canvas.Left="64" Canvas.Top="64" RenderTransformOrigin="0.5,0.5">
  	<Ellipse.RenderTransform>
		<TransformGroup>
			<ScaleTransform x:Name="spinningScaleTx" CenterX="0.5" CenterY="0.5"/>
			<SkewTransform/>
			<RotateTransform/>
			<TranslateTransform/>
		</TransformGroup>
  	</Ellipse.RenderTransform>
...
and in code he was doing something like:

...
this.spinningScaleTx = new ScaleTransform(2,2);
...

expecting the ellipse to double its size...but nothing happened.

A quick test shows why:

this.spinningScaleTx = new ScaleTransform(2,2);
TransformGroup tg = spinningWheel.RenderTransform as TransformGroup;
if (tg.Children.Contains(this.spinningScaleTx))
	MessageBox.Show("We will NEVER see this MessageBox!");

Changing the ScaleTransform object in this way wasn’t altering the TransformGroup associated with the object, me and him where doing the same mistake: we gave it for granted that under the hood the XAML code was converted in something like this metacode:

spinningWheel.RenderTransform = (new TransformGroup()).Children.Add(spinningScaleTx);

Giving an in-dept look with Reflector clarifies everything:

[GeneratedCode("PresentationBuildTasks", "4.0.0.0")]
public class MainWindow : Window, IComponentConnector
{
    // Fields
    ...
    internal ScaleTransform spinningScaleTx;
    internal Ellipse spinningWheel;

    // Methods
    ...
    [EditorBrowsable(EditorBrowsableState.Never), DebuggerNonUserCode]
    void IComponentConnector.Connect(int connectionId, object target)
    {
        switch (connectionId)
        {
            ...
            case 2:
                this.spinningWheel = (Ellipse) target;
                break;

            case 3:
                this.spinningScaleTx = (ScaleTransform) target;
                break;
            ...
        }
    }
	...
}

 

I’ve stripped out non interesting code, here you can see that ‘spinningWheel’ and ‘spinningScaleTx’ are just references to the real objects that are present in the internal object graph that is our window, so changing their instances will not result in altering the graph; a better look at the MSDN documentation would have avoided me (and him) loosing time:

x:Name is a xaml concept, used mainly to reference elements;

"Uniquely identifies object elements for purpose of access to the instantiated element from code-behind or general code. Once applied to a backing programming model, x:Name can be considered equivalent to the variable holding an object reference, as returned by a constructor. the specified x:Name becomes the name of a field that is created in the underlying code when xaml is processed, and that field holds a reference to the object." (MSDN)

So, it's a designer-generated field, which has internal access by default; the correct way to alter properties of an element qualified with an x:Name is to edit its properties directly and not substituting the instances. We could also have modified the RenderTransform of the ellipse changing the ScaleTransform instance there, but it would have be another conceptual mistake because the spinningScaleTx member would have been gone out of sync referencing a ‘dead object’.

Lesson learned: never give anything for granted and double check the documentation (every time!).

Related Content