matplotlib event.mouseevent.key 获得 "stuck"

matplotlib event.mouseevent.key gets "stuck"

我已将我的问题归结为 wxWidgets 中的以下简单应用程序(我认为它已经尽可能小了...):

import wx
import matplotlib

matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas

class GraphPanel(wx.Panel):
   def __init__(self, *args, **kwargs):
      super(GraphPanel, self).__init__(*args, **kwargs)
      self._win             = None
      self._figure          = matplotlib.figure.Figure()
      self._canvas          = FigureCanvas(self, -1, self._figure) 
      self._ax              = self._figure.add_subplot(111)
      self._canvas.mpl_connect('pick_event', self._OnGraphPickEvent)
      x = [1,2,3]
      y = [ 3,1,2]
      self._ax.scatter(x, y, picker=True)

   def _OnGraphPickEvent(self, event):
      print event.mouseevent.key, event.guiEvent.ControlDown()
      if event.mouseevent.key == 'control':
         if self._win is None:
            self._win = SecondFrame(self)
            self._win.Show()
         self._win.SetFocus()
         self._win.Raise()

class SecondFrame(wx.Frame):
   def __init__(self, *args, **kwargs):
      wx.Frame.__init__(self, *args, **kwargs)
      self.Show()

class FirstFrame(wx.Frame):
   def __init__(self, *args, **kwargs):
      wx.Frame.__init__(self, *args, **kwargs)
      self._graph = GraphPanel(self, wx.ID_ANY)
      szr = wx.BoxSizer(wx.VERTICAL)
      szr.Add(self._graph, 1, wx.EXPAND)
      self.SetSizerAndFit(szr)
      self.Show()

if __name__ == "__main__":
   app = wx.App(False)
   frame = FirstFrame(None)
   app.MainLoop()

现在对我来说有趣的是 event.mouseevent.key 的值似乎卡住了。第一次按住 ctrl 单击散点时,我得到的值为 None。如果我重新按下 ctrl,那么我会得到预期的值,并显示一个新的 window。但是如果我点击,没有 按住 ctrl,任何其他散点,event.mouseevent.key 卡住并不断报告 control

我发现的唯一变通方法是进入 wx 层并使用 event.guiEvent.ControlDown(),这似乎总是准确的。

与显示新帧有关,因为如果删除创建和显示第二帧的代码,则不会出现此问题

有谁知道我在这里是否错误地使用了 matplotlib 事件?我一直在关注在线示例,所以也许我误解了某些内容或遗漏了与新框架和事件循环有关的内容??

我在 Ubuntu 12.04.3 LTS

上使用 wxWidgets 2.8.12.1 和 python 2.7.3

谢谢:)

tl;博士

好的,所以我做了一些挖掘。按键在 matplotlib 事件中获得 "stuck" 的原因是因为 matplotlib 后端使用按键按下和按键向上事件来确定单击鼠标时当前按下的键。当按下一个键时,它会被记住,直到它被释放,所以如果用户这样做 key press --> mouse click --> key release,他们按下的键会被记住并传递给鼠标单击事件处理程序。

所以当我启动一个新的 window 并通过点击事件给它焦点时,随后的按键释放事件是 "lost":第一帧从未接收到它,因此 后端不会重置其缓存的按键并报告所有后续鼠标点击的陈旧值,直到生成新的按键事件。

对于主要 window 在按键和释放事件之间失去焦点的特定问题似乎没有很好的解决方案...

剩下的解释

此密钥缓存可以在 backend_bases.py 中找到,在我的 Ubuntu 系统中可以在 /usr/local/lib/python2.7/dist-packages/matplotlib/backends 中找到。按键事件将当前键存储在 self._key 中,并在释放键时将其重置为 None

我第一帧的拾取事件的堆栈跟踪是这样的:

File "test.py", line 57, in <module>
    app.MainLoop()
File "/usr/lib/python2.7/dist-packages/wx-2.8-gtk2-unicode/wx/_core.py", line 8010, in MainLoop
    wx.PyApp.MainLoop(self)
