matplotlib event.mouseevent.key得到"卡住"

时间:2016-04-21 12:45:42

标签: python matplotlib wxwidgets

我把我的问题归结为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,我会得到预期的值并显示一个新窗口。但是如果我点击,没有按住ctrl,任何其他散点,event.mouseevent.key就会卡住并不断报告control

我发现的唯一工作就是沿着wx层走下去并使用event.guiEvent.ControlDown(),这似乎是准确的。

它与显示新帧有关,因为如果删除创建并显示第二帧的代码,则不会发生此问题。

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

我在Ubuntu 12.04.3 LTS上使用wxWidgets 2.8.12.1和python 2.7.3

谢谢:)

1 个答案:

答案 0 :(得分:0)

TL;博士

好的,所以我做了一点挖掘。密钥在matplotlib事件中“卡住”的原因是因为matplotlib后端使用按键向下和按键事件来确定单击鼠标时当前按下的按键。当按下某个键时,它会被记住,直到它被释放为止,因此如果用户执行了key press --> mouse click --> key release,则会记住按下的键并将其传递给鼠标单击事件处理程序。

因此,当我启动一个新窗口并从click事件中获得焦点时,后续的键释放事件将“丢失”:第一帧永远不会收到它,因此后端不会重置其缓存的按键并报告所有后续鼠标点击的陈旧值,直到生成新的按键事件。

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

其余解释

可以在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():

在课程FigureCanvasWx中找到一个级别的功能 _onKeyDown()_onKeyUp()绑定到wxWidgets按键事件。这些调用基类FigureCanvasBase对应的事件处理函数。如上所述,FigureCanvasBase然后缓存按键信息,以便它可以将当前按键传递给其鼠标事件,最终到达选择事件。

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

测试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 :现在我按下控制键并点击图表,但是在一个点上,只是一个空格,再次看到效果而不是触发选择事件,其中显示新框架。

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:* 现在我按住控制键并点击我的图表上的实际点,该点触发我的选择事件,其中创建一个新窗口,显示并给予焦点:

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仍然同意......但等等......

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

所以现在当我点击图表上的任何一点,事件空格时,我会看到控制键被按下了!

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

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

有趣的是,第二帧似乎没有接收到释放事件,除非我在释放控制键之前故意点击它,这阻止我能够将此事件发送回第一个窗口并以此方式解决它。

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