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,那么你应该把Bind
从wx.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_UP
。 EVT_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()
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,那么你应该把Bind
从wx.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_UP
。 EVT_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()