2017-10-06 31 views
0

我正在從一個引腳連接到另一個引腳的線路連接器控件上。典型的WPF解決方案是在用戶開始拖動連接線時使用鼠標捕獲。不幸的是,如果用戶超過了有效的引腳,我需要一個鼠標懸停指示器。但是從未顯示指示器,因爲當我已經捕獲鼠標之前,目標引腳從未獲取鼠標事件。即使另一個控件已經捕獲了鼠標,如何獲得控件的MouseEvents

我寫了一個lighweight樣品展示我的問題:

<Window x:Class="WpfApp1.MainWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:local="clr-namespace:WpfApp1" 
    mc:Ignorable="d" 
    WindowState="Maximized" 
    Title="MainWindow" Height="350" Width="525"> 
    <Canvas> 
     <CheckBox x:Name="EnableMouseCapture" IsChecked="True" Content="Enable Mouse Capture" /> 
     <Rectangle x:Name="Test" Fill="Blue" Width="40" Height="40" Canvas.Left="200" Canvas.Top="200" /> 
     <Line x:Name="Line" Stroke="Black" StrokeThickness="1" IsHitTestVisible="False" /> 
    </Canvas> 
</Window> 

而且隱藏文件中的代碼:

using System; 
using System.Windows.Input; 
using System.Windows.Media; 

namespace WpfApp1 
{ 
    /// <summary> 
    /// Interaction logic for MainWindow.xaml 
    /// </summary> 
    public partial class MainWindow 
    { 
     public MainWindow() 
     { 
      InitializeComponent(); 

      Test.MouseEnter += TestOnMouseEnter; 
      Test.MouseLeave += TestOnMouseLeave; 

      MouseDown += OnMouseDown; 
     } 

     private void TestOnMouseEnter(object sender, MouseEventArgs mouseEventArgs) 
     { 
      Console.WriteLine("(Test) MouseEnter"); 

      Test.Fill = Brushes.Coral; 
     } 

     private void TestOnMouseLeave(object sender, MouseEventArgs mouseEventArgs) 
     { 
      Console.WriteLine("(Test) MouseLeave"); 

      Test.Fill = Brushes.Blue; 
     } 

     private void OnMouseMove(object sender, MouseEventArgs mouseEventArgs) 
     { 
      Console.WriteLine("(Window) MouseMove"); 

      var pos = mouseEventArgs.GetPosition(this); 
      Line.X2 = pos.X; 
      Line.Y2 = pos.Y; 
     } 

     private void OnMouseDown(object sender, MouseButtonEventArgs mouseButtonEventArgs) 
     { 
      Console.WriteLine("(Window) MouseDown"); 

      MouseUp += OnMouseUp; 
      MouseMove += OnMouseMove; 

      var pos = mouseButtonEventArgs.GetPosition(this); 
      Line.X1 = pos.X; 
      Line.Y1 = pos.Y; 

      if (EnableMouseCapture.IsChecked == true) 
      { 
       CaptureMouse(); 
      } 
     } 

     private void OnMouseUp(object sender, MouseButtonEventArgs mouseButtonEventArgs) 
     { 
      Console.WriteLine("(Window) MouseUp"); 

      ReleaseMouseCapture(); 

      MouseUp -= OnMouseUp; 
      MouseMove -= OnMouseMove; 
     } 
    } 
} 

如果在畫布上鼠標捕獲被啓用,功能TestOnMouseEnterTestOnMouseLeave不調用。如果禁用鼠標捕捉,則會調用這兩個函數。 我知道這是一個WPF的典型行爲,但是有沒有人可能知道我是如何得到消息,即使另一個控件有捕獲?

+0

您是否嘗試過使用 「OnPreview ...」 事件? –

+0

由於MouseCapturing的概念,我認爲這是行不通的。 – Rod

回答

1

那麼,我的理解是,MouseCapture是在那裏使代碼整潔,所以你爲什麼不不使用鼠標捕獲。

https://msdn.microsoft.com/en-us/library/ms771301.aspx

鼠標捕獲從MSDN:當一個對象捕獲鼠標,好象用鼠標捕獲的對象執行的情況下,即使鼠標指針位於另一對象中的所有小鼠相關的事件進行處理。

