From 0f7e0e8d217c49eba8ee36594b73be493ea7d7ed Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Fri, 7 Jun 2019 13:10:52 -0700 Subject: [PATCH] Fix multi-multi-key inputs When we match a multi-key sequence, send an event to all other multi-key handlers to tell them to reset. --- CommonWPF/MultiKeyInputGesture.cs | 66 ++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/CommonWPF/MultiKeyInputGesture.cs b/CommonWPF/MultiKeyInputGesture.cs index 8c2bed0..b7ec4eb 100644 --- a/CommonWPF/MultiKeyInputGesture.cs +++ b/CommonWPF/MultiKeyInputGesture.cs @@ -1,5 +1,5 @@ /* - * Copyright 2018 faddenSoft + * Copyright 2019 faddenSoft * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,34 +14,26 @@ * limitations under the License. */ using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using System.Text; -using System.Threading.Tasks; using System.Windows.Input; -/// -/// Handle a multi-key input sequence for WPF windows. -/// -/// -/// Also posted as https://stackoverflow.com/a/56452142/294248 -/// -/// Example: -/// {RoutedUICommand}.InputGestures.Add( -/// new MultiKeyInputGesture(new KeyGesture[] { -/// new KeyGesture(Key.H, ModifierKeys.Control, "Ctrl+H"), -/// new KeyGesture(Key.C, ModifierKeys.Control, "Ctrl+C") -/// }) ); -/// -/// TODO: if you have more than one handler, the handler that completes a sequence will -/// "eat" the final key, and the other handlers won't reset. Might need to define an event -/// that all gesture objects subscribe to, so they all reset at once. In the mean time, the -/// reset-after-time handler solves the problem if the user is moving slowly enough. -/// namespace CommonWPF { + /// + /// Handle a multi-key input sequence for WPF windows. + /// + /// + /// Also posted as https://stackoverflow.com/a/56452142/294248 + /// + /// Example: + /// {RoutedUICommand}.InputGestures.Add( + /// new MultiKeyInputGesture(new KeyGesture[] { + /// new KeyGesture(Key.H, ModifierKeys.Control, "Ctrl+H"), + /// new KeyGesture(Key.C, ModifierKeys.Control, "Ctrl+C") + /// }) ); + /// public class MultiKeyInputGesture : InputGesture { - private const int MAX_PAUSE_MILLIS = 1500; + private const int MAX_PAUSE_MILLIS = 2000; private InputGestureCollection mGestures = new InputGestureCollection(); @@ -49,9 +41,22 @@ namespace CommonWPF { private int mCheckIdx; private string mIdStr; + // On a successful match, the handler "eats" the final keypress. If you have multiple + // handlers, the ones that are called later won't see the non-matching key and will + // still be waiting. This can be a problem if the user types multiple multi-key + // sequences in rapid succession (or even not-so-rapid if you disable the timeout). To + // deal with this, all instances subscribe to this event, which fires when a match is + // found. + private delegate void GotMatchHandler(object sender, EventArgs e); + private static event GotMatchHandler sGotMatch; + + /// + /// Constructor. + /// + /// Sequence of keys to watch for. public MultiKeyInputGesture(KeyGesture[] keys) { - Debug.Assert(keys.Length > 0); + Debug.Assert(keys.Length > 0); // arguably also bad input if == 1 StringBuilder idSb = new StringBuilder(); @@ -61,8 +66,18 @@ namespace CommonWPF { idSb.Append(kg.DisplayString[kg.DisplayString.Length - 1]); } mIdStr = idSb.ToString(); + + sGotMatch += delegate(object sender, EventArgs e) { + mCheckIdx = 0; + }; } + /// + /// InputGesture interface. Tests an input event to see if it's part of a sequence. + /// + /// Not used. + /// Input event. Ignored if not a key event. + /// True if the key matches and we're at the end of the sequence. public override bool Matches(object targetElement, InputEventArgs inputEventArgs) { if (!(inputEventArgs is KeyEventArgs)) { // does this actually happen? @@ -96,6 +111,9 @@ namespace CommonWPF { //Debug.WriteLine("MKIG " + mIdStr + ": match"); mCheckIdx = 0; inputEventArgs.Handled = true; + + // signal other instances + sGotMatch(this, null); return true; }