Могу ли я нарисовать объекты TKinter поверх встроенного FigureCanvasTkAgg? - PullRequest
0 голосов
/ 24 февраля 2020

коротко сказал:

  • Я создаю быстрый API TKinter и сначала создаю tk.Canvas
  • Я встраиваю холст FigureCanvasTkAgg с master = tk.Canvas выше
  • Теперь я могу показать изображение с помощью Matplotlib
  • Теперь я хочу нарисовать объекты TKinter НА ВЕРХЕ холста FigureCanvasTkAgg (например, прямоугольники или кнопки)

Это возможно? Или есть какая-то конкретная рекомендация (т. Е. Использование только одного холста или другого)?

Вот краткий код:

import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure

class MyApp(tk.Tk):
  def __init__(self):
    tk.Tk.__init__(self)
    self.canvas = tk.Canvas(self, width=500, height=500, cursor="cross")
    self.canvas.pack(side="top", fill="both", expand=True)

  def draw_image_and_button(self):
    self.figure_obj = Figure()
    a = self.figure_obj.add_axes([0, 0, 1, 1])
    imgplot = a.imshow(some_preloaded_data_array, cmap='gray')
    # create tkagg canvas
    self.canvas_agg = FigureCanvasTkAgg(self.figure_obj, master=self.canvas)
    self.canvas_agg.draw()
    self.canvas_agg.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
    # attempt to draw rectangle
    self.rectangle = self.canvas.create_rectangle(0, 0, 100, 100, fill='red')

if __name__ == "__main__":
    app = MyApp()
    app.draw_image()
    app.mainloop()

Я имею в виду, что прямоугольник рисуется перед изображением , Возможно, это мое непонимание того, как FigureCanvasTkAgg прикреплен к tk.canvas

Спасибо!

1 Ответ

0 голосов
/ 25 февраля 2020

Хорошо, это приложение, которое я недавно разработал, где у меня есть виджеты matplotlib и события мыши. Вы также можете иметь виджеты tkinter, но я не нашел способа поместить их поверх холста matplolib. Лично мне больше нравятся виджеты matplotlib, чем виджеты tkinter, поэтому я думаю, что это не так уж плохо.

Единственный предварительный шаг, который вам нужно сделать, - это изменить исходный код matplotlib, потому что вам нужно передать холст классу виджетов, в то время как по умолчанию виджет берет холст рисунка, который не будет работать при встраивании в tk (кнопка не отвечает). Модификация на самом деле довольно проста, но давайте по порядку go.

  1. Откройте 'widgets.py' в папке matplotlib (в зависимости от того, где вы ее установили, в моем случае она у меня есть) C: \ Program Files \ Python37 \ Lib \ site-packages \ matplotlib ").
  2. Go до class AxesWidget(Widget) (около строки 90) и измените метод __init__ с помощью следующего кода :
def __init__(self, ax, canvas=None):
        self.ax = ax
        if canvas is None:
            self.canvas = ax.figure.canvas
        else:
            self.canvas = canvas
        self.cids = []

Как вы можете видеть по сравнению с исходным кодом, я добавил ключевое слово аргумент canvas=None. Таким образом сохраняется оригинальная функциональность, но теперь вы можете передать холст виджету.

Чтобы иметь отзывчивую кнопку на холсте matplolib, встроенном в tk, теперь вы создаете виджет и передаете холст matplolib, созданный с помощью FigureCanvasTkAgg. Например, для Button вы бы написали
from matplotlib.widgets import Button

ok_button = Button(ax_ok_button, 'Ok', canvas=canvas)  # canvas created with FigureCanvasTkAgg

Хорошо, теперь у нас есть все функциональные возможности, необходимые для добавления виджетов matplolib на холст matplolib, встроенный в tk, плюс вы также можете использовать мышь и клавишу события, которые, я думаю, покрывают 95% того, что вы ожидаете от GUI. Обратите внимание, что если вы не хотите изменять исходный код, вы, конечно, можете создать свой собственный класс, копирующий класс AxesWidget.

Все доступные виджеты Matplolib вы найдете здесь https://matplotlib.org/3.1.1/api/widgets_api.html

Вот модифицированная версия вашего приложения, в которой мы все собрали:

import tkinter as tk из matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk из matplotlib.figure import Рисунок из matplotlib.widgets import Кнопка импорта numpy как np

