Рисунок matplotlib не продолжает выполнение программы после события закрытия, инициированного внутри приложения tk

Я столкнулся с действительно раздражающей разницей между тем, как Windows и Mac обрабатывают окно tk python и фигуру matplotlib close_event.

Моя проблема, таким образом,

  1. Я пытаюсь загрузить фигуру matplotlib из события кнопки tk.
  2. Я хочу, чтобы рисунок отображался и блокировал поток программы tk UI, пока график активен, и перехватывал пользовательские события, пока график не был закрыт.
  3. После закрытия сюжета приложение tk должно продолжаться.

Минимальный пример приложения, показывающий проблему.

from Tkinter import *
from matplotlib import pyplot as plt


class Plotter:
    def __init__(self):
        self.fig = plt.figure()
        self.fig.canvas.mpl_connect('close_event', self.dispose)

        plt.plot(1, 2, 'r*')
        plt.show()
        print "done with plotter"

    def dispose(self, event):
        plt.close('all')
        print "disposed"


if __name__ == '__main__':
    def pressed():
        print 'button pressed'
        Plotter()
        print 'YAY'

    root = Tk()
    button = Button(root, text='Press', command=pressed)
    button.pack(pady=20, padx=20)

    root.mainloop()

К сожалению, я обнаружил, что это работает, как и ожидалось, в Windows, но не на Mac с использованием тех же версий python2.7, matplotlib (1.5.2). Помимо того факта, что это не очень хорошая практика пользовательского интерфейса, меня беспокоит, что на Mac и Windows есть разница для этого фрагмента кода. Я был бы признателен за любые отзывы, которые помогут решить эту проблему, тем временем я начну работу над реализацией плоттера в неблокирующем потоке и передаче результата обратно в основное приложение при закрытии.


person eugene_pret    schedule 24.08.2016    source источник


Ответы (1)


Вы можете использовать plt.ion() для включения интерактивного режима Matplotlib, но это само по себе приведет к тому, что программа продолжит работу, не блокируя поток. Чтобы вручную заблокировать поток, используйте self.fig.canvas.start_event_loop_default() и self.fig.canvas.stop_event_loop(), чтобы приостановить поток программы до тех пор, пока события не будут перехвачены.

Реализовано в вашем минимальном примере:

from Tkinter import *
from matplotlib import pyplot as plt

class Plotter:
    def __init__(self):
        plt.ion()
        self.fig = plt.figure()
        self.fig.canvas.mpl_connect('close_event', self.dispose)
        self.fig.canvas.mpl_connect('button_press_event', self.on_mouse_click)

        plt.plot(1, 2, 'r*')
        plt.show()
        self.fig.canvas.start_event_loop_default()
        print "done with plotter"

    def dispose(self, event):
        self.fig.canvas.stop_event_loop()
        print "disposed"

    def on_mouse_click(self, event):
        print 'mouse clicked!'


if __name__ == '__main__':
    def pressed():
        print 'button pressed'
        Plotter()
        print 'YAY'

root = Tk()
button = Button(root, text='Press', command=pressed)
button.pack(pady=20, padx=20)

root.mainloop()
person Nemo    schedule 30.08.2016
comment
Спасибо @nemo! Это действительно работает так, как ожидалось. Проведу дальнейшее тестирование с этим решением. - person eugene_pret; 31.08.2016