Search code examples
wpftouchtouch-event

Why do all WPF Touch events now only fire as MouseEvents?


Is there some obscure way that I might have globally (accidentally) disabled all Touch events in my entire WPF application all at once -- so that they get "promoted" to mouse events? Is there some global WPF flag somewhere that turns them off. If so can anyone give me an idea of what it might be?

Detailed Explanation:

I've got a WPF desktop application (.NET Core 3.1) running on an MS Surface. For almost 2 years it's been working fine with both touch and Mouse handlers; If the user clicked with the mouse, the mouse handler got invoked. If the user touched with their finger, the ManipulationEvent (or TouchEvent) handler got invoked.

But recently I was seeing strange behavior and I finally realized that every single touch manipulation event was getting "promoted" to mouse a event before it ever reached any of my touch/manipulation handlers.

For example, here is a style I apply to Path objects in one of my views:

<Style x:Key="ShapeDragPathStyle" TargetType="{x:Type Path}">
    <Setter      Property="Cursor"           Value="Hand" />
    <EventSetter Event="MouseLeftButtonDown" Handler="Shape_OnLeftButtonDown" />
    <EventSetter Event="MouseMove"           Handler="Shape_OnMouseMove" />
    <EventSetter Event="MouseLeftButtonUp"   Handler="Shape_OnMouseLeftButtonUp" />
    <EventSetter Event="TouchDown"           Handler="Shape_OnTouchDown" />
</Style>

Now, if I put my finger down and drag a path that has this style, the OnLeftButtonDown handler will be called. The TouchDown handler never will.

To be complete, here is a list of all the touch events for which I have declared handlers in various places in my XAML and which I have been using for a long time.

ManipulationStarting
ManipulationStarted 
ManipulationDelta
ManipulationCompleted
TouchUp
TouchDown
PreviewTouchDown

Not a single handler of these ever gets invoked anymore. I literally went and put break points on every handler of them, it doesn't matter where they are. It doesn't matter if I have IsManipulationEnabled="True" on them or not. The mouse-equivalent always hits invoked instead.

Nor does this appear to be something in Windows Settings. I've verified that it happens on multiple MS Surface machines and that it only appears to be happening in my application; I created a simple .NET Core 3.1 test app and added a ManipulationStarting handler and that worked fine.

Any ideas what I should look for?

(I'm sure I'm going to feel stupid when I find out but I feel that way already...)


Solution

  • Answering my own question because I have guessed at the cause by comparing touch-handling call stacks of the test app vs my app. The problem seems obscure but I can see someone else having this problem so here's what happened:

    In short, the culprit was System.Management.ManagementEventWatcher

    I wanted my app to be notified when a USB device was plugged in. So I created a ManagementEventWatcher that watched for such events

    Watcher = new ManagementEventWatcher(
        new ManagementScope("root\\CIMV2"),
        new WqlEventQuery()
        {
            EventClassName = "Win32_DeviceChangeEvent",
            Condition      = "EventType = 2 OR EventType = 3",
        });
    
    Watcher.EventArrived += (sender, args) => { DeviceChanged?.Invoke(this, true); };
    
    Watcher.Start();  // THIS WAS THE PROBLEM
    

    Calling Start() on the main UI thread appears to change how the main thread handles window messages. The entire callstack looks different when I do this. Incoming Touch messages go right to mouse handlers before anything even reaches my code.

    When I comment out the call to Start(), all the touch handlers are invoked again.

    So I tried putting the call to Start() on a background thread:

    Task.Run(() => Watcher.Start());
    

    And now everything works fine. I'm not sure that's the final answer - I might even want to create my own dedicated thread to avoid messing up any thread pool threads - but clearly that's it.

    So it would seem that ManagementEventWatcher is something best left off of UI threads, at least in WPF