Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / XAML

PanView - The Design

0.00/5 (No votes)
19 Oct 2012CPOL2 min read 8.6K  
Design of the PanView custom control discussing both the C# and XAML that achieves the desired functionality

This post describes the design of the PanView custom control, discussing both the C# and XAML that achieves the desired functionality.

Posts in this series:

pan4[2]

PanView is a custom control with the following dependency properties:

  • TransformGroup TransformGroup – The transform to be applied to the canvas
  • double MinTranslateX – A translation constraint
  • double MaxTranslateX – A translation constraint
  • double MinTranslateY – A translation constraint
  • double MaxTranslateY – A translation constraint
XML
<ResourceDictionary
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:local="using:PanViewLibrary">
    <Style
       TargetType="local:PanView">
        <Setter
           Property="Template">
            <Setter.Value>
                <ControlTemplate
                   TargetType="local:PanView">
                    <Grid
                       Background="Transparent">
                        <ContentPresenter
                           RenderTransform="{TemplateBinding TransformGroup}"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

The XAML above shows how the RenderTransform of the content is bound to the custom control’s TransformGroup dependency property.

One approach for developing PanView is to keep appending transformations into the TransformGroup each time the user performs a manipulation. If PanView kept appending each transformation into a transformation group, then the number of transformations would grow each time the user touched the control. This growth would make PanView unreliable in commercial applications. The PanView code demonstrates how to keep the TransformGroup from growing beyond two transformations.

The public method Reset is a quick programmatic way of resetting the transformation back to the default.

The method ConstrainDelta is employed to enforce the user-defined constraints on x/y panning distances.

C#
using Windows.UI.Input;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Markup;
using Windows.UI.Xaml.Media;

namespace PanViewLibrary
{
    [ContentProperty(Name = "Content")]
    public class PanView : ContentControl
    {
        public PanView()
        {
            DefaultStyleKey = typeof(PanView);
            ManipulationMode = ManipulationModes.All;

            _currentTransformation = new CompositeTransform();
            _previousTransformations = new MatrixTransform() { Matrix = Matrix.Identity };
            TransformGroup = new TransformGroup();
            TransformGroup.Children.Add(_previousTransformations);
            TransformGroup.Children.Add(_currentTransformation);

            ManipulationStarting += (sender, args) => { args.Handled = true; };
            ManipulationStarted += OnManipulationStarted;
            ManipulationDelta += OnManipulationDelta;
            ManipulationCompleted += (sender, args) => { args.Handled = true; };
            ManipulationInertiaStarting += (sender, args) => { args.Handled = true; };

            MinTranslateX = double.MinValue;
            MaxTranslateX = double.MaxValue;
            MinTranslateY = double.MinValue;
            MaxTranslateY = double.MaxValue;
        }

        public void Reset()
        {
            _currentTransformation.Reset();
            _previousTransformations.Matrix = Matrix.Identity;
        }

        CompositeTransform _currentTransformation;
        MatrixTransform _previousTransformations;

        void OnManipulationStarted(object sender, ManipulationStartedRoutedEventArgs args)
        {
            _previousTransformations.Matrix = TransformGroup.Value;
            _currentTransformation.Reset();
            _currentTransformation.CenterX = args.Position.X;
            _currentTransformation.CenterY = args.Position.Y;
            args.Handled = true;
        }

        private void OnManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs args)
        {
            var delta = ConstrainDelta(args.Delta);
            _currentTransformation.TranslateX += delta.Translation.X;
            _currentTransformation.TranslateY += delta.Translation.Y;
            _currentTransformation.Rotation += delta.Rotation;
            _currentTransformation.ScaleX *= delta.Scale;
            _currentTransformation.ScaleY *= delta.Scale;
            args.Handled = true;
        }

        private ManipulationDelta ConstrainDelta(ManipulationDelta delta)
        {
            var newTranslateX = _previousTransformations.Matrix.OffsetX + 
            _currentTransformation.TranslateX + delta.Translation.X;

            var tooLittleX = MinTranslateX - newTranslateX;
            if (tooLittleX > 0)
            {
                delta.Translation.X += tooLittleX;
            }

            var tooMuchX = newTranslateX - MaxTranslateX;
            if (tooMuchX > 0)
            {
                delta.Translation.X -= tooMuchX;
            }

            var newTranslateY = _previousTransformations.Matrix.OffsetY + 
            _currentTransformation.TranslateY + delta.Translation.Y;

            var tooLittleY = MinTranslateY - newTranslateY;
            if (tooLittleY > 0)
            {
                delta.Translation.Y += tooLittleY;
            }

            var tooMuchY = newTranslateY - MaxTranslateY;
            if (tooMuchY > 0)
            {
                delta.Translation.Y -= tooMuchY;
            }

            return delta;
        }

        public TransformGroup TransformGroup
        {
            get { return (TransformGroup)GetValue(TransformGroupProperty); }
            private set { SetValue(TransformGroupProperty, value); }
        }

        public static readonly DependencyProperty TransformGroupProperty =
            DependencyProperty.Register("TransformGroup", 
            typeof(TransformGroup), typeof(PanView), new PropertyMetadata(null));

        public double MinTranslateX
        {
            get { return (double)GetValue(MinTranslateXProperty); }
            set { SetValue(MinTranslateXProperty, value); }
        }

        public static readonly DependencyProperty MinTranslateXProperty =
            DependencyProperty.Register("MinTranslateX", 
            typeof(double), typeof(PanView), new PropertyMetadata(null));

        public double MaxTranslateX
        {
            get { return (double)GetValue(MaxTranslateXProperty); }
            set { SetValue(MaxTranslateXProperty, value); }
        }

        public static readonly DependencyProperty MaxTranslateXProperty =
            DependencyProperty.Register("MaxTranslateX", 
            typeof(double), typeof(PanView), new PropertyMetadata(null));

        public double MinTranslateY
        {
            get { return (double)GetValue(MinTranslateYProperty); }
            set { SetValue(MinTranslateYProperty, value); }
        }

        public static readonly DependencyProperty MinTranslateYProperty =
            DependencyProperty.Register("MinTranslateY", 
            typeof(double), typeof(PanView), new PropertyMetadata(null));

        public double MaxTranslateY
        {
            get { return (double)GetValue(MaxTranslateYProperty); }
            set { SetValue(MaxTranslateYProperty, value); }
        }

        public static readonly DependencyProperty MaxTranslateYProperty =
            DependencyProperty.Register("MaxTranslateY", 
            typeof(double), typeof(PanView), new PropertyMetadata(null));
    }
}

The code above is the entirety of the PanView custom control.

We construct the TransformGroup to consist of the two transforms, previousTransformations and currentTransformation.

When starting a manipulation, we make sure to copy (flatten) the TransformGroup’s end result (Value) into _previousTransformations. We also reset _currentTransformation. At this point, the TransformGroups’ end result should be the same.

We record the center point of the manipulation at the start of the manipulation. I’ve seen some code that places this assignment in the ManipulationDelta handler. As far as I can tell on my Samsung Slate, the end result of that variation is very shaky and unreliable performance.

For each ManipulationDelta, we simply update currentTransformation.

That’s it!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)