[Code Index] by Mike Marynowski

programming for fun and work

Getting Rid of Ugly TransformGroup Blocks in WPF

Just a quick post. If you're obsessed with beautiful and clean XAML code like I am, a real thorn in your side is probably seeing this all over the place in a project with lots of animations:

            <Rectangle Width="100" Height="100" Fill="LightGreen" RenderTransformOrigin="0.5,0.5">
                <Rectangle.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform ScaleX="2.5" ScaleY="1" />
                        <SkewTransform AngleX="-60" />
                        <RotateTransform Angle="145" />
                        <TranslateTransform />
                    </TransformGroup>
                </Rectangle.RenderTransform>
            </Rectangle>

That's because Blend crashes and burns if you don't have the whole damn TransformGroup in there even if all you need is a TranslateTransform to animate over. Wouldn't it be nice to be able to work on your animations in Blend while having something like this instead:

            <Rectangle Width="100" Height="100" Fill="LightGreen"
                       RenderTransform="{data:CompositeTransform ScaleX=2.5, ScaleY=1, SkewX=-60, Rotation=145}"
                       RenderTransformOrigin="0.5,0.5" />

Well you're in luck :) Unlike some other approaches, the following code still works with Blend because it returns a TransformGroup with the expected transforms in the expected order. Here you go:

    public class CompositeTransformExtension : MarkupExtension
    {
        private ScaleTransform _scale = new ScaleTransform();
        private SkewTransform _skew = new SkewTransform();
        private RotateTransform _rotate = new RotateTransform();
        private TranslateTransform _translate = new TranslateTransform();
        
        public CompositeTransformExtension()
        {
        }

        public double CenterX
        {
            get { return _scale.CenterX; }
            set
            {
                _scale.CenterX = value;
                _skew.CenterX = value;
                _rotate.CenterX = value;
            }
        }
                
        public double CenterY
        {
            get { return _scale.CenterY; }
            set
            {
                _scale.CenterY = value;
                _skew.CenterY = value;
                _rotate.CenterY = value;
            }
        }
                
        public double ScaleX
        {
            get { return _scale.ScaleX; }
            set { _scale.ScaleX = value; }
        }

        public double ScaleY
        {
            get { return _scale.ScaleY; }
            set { _scale.ScaleY = value; }
        }
        
        public double SkewX
        {
            get { return _skew.AngleX; }
            set { _skew.AngleX = value; }
        }

        public double SkewY
        {
            get { return _skew.AngleY; }
            set { _skew.AngleY = value; }
        }
                
        public double Rotation
        {
            get { return _rotate.Angle; }
            set { _rotate.Angle = value; }
        }
        
        public double TranslateX
        {
            get { return _translate.X; }
            set { _translate.X = value; }
        }

        public double TranslateY
        {
            get { return _translate.Y; }
            set { _translate.Y = value; }
        }
        
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var group = new TransformGroup();

            group.Children.Add(_scale);
            group.Children.Add(_skew);
            group.Children.Add(_rotate);
            group.Children.Add(_translate);

            return group;
        }
    }