17

マウスの出入りイベントに問題があります。カーソルがコントロール内にある状態でマウス ボタンを押したままにしてから、カーソルをコントロールの外に十分な速さで移動すると、このイベントはトリガーされません。

なぜそれが起こるのか教えてください。これらのイベントを適切に取得する方法はありますか?

サンプル プロジェクトで動作を確認してください: https://www.dropbox.com/s/w5ra2vzegjtauso/SampleApp.zip

アップデート。ここで答えのない同じ問題を見つけました。そこでバウンティを始めました。

4

4 に答える 4

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

注:
状況に応じて、これを終了する必要があります。マウスを「ダミー配線」してそこに移動しEnterLeaveそこにスマートアルゴリズムを使用しません(つまりgenerated、入る/離れると発火し続けます)。stateつまり、実際にエンター/リーブを適切 に保存するためのフラグを追加します。

また、マウスがウィンドウの「クライアント境界」内にあるかどうかを測定しています。境界線などに関して必要な場合は、調整する必要があります。

また、明らかなことを追加するのを忘れていました-新しいイベントを接続しますMouseDown="Window_MouseDown" MouseUp="Window_MouseUp"

于 2013-04-14T15:22:42.720 に答える
2

編集

必要な場合に備えて、使いやすいように簡略化されたラッパーで編集しました(ビューモデルにコマンドを追加するだけです)

アプローチ #2 - グローバル マウス フックを使用してマウスの動きを追跡する - 残りは #1 と同様です。
実際、これは C# からグローバル フックを実行する方法の例です。


XAML では、3 つすべてまたは 1 つまたは 2 つのイベントを接続できます。

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() { }
}


注:グローバル マウス フックは、パフォーマンスの問題で有名です。また、ローカルのものを使用することはできません (推奨されますが、ほとんどの場合は役に立ちません) - マウスの動きの外に出ないためです。

また、イベント内に「重い」もの、またはイベントから「発生する」ものを配置しないでください。イベントの処理に費やすことができる時間には実際には制限があります。そうしないと、フックが削除されます。つまり、機能しなくなります。イベントから何らかの処理を行う必要がある場合は、新しいスレッドをポップアップして呼び出します。
私のお気に入りの解決策は、実際にはフックに独自のスレッドを与えてから、イベントを呼び出す必要があることですが、それは範囲外であり、もう少し複雑です (そこに「ポンプ」が必要など)。

「なぜ」これがすべて必要なのかについて:
私は推測するのは好きではありませんが、イベントが抑制されているようです-そして「国境を越える」ときに重要な「1」が見逃されているようです. とにかく、どう見てもマウスの動きばかりです。

于 2013-04-15T19:12:37.100 に答える