WPF测试中生成指定坐标的MouseDown、MouseMove、MouseUp事件
我之前做拖拽选框功能时也踩过类似的坑,TestStack.White的封装确实太死,没法拆分成单个Mouse事件,WPF原生触发坐标事件的方法如果没做对也容易失效。给你分享两个亲测有效的实现思路:
方案一:WPF原生控件自定义拖拽选框(应用内场景)
如果你的需求是在自己的WPF应用里实现拖拽选框,直接用WPF的原生鼠标事件是最稳定的方式,不需要依赖第三方库。
实现步骤:
- 在容器控件(比如Grid)上绑定
MouseDown、MouseMove、MouseUp事件 - 用一个
Rectangle作为选框的可视化元素,默认隐藏 - 鼠标按下时记录起始坐标,显示选框并初始化位置
- 鼠标移动时实时计算选框的位置和尺寸(要处理从右下往左上拖拽的反向情况)
- 鼠标松开时隐藏选框,根据选框尺寸执行计算逻辑
代码示例:
XAML部分:
<Grid x:Name="MainGrid" Background="Transparent" MouseDown="MainGrid_MouseDown" MouseMove="MainGrid_MouseMove" MouseUp="MainGrid_MouseUp"> <!-- 选框元素,默认隐藏 --> <Rectangle x:Name="SelectionBox" Stroke="Blue" StrokeThickness="2" Fill="#200000FF" Visibility="Collapsed"/> <!-- 你的其他业务控件 --> </Grid>
注意:一定要把Grid的Background设为Transparent,否则空白区域不会响应鼠标事件
C#后台代码:
using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace WpfDragSelection { public partial class MainWindow : Window { private Point _dragStartPoint; private bool _isDragging; public MainWindow() { InitializeComponent(); } private void MainGrid_MouseDown(object sender, MouseButtonEventArgs e) { // 仅处理左键按下事件 if (e.LeftButton != MouseButtonState.Pressed) return; _dragStartPoint = e.GetPosition(MainGrid); _isDragging = true; // 显示选框并初始化位置 SelectionBox.Visibility = Visibility.Visible; Canvas.SetLeft(SelectionBox, _dragStartPoint.X); Canvas.SetTop(SelectionBox, _dragStartPoint.Y); SelectionBox.Width = 0; SelectionBox.Height = 0; } private void MainGrid_MouseMove(object sender, MouseEventArgs e) { if (!_isDragging) return; Point currentPoint = e.GetPosition(MainGrid); // 计算选框的正确位置和尺寸,兼容反向拖拽 double left = Math.Min(_dragStartPoint.X, currentPoint.X); double top = Math.Min(_dragStartPoint.Y, currentPoint.Y); double width = Math.Abs(currentPoint.X - _dragStartPoint.X); double height = Math.Abs(currentPoint.Y - _dragStartPoint.Y); // 更新选框UI Canvas.SetLeft(SelectionBox, left); Canvas.SetTop(SelectionBox, top); SelectionBox.Width = width; SelectionBox.Height = height; } private void MainGrid_MouseUp(object sender, MouseButtonEventArgs e) { if (!_isDragging) return; _isDragging = false; SelectionBox.Visibility = Visibility.Collapsed; // 这里执行选框尺寸的计算逻辑 double boxWidth = SelectionBox.Width; double boxHeight = SelectionBox.Height; // 示例:弹出尺寸信息 MessageBox.Show($"选框尺寸:宽{boxWidth:F2},高{boxHeight:F2}\n面积:{boxWidth * boxHeight:F2}"); } } }
方案二:Windows API模拟全局鼠标事件(跨进程/全局场景)
如果你的需求是给其他应用模拟拖拽选框(比如TestStack.White的使用场景),可以直接调用Windows的SendInput API,它能模拟最底层的鼠标输入事件,完全拆分MouseDown、MouseMove、MouseUp。
实现步骤:
- 导入
user32.dll的SendInput函数和相关结构体 - 实现坐标转换方法(将屏幕坐标转为API需要的绝对坐标范围)
- 按顺序发送“移动到起始点→左键按下→移动到结束点→左键松开”的输入序列
- 根据起始和结束坐标计算选框尺寸,执行你的计算逻辑
代码示例:
using System; using System.Runtime.InteropServices; using System.Windows; namespace GlobalDragSimulation { public static class MouseSimulator { [StructLayout(LayoutKind.Sequential)] private struct INPUT { public int type; public MOUSEINPUT mi; } [StructLayout(LayoutKind.Sequential)] private struct MOUSEINPUT { public int dx; public int dy; public int mouseData; public int dwFlags; public int time; public IntPtr dwExtraInfo; } private const int INPUT_MOUSE = 0; private const int MOUSEEVENTF_LEFTDOWN = 0x0002; private const int MOUSEEVENTF_LEFTUP = 0x0004; private const int MOUSEEVENTF_MOVE = 0x0001; private const int MOUSEEVENTF_ABSOLUTE = 0x8000; [DllImport("user32.dll", SetLastError = true)] private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); // 将屏幕坐标转换为SendInput需要的绝对坐标(范围0-65535) private static int ConvertToAbsoluteX(int x) { return (x * 65535) / System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width; } private static int ConvertToAbsoluteY(int y) { return (y * 65535) / System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height; } public static void SimulateDragSelection(Point startPoint, Point endPoint) { var inputSequence = new INPUT[4]; // 1. 移动鼠标到起始坐标 inputSequence[0].type = INPUT_MOUSE; inputSequence[0].mi.dx = ConvertToAbsoluteX((int)startPoint.X); inputSequence[0].mi.dy = ConvertToAbsoluteY((int)startPoint.Y); inputSequence[0].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; // 2. 按下左键 inputSequence[1].type = INPUT_MOUSE; inputSequence[1].mi.dwFlags = MOUSEEVENTF_LEFTDOWN; // 3. 移动鼠标到结束坐标 inputSequence[2].type = INPUT_MOUSE; inputSequence[2].mi.dx = ConvertToAbsoluteX((int)endPoint.X); inputSequence[2].mi.dy = ConvertToAbsoluteY((int)endPoint.Y); inputSequence[2].mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; // 4. 松开左键 inputSequence[3].type = INPUT_MOUSE; inputSequence[3].mi.dwFlags = MOUSEEVENTF_LEFTUP; // 发送输入序列 SendInput(4, inputSequence, Marshal.SizeOf(typeof(INPUT))); // 计算选框尺寸并执行逻辑 double width = Math.Abs(endPoint.X - startPoint.X); double height = Math.Abs(endPoint.Y - startPoint.Y); Console.WriteLine($"模拟选框尺寸:宽{width:F2},高{height:F2}"); } } }
注意:需要引用System.Windows.Forms程序集,并且如果是WPF项目,要在项目里添加对它的引用
两种方案对比:
- 方案一适合WPF应用内部的拖拽选框,完全原生,无外部依赖,响应速度快
- 方案二适合跨进程或全局场景,能绕过TestStack.White的封装限制,但需要处理API调用的细节
内容的提问来源于stack exchange,提问作者TheMachinist
相关产品推荐
相关产品推荐