What does it mean to "Capture the mouse" in WPF?

捕獲鼠標是用於拖拉,因爲只有捕獲控制接收鼠標事件直到被釋放是有用的。所有拖動代碼都可以存在於一個控件中,而不是分散在多個控件中。

如果這樣做讓你的應用按照你想要的方式行事,那麼爲什麼不避免使用它呢?聽起來像鼠標捕獲的目的擊敗了你想要實現的目標。

我發現這個類似的問題也一樣,如果你想看看:

How to fire MouseEnter for one object if another object has mousecapture?

編輯:我確實有一個例子,在這裏,但它並沒有正常工作,基本上你需要手動命中測試,然後手動觸發鼠標輸入和鼠標離開。

+0

謝謝你的快速回答。我會檢查你的建議。 – Rod

1

在評估了一些替代解決方案之後,我找到了解決問題的另一種方法。它使用的是Win32 api。 所以有兩種可能的方法來解決這類問題。 Colins的方式更像WPF,但你需要手動模擬鼠標事件,這可能是一個麻煩。第二種解決方案不幸的是使用非託管的Win32鉤子,但WPF鼠標事件系統沒有限制。

這裏是我的示例代碼:

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Runtime.InteropServices; 
using System.Windows; 
using System.Windows.Input; 

namespace WpfApp1 
{ 
    public static class NativeMouseHook 
    { 
     private static readonly Dictionary<MouseMessages, List<MouseEventHandler>> MouseHandlers = new Dictionary<MouseMessages, List<MouseEventHandler>>(); 
     private static readonly Dictionary<MouseMessages, List<MouseButtonEventHandler>> MouseButtonHandlers = new Dictionary<MouseMessages, List<MouseButtonEventHandler>>(); 
     private static readonly Dictionary<MouseMessages, List<MouseWheelEventHandler>> MouseWheelHandlers = new Dictionary<MouseMessages, List<MouseWheelEventHandler>>(); 

     public static void RegisterMouseHandler(MouseMessages mouseMessage, MouseEventHandler handler) 
     { 
      AddHandler(mouseMessage, MouseHandlers, handler); 
      Start(); 
     } 

     public static void UnregisterMouseHandler(MouseMessages mouseMessage, MouseEventHandler handler) 
     { 
      RemoveHandler(mouseMessage, MouseHandlers, handler); 
      CheckAndStop(); 
     } 

     public static void RegisterMouseHandler(MouseMessages mouseMessage, MouseButtonEventHandler handler) 
     { 
      AddHandler(mouseMessage, MouseButtonHandlers, handler); 
      Start(); 
     } 

     public static void UnregisterMouseHandler(MouseMessages mouseMessage, MouseButtonEventHandler handler) 
     { 
      RemoveHandler(mouseMessage, MouseButtonHandlers, handler); 
      CheckAndStop(); 
     } 

     public static void RegisterMouseHandler(MouseMessages mouseMessage, MouseWheelEventHandler handler) 
     { 
      AddHandler(mouseMessage, MouseWheelHandlers, handler); 
      Start(); 
     } 

     public static void UnregisterMouseHandler(MouseMessages mouseMessage, MouseWheelEventHandler handler) 
     { 
      RemoveHandler(mouseMessage, MouseWheelHandlers, handler); 
      CheckAndStop(); 
     } 

     private static void AddHandler<T>(MouseMessages mouseMessage, Dictionary<MouseMessages, List<T>> targetHandlerDictionary, T handler) 
     { 
      if (!targetHandlerDictionary.ContainsKey(mouseMessage)) 
      { 
       targetHandlerDictionary.Add(mouseMessage, new List<T>()); 
      } 

      targetHandlerDictionary[mouseMessage].Add(handler); 
     } 

     private static void RemoveHandler<T>(MouseMessages mouseMessage, Dictionary<MouseMessages, List<T>> targetHandlerDictionary, T handler) 
     { 
      if (targetHandlerDictionary.ContainsKey(mouseMessage)) 
      { 
       var handlerList = targetHandlerDictionary[mouseMessage]; 
       handlerList.Remove(handler); 

       if (handlerList.Count == 0) 
       { 
        targetHandlerDictionary.Remove(mouseMessage); 
       } 
      } 
     } 

