65.9K
CodeProject 正在变化。 阅读更多。
Home

在几秒钟内为您的 .NET 应用程序添加鼠标手势功能

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (55投票s)

2008年5月15日

CPOL

10分钟阅读

viewsIcon

117567

downloadIcon

2929

该项目允许您仅用几行代码即可为您的 Windows Forms 应用程序添加鼠标手势功能。

引言

鼠标手势命令丰富了应用程序的用户界面。它们非常易于学习,对用户来说也很直观。CodeProject 上有一些帖子以某种方式提到了鼠标手势(不幸的是,我没有让 AI 的那些运行起来),所以我想到将我的解决方案提供给 CP 社区。我第一次看到鼠标手势功能是在 90 年代早期,在一个用 C 语言编写的 CAD 内核包中,我在一个项目中使用了它。

本文使您能够以一种非常简单的方式在 .NET 项目中添加鼠标手势功能。它提供以下优点:

  • 定义要屏蔽的鼠标按钮和触发鼠标手势捕获的鼠标按钮
  • 定义键盘修饰键(Shift、Ctrl、Alt)
  • 定义不同的控件以允许开始捕获鼠标手势
  • 定义自己的鼠标手势命令
  • 捕获鼠标手势时向用户提供视觉反馈
  • 用于管理(教学、删除、复制、导入、导出、属性)鼠标手势的管理器窗体
  • 在任何控件中(窗体、面板、组等)简单使用
  • 当输入鼠标手势时触发事件以进行响应

想法

这真的不需要说,但首先我们必须捕获鼠标的移动/位置。完成后,可以分析这些 x/y 位置以找到合适的鼠标手势命令。

查找鼠标手势的思路如下:

  1. 找到鼠标移动的边界矩形
  2. 将该矩形分成相同数量的相同大小的矩形区域(宽度和高度按相同数字划分)——换句话说:在该矩形上铺设一个方形网格
  3. 从左上角到右下角,从左到右,从 0 开始对网格的区域进行编号
  4. 跟踪鼠标移动的路径/区域,从 MouseDown 事件开始,到 MouseUp 事件停止
  5. 将区域编号/路径转换为一个键
  6. 查找找到的键对应的命令

示例

路径:0, 1, 2, 3, 4, 5, 4, 10, 9, 15, 14, 20, 19, 18, 24, 30, 31, 32, 33, 34, 35
键:ABCDEFEKJPOUTSYefghij

通过此解决方案,一个特定命令有多个键——每个鼠标手势的每条可能路径都有一个键。这意味着我们将边界矩形划分的区域越多,一个命令需要被找到的键就越多。一个更复杂的鼠标手势由更长的路径组成,这导致更多的可能键。

正如我们所见,一个非常重要的点是我们将边界矩形划分成的矩形数量。如果数量太小,我们将没有足够的区域来区分相似的鼠标手势。为了说明这一点,想象一下将边界矩形的每一边划分的最小可能除数等于 1;换句话说,不划分边界矩形。所以每个鼠标手势的路径都是 0,无论它只是一个点还是数百万次移动。另一方面,如果我们将矩形划分成太多区域,就会有很多非常相似的路径。

我对边界矩形的不同除数进行了多次测试,最终使用 6 作为最佳平均值,得到 36 个区域。

键应该尽可能短,因为对于一个命令,如果鼠标手势越复杂,我们就会有更多的键。由于鼠标手势不限于特定长度或移动,我们不知道键能有多长。最简单的解决方案是使用一个由 Base64 编码的区域编号(即路径)组成的字符串。

实现

MouseGestureData 类

MouseGestureData 类没有代码,它只保存所有 MouseGesture 实例相同的变量。它按照 MSDN 上微软建议的单例模式 实现。它有一个私有构造函数,并且成员属性可以使用公共静态只读 Instance 变量访问。

MouseGesture 类

MouseGesture 类是主类,负责所有重要的工作。唯一的构造函数接受两个参数。第一个是对 Control 对象的引用。这个对象是鼠标手势在其内部工作的父控件。构造函数注册到父控件及其所有允许的子控件的 MouseUpMouseMoveMouseDown 事件。第二个参数是一个 List<Type> 列表,用于指定允许的控件类型。如果它是 null,则使用 GetDefaultAllowedControls() 方法返回的列表。其思想是允许所有类型的“容器控件”,这意味着这些控件“显示”某种父控件的背景,并且没有可选元素,也不是数据输入控件。默认允许 LabelGroupBoxPictureBoxProgressBarScrollableControlTabControl

