【问题标题】:Ways to address WPF Touch Screen Sensitivity解决 WPF 触摸屏灵敏度问题的方法
【发布时间】:2014-09-04 05:43:22
【问题描述】:

我正在尝试解决电容式触摸屏的灵敏度问题,如果用户的手指太靠近屏幕表面,则会触发 WPF 按钮。

此问题是,许多用户最终将手指或手的一部分(而不是他们的主要触摸手指)靠近屏幕表面,这会导致触发不正确的按钮。

调整屏幕的灵敏度似乎没什么区别,我想我可以尝试修改按钮按下事件,仅在按下按钮超过一定时间时才触发点击。

谁能解释我如何创建一个自定义按钮,该按钮在触发 Clicked 事件之前具有可调整的“按下”时间。

如果可能的话,也许您会很乐意包含一个非常简单的 C#/WPF 应用程序和这样一个自定义按钮。

编辑

好的,所以我使用下面的代码创建了一个子类 Button,根据 @kidshaw 的回答,但我认为我一定遗漏了一些东西,因为除了默认的 Click 事件之外什么都没有被调用。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;

namespace AppName
{
    public class TouchButton : Button
    {
        DoubleAnimationUsingKeyFrames _animation;

        public static readonly DependencyProperty DelayElapsedProperty =
         DependencyProperty.Register("DelayElapsed", typeof(double), typeof(TouchButton), new PropertyMetadata(0d));

        public static readonly DependencyProperty DelayMillisecondsProperty =
                DependencyProperty.Register("DelayMilliseconds", typeof(int), typeof(TouchButton), new PropertyMetadata(100));

        public double DelayElapsed
        {
            get { return (double)this.GetValue(DelayElapsedProperty); }
            set { this.SetValue(DelayElapsedProperty, value); }
        }

        public int DelayMilliseconds
        {
            get { return (int)this.GetValue(DelayMillisecondsProperty); }
            set { this.SetValue(DelayMillisecondsProperty, value); }
        }
        private void BeginDelay()
        {
            this._animation = new DoubleAnimationUsingKeyFrames() { FillBehavior = FillBehavior.Stop };
            this._animation.KeyFrames.Add(new EasingDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(0)), new CubicEase() { EasingMode = EasingMode.EaseIn }));
            this._animation.KeyFrames.Add(new EasingDoubleKeyFrame(1, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(this.DelayMilliseconds)), new CubicEase() { EasingMode = EasingMode.EaseIn }));
            this._animation.Completed += (o, e) =>
            {
                this.DelayElapsed = 0d;
                //this.Command.Execute(this.CommandParameter);    // Replace with whatever action you want to perform
                Console.Beep();
                this.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
            };

            this.BeginAnimation(DelayElapsedProperty, this._animation);
        }

        private void CancelDelay()
        {
            // Cancel animation
            this.BeginAnimation(DelayElapsedProperty, null);
        }
        private void TouchButton_TouchDown(object sender, System.Windows.Input.TouchEventArgs e)
        {
            this.BeginDelay();
        }

        private void TouchButton_TouchUp(object sender, System.Windows.Input.TouchEventArgs e)
        {
            this.CancelDelay();
        }

    }
}

TouchButton_TouchDown 方法是如何被调用的?我不必以某种方式将其分配给 TouchDown 偶数处理程序吗?

好的,我添加了一个构造函数并设置了 TouchDown/Up 事件处理程序,以便可以正常工作,但 CancelDelay() 不会阻止事件被触发。它似乎工作正常,并在用户抬起手指时被调用,但不会阻止事件被触发。

【问题讨论】:

  • 你能不能用一个按钮创建一个用户控件,它会在按下时启动一个计时器,并在 100 毫秒后手动触发点击事件?
  • 问题在于 Windows 触摸驱动程序似乎无法区分轻触或长触。因此,我需要该按钮仅在长按按钮时触发事件(并且需要进行一些实验来确定可用性的最佳持续时间)。我的猜测是按下大约 200 - 300 毫秒,而传递的触发器会少于 200。我想我可以在 Press 上启动计时器并在 Release 上停止它,如果 > X 触发事件。
  • 这就是我的意思;)
  • 与其使用 ClickEvent - 创建一个新的并引发它,比如说 DeferredClickEvent。 ClickEvent 将在第一次单击时由按钮触发,通过将其分离到自己的事件中,您只处理延迟后的延迟触发。
  • 谢谢我已经这样做了,但是我必须添加一个标志IsCancelled,它会在收到TouchUp 事件时设置。因此,当动画完成时,它会检查 IsCancelled = true 是否会引发 IsTouched 事件。然后我必须在引发事件后将 IsTouched 属性重置为 false,因为它必须是一个瞬时属性,我用它来为颜色渐变设置动画。现在在真正的触摸屏上与用户一起测试它!

标签: c# wpf multi-touch


【解决方案1】:

延时按钮是最好的选择。

我在其他堆栈溢出答案中提供了一个示例。

它使用动画来延迟触发命令。

