WPF --- 觸控式螢幕下的兩個問題

NiueryDiary發表於2024-03-08

引言

本片文章分享一下之前遇到的WPF應用在觸控式螢幕下使用時的兩個問題。

場景

具體場景就是一個配置介面, ScrollViewer 中包含一個StackPanel 然後縱向堆疊,已滾動的方式檢視,然後包含多個 TextBlockTextBox 以及DataGrid ,期間遇到了兩個問題:

  • WPF在觸控式螢幕下,如果有捲軸(ScrollViewer)的情況下,預設包含觸底反饋的功能,就是觸控式螢幕滑動到底或從底滑到頂,介面都會出現抖動的情況。
  • 觸控式螢幕下,當觸點處於 DataGrid 中時,無法滾動介面。

大概像這樣:
image

解決方案

觸底反饋抖動的問題

先來看第一個問題,這個其實是由於 ManipulationBoundaryFeedback 這個事件引起的:

image.png

最簡單的做法,就是在對應包含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中有,觸點在 TextBoxListViewListBox,這一類內建有 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 中無法滾動的問題,就解決了。

小結

總的來說,大部分滑鼠和觸控式螢幕事件是類似的,但是有些場景下,可能兩者不通用的。所以可能需要自行測試一下,保證軟體的穩定性。

本文中的解決方案不一定最完美的解決方案,如果各位看官有更好的解決方案,望不吝賜教。

相關文章