wpfscrollviewerpanning

Gain PanningMode behavior of ScrollViewer with mouse events


I'm attempting to pan around the contents of a ScrollViewer in the same way you would pan around in a PDF document (scroll to zoom in/out, click + drag to pan) ScrollViewer has this functionality built in for Touch events (PanningMode), however this doesn't seem to translate to Click+Drag events. Is there a way to tell it to/emulate this functionality?


Solution

  • Panning is enabled internally by four virtual methods implemented by ScrollViewer:

    OnManipulationCompleted
    OnManipulationDelta
    OnManipulationInertiaStarting
    and OnManipulationStarting
    

    So where are these virtual methods defined. Lets go up the hierarchy. We see that it's called on UIElement within the OnManipulationCompletedThunk (I'm sure there are concomitant methods for the rest as well).

    Everything is still private at this point, we want something to tap into. Unfortunately this is the point at which both reflector and ILSpy failed me (well actually it didn't, the call site is in a different dll (PresentationCore) that I don't have loaded brb). Ok back. Once we look in PresentationCore, we have a vague idea that dependencyProperties are registered statically, so we find the .cctor. There are a couple lines of interest here.

    ManipulationCompletedEvent = Manipulation.ManipulationCompletedEvent.AddOwner(typeof(UIElement));

    and

    EventManager.RegisterClassHandler(typeof(UIElement), ManipulationCompletedEvent, new EventHandler(UIElement.OnManipulationCompletedThunk));

    We see that OnManipulationCompletedThunk is the registered callback for this class handler that listens for ManipulationCompletedEvent. Also, ManipulationCompletedEvent is not originally defined on UIElement, it is borrowed from the Manipulation static class via AddOwner.

    Doing a search for the Manipulation class, I see that it's in the System.Windows.Input namespace within the same assembly. Is it public, yep. Cool! so at this point I know that if I fire the ManipulationCompletedEvent or any of it's buddies, it will eventually call into ScrollViewer. http://msdn.microsoft.com/en-us/library/system.windows.input.manipulation.aspx

    In the documentation for this public static class, I see there are a bunch of interesting and possibly useful methods. The only one that isn't readily obvious is AddManipulator. What does this thing do? Clicks.. reads.. oh, "Each touch point is an IManipulator object. For example, if you use two fingers to resize an object, a TouchDevice, which implements IManipulator, is created for each finger." So TouchDevice is an IManipulator. Maybe that will give me some idea of how to create my own manipulator.

    The Properties on TouchDevice give some clue as to features it supports. It's sort of like a MouseDevice (has the concept of capture, DirectlyOver etc.), but it doesn't support manipulation in the same way. Rather we want to do Manipulation in response to Mouse events. Lets look harder at TouchDevice to see how it really implements some of these features.

    The methods TouchDevice is implementing are GetPosition and ManipultionEnded

    GetPosition returns this.GetTouchPoint(relativeTo).Position; relativeTo is a parameter

    ManipulationEnded calls OnManipulationEnded forwarding a bool parameter named cancel. Not sure what cancel does yet. oh, turns out it's not used, weird but ok. This basically sets capture to null. Kindof at the end of the rabbit hole here so we'll have to back up and reevaluate.

    All I really want to do is raise events manually on the UIElement and see if it works. The RaiseEvent method on UIElement should work for this. going to try brb. Err wait I missed something, all the events defined on the Manipulation class are marked as internal.

    Clearly these events are meant only for internal consumption, and short of doing reflection we don't have an avenue there.

    I think maybe using the Manipulation feature is overkill for what you're trying to do. There is probably a way to implement this with just drag event and a canvas.

    Also, found this while googling and thought it might be of some relevance http://multitouchvista.codeplex.com/