希望对你有帮助。

Do wpf have touch and gold gesture

【讨论】:

  • 谢谢,看起来它会通过确保按钮在最短的时间内关闭来完成这项工作。你能解释一下这段代码的去向,还是全部在一个类文件中。最好提供一个带有单个屏幕和这样一个按钮的小示例应用程序。
  • 在一个类文件中创建它。构建您的应用程序,它应该可以从工具箱中拖入
  • 好的,已经完成了,但是编译器不喜欢 this.GetValuethis.SetValue 调用或 this._animation、this.Command 等。这些需要一些参考吗?
  • 添加了 System.Windows.Controls 而不是 System.Windows.Forms,它修复了除“this._animation”之外的所有内容。
  • 那需要声明为类成员变量或字段
【解决方案2】:

您几乎可以肯定地想出一个解决方案来做到这一点。我会考虑两种方法:

  1. 创建从 Button 派生的特化。您将覆盖各种处理程序以实现您自己的行为。
  2. 创建一个订阅预览鼠标事件的附加依赖属性。预览事件将允许您在标准按钮处理生成点击事件之前拦截向上/向下事件以注入您自己的行为。

选项#1 可能是最容易理解的。生成单击事件的处理位于 OnMouseLeftButtonDown 和 OnMouseLeftButtonUp 处理程序中的 ButtonBase 中。如果您实现(覆盖)您自己的这两个处理程序的版本,您应该能够相当容易地引入一个计时器,该计时器仅在用户按下(并按住)按钮后的某个时间到期后才调用 OnClick 以生成单击事件。

PS:如果您还没有,我强烈建议您获取 .NET Reflector 的副本。它将允许您轻松查看 WPF 按钮实现的代码。我很快用它查看了 WPF 按钮的实现,以了解它是如何工作的,以便回答这个问题。

