Существуют ли какие-либо инструменты / библиотеки (.Net/WPF) для измерения и хранения навигационных данных пользовательского интерфейса для анализа?

Я хочу измерять и анализировать пользовательские движения и жесты в пользовательском интерфейсе, чтобы улучшить взаимодействие с пользователем приложения. Я предполагал, что библиотеки отслеживания функций (такие как EQATEC или Preemptive Runtime Intelligence) позволят это. Тем не менее, это не так.

В идеале я хотел бы иметь возможность настраивать пользовательский интерфейс, а затем захватывать жесты навигации мыши и клавиатуры для отображения через тепловую карту.

Мои поиски оказались пустыми. Есть ли здесь что-нибудь OSS или реклама?

5 ответов

Решение

Попробовав несколько подходов, в том числе и здесь, а также использование UIAutomation Events и ETW для 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, которая управляет представлением, чтобы мы могли найти шаблоны использования во время определенных рабочих процессов и состояний данных, а также визуальных состояний. Затем мы можем получать отчеты по этому вопросу, а также дифференцировать и сравнивать наши тепловые карты по путям через рабочий процесс и значения данных.

  1. Загрузите Sources Version 2 из этой статьи о проекте code
  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. Желательно создать новый проект WPF с именем WpfApplication1. Добавьте ссылку на проект / сборку, созданную на шаге 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. Вся заслуга принадлежит Джорджу Мамаладзе, который изначально его построил, вы можете отправить ему письмо с благодарностью, а также проверить лицензию на распространение этого кода у оригинального автора, если хотите заработать на этом деньги.

11. Я только что сделал несколько небольших изменений, чтобы он работал с WPF. Спасибо за то, что вы прочитали этот длинный пост, я не уверен, как поделиться этим кодом, поэтому предоставил такие инструкции.

12. Я серьезно отстой в хорошем форматировании кода на SO, может кто-то оставить комментарий о том, как правильно отформатировать код, XML. Также кто-то поможет мне отформатировать фрагменты в этом посте. Спасибо.

Попробуйте http://iographica.com/ он создает линии, где курсор мыши перемещался, и круги, где курсор мыши остановился, чем больше круг, тем дольше он был остановлен там.

Посмотрите на это приложение. Он не делает то, что вы хотите, но он может быть полезен в качестве отправной точки для реализации необходимых вам функций: Perceptor: искусственно управляемая навигационная система для WPF

Я знаю, что Snoop - это больше инструмент для проверки визуального дерева приложения WPF, но он также фиксирует события (особенно он фиксирует их с информацией о том, с какими элементами они произошли, как они перемещались в визуальном дереве и где они обрабатывались). Поскольку это открытый исходный код, вы можете извлечь эту часть об отслеживании событий и записать эту информацию в соответствии с вашими потребностями.

Другие вопросы по тегам