18

我想测量和分析 UI 中的用户动作和手势,以优化应用程序的用户体验。我曾想象过特征跟踪库(如 EQATEC 或 Preemptive 的运行时智能)会允许这样做。然而,情况似乎并非如此。

理想情况下,我希望能够检测 UI,然后捕获鼠标和键盘导航手势以通过热图显示。

我的搜索是空的。这里是否存在任何 OSS 或商业内容?

4

5 回答 5

6
  1. 这篇关于代码项目的文章下载Sources Version 2
  2. 打开解决方案或仅Gma.UserActivityMonitor项目并盲目地将其转换为.NET 4.0
  3. HookManager.Callbacks.cs文件中进行以下更改。

    1. 添加using System.Diagnostics;
    2. 代替

      s_MouseHookHandle = SetWindowsHookEx(
          WH_MOUSE_LL,
          s_MouseDelegate,
          Marshal.GetHINSTANCE(
              Assembly.GetExecutingAssembly().GetModules()[0]),
          0);
      

      using (Process curProcess = Process.GetCurrentProcess())
      using (ProcessModule curModule = curProcess.MainModule)
      {
          s_MouseHookHandle = SetWindowsHookEx(
              WH_MOUSE_LL,
              s_MouseDelegate,
             GetModuleHandle(curModule.ModuleName), 0);
      }
      
    3. 代替

      s_KeyboardHookHandle = SetWindowsHookEx(
          WH_KEYBOARD_LL,
          s_KeyboardDelegate,
          Marshal.GetHINSTANCE(
              Assembly.GetExecutingAssembly().GetModules()[0]),
          0);
      

      using (Process curProcess = Process.GetCurrentProcess())
      using (ProcessModule curModule = curProcess.MainModule)
      {
          s_KeyboardHookHandle = SetWindowsHookEx(
          WH_KEYBOARD_LL,
          s_KeyboardDelegate, 
          GetModuleHandle(curModule.ModuleName), 0);
      }
      
  4. 在类定义的HookManager.Windows.cs任何地方添加以下两行。HookManager

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr GetModuleHandle(string lpModuleName);
    
  5. 现在您应该能够构建它并将其放在一边。现在开始 WPF 部分。

  6. 最好创建名称为WpfApplication1的新 WPF 项目。添加对您在步骤 1-5 中构建的项目/程序集的引用,添加对System.Windows.Forms的引用。
  7. 现在MainWindow.xaml用以下 XAML 替换,确保检查类名和项目名,我刚刚创建了 WpfApplication1 进行测试。

    <Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="0.30*"/>
                <RowDefinition Height="0.70*"/>
            </Grid.RowDefinitions>
            <StackPanel Grid.Row="0" Orientation="Horizontal">
                <StackPanel Orientation="Vertical">
                    <CheckBox Name="MouseMove" Padding="5" Content="Mouse Move" Width="120" Height="30" Click="checkBoxOnMouseMove_CheckedChanged"/>
                    <CheckBox Name="MouseClick" Padding="5" Content="Mouse Click" Width="120" Height="30" Click="checkBoxOnMouseClick_CheckedChanged"/>
                    <CheckBox Name="MouseDown" Padding="5" Content="Mouse Down" Width="120" Height="30" Click="checkBoxOnMouseDown_CheckedChanged"/>
                </StackPanel>
                <StackPanel Orientation="Vertical">
                    <CheckBox Name="MouseUp" Padding="5" Content="Mouse Up" Width="120" Height="30" Click="checkBoxOnMouseUp_CheckedChanged"/>
                    <CheckBox Name="MouseDouble" Padding="5" Content="Mouse Double" Width="120" Height="30" Click="checkBoxMouseDoubleClick_CheckedChanged"/>
                    <CheckBox Name="MouseWheel" Padding="5" Content="Mouse Wheel" Width="120" Height="30" Click="checkBoxMouseWheel_CheckedChanged"/>
                </StackPanel>
                <StackPanel Orientation="Vertical">
                    <CheckBox Name="KeyDown" Padding="5" Content="Key Down" Width="120" Height="30" Click="checkBoxKeyDown_CheckedChanged"/>
                    <CheckBox Name="KeyPress" Padding="5" Content="Key Press" Width="120" Height="30" Click="checkBoxKeyPress_CheckedChanged"/>
                    <CheckBox Name="KeyUp" Padding="5" Content="Key Up" Width="120" Height="30" Click="checkBoxKeyUp_CheckedChanged"/>
                </StackPanel>
                <StackPanel Orientation="Vertical">
                    <TextBlock Name="labelMousePosition" Text="x={0:####}; y={1:####}"/>
                    <TextBlock Name="labelWheel" Text="Wheel={0:####}"/>
                </StackPanel>
            </StackPanel>
            <TextBlock Name="textBoxLog" Text="START" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Visible"/>
        </Grid>
    </Window>
    
  8. 现在将以下代码添加到 MainWindow 类定义的 MainWindow.xaml.cs 文件中。

    #region Check boxes to set or remove particular event handlers.
    
    private void checkBoxOnMouseMove_CheckedChanged(object sender, EventArgs e)
    {
        if ((bool)MouseMove.IsChecked)
        {
            HookManager.MouseMove += HookManager_MouseMove;
        }
        else
        {
            HookManager.MouseMove -= HookManager_MouseMove;
        }
    }
    
    private void checkBoxOnMouseClick_CheckedChanged(object sender, EventArgs e)
    {
        if ((bool)MouseClick.IsChecked)
        {
            HookManager.MouseClick += HookManager_MouseClick;
        }
        else
        {
            HookManager.MouseClick -= HookManager_MouseClick;
        }
    }
    
    private void checkBoxOnMouseUp_CheckedChanged(object sender, EventArgs e)
    {
        if ((bool)MouseUp.IsChecked)
        {
            HookManager.MouseUp += HookManager_MouseUp;
        }
        else
        {
            HookManager.MouseUp -= HookManager_MouseUp;
        }
    }
    
    private void checkBoxOnMouseDown_CheckedChanged(object sender, EventArgs e)
    {
        if ((bool)MouseDown.IsChecked)
        {
            HookManager.MouseDown += HookManager_MouseDown;
        }
        else
        {
            HookManager.MouseDown -= HookManager_MouseDown;
        }
    }
    
    private void checkBoxMouseDoubleClick_CheckedChanged(object sender, EventArgs e)
    {
        if ((bool)this.MouseDouble.IsChecked)
        {
            HookManager.MouseDoubleClick += HookManager_MouseDoubleClick;
        }
        else
        {
            HookManager.MouseDoubleClick -= HookManager_MouseDoubleClick;
        }
    }
    
    private void checkBoxMouseWheel_CheckedChanged(object sender, EventArgs e)
    {
        if ((bool)MouseWheel.IsChecked)
        {
            HookManager.MouseWheel += HookManager_MouseWheel;
        }
        else
        {
            HookManager.MouseWheel -= HookManager_MouseWheel;
        }
    }
    
    private void checkBoxKeyDown_CheckedChanged(object sender, EventArgs e)
    {
        if ((bool)KeyDown.IsChecked)
        {
            HookManager.KeyDown += HookManager_KeyDown;
        }
        else
        {
            HookManager.KeyDown -= HookManager_KeyDown;
        }
    }
    
    
    private void checkBoxKeyUp_CheckedChanged(object sender, EventArgs e)
    {
        if ((bool)KeyUp.IsChecked)
        {
            HookManager.KeyUp += HookManager_KeyUp;
        }
        else
        {
            HookManager.KeyUp -= HookManager_KeyUp;
        }
    }
    
    private void checkBoxKeyPress_CheckedChanged(object sender, EventArgs e)
    {
        if ((bool)KeyPress.IsChecked)
        {
            HookManager.KeyPress += HookManager_KeyPress;
        }
        else
        {
            HookManager.KeyPress -= HookManager_KeyPress;
        }
    }
    
    #endregion
    
    //##################################################################
    #region Event handlers of particular events. They will be activated when an appropriate check box is checked.
    
    private void HookManager_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
    {
        textBoxLog.Text += (string.Format("KeyDown - {0}\n", e.KeyCode));
    
    }
    
    private void HookManager_KeyUp(object sender, System.Windows.Forms.KeyEventArgs e)
    {
        textBoxLog.Text += (string.Format("KeyUp - {0}\n", e.KeyCode));
    
    }
    
    
    private void HookManager_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
    {
        textBoxLog.Text += (string.Format("KeyPress - {0}\n", e.KeyChar));
    
    }
    
    
    private void HookManager_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        labelMousePosition.Text = string.Format("x={0:0000}; y={1:0000}", e.X, e.Y);
    }
    
    private void HookManager_MouseClick(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        textBoxLog.Text += (string.Format("MouseClick - {0}\n", e.Button));
    
    }
    
    
    private void HookManager_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        textBoxLog.Text += (string.Format("MouseUp - {0}\n", e.Button));
    
    }
    
    
    private void HookManager_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        textBoxLog.Text += (string.Format("MouseDown - {0}\n", e.Button));
    
    }
    
    
    private void HookManager_MouseDoubleClick(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        textBoxLog.Text += (string.Format("MouseDoubleClick - {0}\n", e.Button));
    
    }
    
    
    private void HookManager_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
    {
        labelWheel.Text = string.Format("Wheel={0:000}", e.Delta);
    }
    
    #endregion
    

