English 中文(简体)
Surface development: Translate/Rotate/Scale items without ScatterView
原标题:

Is it possible to transalte/rotate/scale items without a ScatterView? I want to manipulate items which can be on top of other elements like a button, list or a custom control which should be static. When I add them to a ScatterView, they all become ScatterViewItems which is not the desired effect.

问题回答

Expanding a bit on Mark s answer...

Yes, you can use the manipulation and inertia API s to accomplish this, see this overview page.

A while back I created my own very basic scatterview control that essentially did what scatterview does, but with the following limitations:

  • Only one child, so it works more like a Border
  • No default visual appearance or special behavior of the child item like SV does

One problem that hit me while developing this is that you have to make your container control occupy a larger area than the actual child. Otherwise, you will not be able to reliably capture the contact events when the fingers are outside your manipulated object. What I did was making my container fullscreen (1024x768) and be transparent and it works ok for my case.

For the manipulation itself, you use an instance of the Affine2DManipulationProcessor class. This class requires you to start the manipulation and then it will constantly feed you with delta event (Affine2DManipulationDelta).

If you want your manipulation to have a more real behavior after the user releases their fingers, you will use the Affine2DInertiaProcessor class which works in a similar way to the manipulation processor. The basic setup is that as soon as the manipulation processor is done (user released fingers) you tell the inertia processor to start.

Let s look at some code :) Here s my setup method in my container class:

private void InitializeManipulationInertiaProcessors()
{
    manipulationProcessor = new Affine2DManipulationProcessor(
      Affine2DManipulations.TranslateY | Affine2DManipulations.TranslateX |
      Affine2DManipulations.Rotate | Affine2DManipulations.Scale, 
      this);
    manipulationProcessor.Affine2DManipulationCompleted += new EventHandler<Affine2DOperationCompletedEventArgs>(processor_Affine2DManipulationCompleted);
    manipulationProcessor.Affine2DManipulationDelta += new EventHandler<Affine2DOperationDeltaEventArgs>(processor_Affine2DManipulationDelta);
    inertiaProcessor = new Affine2DInertiaProcessor();
    inertiaProcessor.Affine2DInertiaDelta += new EventHandler<Affine2DOperationDeltaEventArgs>(processor_Affine2DManipulationDelta);
}

To start it all, I trap ContactDown in my container class:

protected override void OnContactDown(ContactEventArgs e)
{
    base.OnContactDown(e);
    e.Contact.Capture(this);
    // Tell the manipulation processor what contact to track and it will 
    // start sending manipulation delta events based on user motion.
    manipulationProcessor.BeginTrack(e.Contact);
    e.Handled = true;
}

That s all, now sit back and let the manipulation processor do its work. Whenever it has new data it will raise the delta event (happens several times / second while user moves the fingers). Note that it is up to you as a consumer of the processor to do something with the values. It will only tell you things like "user has applied a rotation of X degrees" or "user moved finger X,Y pixels". What you typically do then is to forward these values to rendertransforms to actually show the user what has happened.

In my case, my child object has three hard coded rendertransforms: "translate", "rotate" and "scale" which I update with the values from the processor. I also do some boundary checking to make sure the object isn t moved outside the surface or scaled too large or too small:

/// <summary>
/// This is called whenever the manipulator or the inertia processor has calculated a new position
/// </summary>
/// <param name="sender">The processor who caused the change</param>
/// <param name="e">Event arguments containing the calculations</param>
void processor_Affine2DManipulationDelta(object sender, Affine2DOperationDeltaEventArgs e)
{            
    var x = translate.X + e.Delta.X;
    var y = translate.Y + e.Delta.Y;
    if (sender is Affine2DManipulationProcessor)
    {
        // Make sure we don t move outside the screen
        // Inertia processor does this automatically so only adjust if sender is manipulation processor
        y = Math.Max(0, Math.Min(y, this.ActualHeight - box.RenderSize.Height));
        x = Math.Max(0, Math.Min(x, this.ActualWidth - box.RenderSize.Width));
    }
    translate.X = x;
    translate.Y = y;
    rotate.Angle += e.RotationDelta;
    var newScale = scale.ScaleX * e.ScaleDelta;
    Console.WriteLine("Scale delta: " + e.ScaleDelta + " Rotate delta: " + e.RotationDelta);
    newScale = Math.Max(0.3, Math.Min(newScale, 3));
    scale.ScaleY = scale.ScaleX = newScale;
}

One thing to notice here is that both the manipulation and the inertia processor uses the same callback for delta events.

The final piece of the puzzle is when the user releases the finger and I want to start the inertia processor:

/// <summary>
/// This is called when the manipulator has completed (i.e. user released contacts) and we let inertia take over movement to make a natural slow down
/// </summary>
void processor_Affine2DManipulationCompleted(object sender, Affine2DOperationCompletedEventArgs e)
{
    inertiaProcessor.InitialOrigin = e.ManipulationOrigin;

    // Set the deceleration rates. Smaller number means less friction (i.e. longer time before it stops)
    inertiaProcessor.DesiredAngularDeceleration = .0010;
    inertiaProcessor.DesiredDeceleration = .0010;
    inertiaProcessor.DesiredExpansionDeceleration = .0010;
    inertiaProcessor.Bounds = new Thickness(0, 0, this.ActualWidth, this.ActualHeight);
    inertiaProcessor.ElasticMargin = new Thickness(20);

    // Set the initial values.
    inertiaProcessor.InitialVelocity = e.Velocity;
    inertiaProcessor.InitialExpansionVelocity = e.ExpansionVelocity;
    inertiaProcessor.InitialAngularVelocity = e.AngularVelocity;

    // Start the inertia.
    inertiaProcessor.Begin();
}

yes, you can use the ManipulationProcessor s that come with the API





相关问题
WPF convert 2d mouse click into 3d space

I have several geometry meshes in my Viewport3D, these have bounds of (w:1800, h:500, d:25). When a user clicks in the middle of the mesh, I want the Point3D of (900, 500, 25)... How can I achieve ...

Editing a xaml icons or images

Is it possible to edit a xaml icons or images in the expression design or using other tools? Is it possible to import a xaml images (that e.g you have exported) in the expression designer for editing?...

WPF: writing smoke tests using ViewModels

I am considering to write smoke tests for our WPF application. The question that I am faced is: should we use UI automation( or some other technology that creates a UI script), or is it good enough to ...

WPF - MVVM - NHibernate Validation

Im facing a bit of an issue when trying to validate a decimal property on domain object which is bound to a textbox on the view through the viewmodel. I am using NHibernate to decorate my property on ...

How do WPF Markup Extensions raise compile errors?

Certain markup extensions raise compile errors. For example StaticExtension (x:Static) raises a compile error if the referenced class cannot be found. Anyone know the mechanism for this? Is it baked ...

WPF design-time context menu

I am trying to create a custom wpf control, I m wondering how I can add some design-time features. I ve googled and can t seem to get to my goal. So here s my simple question, how can I add an entry ...

How to combine DataTrigger and EventTrigger?

NOTE I have asked the related question (with an accepted answer): How to combine DataTrigger and Trigger? I think I need to combine an EventTrigger and a DataTrigger to achieve what I m after: when ...

热门标签