MouseGesture 类中,您可以定义开始捕获鼠标位置和触发鼠标手势命令所需的各种属性。MouseButtonTrigger 定义了必须按下以触发/开始和停止捕获鼠标位置的鼠标按钮。这意味着触发 MouseDownMouseUp 事件的鼠标按钮必须等于此属性。此外,在开始捕获时,所有鼠标按钮状态必须等于 MouseButtonMask,并且修饰键(Ctrl、Shift、Alt)必须等于 ModifierKeyMask

private void OnMouseDown( object sender, MouseEventArgs e )
{
    // only enter capturing if...
    if( !_bCapturing &&                                     // ...not capturing in
                                                            // progress
        e.Button == _data.MouseButtonTrigger &&             // ...the button pressed last
                                                            // to trigger
        Control.MouseButtons == _data.MouseButtonMask &&    // ...this/these button/s
                                                            // pressed together
        Control.ModifierKeys == _data.ModifierKeyMask       // ...this/these modifier
                                                            // key/s (shift/ctrl/alt)
      )
    {
        // capturing mouse positions starts here
        ...
    }
}

默认情况下,只需按下右键。但是通过附加属性 MouseButtonMaskModifierKeyMask,我们可以在第一步按下并按住左键,第二步按下右键时开始捕获。

MouseButtonTrigger = MouseButtons.Right;
MouseButtonMask = MouseBottons.Left | MouseButtons.Right

或者按下 Ctrl 键和右键

MouseButtonTrigger = MouseButtons.Right;
MouseButtonMask = MouseButtons.Right
ModifierKeyMask = Keys.Control;

在捕获鼠标位置时,可以向用户提供视觉反馈。这分为三个级别。第一个级别是我们显示一个窗口,并在其上绘制鼠标手势。这个窗口没有边框,但我们设置了 OpacityBackColor,以向用户显示输入窗口已更改。属性 WindowAppearance 定义了如何显示窗口。

  • None 不显示任何捕获位置和/或窗口效果。
  • FullScreenOpaque 一个不透明窗口覆盖整个屏幕,在其上绘制捕获点/线。这意味着捕获点/线可以绘制在父窗口之外。
  • ParentOpaque 一个不透明窗口覆盖父窗口,在其上绘制捕获点/线。这意味着捕获点/线只能绘制在父窗口内部。
  • ParentClear 父窗口不改变,在其上绘制捕获点/线。这意味着捕获点/线只能绘制在父窗口内部。

视觉反馈的第二个级别是绘制鼠标位置,并在属性 GestureAppearance 中定义。

第三个级别是一些属性,它们定义是否应绘制鼠标手势的边界矩形、网格和路径及其颜色。还有一个属性定义了捕获鼠标位置完成后,捕获窗口应该显示多长时间。

捕获的鼠标位置的分析是直接的,背后没有任何真正复杂的代码。但是有两个属性需要提及。属性 MinimumMovement 定义了鼠标必须移动的最小像素数,才能创建键并查找合适的命令。属性 UnidimensionalLimit 定义了边界矩形被划分的区域的最小宽度或高度。之所以这样做,是因为几乎不可能用鼠标输入正交移动(x 或 y 方向)。这是我看到可以减少命令键数的唯一部分。

一旦鼠标位置捕获开始,并且 MouseButtonTrigger 被释放,在 MouseUp 事件上,鼠标位置将被分析。之后,MouseGesture 会触发 MouseGestureEntered 事件,无论是否找到键及其相应的命令。即使鼠标移动无效,它也会触发该事件。MouseGestureEventHandler 委托的 MouseGestureEventArgs 具有以下属性:

  • Key 是输入的鼠标手势的键。如果没有找到键,则为 string.Empty
  • Command 是找到的键对应的命令。如果没有找到命令,则为 string.Empty
  • Bounds 是输入的鼠标手势的边界矩形。这始终相对于构造函数中传入的父控件的左上角。
  • Control 是鼠标手势开始的控件。这样做的目的是能够根据用户开始绘制鼠标手势的位置来处理不同的命令。

最后一次 MouseGestureEntered 事件的这些值可以从以“Last”开头的 MouseGesture 对象属性中读取。