9.构建并运行您的 WPF 应用程序,您可能需要using Gma.UserActivityMonitor;在 MainWindow 中添加指令。

10.所有的功劳归于最初构建它的George Mamaladze,您可能需要向他发送一封感谢信,如果您想从中赚钱,还请查看原始作者的此代码的分发许可证。

11.我只是做了几个小改动,使它与 WPF 一起工作。感谢您关注这篇长文,我不知道如何分享这段代码,这就是为什么提供这样的说明。

12.我非常讨厌在 SO 上很好地格式化代码,有人可以就如何格式化代码、XML 格式发表评论。还有人帮我格式化这篇文章中的片段。谢谢你。

于 2011-03-05T10:23:23.770 回答
2

在尝试了多种方法(包括此处的方法)以及使用UIAutomation EventsETW for WPF之后,我决定将处理程序简单地附加到 WPF 事件。这使我不仅可以捕获事件数据,还可以捕获用户关注的 UIElement,因此更容易跟踪用户的操作和意图。如果没有这个,我需要捕捉屏幕的视觉效果并直观地确定正在发生的事情。

这是一个示例:

private Int32 _eventCount;

public MainWindow()
{
    InitializeComponent();
    EventManager.RegisterClassHandler(typeof(UIElement), MouseEnterEvent, (RoutedEventHandler)handleEvent, true);
    EventManager.RegisterClassHandler(typeof(UIElement), MouseLeaveEvent, (RoutedEventHandler)handleEvent, true);
    EventManager.RegisterClassHandler(typeof(UIElement), MouseMoveEvent, (RoutedEventHandler)handleEvent, true);
    EventManager.RegisterClassHandler(typeof(UIElement), MouseUpEvent, (RoutedEventHandler)handleEvent, true);
    EventManager.RegisterClassHandler(typeof(UIElement), MouseDownEvent, (RoutedEventHandler)handleEvent, true);
    EventManager.RegisterClassHandler(typeof(UIElement), KeyUpEvent, (RoutedEventHandler)handleEvent, true);
    EventManager.RegisterClassHandler(typeof(UIElement), KeyDownEvent, (RoutedEventHandler)handleEvent, true);
}

