我遇到鼠标进入/离开事件的问题。按下鼠标按钮并用光标在控件内保持,然后光标快速移出控件,此事件不会触发。
你能告诉我为什么会这样吗?有没有办法正确地获得这些活动?
请检查示例项目以查看其实际效果:https://www.dropbox.com/s/w5ra2vzegjtauso/SampleApp.zip
更新。我发现同样的问题here没有答案。在那里开始赏金。
答案 0 :(得分:7)
编辑:在Sisyphe正确地注意到该行为对于具有鼠标交互的元素不起作用之后,我已经重写了代码。
可以将行为附加到窗口或任何其他FrameworkElement。默认情况下,当鼠标左键按下并执行处理程序时,将监视MouseLeave的所有包含元素。通过设置MonitorSubControls="False"
。
行为的作用,基本上(请参阅代码中的注释以获取更多详细信息):
已知的限制(我认为可以通过更多努力解决,但对我来说似乎不太重要):
此脚本生成以下输出:将行为附加到窗口,在orangeBorder内部移动(在释放鼠标按钮的情况下通过内边界留下blueBorder:0),在橙色边框内按下鼠标左键并在外部移动(快速)窗口执行离开处理程序(1 - 4)。释放窗口外的鼠标按钮,向上移回goldTextBox(5),在文本框中按下鼠标左键,在窗口外面(快或慢)再次执行正确的处理程序(6 - 9)。
Xaml(示例):
<Window x:Class="WpfApplication1.MouseLeaveControlWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:beh="clr-namespace:WpfApplication1.Behavior"
Title="MouseLeaveControlWindow" Height="300" Width="300" x:Name="window" MouseLeave="OnMouseLeave">
<i:Interaction.Behaviors>
<beh:MonitorMouseLeaveBehavior />
</i:Interaction.Behaviors>
<Grid x:Name="grid" MouseLeave="OnMouseLeave" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border x:Name="blueBorder" MouseLeave="OnMouseLeave" Background="SteelBlue" Margin="50" Grid.RowSpan="2" />
<Border x:Name="orangeBorder" MouseLeave="OnMouseLeave" Background="DarkOrange" Margin="70, 70, 70, 20" />
<TextBox x:Name="goldTextBox" MouseLeave="OnMouseLeave" Background="Gold" Margin="70, 20, 70, 70" Grid.Row="1" Text="I'm a TextBox" />
</Grid>
</Window>
后面的代码(仅用于调试目的):
public partial class MouseLeaveControlWindow : Window
{
public MouseLeaveControlWindow()
{
InitializeComponent();
}
private int i = 0;
private void OnMouseLeave(object sender, MouseEventArgs e)
{
FrameworkElement fe = (FrameworkElement)sender;
if (e.LeftButton == MouseButtonState.Pressed)
{
System.Diagnostics.Debug.WriteLine(string.Format("{0}: Left {1}.", i, fe.Name)); i++;
}
else
{
System.Diagnostics.Debug.WriteLine(string.Format("{0}: Left {1} (Released).", i, fe.Name)); i++;
}
}
}
MonitorMouseLeaveBehavior:
using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Interop;
using System.ComponentModel;
using System.Windows.Media;
using WpfApplication1.Helpers;
namespace WpfApplication1.Behavior
{
public class MonitorMouseLeaveBehavior : Behavior<FrameworkElement>
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(ref Win32Point pt);
[StructLayout(LayoutKind.Sequential)]
internal struct Win32Point
{
public Int32 X;
public Int32 Y;
};
[DllImport("user32.dll")]
public static extern short GetAsyncKeyState(UInt16 virtualKeyCode);
private enum VK
{
LBUTTON = 0x01
}
private bool _tracking;
private const int _interval = 1;
private Timer _checkPosTimer = new Timer(_interval);
private Dictionary<FrameworkElement, RoutedEventHandlerInfo[]> _leaveHandlersForElement = new Dictionary<FrameworkElement, RoutedEventHandlerInfo[]>();
private Window _window;
private Dictionary<FrameworkElement, Rect> _boundsByElement = new Dictionary<FrameworkElement, Rect>();
private Dictionary<FrameworkElement, bool> _wasInside = new Dictionary<FrameworkElement, bool>();
private List<FrameworkElement> _elements = new List<FrameworkElement>();
/// <summary>
/// If true, all subcontrols are monitored for the mouseleave event when left mousebutton is down.
/// True by default.
/// </summary>
public bool MonitorSubControls { get { return (bool)GetValue(MonitorSubControlsProperty); } set { SetValue(MonitorSubControlsProperty, value); } }
public static readonly DependencyProperty MonitorSubControlsProperty = DependencyProperty.Register("MonitorSubControls", typeof(bool), typeof(MonitorMouseLeaveBehavior), new PropertyMetadata(true, OnMonitorSubControlsChanged));
private static void OnMonitorSubControlsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MonitorMouseLeaveBehavior beh = (MonitorMouseLeaveBehavior)d;
beh.AddOrRemoveLogicalChildren((bool)e.NewValue);
}
/// <summary>
/// Initial actions
/// </summary>
protected override void OnAttached()
{
_window = this.AssociatedObject is Window ? (Window)this.AssociatedObject : Window.GetWindow(this.AssociatedObject); // get window
_window.SourceInitialized += (s, e) =>
{
this.AddOrRemoveLogicalChildren(this.MonitorSubControls); // get all monitored elements
this.AttachHandlers(true); // attach mousedown and sizechanged handlers
this.GetAllBounds(); // determine bounds of all elements
_checkPosTimer.Elapsed += (s1, e1) => Dispatcher.BeginInvoke((Action)(() => { CheckPosition(); }));
};
base.OnAttached();
}
protected override void OnDetaching()
{
this.AttachHandlers(false);
base.OnDetaching();
}
/// <summary>
/// Starts or stops monitoring of the AssociatedObject's logical children.
/// </summary>
/// <param name="add"></param>
private void AddOrRemoveLogicalChildren(bool add)
{
if (_window != null && _window.IsInitialized)
{
AddOrRemoveSizeChangedHandlers(false);
_elements.Clear();
if (add)
_elements.AddRange(VisualHelper.FindLogicalChildren<FrameworkElement>(this.AssociatedObject));
_elements.Add(this.AssociatedObject);
AddOrRemoveSizeChangedHandlers(true);
}
}
/// <summary>
/// Attaches/detaches size changed handlers to the monitored elements
/// </summary>
/// <param name="add"></param>
private void AddOrRemoveSizeChangedHandlers(bool add)
{
foreach (var element in _elements)
{
element.SizeChanged -= element_SizeChanged;
if (add) element.SizeChanged += element_SizeChanged;
}
}
/// <summary>
/// Adjusts the stored bounds to the changed size
/// </summary>
void element_SizeChanged(object sender, SizeChangedEventArgs e)
{
FrameworkElement fe = sender as FrameworkElement;
if (fe != null)
GetBounds(fe);
}
/// <summary>
/// Attaches/Detaches MouseLeftButtonDown and SizeChanged handlers
/// </summary>
/// <param name="attach">true: attach, false: detach</param>
private void AttachHandlers(bool attach)
{
AddOrRemoveSizeChangedHandlers(attach);
if (attach)
_window.PreviewMouseLeftButtonDown += window_PreviewMouseLeftButtonDown;
else // detach
_window.PreviewMouseLeftButtonDown -= window_PreviewMouseLeftButtonDown;
}
/// <summary>
/// Gets the bounds for all monitored elements
/// </summary>
private void GetAllBounds()
{
_boundsByElement.Clear();
foreach (var element in _elements)
GetBounds(element);
}
/// <summary>
/// Gets the bounds of the control, which are used to check if the mouse position
/// is located within. Note that this only covers rectangular control shapes.
/// </summary>
private void GetBounds(FrameworkElement element)
{
Point p1 = new Point(0, 0);
Point p2 = new Point(element.ActualWidth, element.ActualHeight);
p1 = element.TransformToVisual(_window).Transform(p1);
p2 = element.TransformToVisual(_window).Transform(p2);
if (element == _window) // window bounds need to account for the border
{
var titleHeight = SystemParameters.WindowCaptionHeight + 2 * SystemParameters.ResizeFrameHorizontalBorderHeight; // not sure about that one
var verticalBorderWidth = SystemParameters.ResizeFrameVerticalBorderWidth;
p1.Offset(-verticalBorderWidth, -titleHeight);
p2.Offset(-verticalBorderWidth, -titleHeight);
}
Rect bounds = new Rect(p1, p2);
if (_boundsByElement.ContainsKey(element))
_boundsByElement[element] = bounds;
else
_boundsByElement.Add(element, bounds);
}
/// <summary>
/// For all monitored elements, detach the MouseLeave event handlers and store them locally,
/// to be executed manually.
/// </summary>
private void RerouteLeaveHandlers()
{
foreach (var element in _elements)
{
if (!_leaveHandlersForElement.ContainsKey(element))
{
var handlers = ReflectionHelper.GetRoutedEventHandlers(element, UIElement.MouseLeaveEvent);
if (handlers != null)
{
_leaveHandlersForElement.Add(element, handlers);
foreach (var handler in handlers)
element.MouseLeave -= (MouseEventHandler)handler.Handler; // detach handlers
}
}
}
}
/// <summary>
/// Reattach all leave handlers that were detached in window_PreviewMouseLeftButtonDown.
/// </summary>
private void ReattachLeaveHandlers()
{
foreach (var kvp in _leaveHandlersForElement)
{
FrameworkElement fe = kvp.Key;
foreach (var handler in kvp.Value)
{
if (handler.Handler is MouseEventHandler)
fe.MouseLeave += (MouseEventHandler)handler.Handler;
}
}
_leaveHandlersForElement.Clear();
}
/// <summary>
/// Checks if the mouse position is inside the bounds of the elements
/// If there is a transition from inside to outside, the leave event handlers are executed
/// </summary>
private void DetermineIsInside()
{
Point p = _window.PointFromScreen(GetMousePosition());
foreach (var element in _elements)
{
if (_boundsByElement.ContainsKey(element))
{
bool isInside = _boundsByElement[element].Contains(p);
bool wasInside = _wasInside.ContainsKey(element) && _wasInside[element];
if (wasInside && !isInside)
ExecuteLeaveHandlers(element);
if (_wasInside.ContainsKey(element))
_wasInside[element] = isInside;
else
_wasInside.Add(element, isInside);
}
}
}
/// <summary>
/// Gets the mouse position relative to the screen
/// </summary>
public static Point GetMousePosition()
{
Win32Point w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
return new Point(w32Mouse.X, w32Mouse.Y);
}
/// <summary>
/// Gets the mouse button state. MouseEventArgs.LeftButton is notoriously unreliable.
/// </summary>
private bool IsMouseLeftButtonPressed()
{
short leftMouseKeyState = GetAsyncKeyState((ushort)VK.LBUTTON);
bool ispressed = leftMouseKeyState < 0;
return ispressed;
}
/// <summary>
/// Executes the leave handlers that were attached to the controls.
/// They have been detached previously by this behavior (see window_PreviewMouseLeftButtonDown), to prevent double execution.
/// After mouseup, they are reattached (see CheckPosition)
/// </summary>
private void ExecuteLeaveHandlers(FrameworkElement fe)
{
MouseDevice mouseDev = InputManager.Current.PrimaryMouseDevice;
MouseEventArgs mouseEvent = new MouseEventArgs(mouseDev, 0) { RoutedEvent = Control.MouseLeaveEvent };
if (_leaveHandlersForElement.ContainsKey(fe))
{
foreach (var handler in _leaveHandlersForElement[fe])
{
if (handler.Handler is MouseEventHandler)
((MouseEventHandler)handler.Handler).Invoke(fe, mouseEvent);
}
}
}
/// <summary>
/// Sets the mouse capture (events outside the window are still directed to it),
/// and tells the behavior to watch out for a missed leave event
/// </summary>
private void window_PreviewMouseLeftButtonDown(object sender, MouseEventArgs e)
{
System.Diagnostics.Debug.WriteLine("--- left mousebutton down ---"); // todo remove
this.RerouteLeaveHandlers();
_tracking = true;
_checkPosTimer.Start();
}
/// <summary>
/// Uses the _tracking field as well as left mouse button state to determine if either
/// leave event handlers should be executed, or monitoring should be stopped.
/// </summary>
private void CheckPosition()
{
if (_tracking)
{
if (IsMouseLeftButtonPressed())
{
this.DetermineIsInside();
}
else
{
_wasInside.Clear();
_tracking = false;
_checkPosTimer.Stop();
System.Diagnostics.Debug.WriteLine("--- left mousebutton up ---"); // todo remove
// invoking ReattachLeaveHandlers() immediately would rethrow MouseLeave for top grid/window
// if both a) mouse is outside window and b) mouse moves. Wait with reattach until mouse is inside window again and moves.
_window.MouseMove += ReattachHandler;
}
}
}
/// <summary>
/// Handles the first _window.MouseMove event after left mouse button was released,
/// and reattaches the MouseLeaveHandlers. Detaches itself to be executed only once.
/// </summary>
private void ReattachHandler(object sender, MouseEventArgs e)
{
ReattachLeaveHandlers();
_window.MouseMove -= ReattachHandler; // only once
}
}
}
VisualHelper.FindLogicalChildren,ReflectionHelper。GetRoutedEventHandlers:
public static List<T> FindLogicalChildren<T>(DependencyObject obj) where T : DependencyObject
{
List<T> children = new List<T>();
foreach (var child in LogicalTreeHelper.GetChildren(obj))
{
if (child != null)
{
if (child is T)
children.Add((T)child);
if (child is DependencyObject)
children.AddRange(FindLogicalChildren<T>((DependencyObject)child)); // recursive
}
}
return children;
}
/// <summary>
/// Gets the list of routed event handlers subscribed to the specified routed event.
/// </summary>
/// <param name="element">The UI element on which the event is defined.</param>
/// <param name="routedEvent">The routed event for which to retrieve the event handlers.</param>
/// <returns>The list of subscribed routed event handlers.</returns>
public static RoutedEventHandlerInfo[] GetRoutedEventHandlers(UIElement element, RoutedEvent routedEvent)
{
var routedEventHandlers = default(RoutedEventHandlerInfo[]);
// Get the EventHandlersStore instance which holds event handlers for the specified element.
// The EventHandlersStore class is declared as internal.
var eventHandlersStoreProperty = typeof(UIElement).GetProperty("EventHandlersStore", BindingFlags.Instance | BindingFlags.NonPublic);
object eventHandlersStore = eventHandlersStoreProperty.GetValue(element, null);
if (eventHandlersStore != null)
{
// Invoke the GetRoutedEventHandlers method on the EventHandlersStore instance
// for getting an array of the subscribed event handlers.
var getRoutedEventHandlers = eventHandlersStore.GetType().GetMethod("GetRoutedEventHandlers", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
routedEventHandlers = (RoutedEventHandlerInfo[])getRoutedEventHandlers.Invoke(eventHandlersStore, new object[] { routedEvent });
}
return routedEventHandlers;
}
答案 1 :(得分:3)
方法#1 - 如果您确定具体细节,仍然是有效的(作为纯托管解决方案)。
(捕获可以给予特定控件以避免问题,但我还没有尝试过)
这可以帮助您获得事件('固定'事件)。
键是在外窗时跟踪鼠标移动(仅当鼠标关闭时)。
为此,您需要执行capture
(但与建议略有不同,因为它不起作用 - 而不是向下/向上)。
private void Window_MouseDown(object sender, MouseEventArgs e)
{
this.CaptureMouse();
}
private void Window_MouseUp(object sender, MouseEventArgs e)
{
this.ReleaseMouseCapture();
}
private void Window_MouseLeave(object sender, MouseEventArgs e)
{
test1.Content = "Mouse left";
}
private void Window_MouseEnter(object sender, MouseEventArgs e)
{
test1.Content = "Mouse entered";
}
private void Window_MouseMove(object sender, MouseEventArgs e)
{
if (Mouse.Captured == this)
{
if (!this.IsMouseInBounds(e))
Window_MouseLeave(sender, e);
else
Window_MouseEnter(sender, e);
}
test2.Content = e.GetPosition(this).ToString();
}
private bool IsMouseInBounds(MouseEventArgs e)
{
var client = ((FrameworkElement)this.Content);
Rect bounds = new Rect(0, 0, client.ActualWidth, client.ActualHeight);
return bounds.Contains(e.GetPosition(this));
}
private Point GetRealPosition(Point mousePoint)
{
return Application.Current.MainWindow.PointFromScreen(mousePoint);
}
注意:
您需要根据自己的情况完成此操作。我只是'虚拟连接'鼠标移动到Enter
和Leave
并且没有任何智能算法(即generated
进入/离开将继续发射)。即添加一些标志以实际保存输入/离开的state
。
此外,我正在测量鼠标是否在Window的“客户端范围”内。如果你需要边框等,你需要调整它。
此外,我忘了添加明显的内容 - 连接新事件MouseDown="Window_MouseDown" MouseUp="Window_MouseUp"
答案 2 :(得分:2)
这是“正常”行为。在MouseEnter处理程序中捕获鼠标。
Mouse.Capture(yourUIElement);
然后在MouseLeave中发布它,
Mouse.Capture(null);
编辑:更多解释。 WPF不会精确跟踪鼠标移动。您可以从以下事实推断出:如果您捕获MouseMove事件,您可以看到它每20毫秒间隔报告一次事件而不是像素精度..更像每个事件8个像素。
现在这并不是那么可怕,但如果碰巧移动鼠标,WPF也不会报告窗外的鼠标移动。这是默认行为。你可以通过Mouse.Capture来改变它。
现在,你可以想象为什么会出现这个问题。如果您可以比鼠标移动报告更快地将鼠标移动到窗口之外,那么WPF仍然认为它在应用程序内部。
答案 3 :(得分:2)
如果您需要它 - 我在编辑了一个简化的包装器以便于使用(只需添加命令即可 视图模型)
方法#2 - 使用Global Mouse Hook跟踪鼠标移动 - 其余类似于#1。
实际上,这是关于如何从C#进行全局钩子的更多示例。
在XAML中,你可以连接所有3个或只有一个,两个事件
my:Hooks.EnterCommand="{Binding EnterCommand}"
my:Hooks.LeaveCommand="{Binding LeaveCommand}"
my:Hooks.MouseMoveCommand="{Binding MoveCommand}"
在视图模型中定义命令
RelayCommand _enterCommand;
public RelayCommand EnterCommand
{
get
{
return _enterCommand ?? (_enterCommand = new RelayCommand(param =>
{
var point = (Point)param;
test1.Content = "Mouse entered";
// test2.Content = point.ToString();
},
param => true));
}
}
附加属性('漂亮'包装器)......
public static class Hooks
{
private static Dictionary<ContentControl, Action> _hash = new Dictionary<ContentControl, Action>();
#region MouseMoveCommand
public static ICommand GetMouseMoveCommand(ContentControl control) { return (ICommand)control.GetValue(MouseMoveCommandProperty); }
public static void SetMouseMoveCommand(ContentControl control, ICommand value) { control.SetValue(MouseMoveCommandProperty, value); }
public static readonly DependencyProperty MouseMoveCommandProperty =
DependencyProperty.RegisterAttached("MouseMoveCommand", typeof(ICommand), typeof(Hooks), new UIPropertyMetadata(null, OnMouseMoveCommandChanged));
static void OnMouseMoveCommandChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
ContentControl control = depObj as ContentControl;
if (control != null && e.NewValue is ICommand)
SetupMouseMove(control);
}
static void Instance_MouseMoveLL(object sender, WinHook.MouseLLMessageArgs e)
{
}
static void OnAutoGeneratingColumn(ICommand command, object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if (command.CanExecute(e)) command.Execute(e);
}
#endregion
#region EnterCommand
public static ICommand GetEnterCommand(ContentControl control) { return (ICommand)control.GetValue(EnterCommandProperty); }
public static void SetEnterCommand(ContentControl control, ICommand value) { control.SetValue(EnterCommandProperty, value); }
public static readonly DependencyProperty EnterCommandProperty =
DependencyProperty.RegisterAttached("EnterCommand", typeof(ICommand), typeof(Hooks), new UIPropertyMetadata(null, OnEnterCommandChanged));
static void OnEnterCommandChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
ContentControl control = depObj as ContentControl;
if (control != null && e.NewValue is ICommand)
SetupMouseMove(control);
}
#endregion
#region LeaveCommand
public static ICommand GetLeaveCommand(ContentControl control) { return (ICommand)control.GetValue(LeaveCommandProperty); }
public static void SetLeaveCommand(ContentControl control, ICommand value) { control.SetValue(LeaveCommandProperty, value); }
public static readonly DependencyProperty LeaveCommandProperty =
DependencyProperty.RegisterAttached("LeaveCommand", typeof(ICommand), typeof(Hooks), new UIPropertyMetadata(null, OnLeaveCommandChanged));
static void OnLeaveCommandChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
ContentControl control = depObj as ContentControl;
if (control != null && e.NewValue is ICommand)
SetupMouseMove(control);
}
#endregion
static void SetupMouseMove(ContentControl control)
{
Action onmove;
if (_hash.TryGetValue(control, out onmove) == false)
{
onmove = () =>
{
var entered = false;
var moveCommand = control.GetValue(Hooks.MouseMoveCommandProperty) as ICommand;
var enterCommand = control.GetValue(Hooks.EnterCommandProperty) as ICommand;
var leaveCommand = control.GetValue(Hooks.LeaveCommandProperty) as ICommand;
// hook is invoked on the 'caller thread' (i.e. your GUI one) so it's safe
// don't forget to unhook and dispose / release it, handle unsubscribe for events
WinHook.Instance.MouseMoveLL += (s, e) =>
{
Point point = control.PointFromScreen(new Point(e.Message.Pt.X, e.Message.Pt.Y));
if (moveCommand != null && moveCommand.CanExecute(point))
moveCommand.Execute(point);
var newEntered = control.IsMouseInBounds(point); // don't use 'IsMouseOver'
if (newEntered != entered)
{
entered = newEntered;
if (entered)
{
if (enterCommand != null && enterCommand.CanExecute(point))
enterCommand.Execute(point);
}
else
{
if (leaveCommand != null && leaveCommand.CanExecute(point))
leaveCommand.Execute(point);
}
}
};
};
control.Loaded += (s, e) => onmove();
_hash[control] = onmove;
}
}
private static bool IsMouseInBounds(this ContentControl control, Point point)
{
var client = ((FrameworkElement)control.Content);
Rect bounds = new Rect(0, 0, client.ActualWidth, client.ActualHeight);
return bounds.Contains(point);
}
}
您可以使用文章中的HookManager。
或者最小钩子代码(请注意,需要正确的IDisoposable,异常处理等):
public sealed class WinHook : IDisposable
{
public static readonly WinHook Instance = new WinHook();
public const int WH_MOUSE_LL = 14;
public const uint WM_MOUSEMOVE = 0x0200;
public delegate void MouseLLMessageHandler(object sender, MouseLLMessageArgs e);
public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern int GetCurrentThreadId();
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern bool UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential)]
public class MouseLLHookStruct
{
public POINT Pt;
public uint mouseData;
public uint flags;
public uint time;
public uint dwExtraInfo;
}
public class MouseLLMessageArgs : EventArgs
{
public bool IsProcessed { get; set; }
public MouseLLHookStruct Message { get; private set; }
public MouseLLMessageArgs(MouseLLHookStruct message) { this.Message = message; }
}
static IntPtr GetModuleHandle()
{
using (Process process = Process.GetCurrentProcess())
using (ProcessModule module = process.MainModule)
return GetModuleHandle(module.ModuleName);
}
public event MouseLLMessageHandler MouseMoveLL;
int _hLLMouseHook = 0;
HookProc LLMouseHook;
private WinHook()
{
IntPtr hModule = GetModuleHandle();
LLMouseHook = LowLevelMouseProc;
_hLLMouseHook = SetWindowsHookEx(WH_MOUSE_LL, LLMouseHook, hModule, 0);
if (_hLLMouseHook == 0) { } // "failed w/ an error code: {0}", new Win32Exception(Marshal.GetLastWin32Error()).Message
}
public void Release()
{
if (_hLLMouseHook == 0) return;
int hhook = _hLLMouseHook;
_hLLMouseHook = 0;
bool ret = UnhookWindowsHookEx(hhook);
if (ret == false) { } // "failed w/ an error code: {0}", new Win32Exception(Marshal.GetLastWin32Error()).Message
}
public int LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && lParam.ToInt32() > 0
&& wParam.ToInt32() == (int)WM_MOUSEMOVE)
{
MouseLLHookStruct msg = (MouseLLHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseLLHookStruct));
MouseLLMessageArgs args = new MouseLLMessageArgs(msg);
if (MouseMoveLL != null)
MouseMoveLL(this, args);
if (args.IsProcessed)
return -1; // return 1;
}
return CallNextHookEx(_hLLMouseHook, nCode, wParam, lParam);
}
// implement IDisposable properly and call `Release` for unmanaged resources / hook
public void Dispose() { }
}
同样避免在活动中放置任何“沉重”的东西 - 或者从其中“产生”的任何东西。实际上你可以花费在处理事件上的时间限制 - 或者你的钩子会被移除,即停止工作。如果您需要从事件中进行一些处理,请弹出一个新线程并重新调用 我最喜欢的解决方案实际上是给钩子自己的线程,然后需要调用事件 - 但是这超出了范围而且有点复杂(你需要'泵'等)。
至于'为什么'这一切都是必要的:
我不喜欢猜测,但似乎事件受到限制 - 当'越过边界'时,错过了关键的'一个',就像那样。无论如何,无论你怎么看它都是关于鼠标移动的。