引言
本篇文章分享一下之前遇到的WPF应用在触摸屏下使用时的两个问题。
场景
具体场景就是一个配置界面, ScrollViewer
中包含一个StackPanel
然后纵向堆叠,以滚动的方式查看,然后包含多个 TextBlock
、 TextBox
以及DataGrid
,期间遇到了两个问题:
WPF在触摸屏下,如果有滚动条( ScrollViewer
)的情况下,默认包含触底反馈的功能,就是触摸屏滑动到底或从底滑到顶,界面都会出现抖动的情况。触摸屏下,当触点处于 DataGrid
中时,无法滚动界面。
解决方案
触底反馈抖动的问题
先来看第一个问题,这个其实是由于 ManipulationBoundaryFeedback
这个事件引起的:
最简单的做法,就是在对应包含ScrollViewer
的 UI 元素绑定它的反馈事件,然后在注册方法中设置 e.Handled = true;
,这样中断了事件继续冒泡或隧道传播,比如这样
// 在Xaml中,在对应的 UIElement 上绑定ManipulationBoundaryFeedback="UIElement_ManipulationBoundaryFeedback"
//Code-Behind中 ,
private void UIElement_ManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e)
{
e.Handled = true;
}
但是这样就需要你在每一个界面都添加该事件,代码冗余,那么就可以使用附加属性的方式,写一个 ManipulationBoundaryFeedbackAttachedProperties
,各个界面直接使用,像这样实现:
public class ManipulationBoundaryFeedbackAttachedProperties
{
public static bool GetIsFeedback(DependencyObject obj)
{
return (bool)obj.GetValue(IsFeedbackProperty);
}
public static void SetIsFeedback(DependencyObject obj, bool value)
{
obj.SetValue(IsFeedbackProperty, value);
}
public static readonly DependencyProperty IsFeedbackProperty =
DependencyProperty.RegisterAttached("IsFeedback", typeof(bool), typeof(UIElement), new PropertyMetadata(true,
(s, e) =>
{
var target = s as UIElement;
if (target != null)
target.ManipulationBoundaryFeedback += Target_ManipulationBoundaryFeedback;
}));
private static void Target_ManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e)
{
var target = sender as UIElement;
if (target != null)
{
if (!GetIsFeedback(target))
{
e.Handled = true;
}
}
}
}
像这样使用:
<ScrollViewer local:ManipulationBoundaryFeedbackAttachedProperties.IsFeedback="true">
...
</ScrollViewer>
这样就可以解决了!
触点在DataGrid中无法滚动的问题
这个问题,其实不光在 DataGrid
中有,触点在 TextBox
、ListView
、ListBox
,这一类内置有 ScrollViewer
的控件内,都有同样的问题,而且不光是触摸屏无法滚动,鼠标滑轮也无法滚动。我处理这个问题的时候,是先处理的鼠标滑轮无法滚动,处理方案就是根据鼠标的偏移量,手动设置 ScrollViewer
的位置,如下:
private void DataGrid_MouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
var dataGrid = (DataGrid)sender;
// 获取
var scrollViewer = GetScrollViewer(dataGrid);
if (scrollViewer != null)
{
if (scrollViewer.ViewportHeight + scrollViewer.VerticalOffset >= scrollViewer.ExtentHeight && e.Delta <= 0)
{
scrollViewer.LineDown();
}
else if (scrollViewer.VerticalOffset == 0 && e.Delta >= 0)
{
scrollViewer.LineUp();
}
}
}
public ScrollViewer GetScrollViewer(UIElement element)
{
if (element == null) return null;
ScrollViewer retour = null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element) && retour == null; i++)
{
if (VisualTreeHelper.GetChild(element, i) is ScrollViewer)
{
retour = (ScrollViewer)(VisualTreeHelper.GetChild(element, i));
}
else
{
retour = GetScrollViewer(VisualTreeHelper.GetChild(element, i) as UIElement);
}
}
return retour;
}
这样就解决了当鼠标位于 DataGrid
中时,使用滑轮界面无法滚动的问题,那么解决触摸屏触点在 DataGrid
中无法滚动的问题,也是一样的思路,根据触点的偏移量,模拟鼠标滚轮的偏移量,在调用鼠标滚动事件,模拟滚动,代码如下:
private const double TouchMoveThreshold = 20; // 触摸滚动的阈值
private Point lastTouchPosition; // 上一次触摸的位置
private void DataGrid_PreviewTouchMove(object sender, System.Windows.Input.TouchEventArgs e)
{
// 获取当前触摸位置
Point currentTouchPosition = e.GetTouchPoint((IInputElement)sender).Position;
// 计算触摸移动的差值
double deltaY = currentTouchPosition.Y - lastTouchPosition.Y;
// 如果触摸移动超过阈值,则模拟鼠标滚动
if (Math.Abs(deltaY) > TouchMoveThreshold)
{
// 设置鼠标滚动的差值
int mouseWheelDelta = (int)(deltaY / TouchMoveThreshold) * SystemParameters.WheelScrollLines;
// 创建模拟的鼠标滚动事件参数
var mouseWheelEventArgs = new MouseWheelEventArgs(Mouse.PrimaryDevice, Environment.TickCount, mouseWheelDelta);
mouseWheelEventArgs.RoutedEvent = UIElement.MouseWheelEvent;
DataGrid_MouseWheel(sender, mouseWheelEventArgs);
// 更新上一次触摸位置
lastTouchPosition = currentTouchPosition;
}
}
这样,触摸屏下,触点在 DataGrid
中无法滚动的问题,就解决了。
小结
总的来说,大部分鼠标和触摸屏事件是类似的,但是有些场景下,可能两者不通用的。所以可能需要自行测试一下,保证软件的稳定性。
本文中的解决方案不一定最完美的解决方案,如果各位看官有更好的解决方案,望不吝赐教。