     private static void CheckAndStop() 
     { 
      if (MouseHandlers.Count == 0 && MouseButtonHandlers.Count == 0 && MouseWheelHandlers.Count == 0) 
      { 
       Stop(); 
      } 
     } 

     private static void Start() 
     { 
      if (_hookId == IntPtr.Zero) 
      { 
       _hookId = SetHook(Proc); 
      } 
     } 

     private static void Stop() 
     { 
      if (_hookId != IntPtr.Zero) 
      { 
       UnhookWindowsHookEx(_hookId); 
       _hookId = IntPtr.Zero; 
      } 
     } 

     private static readonly LowLevelMouseProc Proc = HookCallback; 
     private static IntPtr _hookId = IntPtr.Zero; 

     private static IntPtr SetHook(LowLevelMouseProc proc) 
     { 
      using (var curProcess = Process.GetCurrentProcess()) 
      { 
       using (var curModule = curProcess.MainModule) 
       { 
        return SetWindowsHookEx(WH_MOUSE_LL, proc, GetModuleHandle(curModule.ModuleName), 0); 
       } 
      } 
     } 

     private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam); 

     private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) 
     { 
      if (nCode >= 0) 
      { 
       var hookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT)); 

       switch ((MouseMessages)wParam) 
       { 
        case MouseMessages.WM_LBUTTONDOWN: 
         CallHandler(MouseMessages.WM_LBUTTONDOWN, MouseButtonHandlers, new MouseButtonEventArgs(Mouse.PrimaryDevice, (int)hookStruct.time, MouseButton.Left)); 
         break; 
        case MouseMessages.WM_LBUTTONUP: 
         CallHandler(MouseMessages.WM_LBUTTONUP, MouseButtonHandlers, new MouseButtonEventArgs(Mouse.PrimaryDevice, (int)hookStruct.time, MouseButton.Left)); 
         break; 
        case MouseMessages.WM_MOUSEMOVE: 
         CallHandler(MouseMessages.WM_MOUSEMOVE, MouseHandlers, new MouseEventArgs(Mouse.PrimaryDevice, (int)hookStruct.time)); 
         break; 
        case MouseMessages.WM_MOUSEWHEEL: 
         CallHandler(MouseMessages.WM_MOUSEWHEEL, MouseWheelHandlers, new MouseWheelEventArgs(Mouse.PrimaryDevice, (int)hookStruct.time, 0)); 
         break; 
        case MouseMessages.WM_RBUTTONDOWN: 
         CallHandler(MouseMessages.WM_LBUTTONDOWN, MouseButtonHandlers, new MouseButtonEventArgs(Mouse.PrimaryDevice, (int)hookStruct.time, MouseButton.Right)); 
         break; 
        case MouseMessages.WM_RBUTTONUP: 
         CallHandler(MouseMessages.WM_LBUTTONUP, MouseButtonHandlers, new MouseButtonEventArgs(Mouse.PrimaryDevice, (int)hookStruct.time, MouseButton.Right)); 
         break; 
       } 
      } 

      return CallNextHookEx(_hookId, nCode, wParam, lParam); 
     } 

     private static void CallHandler<T>(MouseMessages mouseMessage, Dictionary<MouseMessages, List<T>> targetHandlerDictionary, EventArgs args) 
     { 
      if (targetHandlerDictionary.ContainsKey(mouseMessage)) 
      { 
       var handlerList = targetHandlerDictionary[mouseMessage]; 
       foreach (var handler in handlerList.Cast<Delegate>()) 
       { 
        handler.DynamicInvoke(null, args); 
       } 
      } 
     } 

     private const int WH_MOUSE_LL = 14; 

     public enum MouseMessages 
     { 
      WM_LBUTTONDOWN = 0x0201, 
      WM_LBUTTONUP = 0x0202, 
      WM_MOUSEMOVE = 0x0200, 
      WM_MOUSEWHEEL = 0x020A, 
      WM_RBUTTONDOWN = 0x0204, 
      WM_RBUTTONUP = 0x0205 
     } 

     [StructLayout(LayoutKind.Sequential)] 
     private struct POINT 
     { 
      public int x; 
      public int y; 
     } 

     [StructLayout(LayoutKind.Sequential)] 
     private struct MSLLHOOKSTRUCT 
     { 
      public POINT pt; 
      public uint mouseData; 
      public uint flags; 
      public uint time; 
      public IntPtr dwExtraInfo; 
     } 

     [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; 
     }; 
     public static Point GetMousePosition() 
     { 
      Win32Point w32Mouse = new Win32Point(); 
      GetCursorPos(ref w32Mouse); 
      return new Point(w32Mouse.X, w32Mouse.Y); 
     } 

     [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
     private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId); 

     [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
     [return: MarshalAs(UnmanagedType.Bool)] 
     private static extern bool UnhookWindowsHookEx(IntPtr hhk); 

     [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
     private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); 

     [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
     private static extern IntPtr GetModuleHandle(string lpModuleName); 
    } 
} 