class MyApp(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.canvas = tk.Canvas(self, width=500, height=500, cursor="cross")
        self.canvas.pack(side="top", fill="both", expand=True)

    def draw_image_and_button(self):
        self.figure_obj = Figure()
        self.ax = self.figure_obj.add_subplot()
        self.figure_obj.subplots_adjust(bottom=0.25)
        some_preloaded_data_array = np.zeros((600,600))
        imgplot = self.ax.imshow(some_preloaded_data_array, cmap='gray')
        # create tkagg canvas
        self.canvas_agg = FigureCanvasTkAgg(self.figure_obj, master=self.canvas)
        self.canvas_agg.draw()
        self.canvas_agg.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
        # add matplolib toolbar
        toolbar = NavigationToolbar2Tk(self.canvas_agg, self.canvas)
        toolbar.update()
        self.canvas_agg._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
        # add matplolib widgets
        self.ax_ok_B = self.figure_obj.add_subplot(position=[0.2, 0.2, 0.1, 0.03]) # axes position doesn't really matter here because we have the resize event that adjusts widget position
        self.ok_B = Button(self.ax_ok_B, 'Ok', canvas=self.canvas_agg)
        # add tkinter widgets (outside of the matplolib canvas)
        button = tk.Button(master=self, text="Quit", command=self._quit)
        button.pack(side=tk.BOTTOM)
        # Connect to Events
        self.ok_B.on_clicked(self.ok)
        self.canvas_agg.mpl_connect('button_press_event', self.press)
        self.canvas_agg.mpl_connect('button_release_event', self.release)
        self.canvas_agg.mpl_connect('resize_event', self.resize)
        self.canvas_agg.mpl_connect("key_press_event", self.on_key_press)
        self.protocol("WM_DELETE_WINDOW", self.abort_exec)

    def abort_exec(self):
        print('Closing with \'x\' is disabled. Please use quit button')

    def _quit(self):
        print('Bye bye')
        self.quit()
        self.destroy()

    def ok(self, event):
        print('Bye bye')
        self.quit()
        self.destroy()

    def press(self, event):
        button = event.button
        print('You pressed button {}'.format(button))
        if event.inaxes == self.ax and event.button == 3:
            self.xp = int(event.xdata)
            self.yp = int(event.ydata)
            self.cid = (self.canvas_agg).mpl_connect('motion_notify_event',
                                                            self.draw_line)
            self.pltLine = Line2D([self.xp, self.xp], [self.yp, self.yp])

    def draw_line(self, event):
        if event.inaxes == self.ax and event.button == 3:
            self.yd = int(event.ydata)
            self.xd = int(event.xdata)
            self.pltLine.set_visible(False)
            self.pltLine = Line2D([self.xp, self.xd], [self.yp, self.yd], color='r')
            self.ax.add_line(self.pltLine)
            (self.canvas_agg).draw_idle()

    def release(self, event):
        button = event.button
        (self.canvas_agg).mpl_disconnect(self.cid)
        print('You released button {}'.format(button))

    def on_key_press(self, event):
        print("you pressed {}".format(event.key))

    # Resize event is needed if you want your widget to move together with the plot when you resize the window
    def resize(self, event):
        ax_ok_left, ax_ok_bottom, ax_ok_right, ax_ok_top = self.ax.get_position().get_points().flatten()
        B_h = 0.08 # button width
        B_w = 0.2 # button height
        B_sp = 0.08 # space between plot and button
        self.ax_ok_B.set_position([ax_ok_right-B_w, ax_ok_bottom-B_h-B_sp, B_w, B_h])
        print('Window was resized')


if __name__ == "__main__":
    app = MyApp()
    app.draw_image_and_button()
    app.mainloop()

Хорошо, давайте посмотрим на функциональность этого приложения: 1036 *

  • Нажать клавишу на клавиатуре → напечатать нажатую клавишу
  • Нажать кнопку мыши → напечатать нажатую кнопку (1 = влево, 2 = колесо, 3 = вправо)
  • Отпустите кнопку мыши → распечатайте отпущенную кнопку
  • Нажмите правую кнопку в любой точке графика и нарисуйте линию, удерживая кнопку мыши нажатой
  • Нажмите OK или выйдите, чтобы закрыть приложение
  • Нажатие «x» для закрытия окна отключено.
  • Изменение размера окна → График и виджеты масштабируются соответственно

Я также т Вы можете добавить панель инструментов classi c matplotlib для других функций, таких как масштабирование.

Обратите внимание, что графическое изображение добавляется с помощью метода add_suplot(), который добавляет функцию изменения размера. Таким образом, при изменении размера окна график масштабируется соответственно.

Большинство вещей, которые я реализовал, вы также найдете в официальном туториале от matplotlib о том, как встраивать в tk (https://matplotlib.org/3.1.3/gallery/user_interfaces/embedding_in_tk_sgskip.html).

Дайте мне знать, если это отвечает на ваш вопрос. Я хотел поделиться этим, потому что я действительно разработал нечто очень похожее за несколько дней go.

...