Commands 类

这个类持有一个带有键/命令对的 Dictionary。它隐藏了字典,以便能够对操作进行检查;即,如果键已经存在,则不会抛出异常。它还实现了以 XML 格式读取和写入文件。它以结构化的方式(一个命令带多个键)而不是扁平的方式(每个键/命令对一个条目)完成。

MouseGestureManager 类

您可以在管理器窗体上绘制鼠标手势,可以在所有“容器控件”(如上所述)以及第二个选项卡上的列表控件上绘制。允许的控件设置为默认值。当事件发生时,它会在选项卡下方的组框中可视化:红色表示无效鼠标手势,绿色表示输入了有效鼠标手势并生成了其键,但未找到相应的命令,而蓝色表示找到了命令。

参数”选项卡允许设置 MouseGestureData 的所有可能属性。您可以在选项卡上绘制鼠标手势。尝试使用这些参数,立即可看到效果。

键和命令”选项卡允许您管理围绕键和命令的所有内容。在下拉组合框“命令”中选择或输入一个命令,即可在下方的列表中查看所有相应的键。“选定的键”组框显示当前/选定的键,并将其拉伸成一个缩略图,并允许您将其从列表中删除。

全部删除”按钮在删除列表中所有键/命令之前会要求确认。
导入”和“导出”按钮的功能与您期望的一样。
复制命令”按钮允许您将选定命令的所有键复制到新命令中,同时进行镜像或旋转。

最重要的按钮可能位于选项卡右上角的“添加鼠标手势”。它是一个按钮样式的复选框,当按下时,您在“键和命令”选项卡(必须可见)上绘制的每个有效鼠标手势都会添加到当前命令中。它会立即在列表框中被选中,因此如果您不满意,可以立即删除它。如果您想添加一个新命令,只需在下拉组合框中输入其名称并开始绘制鼠标手势。(不幸的是,在组合框中键入的第一个字符不被接受,在我看来这是一个 .NET 问题)。

组框“总计数器”中的“连续命令”在每次找到有效命令时增加,如果未找到则设置为 0。它可以在教授新鼠标手势时使用,以了解系统已经有多好。

如何使用代码

在您的项目中添加对 DcamMouseGesture.dll 的引用后,使用 DcamMouseGesture 功能只需几行代码

  1. 引用命名空间 DcamMouseGesture
  2. 在您的应用程序中一次性加载包含命令和键的文件
  3. 对于每个要使用鼠标手势的 Form,实例化一个 MouseGesture 类的对象并注册到 MouseGestureEvent 事件
  4. 在您注册的 MouseGestureEventHandler 中,处理您想要的命令

就是这样!这是一个所需最小步骤的示例(已删除不必要的 VS 代码)

// a) Refer to namespace "DcamMouseGesture"
using DcamMouseGesture;

namespace WindowsFormsApplication
{
    public partial class Form1 : Form
    {
        MouseGesture _mg;

        public Form1()
        {
            InitializeComponent();

            // b) Load a file with the commands and keys once in your application
            MouseGestureData.Instance.Commands.ReadFile( 
                Environment.CurrentDirectory + @"\MouseGestureCommands.xml" );

            // c) For each Form you want to use mouse gestures...
            _mg = new MouseGesture( this, null ); 
            _mg.MouseGestureEntered += new MouseGestureEventHandler( 
                OnMouseGestureEntered );
        }

        private void OnMouseGestureEntered( object sender, MouseGestureEventArgs e )
        {
            // d) In your registered MouseGestureEventHandler, handle the commands
            // you want
            MessageBox.Show( string.Format( "OnMouseGestureEntered:\n" +
                                            "   Command:\t{0}\n" +
                                            "   Key:\t\t{1}\n" +
                                            "   Control:\t\t{2}\n" +
                                            "   Bounds:\t\t{3}", 
                                            e.Command, e.Key, e.Control,
                                            e.Bounds.ToString() ) );
        }

    }
}

可下载的源代码是一个在 Visual Studio 2008 标准版中创建的 .NET 2.0 解决方案。我尽可能使用了新的“自动实现的属性”功能。因此,如果您想在旧版本的 Visual Studio 中使用源代码,您必须将这些声明为成员变量及其相应的属性。

历史

2008-05-12 - V1.0

  • 首次发布
© . All rights reserved.