Click here to Skip to main content
15,886,788 members
Articles / Desktop Programming / XAML

PanView - The Design

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
19 Oct 2012CPOL2 min read 8.4K   1  
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!

This article was originally posted at http://w8isms.blogspot.com/2012/07/panview-design.html

License

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


Written By
Software Developer (Senior) LECO Corporation
United States United States
John Hauck has been developing software professionally since 1981, and focused on Windows-based development since 1988. For the past 17 years John has been working at LECO, a scientific laboratory instrument company, where he manages software development. John also served as the manager of software development at Zenith Data Systems, as the Vice President of software development at TechSmith, as the lead medical records developer at Instrument Makar, as the MSU student who developed the time and attendance system for Dart container, and as the high school kid who wrote the manufacturing control system at Wohlert. John loves the Lord, his wife, their three kids, and sailing on Lake Michigan.

Comments and Discussions

 
-- There are no messages in this forum --