Click here to Skip to main content
12,891,744 members (51,751 online)
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

4.9K views
1 bookmarked
Posted 19 Oct 2012

PanView - The Design

, 19 Oct 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
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
<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.

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)

Share

About the Author

John Michael Hauck
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.

You may also be interested in...

Comments and Discussions

 
-- There are no messages in this forum --
Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.170424.1 | Last Updated 19 Oct 2012
Article Copyright 2012 by John Michael Hauck
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid