views:

243

answers:

5

My WndProc isn't seeing mouse-up notifications when I click with a modifier key (shift or control) pressed. I see them without the modifier key, and I see mouse-down notifications with the modifier keys.

I'm trying to track user actions in a component I didn't write, so I'm using the Windows Forms NativeWindow wrapper (wrapping the component) to get Windows messages from the WndProc() method.

I've tried tracking the notifications I do get, and I the only clue I see is WM_CAPTURECHANGED. I've tried calling SetCapture when I receive the WM_LBUTTONDOWN message, but it doesn't help.

Without modifier (skipping paint, timer and NCHITTEST messages):

WM_PARENTNOTIFY
WM_MOUSEACTIVATE
WM_MOUSEACTIVATE
WM_SETCURSOR
WM_LBUTTONDOWN
WM_SETCURSOR
WM_MOUSEMOVE
WM_SETCURSOR
WM_LBUTTONUP

With modifier (skipping paint, timer and NCHITTEST messages):

WM_KEYDOWN
WM_PARENTNOTIFY
WM_MOUSEACTIVATE
WM_MOUSEACTIVATE
WM_SETCURSOR
WM_LBUTTONDOWN
WM_SETCURSOR (repeats)
WM_KEYDOWN (repeats)
WM_KEYUP

If I hold the mouse button down for a long time, I can usually get a WM_LBUTTONUP notification, but it should be possible to make it more responsive..

Edit: I've tried control-clicking outside of the component of interest and moving the cursor into it before releasing the mouse button, and then I do get a WM_LBUTTONUP notification, so it looks like the component is capturing the mouse on mouse-down. Is there any way to receive that notification when another window has captured the mouse?

Thanks.

A: 

Within your handler you need to check the WPARAM key when you receive a WM_LBUTTONUP message.

http://msdn.microsoft.com/en-us/library/ms645608%28VS.85%29.aspx

Holger Kretzschmar
I do not receive a WM_LBUTTONUP message when I click while holding down a modifier key.
Greg
A: 

If the component captures mouse messages, the WM_LBUTTONUP message might bypass your wrapper and go directly to the component.

Mark Ransom
That appears to be the case. Is there any way to work around this and still get the mouse-up event?
Greg
+1  A: 

Frequently, when the mouse is clicked down on a (native) windows control, some kind of modal tracking loop is entered to manage the "drag" operation. During the duration of the modal loop messages are directly extracted from the message queue and processed - the mouse up notification would be one of the terminating conditions for the modal loop and thus typically consumed without being dispatched.

Can you click elsewhere on the desktop, move the mouse over the window and release and see the click? That would indicate some kind of modal code is being triggered on mouse-down messages.


I can think of four ways you can possibly get around this problem.

  • Find out what sort of drag operation the control supports - and disable it. Hopefully if the built in WindowProc knows that no modal drags are allowed it won't enter a modal loop.
  • Prevent the WindowProc finding out about the modal drag: i.e. intercept AND DON'T PASS ON any WM_LBUTTONDOWN messages to the next Windowproc in the chain.
  • Install a message hook using SetWindowsHookEx.

All these solutions are very windows API. No idea how they translate in the managed environment.

Chris Becke
+1 for SetWindowsHookEx approach
hemp
I was able to work around the problem by installing a Windows message hook and setting a flag for my other code to read when a WM_LBUTTONUP message arrived. Thanks.
Greg
A: 

Modifier and navigation keys are reserved for the OS's internal use by default. The default keyboard handler interprets them and generates appropriate messages based on them as needed. If a control wants to act on them directly, it needs to handle the WM_GETDLGCODE message, optionally with a result that includes the appropriate DLGC_WANT... flags, such as DLGC_WANTALLKEYS and DLGC_WANTARROWS, so the keys are delivered to the message queue as normal messages (for instance, DLGC_WANTALLKEYS will generate WM_KEYDOWN/UP messages).

Remy Lebeau - TeamB
A: 

Use Application.AddMessageFilter to add a handler to the native message pump. Something like this:

[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
private class ZoomGestureHandler : IMessageFilter
{
    private const UInt32 WM_MOUSEWHEEL = 0x20A;
    private const UInt32 MK_CONTROL = 0x08;

    private readonly ImageListView _target;

    public ZoomGestureHandler(ImageListView target)
    {
        _target = target;
    }

    public bool PreFilterMessage(ref Message m)
    {
        if (m.Msg != WM_MOUSEWHEEL)
        {
            // Not a mouse wheel message
            return false;
        }

        int wheelDelta = HiWord(m.WParam.ToInt32());
        int keyState = LoWord(m.WParam.ToInt32());

        // Mouse wheel scrolled while the Control key was down
        if ((wheelDelta != 0) && (MK_CONTROL == keyState))
        {
            // Hit test the mouse location
            int xPos = LoWord(m.LParam.ToInt32());
            int yPos = HiWord(m.LParam.ToInt32());

            Point controlLocation = _target.Parent.PointToScreen(_target.Location);
            if ((xPos >= controlLocation.X) && (xPos < (controlLocation.X + _target.Width))
                && (yPos >= controlLocation.Y) && (yPos < (controlLocation.Y + _target.Height)))
            {
                // Determine whether to zoom in or out
                if (wheelDelta > 0)
                {
                    _target.ViewModel.TryZoomIn();
                }
                if (wheelDelta < 0)
                {
                    _target.ViewModel.TryZoomOut();
                }
            }
        }
        return false;
    }

    private static int HiWord(int number)
    {
        if ((number & 0x80000000) == 0x80000000)
            return (number >> 16);
        return (number >> 16) & 0xffff;
    }

    private static int LoWord(int number)
    {
        return number & 0xffff;
    }
}

That sample isn't specific to your particular goal, but you can modify it to suit. That just happened to be one I've written recently.

hemp
I've tried adding a message filter, but it doesn't get called. My code is running in an Internet Explorer browser helper object. Could that interfere with the IMessageFilter working? I found a note [1] that message filters don't work with COM controls.[1] http://www.codeproject.com/KB/cs/imessagefilterarticle.aspx?msg=640160#xx640160xx
Greg
The COM control may be running in a separate apartment space with its own message pump. I haven't ever worked with browser helper objects, but it definitely seems like a strong possibility that you're bumping into an inherent limitation of that model. The codeproject comment thread you referenced includes suggestions to use a Windows Hook to accomplish this. That strikes me is the approach most likely to be successful for your scenario.
hemp
Thanks, I'll try that.
Greg