File "/usr/lib/python2.7/dist-packages/wx-2.8-gtk2-unicode/wx/_core.py", line 7306, in MainLoop
    return _core_.PyApp_MainLoop(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/matplotlib/backends/backend_wx.py", line 1069, in _onLeftButtonDown
    FigureCanvasBase.button_press_event(self, x, y, 1, guiEvent=evt)
File "/usr/local/lib/python2.7/dist-packages/matplotlib/backend_bases.py", line 1910, in button_press_event
    self.callbacks.process(s, mouseevent)
<snip>
File "/usr/local/lib/python2.7/dist-packages/matplotlib/backend_bases.py", line 1787, in pick
    self.figure.pick(mouseevent)
File "/usr/local/lib/python2.7/dist-packages/matplotlib/artist.py", line 450, in pick
    a.pick(mouseevent)
<snip>
File "/usr/local/lib/python2.7/dist-packages/matplotlib/artist.py", line 436, in pick
    self.figure.canvas.pick_event(mouseevent, self, **prop)
File "/usr/local/lib/python2.7/dist-packages/matplotlib/backend_bases.py", line 1874, in pick_event
    self.callbacks.process(s, event)
File "/usr/local/lib/python2.7/dist-packages/matplotlib/cbook.py", line 563, in process
    proxy(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/matplotlib/cbook.py", line 430, in __call__
    return mtd(*args, **kwargs)
File "test.py", line 22, in _OnGraphPickEvent
    for line in traceback.format_stack():

往下一层,在 class FigureCanvasWx 函数中 _onKeyDown()_onKeyUp() 绑定到 wxWidgets 按键事件。这些调用基 class FigureCanvasBase 对应的事件处理函数。正如所说,FigureCanvasBase 然后缓存按键信息,以便它可以将当前按下的键传递给它的鼠标事件,最终到达选择事件。

我在上面的 classes 中添加了一些调试打印。给出以下...

测试 1:显示第一帧。我单击图表,但 没有 点,因为我不想触发显示新帧的拾取事件。

FigureCanvasWx:onLeftButtonDown: Ctrl down is "False"
FigureCanvasBase:button_press_event: KEY IS None
Artist:Pick:MPL MouseEvent: xy=(475,156) xydata=(2.88911290323,1.34375) button=1 dblclick=False inaxes=Axes(0.125,0.1;0.775x0.8) None

好的,按预期工作。 wxPython 和 matplot lib 同意不按下控制键。

测试2:现在我按下control键并点击图表,但是没有在一个点上,只是一片空白space,再次查看效果,而不触发显示新帧的 pick 事件。

FigureCanvasBase:key_press_event: SETTING KEY TO control
FigureCanvasWx:onLeftButtonDown: Ctrl down is "True"
FigureCanvasBase:button_press_event: KEY IS control
Artist:Pick:MPL MouseEvent: xy=(440,144) xydata=(2.67741935484,1.25) button=1 dblclick=False inaxes=Axes(0.125,0.1;0.775x0.8) control
FigureCanvasBase:key_release_event: RESETTING KEY #<<!! THE IMPORTANT BIT

当我按下控制键时,FigureCanvasBase 缓存按键。然后当来自 wxPython 的鼠标事件被包装到 matplotlib 事件中时,这个缓存的键值被添加到 matplotlib 事件中。在这一点上matplotlib和wxpython还是一致的。

单击后我释放了控制键,FigureCanvasBase 正确地重置了键缓存。因此,当我在不按住控制键的情况下再次单击时,matplotlib 事件正确地报告没有按键被按下。

测试 3:* 现在我按住 control 键并单击图表上的实际点,这会触发我的选择事件,其中新的 window 是创建、显示和给予焦点:

FigureCanvasBase:key_press_event: SETTING KEY TO control
FigureCanvasWx:onLeftButtonDown: Ctrl down is "True"
FigureCanvasBase:button_press_event: KEY IS control
Artist:Pick:MPL MouseEvent: xy=(494,238) xydata=(3.00403225806,1.984375) button=1 dblclick=False inaxes=Axes(0.125,0.1;0.775x0.8) control
FigureCanvasBase:pick_event: CREATED PICK EVENT <matplotlib.backend_bases.PickEvent object at 0xaf631ec> control
control True

这样可以...matplotlib 和 wxpython 仍然一致...但是等等...

哦不...我释放控制键但是第一帧不再有焦点...没有按键释放事件

现在当我点击图表上的任意点时,事件空白 space,我将看到控制键被按下!

FigureCanvasWx:onLeftButtonDown: Ctrl down is "False" ##<< !!!!
FigureCanvasBase:button_press_event: KEY IS control   ##<< !!!!
ARTIST PICK MPL MouseEvent: xy=(475,475) xydata=(None,None) button=1 dblclick=False inaxes=None control

清除这个的唯一方法是给第一帧焦点并故意按下一个键,以便重置事件可以清除缓存。

有趣的是,第二帧似乎也没有接收到释放事件,除非我在释放控制键之前故意点击它,这使我无法将此事件发送回第一帧 window 并解决就这样。

所以...在这种特定情况下,密钥缓存方案似乎是个问题。变通办法似乎是真正处理这个问题的唯一方法。如果我们可以强制 wxPython 手动重新读取键盘状态,我们可以纠正这个问题,但在大多数情况下,即使对于 wx.GetKeyState() 函数,它也只会报告控制键是否按下而不是任何键,这就是 matplotlib想要。