我可以在嵌入式FigureCanvasTkAgg顶部绘制TKinter对象吗?

时间:2020-02-24 19:07:10

标签: matplotlib tkinter tkinter-canvas

简短地说:

  • 我正在创建一个快速的TKinter API,首先我生成了一个tk.Canvas
  • 我正在将FigureCanvasTkAgg画布嵌入master = tk.Canvas上方
  • 有了这个,我就能通过Matplotlib显示图像
  • 现在我想在FigureCanvasTkAgg画布的顶部(例如矩形或按钮)上绘制TKinter对象

这可能吗?还是有什么特别的建议(即仅使用一张画布或另一幅画布)?

下面是一些快速代码:

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 :(得分:0)

好的,这是我最近开发的应用程序,其中有matplotlib小部件和鼠标事件。您也可以使用tkinter小部件,但是我找不到将它们放在matplolib画布顶部的方法。就个人而言,我比tkinter部件更喜欢matplotlib部件,因此我认为它还不错。

您唯一需要采取的步骤是修改matplotlib源代码,因为您需要将画布传递给widget类,而默认情况下,widget会使用图画布,当嵌入tk时它将不起作用(按钮会无反应)。修改实际上很简单,但让我们按顺序进行。

  1. 在matplotlib文件夹中打开“ widgets.py”(取决于安装位置,在我的情况下,我将其保存在“ C:\ Program Files \ Python37 \ Lib \ site-packages \ matplotlib”中)。
  2. 转到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。这样就可以保留原始功能,但是现在您可以将画布传递给小部件。

  1. 要在嵌入在tk中的matplolib画布上具有响应按钮,您现在创建一个小部件,然后传递用FigureCanvasTkAgg创建的matplolib画布。例如,对于Button,您将编写
from matplotlib.widgets import Button

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

好吧,现在我们拥有在tk中嵌入matplolib画布上具有matplolib小部件所需的所有功能,此外,您还可以拥有鼠标和按键事件,我猜想它涵盖了您对GUI的期望的95%。请注意,如果您不想修改原始源代码,则可以创建自己的复制AxesWidget类的类。

您可以在https://matplotlib.org/3.1.1/api/widgets_api.html

上找到所有可用的matplolib小部件。

这是您应用的修改版本,我们将所有内容放在一起:

将tkinter导入为tk 从matplotlib.backends.backend_tkagg导入FigureCanvasTkAgg,NavigationToolbar2Tk 从matplotlib.figure导入图 从matplotlib.widgets导入按钮 将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()

好,让我们看看这个应用程序的功能:

  • 按下键盘上的一个键→打印所按下的键
  • 按下鼠标按钮→打印按下的按钮(1 =左,2 =滚轮,3 =右)
  • 释放鼠标按钮→打印释放的按钮
  • 在绘图上的任意点上按右键,并在按住鼠标键的同时画一条线
  • 按确定或退出以关闭应用程序
  • 禁止按“ x”关闭窗口。
  • 调整窗口大小→绘图和小部件会相应缩放

我还随意添加了经典的matplotlib工具栏以用于其他功能,例如缩放。

请注意,图像图是通过add_suplot()方法添加的,该方法添加了调整大小的功能。这样,当您调整窗口大小时,绘图会相应缩放。

在我实现的大多数事情中,您也可以从matplotlib的官方教程中找到有关如何嵌入tk(https://matplotlib.org/3.1.3/gallery/user_interfaces/embedding_in_tk_sgskip.html)的信息。

让我知道这是否回答了您的问题。我想分享它,因为几天前我实际上开发了非常相似的东西。