原文:http://blogs.msdn.com/b/wsdevsol/archive/2013/02/16/where-did-all-my-gestures-go.aspx
第一次翻译东西,别见笑,如翻译的有问题请联系我。
欢迎来我的个人博客:http://blog.s2003zy.com
经常在我们的用户哪里被问道:“ 怎么在collection控件中控制pointer输入和处理事件(manipulations),比如在ListViews, GridViews或者是 FlipViews中,应用程序可以在View中的items获取到PointerPressed事件,但是在那之后点击事件(pointer events)就消失了,应用程序无法将其转换成手势或操作。”
这里到底发生了什么?
这些控件的共同点是,他们都拥有一个ScrollViewer,并且ScrollViewer获取了pointer的控制来进行ScrollViewer的滚动。 一旦ScrollViewer获得了所有的pointer和处理事件(manipulation events)权的话,应用程序将没有机会再去控制它了。
这意味着如果应用程序需要在滚动控件中处理Pointer事件的话,需要disable掉ScrollViewer(下面会说说更多)。
为甚么ScrollViewer要获取pointerd?
效率和响应能力,Xaml应用在不同的线程中组合和渲染他们的图形。渲染线程负责保持屏幕流畅的更新。组合线程是应用程序的UI执行并且可以更新控件的布局的地方。每当布局不更新,渲染线程可以保持视觉显示清晰、快捷,即使其他工作正在被完成。非布局的更新,例如如独立的动画和渲染变换,可以在渲染线程上完成,而不会减慢同步两个线程。
因为触碰事件需要很快的响应。Xaml应用程序利用一个叫Direct Manipulation 的Windows特性,在底层的渲染线程上来处理。Direct Manipulation 可以探测到的触控事件比如滚动,平移和缩放。ScrollViewer 利用它迅速利落的进行滚动。应用程序可以在渲染线程中通过Xaml Manipulation 事件监听到Direct Manipulation,来实现缩放、平移、旋转变换等渲染变换并快速响应。
Pointer事件就是去那儿了,当ScrollViewer处理PointerPressed event时Direct Manipulation引擎接手了pointer 的控制,而应用程序不会再接受到任何Pointer事件直到当前的操控结束。
那如果应用程序需要Pointer的信息怎么办?
一个简单的方法是,限制,让一个子元素(比如一个Image)隔断滚动并设置让其受自己控制。 例如设置一个在FlipView的ItemTemplate中的Image的ManipulationMode设置为"All"(ManipulationMode=”All”),这样触控图片的事件就不会触发ScrollViewer。这个图像可以接收到Xaml manipulation事件和pointer 事件,这样就可以将其传递给一个手势识别的处理程序。
如果应用不适用于讲事件传递给一个子元素,那么就有点麻烦了。最好的方式就是限制信息到一个子区域中去或者是利用设置模式。在这种情况下PointerPressed处理者可以判断是否在子区域中或者是非滚动模式,这样的话它就能通过可视化树来找到ScrollViewer结束Direct Manipulation的魔咒了。
为了实现上面的,需要改变ScrollViewer的HorizontalScrollMode 以及 VerticalScrollMode为Disabled。如果你有GridView或FlipView的一个方向的滚动(水平或者是垂直)的话,只需要改变一个味Disabled。
ScrollViewer FindParentScrollViewer(DependencyObject start)
{
DependencyObject item = start;
DependencyObject parent = null;
do
{
parent = (DependencyObject)VisualTreeHelper.GetParent(item);
if (parent is ScrollViewer)
{
return parent as ScrollViewer;
}
item = parent;
} while (item != null);
return null;
}
bool InDisableScrollViewerRegion(PointerPoint pt)
{
// For demonstration purposes, scroll at the top and block the rest
return pt.Position.Y > 400;
}
private void OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
DependencyObject depObj = (DependencyObject)e.OriginalSource;
if (InDisableScrollViewerRegion(e.GetCurrentPoint(this)))
{
DisableScrolling((DependencyObject)e.OriginalSource);
}
UIElement target = sender as UIElement;
PointerPoint point = e.GetCurrentPoint(itemFlipView);
gestureRecognizer.ProcessDownEvent(point);
target.CapturePointer(e.Pointer);
e.Handled = true;
}
ScrollMode _originalScrollMode;
bool _scrollingDisabled = false;
private void DisableScrolling(DependencyObject depObj)
{
ScrollViewer parentElem;
parentElem = FindParentScrollViewer(depObj);
if (parentElem != null)
{
_originalScrollMode = parentElem.HorizontalScrollMode;
_scrollingDisabled = true;
parentElem.HorizontalScrollMode = ScrollMode.Disabled;
}
}
private void RestoreScrolling(DependencyObject depObj)
{
if (_scrollingDisabled)
{
_scrollingDisabled = false;
ScrollViewer parentElem = FindParentScrollViewer(depObj);
if (parentElem != null)
{
parentElem.HorizontalScrollMode = _originalScrollMode;
}
}
}
很不幸的是,这不是一个很好的方法,如果应用程序同时需要滚动和手势。这样的情况下,唯一的选择就是在获取Pointer 消息的地方关闭Direct Manipulation,但是这样同样会使滚动失效,为了让应用程序能够进行滚动的手势,需要使用ScrollViewer的ScrollToHorizontalOffset 或者 ScrollToVerticalOffset(8.1里变成ChangeView了)来更新ScrollViewer的位置。这很麻烦而且会比让ScrollViewer自己做事情要慢,应当尽量避免。