【讨论】:

    【解决方案3】:

    为了完整起见,这里是我根据@kidshaw 的原始答案使用的完整解决方案。可能会节省其他人一些时间摆弄拼凑在一起。

    请注意,我收到 VS Designer 错误,抱怨在应用程序命名空间中找不到自定义类。奇怪的是,这似乎不会发生在早期版本的 VS 上,所以可能是 VS 中的一个错误。

    TouchButton.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media.Animation;
    
    namespace TouchButtonApp
    {
        public class TouchButton : Button
        {
            DoubleAnimationUsingKeyFrames _animation;
            bool _isCancelled = false;
    
            public static readonly DependencyProperty DelayElapsedProperty =
             DependencyProperty.Register("DelayElapsed", typeof(double), typeof(TouchButton), new PropertyMetadata(0d));
    
            public static readonly DependencyProperty DelayMillisecondsProperty =
                    DependencyProperty.Register("DelayMilliseconds", typeof(int), typeof(TouchButton), new PropertyMetadata(Properties.Settings.Default.ButtonTouchDelay));
    
            public static readonly DependencyProperty IsTouchedProperty =
     DependencyProperty.Register("IsTouched", typeof(bool), typeof(TouchButton), new PropertyMetadata(false));
    
    
            // Create a custom routed event by first registering a RoutedEventID 
            // This event uses the bubbling routing strategy 
            public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
                "Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TouchButton));
    
    
            public TouchButton()
            {
                this.TouchDown +=TouchButton_TouchDown;
                this.TouchUp +=TouchButton_TouchUp;
            }
    
            // Provide CLR accessors for the event 
            public event RoutedEventHandler Tap
            {
                add { AddHandler(TapEvent, value); }
                remove { RemoveHandler(TapEvent, value); }
            }
    
            // This method raises the Tap event 
            void RaiseTapEvent()
            {
                if (!_isCancelled)
                {
                    //Console.Beep();
                    this.IsTouched = true;
                    Console.WriteLine("RaiseTapEvent");
                    RoutedEventArgs newEventArgs = new RoutedEventArgs(TouchButton.TapEvent);
                    RaiseEvent(newEventArgs);
                }
            }
    
            public bool IsTouched
            {
                get { return (bool)this.GetValue(IsTouchedProperty); }
                set { this.SetValue(IsTouchedProperty, value); }
            }
    
            public double DelayElapsed
            {
                get { return (double)this.GetValue(DelayElapsedProperty); }
                set { this.SetValue(DelayElapsedProperty, value); }
            }
    
            public int DelayMilliseconds
            {
                get { return (int)this.GetValue(DelayMillisecondsProperty); }
                set { this.SetValue(DelayMillisecondsProperty, value); }
            }
    
            //Start the animation and raise the event unless its cancelled
            private void BeginDelay()
            {
                _isCancelled = false;
                Console.WriteLine("BeginDelay ");
                this._animation = new DoubleAnimationUsingKeyFrames() { FillBehavior = FillBehavior.Stop };
                this._animation.KeyFrames.Add(new EasingDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(0)), new CubicEase() { EasingMode = EasingMode.EaseIn }));
                this._animation.KeyFrames.Add(new EasingDoubleKeyFrame(1, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(this.DelayMilliseconds)), new CubicEase() { EasingMode = EasingMode.EaseIn }));
                this._animation.Completed += (o, e) =>
                {
                    this.DelayElapsed = 0d;
                    //this.Command.Execute(this.CommandParameter);    // Replace with whatever action you want to perform     
    
                    RaiseTapEvent();
                    this.IsTouched = false;
                };
    
                this.BeginAnimation(DelayElapsedProperty, this._animation);
            }
    
            private void CancelDelay()
            {
                // Cancel animation
                _isCancelled = true;
                Console.WriteLine("CancelDelay ");
                this.BeginAnimation(DelayElapsedProperty, null);
            }
            private void TouchButton_TouchDown(object sender, System.Windows.Input.TouchEventArgs e)
            {
                this.BeginDelay();
            }
    
            private void TouchButton_TouchUp(object sender, System.Windows.Input.TouchEventArgs e)
            {
                this.CancelDelay();
            }
    
        }
    }
    

    App.xaml

    中触发 IsTouched 事件时的自定义动画
    <Style x:Key="characterKeyT" TargetType="{x:Type local:TouchButton}">
        <Setter Property="Focusable" Value="False" />
        <Setter Property="HorizontalContentAlignment" Value="Center"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="Padding" Value="1"/>
        <Setter Property="Margin" Value="6,4,8,4"/>
        <Setter Property="FontSize" Value="24"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:TouchButton}">
                    <Grid x:Name="grid">
                        <Border x:Name="border" CornerRadius="0">                                
                            <Border.Background>
                                <SolidColorBrush x:Name="BackgroundBrush" Color="{Binding Source={StaticResource settingsProvider}, Path=Default.ThemeColorPaleGray2}"/>
                            </Border.Background>
                            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" TextElement.Foreground="Black" 
                                              TextElement.FontSize="24"></ContentPresenter>
                        </Border>
                    </Grid>
                    <ControlTemplate.Resources>
                        <Storyboard x:Key="FadeTimeLine" BeginTime="00:00:00.000" Duration="00:00:02.10">
                            <ColorAnimation Storyboard.TargetName="BackgroundBrush" Storyboard.TargetProperty="Color"                                                 
                                             To="#FF22B0E6" 
                                            Duration="00:00:00.10"/>
                            <ColorAnimation Storyboard.TargetName="BackgroundBrush" Storyboard.TargetProperty="Color"                                                 
                                             To="#FFECE8E8" 
                                            Duration="00:00:02.00"/>
                        </Storyboard>
                    </ControlTemplate.Resources>
                    <ControlTemplate.Triggers>                            
                        <Trigger Property="IsTouched" Value="True">
                            <Trigger.EnterActions>
                                <BeginStoryboard Storyboard="{StaticResource FadeTimeLine}"/>
                            </Trigger.EnterActions>
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource ThemeSolidColorBrushPaleGray}"/>
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="False">
                            <Setter Property="Background" TargetName="border"  Value="{StaticResource ThemeSolidColorBrushPaleGray2}"/>
                        </Trigger>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter Property="Opacity" TargetName="grid" Value="0.25"/>
                        </Trigger>
    
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    XAML 用法

    <UserControl x:Class="TouchButtonApp.Keyboard1"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:TouchButtonApp"
                 mc:Ignorable="d" 
                 d:DesignHeight="352" d:DesignWidth="1024">
        <Grid>
            <Grid Margin="0,0,0,0">
                <Grid.RowDefinitions>
                    <RowDefinition Height="90*"/>
                    <RowDefinition Height="90*"/>
                    <RowDefinition Height="90*"/>
                    <RowDefinition Height="90*"/>
                </Grid.RowDefinitions>
                <Grid>
                    <Grid.RowDefinitions>
    
                        <RowDefinition Height="8*"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="93*"/>
                        <ColumnDefinition Width="93*"/>
                        <ColumnDefinition Width="93*"/>
                        <ColumnDefinition Width="93*"/>
                        <ColumnDefinition Width="93*"/>
                        <ColumnDefinition Width="93*"/>
                        <ColumnDefinition Width="93*"/>
                        <ColumnDefinition Width="93*"/>
                        <ColumnDefinition Width="93*"/>
                        <ColumnDefinition Width="93*"/>
                        <ColumnDefinition Width="93*"/>
                    </Grid.ColumnDefinitions>
                    <local:TouchButton x:Name="qButton" Tap="Button_Click" Content="Q"  Grid.Row="1" Style="{DynamicResource characterKeyT}" />
                    <local:TouchButton x:Name="wButton" Tap="Button_Click" Content="W"  Grid.Column="1" Grid.Row="1" Style="{DynamicResource characterKeyT}" />
    ...
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-07-04
      • 1970-01-01
      • 1970-01-01
      • 2023-01-24
      • 1970-01-01
      • 2011-10-19
      • 1970-01-01
      • 2015-01-26
      相关资源
      最近更新 更多