-2

Is there a way with wxPython 4 to recognize double pressing the same key? Especially in my case, I would like to recognize when the SHIFT key is pressed twice in quick succession.

J. Doe
  • 9
  • 3
  • I've already read the how-to-ask, so I don't understand the downvote. Do you think that there are information missing in my question? – J. Doe Aug 06 '18 at 09:15
  • Check this https://stackoverflow.com/questions/36554353/keypress-detection – Crammeur Aug 06 '18 at 09:17
  • And this https://stackoverflow.com/questions/10463702/multiple-key-press-detection-wxpython – Crammeur Aug 06 '18 at 09:21
  • I have already found these questions, the answers to this questions does not help me for two reasons. 1. I have to work with wxPython, 2. I have to recognize if the same(!) key is pressed twice in a row – J. Doe Aug 06 '18 at 09:35
  • so this https://docs.wxpython.org/wx.KeyEvent.html#wx.KeyEvent – Crammeur Aug 06 '18 at 09:39
  • More specific this https://docs.wxpython.org/wx.KeyboardState.html#wx-keyboardstate – Crammeur Aug 06 '18 at 09:42
  • Do you read this https://stackoverflow.com/help/mcve – Crammeur Aug 06 '18 at 09:46
  • 1
    Why I downvote is because is basic question and you don't the `mcve` to understand what you want and what you have tried – Crammeur Aug 06 '18 at 09:49
  • You need to search in the web before asking. – Crammeur Aug 06 '18 at 10:00
  • I have already searched the web and have not found a viable solution. If you think it's that easy, why don't you help me with an example instead of sending useless links? – J. Doe Aug 06 '18 at 11:26
  • And like you said, it's a basic problem. i don't know for what you need an mcve. – J. Doe Aug 06 '18 at 11:31
  • @Crammeur you should read the question before suggesting related "answers" which are cleary useless for the OP. – Henry Henrinson Mar 05 '20 at 15:08

3 Answers3

1

This by no means as obvious as it first looks.

The code below, uses a "one-shot" wx.Timer to re-set the previous key after 250 milliseconds, to resolve the issue of "in quick succession". You can of course set it to whatever is appropriate. For older versions of wxPython the timer does not have the StartOnce option, you would have to use Start(250, oneShot=True)

I have slight complicated it by allowing for keys other than Shift and the dictionary of names is just for test purposes.

I should point out that, because this has to check every key depression, it isn't very efficient but I suppose that you are aware of that and are willing to pay the price.

I do have one caveat, I do not know how holding a key down, such as the shift key, will react on a non Linux machine. If it proves not to be like Linux, then you should change the Bind from wx.EVT_KEY_DOWN to wx.EVT_KEY_UP.

import wx
import time
class Frame(wx.Frame):

    def __init__(self, parent):
        wx.Frame.__init__ (self, parent, -1, title = "Test Frame", size = (500,360))

        self.text_window = wx.TextCtrl(self, wx.ID_ANY, "", size = (450,250), style = wx.TE_MULTILINE)
        self.text_window.Bind(wx.EVT_KEY_DOWN, self.key_info)

        #Define a timer to reset the key values
        self.key_timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.Ontimer, self.key_timer)

        #Define storage for the previous key
        self.prev_key = 0

        #Define the set of double keys we are looking for and a dict  of their names
        # Shift is 306 (on my keyboard), Alt is 307 and Ctrl is 308
        self.double_keys = (306,307,308)
        self.names = {'306':'Shift','307':'Alt','308':'Ctrl'}

        sizer1= wx.BoxSizer(wx.VERTICAL)
        sizer1.Add(self.text_window, 0, wx.ALL, 5)
        self.SetSizer(sizer1)
        self.Show()

    def key_info(self, event):
        self.key = event.GetKeyCode()
        if self.key in self.double_keys and self.prev_key == self.key:
            self.text_window.AppendText("Double key "+self.names[str(self.key)]+" used within a quarter second\n")
        self.prev_key = self.key
        #fire up the timer to execute once to reset the previous key
        if self.key in self.double_keys:
            self.key_timer.StartOnce(250)
        # Skip so this doesn't consume the key event itself
        event.Skip()

    def Ontimer(self,event):
        # Re-set the previous key after 250 milliseconds
        self.prev_key = 0

app = wx.App()
frame = Frame(None)
app.MainLoop()
miken32
  • 42,008
  • 16
  • 111
  • 154
Rolf of Saxony
  • 21,661
  • 5
  • 39
  • 60
  • Thanks for the answer! It is not quite what I had hoped for, but based on your example I have now created a class that solves the problem a bit more generically, which is sufficient for me :-) – J. Doe Aug 07 '18 at 11:30
  • @J.Doe If you didn't feel that my answer merited being accepted or a useful vote, you can always post your own code as an answer. In that way, anyone else with a similar question will be able to find an example. That is, after all, what SO is all about. – Rolf of Saxony Aug 07 '18 at 14:25
0

I think the problem is, that he does not want to get informed, when several keys are triggered at once, but shorty after one another. I did not find a native way to do it, so I tried it myself. My solution results in ugly state within the TestFrame, so I would be interested as well, if a native approach exists within wxPython.

import wx
import time

class TestFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent)

        self._keyCounter = 0
        self._lastKeyTs = -1

        sizer = wx.BoxSizer(wx.VERTICAL)
        self.Bind(wx.EVT_KEY_DOWN, self._OnKeyDown)
        self.SetSizer(sizer)

    def _OnKeyDown(self, event):
        self._keyCounter += 1
        if self._keyCounter % 2 == 0 and time.time() - self._lastKeyTs < 0.3:
            print "Trigger"
        self._lastKeyTs = time.time()

class App(wx.App):
    def OnInit(self):
        frmMain = TestFrame(None)
        frmMain.SetSize(wx.Size(800, 600))

        frmMain.Show()

        return True


if __name__ == '__main__':
    application = App(False)
    application.MainLoop()
0

Here is a more general solution

The event EVT_CHAR_HOOK is used instead of EVT_KEY_UP. The problem with EVT_KEY_UP is that it is swallowed when the event is bound to a frame that contains a panel.

The challenge with EVT_CHAR_HOOK is to determine whether the key is actually pressed twice or only held. Therefore the RawKeyFlags are read out. The bit at position 30 indicates whether the key was held or not.

Please note, however, that this solution only works on Windows systems!

import wx

class DoubleKeyStrokeListener(object):
    def __init__(self, parent, keyCode, callback, timeout=500):
        self._monitoredKey = keyCode
        self._callback = callback
        self._timeout = timeout

        self._firstPressRecognized = False
        self._keyTimer = wx.Timer(parent)

        parent.Bind(wx.EVT_CHAR_HOOK, self._OnKeyPressed)
        parent.Bind(wx.EVT_TIMER, self._OnTimer, self._keyTimer)

    def _OnKeyPressed(self, event):
        event.Skip()

        pressedKey = event.GetKeyCode()

        if pressedKey == self._monitoredKey:
            rawFlags = event.GetRawKeyFlags()

            # bit at position 30 is "previous key state" flag
            prevStateBit = rawFlags >> 30 & 1

            if prevStateBit == 1:  # -> key is held
                return

            if self._firstPressRecognized:
                self._firstPressRecognized = False
                self._callback(event)
            else:
                self._firstPressRecognized = True
                self._keyTimer.StartOnce(self._timeout)
        else:
            self._firstPressRecognized = False

    def _OnTimer(self, event):
        self._firstPressRecognized = False
        event.Skip()
Kallinga
  • 16
  • 2