Click here to Skip to main content
15,906,081 members
Articles / Programming Languages / XML

Multi-input: How to Scale Around a Specific Point and Not the Center of the Element

Rate me:
Please Sign up or sign in to vote.
3.67/5 (2 votes)
21 Feb 2010CPOL2 min read 9.9K   3  
Multi-input: How to scale around a specific point and not the center of the Element

The Problem

The most popular control which has been brought by the Microsoft SDK is certainly the scatterView. Each item is positioned at a random place with a random orientation.

ExampleOfScatterView

You can then rotate, move or scale them with your fingers. Here we will focus on this last point: the scaling. This is a really nice feature and you may want to put it in your application (it may also be replaced by a mouse wheel or stylus events, etc.).

If a user wants to zoom-in on a specific part of the presented items, he will do a 'scale manipulation' with its fingers on the specific part.

Simple, will you think: we just have to change the width and the height of the control based on the scale delta! But the problem is that the control will grow but the specific part wanted by the user will no more be under its fingers. A figure is worth a thousand words:

Schema example

My Solution

Here, we are going to scale a scatterViewItem with the property 'CanMove' set to false. We do it because the scatterView item does already what we want and this is done by a translation.

Also, we are going to use a Affine2DManipulationProcessor which will give us the scale value for a manipulation done by multiple fingers. If some are catching stylus events, you could use a ManipulationProcessor from the multiTouch SDK (available here).

The XAML is as follows:

XML
<s:ScatterView VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
      <s:ScatterViewItem x:Name="_myObjectToScale" Orientation="0" CanRotate="False" 
            CanScale="False" CanMove="False"
            Center="512.0,384.0"  ShowsActivationEffects="False"
            PreviewContactDown="_myObjectToScale_ContactDown" 
            PreviewContactUp="_myObjectToScale_ContactUp">
         <Image Source="Resources/myself.jpg" />
      </s:ScatterViewItem>

The code is as given below:

C#
private Affine2DManipulationProcessor _ourManipProc;
public Affine2DManipulationProcessor OurManipProc { 
   get { return _ourManipProc; } 
   set { _ourManipProc = value; }
 }
 
public SurfaceWindow1()
{
    InitializeComponent();
    DataContext = this;
    _ourManipProc = new Affine2DManipulationProcessor(Affine2DManipulations.Scale, this);
 
    //Catch the event from our manipulation processor
    OurManipProc.Affine2DManipulationDelta += OurManipProc_Affine2DManipulationDelta;
}
 
private void _myObjectToScale_ContactDown(object sender, ContactEventArgs e)
{
    //this contact is tracked by our MP
    OurManipProc.BeginTrack(e.Contact);
}
 
private void _myObjectToScale_ContactUp(object sender, ContactEventArgs e)
{
    //this contact is no more tracked by our MP
    OurManipProc.EndTrack(e.Contact);
}

Then the important part, the Affine2DManipulationDelta handler which will do what we want, I will describe it below.

C#
void OurManipProc_Affine2DManipulationDelta
	(object sender, Affine2DOperationDeltaEventArgs e)
{
    double scaleDelta = e.ScaleDelta;
    if (scaleDelta == 1.0) return;
 
 
    Point manipOrigin = e.ManipulationOrigin;
    Point oldCenter = new Point(_myObjectToScale.Center.X, _myObjectToScale.Center.Y);
 
    double oldHeight = _myObjectToScale.ActualHeight;
    double newHeight = _myObjectToScale.ActualHeight * scaleDelta;
 
    double oldWidth = _myObjectToScale.ActualWidth;
    double newWidth = _myObjectToScale.ActualWidth * scaleDelta;
 
    _myObjectToScale.Height = newHeight;
    _myObjectToScale.Width = newWidth;
 
    double ratioX = Math.Abs(manipOrigin.X - oldCenter.X) / (oldWidth / 2);
    double newCenterXD = ratioX
        * Math.Sign(oldCenter.X - manipOrigin.X) * (newWidth - oldWidth) / 2;
 
    double ratioY = Math.Abs(manipOrigin.Y - oldCenter.Y) / (oldHeight / 2);
    double newCenterYD = ratioY *
        Math.Sign(oldCenter.Y - manipOrigin.Y) * (newHeight - oldHeight) / 2;
 
    if (scaleDelta > 1.0)
        _myObjectToScale.Center += new Vector(newCenterXD, newCenterYD);
    else
        _myObjectToScale.Center += new Vector(newCenterXD, newCenterYD);
}

Explanation

First, we need to calculate the new size of our control. This is done by multiplying its actual size by the scaleDelta given by our processor.

Then, we store some interesting values as the old size, the old center position, etc.

Then we calculate the ratios for X and for Y. What is it? It's ratio of the aimed point (the point on top of which the manipulation is done) and the half of the control size. But why do we need it? Because we want the controls to grow on each side of the aimed point, not only the one near the center. If we do not calculate this, one side of the control would stay at the same position during our manipulation.

algo explanation

Next, we calculate the center delta which is the translation we must operate on our control for the focused point to stay under our fingers (or mouse pointer, or stylus, whatever you want :D).

We finally apply all these measures to our control. That's it!

kick it on DotNetKicks.com Shout it

This article was originally posted at http://blog.lexique-du-net.com/index.php

License

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


Written By
Software Developer http://wpf-france.fr
France (Metropolitan) France (Metropolitan)
Jonathan creates software, mostly with C#,WPF and XAML.

He really likes to works on every Natural User Interfaces(NUI : multitouch, touchless, etc...) issues.



He is awarded Microsoft MVP in the "Client Application Development" section since 2011.


You can check out his WPF/C#/NUI/3D blog http://www.jonathanantoine.com.

He is also the creator of the WPF French community web site : http://wpf-france.fr.

Here is some videos of the projects he has already work on :

Comments and Discussions

 
-- There are no messages in this forum --