XAML:

<Window x:Class="WpfApp1.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:local="clr-namespace:WpfApp1" 
     mc:Ignorable="d" 
     WindowState="Maximized" 
     Title="MainWindow" Height="350" Width="525"> 
    <Canvas> 
     <Rectangle x:Name="Test" Fill="Blue" Width="40" Height="40" Canvas.Left="200" Canvas.Top="200" /> 
     <Line x:Name="Line" Stroke="Black" StrokeThickness="1" IsHitTestVisible="False" /> 
    </Canvas> 
</Window> 

後面的代碼:

using System; 
using System.Windows.Input; 
using System.Windows.Media; 

namespace WpfApp1 
{ 
    /// <summary> 
    /// Interaction logic for MainWindow.xaml 
    /// </summary> 
    public partial class MainWindow 
    { 
     public MainWindow() 
     { 
      InitializeComponent(); 

      Test.MouseEnter += TestOnMouseEnter; 
      Test.MouseLeave += TestOnMouseLeave; 

      MouseDown += OnMouseDown; 
     } 

     private void TestOnMouseEnter(object sender, MouseEventArgs mouseEventArgs) 
     { 
      Console.WriteLine("(Test) MouseEnter"); 

      Test.Fill = Brushes.Coral; 
     } 

     private void TestOnMouseLeave(object sender, MouseEventArgs mouseEventArgs) 
     { 
      Console.WriteLine("(Test) MouseLeave"); 

      Test.Fill = Brushes.Blue; 
     } 

     private void OnMouseMove(object sender, MouseEventArgs mouseEventArgs) 
     { 
      Console.WriteLine("(Window) MouseMove"); 

      var pos = NativeMouseHook.GetMousePosition(); 
      Line.X2 = pos.X; 
      Line.Y2 = pos.Y; 
     } 

     private void OnMouseDown(object sender, MouseButtonEventArgs mouseButtonEventArgs) 
     { 
      Console.WriteLine("(Window) MouseDown"); 

      NativeMouseHook.RegisterMouseHandler(NativeMouseHook.MouseMessages.WM_MOUSEMOVE, (MouseEventHandler)OnMouseMove); 
      NativeMouseHook.RegisterMouseHandler(NativeMouseHook.MouseMessages.WM_LBUTTONUP, OnMouseUp); 

      var pos = mouseButtonEventArgs.GetPosition(this); 
      Line.X1 = pos.X; 
      Line.Y1 = pos.Y; 
     } 

     private void OnMouseUp(object sender, MouseButtonEventArgs mouseButtonEventArgs) 
     { 
      Console.WriteLine("(Window) MouseUp"); 

      NativeMouseHook.UnregisterMouseHandler(NativeMouseHook.MouseMessages.WM_MOUSEMOVE, (MouseEventHandler)OnMouseMove); 
      NativeMouseHook.UnregisterMouseHandler(NativeMouseHook.MouseMessages.WM_LBUTTONUP, OnMouseUp); 
     } 
    } 
} 
相關問題