WPF: Ignore mouse clicks on overlay/adorner, but handle MouseEnter event

stone picture stone · Oct 8, 2010 · Viewed 7.6k times · Source

What I really want is a version of IsHitTestVisible that ignores mouse click events but still traps mouse enter and leave events.

Background: An informational overlay pops up under the control with focus whenever. This is a requirement, so I'm not at liberty to remove this behavior. This is implemented using an adorner containing a Rectangle shape, filled with an image brush. All controls are created programatically, no XAML involved.

Desired behavior: When user mouses over the Rectangle, it should become partially transparent. This is so that they can see the other controls beneath the overlay and click them. When the user clicks on the overlay, the click should be passed through to whatever control is under the overlay, right where the user clicked.

Problem: If I set IsHitTestVisible to True to allow mouse clicks to pass through, I don't get MouseEnter events.

Is there a simple way to leave IsHitTestVisible True, and then pass all but 2-3 events to the correct control beneath the adorner? I'm looking for a solution that does not involve calculating what control is beneath the cursor, since WPF is clearly capable of doing this for me.

Alternatively, could I set IsHitTestVisible to False but then use another simple method to determine when the mouse is over the adorner?

UPDATE: I'm still hoping for an answer, but as of now the most promising solution seems to be leaving IsHitTestVisible true, and using the WPF hit testing APIs to figure out what type of control was underneath the mouse cursor; if it was one I know about, I'd send a Click command to it. Not sure if this is worth doing, though; as of now clicking dismisses my overlay so the user just has to click twice.

Thanks!

Answer

Fredrik Hedblad picture Fredrik Hedblad · Dec 16, 2010

Since IsHitTestVisible="False" disables all mouse interaction, it doesn't seem to be any clean way of doing this. I tried

  • Setting IsHitTestVisible="False" in OnPreviewMouseDown and then re-clicking the adorner using UIAutomation but no dice
  • Binding the Opacity to an "invisible" element under the Adorner so that the click would pass through. It passed through all right, but of course the problem was the same on the next level so no dice.

Seems like the only way to actually get this to work is to set IsHitTestVisible="False" in OnMouseDown and then simulate a new MouseClick using SendInput. Not very pretty but it gets the job done.

protected override void OnMouseDown(MouseButtonEventArgs e)
{
    IsHitTestVisible = false;

    Action action = () =>
    {
        MouseSimulator.ClickLeftMouseButton();
        IsHitTestVisible = true;
    };
    this.Dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle); 
}

public class MouseSimulator
{
    [DllImport("user32.dll", SetLastError = true)]
    static extern uint SendInput(uint nInputs, ref INPUT pInputs, int cbSize);

    [StructLayout(LayoutKind.Sequential)]
    struct INPUT
    {
        public SendInputEventType type;
        public MouseKeybdhardwareInputUnion mkhi;
    }
    [StructLayout(LayoutKind.Explicit)]
    struct MouseKeybdhardwareInputUnion
    {
        [FieldOffset(0)]
        public MouseInputData mi;

        [FieldOffset(0)]
        public KEYBDINPUT ki;

        [FieldOffset(0)]
        public HARDWAREINPUT hi;
    }
    [StructLayout(LayoutKind.Sequential)]
    struct KEYBDINPUT
    {
        public ushort wVk;
        public ushort wScan;
        public uint dwFlags;
        public uint time;
        public IntPtr dwExtraInfo;
    }
    [StructLayout(LayoutKind.Sequential)]
    struct HARDWAREINPUT
    {
        public int uMsg;
        public short wParamL;
        public short wParamH;
    }
    struct MouseInputData
    {
        public int dx;
        public int dy;
        public uint mouseData;
        public MouseEventFlags dwFlags;
        public uint time;
        public IntPtr dwExtraInfo;
    }
    [Flags]
    enum MouseEventFlags : uint
    {
        MOUSEEVENTF_MOVE = 0x0001,
        MOUSEEVENTF_LEFTDOWN = 0x0002,
        MOUSEEVENTF_LEFTUP = 0x0004,
        MOUSEEVENTF_RIGHTDOWN = 0x0008,
        MOUSEEVENTF_RIGHTUP = 0x0010,
        MOUSEEVENTF_MIDDLEDOWN = 0x0020,
        MOUSEEVENTF_MIDDLEUP = 0x0040,
        MOUSEEVENTF_XDOWN = 0x0080,
        MOUSEEVENTF_XUP = 0x0100,
        MOUSEEVENTF_WHEEL = 0x0800,
        MOUSEEVENTF_VIRTUALDESK = 0x4000,
        MOUSEEVENTF_ABSOLUTE = 0x8000
    }
    enum SendInputEventType : int
    {
        InputMouse,
        InputKeyboard,
        InputHardware
    }

    public static void ClickLeftMouseButton()
    {
        INPUT mouseDownInput = new INPUT();
        mouseDownInput.type = SendInputEventType.InputMouse;
        mouseDownInput.mkhi.mi.dwFlags = MouseEventFlags.MOUSEEVENTF_LEFTDOWN;
        SendInput(1, ref mouseDownInput, Marshal.SizeOf(new INPUT()));

        INPUT mouseUpInput = new INPUT();
        mouseUpInput.type = SendInputEventType.InputMouse;
        mouseUpInput.mkhi.mi.dwFlags = MouseEventFlags.MOUSEEVENTF_LEFTUP;
        SendInput(1, ref mouseUpInput, Marshal.SizeOf(new INPUT()));
    }
}