Freigeben über


A couple of Manipulation tips

One of the many cool features about Windows Phone 7 and Silverlight is the manipulation API, which provides super-easy access to manipulation information such as finger drags and other gestures. A basic intro post on manipulation can be found here. There are a couple of things that trip people up though, and I hope to address them in this post.

The first problem is easily stated as "why am I not getting manipulation events?" You have an object setup in XAML and you have hooked up the manipulation handlers, maybe something like this:

<

Border ManipulationStarted="BorderManipulationStarted"

ManipulationDelta="BorderManipulationDelta">

<!--Stuff-->

</

Border>

You wonder why BorderManipulationStarted never gets called... except maybe sometimes if you happen to be hovering over some content (like a TextBlock or a Button) when you put your finger down, in which case it works like a champ. The problem is that you do not have a Background set. Without a background, you will not get any manipulation events (or mouse events, for that matter!) and they will instead go right through your element to whatever happens to be laying behind it.

The solution, of course, is to add a background. But in many cases you don't actually want a visible background because you want the wonderful content in the background to show through. No problems! You can set your Background to a Transparent brush and all is well! You get to pick up manipulation events while the user gets to see the coolness below.

<

Border ManipulationStarted="BorderManipulationStarted"

ManipulationDelta="BorderManipulationDelta"

Background="Transparent">

<!--Stuff-->

</

Border>

The next problem is easily stated as "if the user begins a manipulation on a control (like a button) I don't want them to get a click event." You have some UI that includes controls like Buttons or CheckBoxes and you want to manipulate the content without firing off clicks.

The good news is that the built-in ScrollViewer (and hence things like ListBox) automatically cancel the Click events for you. But any time you're building custom UI and want to avoid this situation, you have to do the dirty work yourself. The good news is that the solution is easy, you just have to know what it is! The cause of this problem is that controls like Button will capture the mouse (no, not the flag!) in order to imitate Windows' intriguing-but-sometimes-useful behaviour of allowing you to move in and out of a controls' bounds with the mouse down and still trigger a "click" if you mouse up while inside the control.

(The most useful application of this feature, at least to me, is when you are looking at a long document and want to view some text above / below without losing your cursor placement. Grab the scrollbar, move it up (or down) until you can see the text you want, then move the mouse away from the scrollbar and let go. If you're lucky, the scroll will be cancelled and you'll be right back where you left off... although some apps with custom scroll bars don't work like this).

Back to the point of the post... if you want to cancel the Click event (and anything else), you need to release the mouse capture on the element that has the capture. Unfortunately, Silverlight doesn't have WPF's Mouse.Captured property, nor does it have the UIElement.IsMouseCaptured property, so the only option is to look at all the elements from the manipulation's OriginalSource up to the container in question and ask them all to "let go" (so to speak).

Simple code to do this:

// Kill mouse capture on all objects from originalSource to container

void KillMouseCapture(object originalSource, object container)

{

UIElement element = originalSource as UIElement;

while (element != null && element != container)

{

element.ReleaseMouseCapture();

element =

VisualTreeHelper.GetParent(element) as UIElement;

}

}

You can call this in the ManipulationDelta handler so that as soon as the user starts moving their finger, you cancel any Click, Checked, SelectionChanged, or other events that might otherwise have happened. As a performance tip, you can also set a flag to only call this for the first delta you get during each manipulation, remembering to re-set the flag when manipulation starts again!

bool firstDelta = true;

void BorderManipulationStarted(object sender, ManipulationStartedEventArgs e)

{

// Reset the flag so we kill capture on the first delta

firstDelta =

true;

}

void BorderManipulationDelta(object sender, ManipulationDeltaEventArgs e)

{

if (firstDelta)

KillMouseCapture(e.OriginalSource, ContentBorder);

firstDelta =

false;

// Rest of your manipulation logic

}

A full sample that illustrates both these tips is attached.

TwoGestureTips.zip