解决WPF触摸屏灵敏度的方法

时间:2014-09-04 05:43:23

标签: c# wpf multi-touch

我正在尝试解决电容式触摸屏的灵敏度问题,如果用户手指太靠近屏幕表面,则会触发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()不会阻止事件被触发。它似乎工作正常,并在用户抬起手指时被调用,但不会阻止事件被触发。

3 个答案:

答案 0 :(得分:0)

时间延迟按钮是最佳选择。

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

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

希望它有所帮助。

Do wpf have touch and gold gesture

答案 1 :(得分:0)

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

  1. 创建从Button派生的特化。您可以覆盖各种处理程序来实现自己的行为。
  2. 创建一个附加的依赖项属性,该属性可以预订预览鼠标事件。预览事件允许您在标准按钮处理生成点击事件之前拦截上/下事件以注入您自己的行为。
  3. 选项#1可能是最容易理解的。生成单击事件的处理位于OnMouseLeftButtonDown和OnMouseLeftButtonUp处理程序的ButtonBase中。如果您实现(覆盖)这两个处理程序的自己版本,您应该能够相当容易地引入一个计时器,该计时器仅在用户按下(并按下)按钮后一定时间到期后才调用OnClick以生成单击事件。

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

答案 2 :(得分:0)

为了完整起见,这里是我根据@ kidshaw原始答案使用的完整解决方案。可能会拯救别人一段时间摆弄各个部分。

请注意,我收到VS Designer错误,抱怨没有在apps命名空间中找到自定义类。奇怪的是,这似乎不会发生在早期版本的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}" />
...