gpt4 book ai didi

WPF:鼠标离开事件不会在鼠标按下时触发

转载 作者:行者123 更新时间:2023-12-03 14:14:08 24 4
gpt4 key购买 nike

我遇到了鼠标进入/离开事件的问题。当鼠标按钮被按下并按住控件内的光标然后光标足够快地移出控件时,此事件不会触发。

你能告诉我为什么会这样吗?有没有办法正确获取这些事件?

请查看示例项目以查看它的实际效果:https://www.dropbox.com/s/w5ra2vzegjtauso/SampleApp.zip

更新。 我发现了同样的问题here没有答案。在那里开始赏金。

最佳答案

编辑:在 Sisyphe 正确指出该行为不适用于具有鼠标交互的元素后,我重写了代码。

该行为可以附加到窗口或任何其他 FrameworkElement。默认情况下,当鼠标左键按下并执行处理程序时,将监视所有包含的元素的 MouseLeave。通过设置 MonitorSubControls="False",该行为也可以仅应用于其关联元素。 .

基本上,该行为的作用(有关更多详细信息,请参阅代码中的注释):

  • 仅在按下鼠标左键时才“激活”
  • 监视鼠标位置从元素内部到外部的变化。在这种情况下,执行事件处理程序。

  • 已知的限制(我认为,都可以通过更多的努力来解决,但对我来说似乎不太重要):
  • 不执行转换到包含元素(“内部”边界)的处理程序
  • 不保证处理程序的正确执行顺序
  • 无法解决缓慢转换到窗口外部的问题,e.LeftButton 被报告为已释放(错误?)。
  • 我决定不使用 Win32 钩子(Hook),而是使用一个计时器,它不会超过大约每 0.15 秒触发一次(尽管设置的间隔更小,时钟漂移?)。对于快速鼠标移动,评估点可能相距太远,错过刚刚掠过的元素。

  • enter image description here

    此脚本产生以下输出: 将行为附加到窗口,在橙色边框内移动(在释放鼠标按钮的情况下将蓝色边框留在内边界:0),在橙色边框内按下鼠标左键并在窗口外移动(快速)执行离开处理程序 (1 - 4)。释放窗口外的鼠标按钮,移回 goldTextBox (5),在文本框中按下鼠标左键,离开(快或慢)窗口外再次执行正确的处理程序 (6 - 9)。

    enter image description here

    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;
    }

    关于WPF:鼠标离开事件不会在鼠标按下时触发,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15970248/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com