我正在尝试计算光标加速度/速度。
我读了 Find the velocity in C#并决定采纳 Hans 的使用建议GetMouseMovePointsEx
(pinvoke.net,MSDN)。
我做了一个演示程序来测试它(见下面的完整代码),但它有一个很大的限制。
一旦光标离开窗口,它就不会返回点。
事实上,如果执行不限于 MainWindow 内的点,该函数会返回 -1(win32Exception 1171,“传递给 GetMouseMovePoints 的点不在缓冲区中”)。
- 我怀疑这可能是因为我
Mouse.GetPosition()
用来提供mp_in
价值。
这可以通过使用GetCursorPos来解决吗?
myBounds.Contains(currentPosition)
当它应该为真时,移动或调整窗口大小会导致为假。
- 这可能是一个与我如何设置界限有关的愚蠢错误,但我并没有立即明白为什么会这样。它也可能连接到
Mouse.GetPosition()
. 不管是什么原因,我的比较不匹配。请帮忙?
它看起来像什么
主窗口.xaml
<Window x:Class="MouseVelocity.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="450" Width="1200" Background="Gray"
PreviewMouseUp="pMouseUp" PreviewMouseDown="pMouseDown">
<Grid Margin="1">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="75"/>
</Grid.RowDefinitions>
<TextBlock x:Name="txtOutput" Background="LightGray" Margin="10" FontSize="18" Padding="20"/>
<Label x:Name="lbl_Velo" FontSize="22" FontWeight="Bold" Background="Black" Foreground="Red"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Row="1" Margin="10"/>
</Grid>
主窗口.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Threading;
using System.Windows.Interop;
using System.Runtime.InteropServices;
namespace MouseVelocity
{
public partial class MainWindow : Window
{
DispatcherTimer GetMousePointsNow;
MOUSEMOVEPOINT[] mp_out = new MOUSEMOVEPOINT[64];
MOUSEMOVEPOINT LastMMP;
Rect myBounds;
double[] XVelocity;
public const int GMMP_USE_DISPLAY_POINTS = 1;
public const int GMMP_USE_HIGH_RESOLUTION_POINTS = 2;
int nNumPointsDesired = 20;
uint mode = GMMP_USE_DISPLAY_POINTS;
string DataCollectionTime;
int nVirtualWidth = GetSystemMetrics(SystemMetric.SM_CXVIRTUALSCREEN);
int nVirtualHeight = GetSystemMetrics(SystemMetric.SM_CYVIRTUALSCREEN);
int nVirtualLeft = GetSystemMetrics(SystemMetric.SM_XVIRTUALSCREEN);
int nVirtualTop = GetSystemMetrics(SystemMetric.SM_YVIRTUALSCREEN);
double ScreenWidth = System.Windows.SystemParameters.PrimaryScreenWidth;
double ScreenHeight = System.Windows.SystemParameters.PrimaryScreenHeight;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct MOUSEMOVEPOINT
{
public int x;
public int y;
public int time;
public IntPtr dwExtraInfo;
}
[DllImport("user32.dll")]
static extern int GetSystemMetrics(SystemMetric smIndex);
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)]
internal static extern int GetMouseMovePointsEx(
uint cbSize,
[In] ref MOUSEMOVEPOINT pointsIn,
[Out] MOUSEMOVEPOINT[] pointsBufferOut,
int nBufPoints,
uint resolution
);
public MainWindow()
{
InitializeComponent();
GetMousePointsNow = new DispatcherTimer(new TimeSpan(0, 0, 0, 0, 20), DispatcherPriority.Background,
GetMousePointsNow_Tick, Dispatcher.CurrentDispatcher); GetMousePointsNow.IsEnabled = false;
myBounds = new Rect();
}
private void pMouseDown(object sender, MouseButtonEventArgs e) {
GetMousePointsNow.Start(); }
private void pMouseUp(object sender, MouseButtonEventArgs e) {
GetMousePointsNow.Stop(); }
private void GetMousePointsNow_Tick(object sender, EventArgs e)
{
double width = System.Windows.SystemParameters.PrimaryScreenWidth;
double height = System.Windows.SystemParameters.PrimaryScreenHeight;
myBounds.Location = new Point(this.Top, this.Left);
myBounds.Size = new Size(this.ActualWidth, this.ActualHeight);
Point currentPosition = PointToScreen(Mouse.GetPosition(this));
if (!myBounds.Contains(currentPosition)) {
GetMousePointsNow.Stop(); return; }
var mp_in = new MOUSEMOVEPOINT();
mp_in.x = ((int)currentPosition.X) & 0x0000FFFF;
mp_in.y = ((int)currentPosition.Y) & 0x0000FFFF;
int cpt = GetMouseMovePointsEx((uint)(Marshal.SizeOf(mp_in)), ref mp_in, mp_out, nNumPointsDesired, mode);
if (cpt == -1) {
int win32Error = Marshal.GetLastWin32Error();
throw new System.ComponentModel.Win32Exception(win32Error);
}
for (int i = 0; i < cpt; i++) { // Fix for multi-display environment
switch (mode) {
case GMMP_USE_DISPLAY_POINTS:
if (mp_out[i].x > 32767)
mp_out[i].x -= 65536;
if (mp_out[i].y > 32767)
mp_out[i].y -= 65536;
break;
case GMMP_USE_HIGH_RESOLUTION_POINTS:
mp_out[i].x = ((mp_out[i].x * (nVirtualWidth - 1)) - (nVirtualLeft * 65536)) / nVirtualWidth;
mp_out[i].y = ((mp_out[i].y * (nVirtualHeight - 1)) - (nVirtualTop * 65536)) / nVirtualHeight;
break;
}
}
DisplayMousePoints();
DisplayVelocity();
}
private void DisplayMousePoints()
{
string Result = "";
XVelocity = new double[20];
for (int i = 0; i < 20; i++)
{
MOUSEMOVEPOINT ThisMMP = mp_out[i];
int DeltaTime = LastMMP.time - ThisMMP.time;
if (ThisMMP.time == LastMMP.time) { } // Do nothing if same timestamp
else {
XVelocity[i] = (LastMMP.x - ThisMMP.x) / (double)DeltaTime; // V = x / t
if (DeltaTime > 0) // Don't include first point in the calculation
{
Result = Result +=
"X: " + mp_out[i].x + ", " +
"Timestamp: " + mp_out[i].time.ToString() + ", " +
"Change: (" + DeltaTime.ToString() + "), " +
"Point Velo: " + (XVelocity[i] * 100).ToString("0.000") + Environment.NewLine;
}
}
DataCollectionTime = DateTime.Now.Second.ToString("00") + DateTime.Now.Millisecond.ToString("000");
txtOutput.Text = "Time Collected: " + DataCollectionTime + Environment.NewLine + Result;
LastMMP = ThisMMP;
}
}
private void DisplayVelocity()
{
// Calculate a moving average of velocity values
double aggregate = 0;
double weight;
int item = 1;
int count = 1;
foreach (var d in XVelocity)
{
weight = (double)item / (double)count;
aggregate += (double)d * weight;
count++;
}
double result = (aggregate / count) * 100;
lbl_Velo.Content = result.ToString("0.000");
}
public enum SystemMetric
{
SM_XVIRTUALSCREEN = 76, // 0x4C
SM_YVIRTUALSCREEN = 77, // 0x4D
SM_CXVIRTUALSCREEN = 78, // 0x4E
SM_CYVIRTUALSCREEN = 79, // 0x4F
}
}
}