private void handleEvent(object sender, RoutedEventArgs e)
{
    var uiElement = e.Source as UIElement;

    if (uiElement == null)
    {
        return;
    }

    EventStatusDisplay.Text = e.Source + " " + e.RoutedEvent.Name;
    EventCountDisplay.Text = (++_eventCount).ToString();
    var over = Mouse.DirectlyOver as UIElement;
    MouseIsOverDisplay.Text = over == null ? "" : over.ToString();
}

虽然这里没有显示,但一旦我得到它,我就会UIElement执行日志记录,甚至可以使用它UIElement.DataContext来确定驱动视图的 ViewModel 的状态,这样我们就可以在某些工作流和数据状态以及可视化中找到使用模式状态。然后,我们可以获得关于此的报告,并通过工作流和数据值的路径区分和比较我们的热图。

于 2011-03-08T23:23:25.600 回答
1

我知道Snoop更像是一个用于检查 WPF 应用程序的可视化树的工具,但它也捕获事件(特别是它捕获这些事件以及它们发生的元素、它们如何在可视化树中移动以及它们被处理的位置)。由于它是开源的,您可能希望提取有关跟踪事件的部分并将此信息记录到您的需要。

于 2011-03-05T09:54:48.507 回答
1

试试http://iographica.com 它会在鼠标光标移动的地方创建线条,并在鼠标光标停止的地方创建圆圈,圆圈越大,停在那里的时间越长。

于 2011-03-03T22:33:07.750 回答
1

看看这个应用程序。它不会做你想做的事,但它可能有助于作为实现所需功能的起点:感知器:用于 WPF 的人工智能引导导航系统

于 2011-03-02T10:55:08.520 回答