wxPython 4:检测双击键

wxPython 4: detection of double keystroke

wxPython 4 有没有办法识别双击 same 键? 特别是在我的情况下,我想识别何时快速连续按下两次 SHIFT 键。

我认为问题是,他不想在一次触发多个键时得到通知,但一个接一个地触发。我没有找到原生的方法来做,所以我自己试了一下。我的解决方案在 TestFrame 中导致丑陋的状态,所以如果 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()

这绝不像乍看起来那么明显。

下面的代码,使用 "one-shot" wx.Timer 在 250 毫秒后重新设置前一个键,以解决 "in quick succession" 的问题。您当然可以将其设置为任何合适的值。 对于旧版本的 wxPython,计时器没有 StartOnce 选项,您必须使用 Start(250, oneShot=True)

我通过允许 Shift 以外的键使它稍微复杂化,并且名称字典仅用于测试目的。

我应该指出,因为这必须检查每个按键的按下,所以效率不是很高,但我想您已经意识到这一点并愿意为此付出代价。

我有一个警告,我不知道按住某个键(例如 shift 键)在非 Linux 机器上会有什么反应。如果证明不是Linux,那么你应该把Bindwx.EVT_KEY_DOWN改成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()

N.B。我从您在评论中的谈话中注意到,这不仅是您在 Stack Overflow 上提出的第一个问题,而且您似乎对这些评论有些不满。

如果你不提供你的代码,工作或失败得很惨,你将吸引反对票。论坛成员喜欢看代码,尤其是您已经尝试过的代码。它基本上只是一个基本指标,表明您是否已经付出任何努力来自己回答问题,然后再提出一个单行问题并希望其他人会为您做腿部工作。

我最近 post 编辑了一个自答问题,其中问题和答案 post 结合在一起。我立即收到了对该问题的反对票,尽管我为自己的问题提供了详细的编码答案。所以不要把它当成个人。我怀疑有些人只是通过严格遵守 "very" "rules".
也就是说,如果您坚持使用 SO 一段时间并自己回答问题,您将开始看到问题中代码和 MCVE 的优点。您会惊讶于某些人 post,期待答案。

这是一个更通用的解决方案

使用事件 EVT_CHAR_HOOK 而不是 EVT_KEY_UPEVT_KEY_UP 的问题在于,当事件绑定到包含面板的框架时,它会被吞没。

EVT_CHAR_HOOK 的挑战是确定该键是实际按下了两次还是仅按住了。因此 RawKeyFlags 被读出。第30位表示key是否被按住

但是请注意,此解决方案仅适用于 Windows 系统!

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()