Tkinter 和 Matplotlib:由 FigureCanvasTkAgg 创建的 canvas 的相对位置

Tkinter and Matplotlib: relative position of canvas created by FigureCanvasTkAgg

我正在创建一个 GUI,它应该在主要 window(或 'root',由 root=tk.Tk() 创建)的左侧显示一些小部件(例如按钮和刻度) ) 和右侧的图表。该图将使用 matplotlib 和 FigureCanvasTkAgg() 后端创建(类似于发现的 here)。以下是我到目前为止编写的代码。

import tkMessageBox, tkFileDialog
import sys
# Tkinter is for python 2; tkinter is for python 3
if sys.version_info[0] < 3:
    import Tkinter as tk
else:
    import tkinter as tk


class MainApp(tk.Frame):

    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.parent.title('App')
        # call the widgets
        self.okButton()
        self.quitButton()
        self.readDataButton()
        self.clearDataButton()
        self.velScale()
        self.canvas()

    # print messages on the screen
    def printMessage(self):
        if (self.data):
            print("Data is loaded and accessible from here (printMessage()).")
        else:
            print('No data loaded...')

    ### OK button
    def okButton(self):
        self.okButton = tk.Button(self, text='Test', command=self.printMessage)
        self.okButton.grid()

    ### Quit button
    def quitButton(self):
        self.quitButton = tk.Button(self, text='Quit', command=self.confirmQuit)
        self.quitButton.grid()
    # confirm quitting
    def confirmQuit(self):
        answer = tkMessageBox.askyesno(title="App", message="Do you really want to quit?")
        if (answer):
            self.quit()

    # Read data button
    def readDataButton(self):
        self.data = None
        self.readDataButton = tk.Button(self, text='Import data', command=self.readData)
        self.readDataButton.grid()
    # reading data
    def readData(self):
        import os
        fullPath = dataList = tkFileDialog.askopenfilename(initialdir='path/to/initialdir')
        dataDir = os.path.split(fullPath)[0]+'/'
        self.data = readData(fullPath)

    # Clear data from current session
    def clearDataButton(self):
        self.clearData = tk.Button(self, text='Clear data', command=self.confirmClearData)
        self.clearData.grid()
    # confirm clearing data
    def confirmClearData(self):
        answer = tkMessageBox.askyesno(title="App", message="Are you sure you want to clear the loaded data?")
        if (answer):
            self.data = None
            tkMessageBox.showwarning(title="App", message="Data has been deleted.")

    # Velocity scale
    def velScale(self):
        self.velVar = tk.StringVar()
        velLabel = tk.Label(self, text="Scale value:", textvariable=self.velVar)
        velLabel.grid(row=4, column=0, columnspan=2, sticky=tk.W+tk.E)
        velScale = tk.Scale(self, from_=-500, to=+500, orient=tk.HORIZONTAL, resolution=20,
                        sliderlength=20, showvalue=0,
                        length=200, width=20,
                        command=self.onVelScale)
        velScale.grid(row=5, column=0)
    # update velLabel
    def onVelScale(self, val):
        self.velVar.set("Scale value: {:+0.0f}".format(float(val)))

    # Canvas
    def canvas(self):
        import matplotlib
        import matplotlib.pyplot as plt
        matplotlib.use("TkAgg")
        from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
        from matplotlib.figure import Figure

        self.f = Figure(figsize=(4,2))
        self.a = self.f.add_subplot(111)
        self.a.plot([1,2,3,4,5,6,7,8],[5,6,1,3,8,9,3,5])

        self.canvas = FigureCanvasTkAgg(self.f, master=self.parent)
        self.canvas.show()
        self.canvas.get_tk_widget().pack()

        self.toolbar = NavigationToolbar2TkAgg(self.canvas, self.parent)
        self.toolbar.update()
        self.canvas._tkcanvas.pack()



if __name__ == "__main__":
    root = tk.Tk()
    root.geometry("800x600+10+10")
    root.resizable(0, 0)
    MainApp(root).pack(side=tk.TOP)
    root.mainloop()

结果是这样的:Screenshot of the App's main window。对按钮和缩放小部件使用 .grid() 可以按预期工作(我可以通过指定 rowcolumn 将它们放置在我想要的任何位置)。但是,我不能对 canvas 部分有相同的行为。似乎只有 .pack() 可以与 canvas 一起使用(结果显示在上面的 link 中)。

有人知道如何解决这个问题吗?还是我遗漏了什么?

感谢您的任何建议。

我正在使用 python3.3,我已将您的代码更改为:

import sys
import matplotlib
import matplotlib.pyplot as plt
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
# Tkinter is for python 2; tkinter is for python 3
if sys.version_info[0] < 3:
    import Tkinter as tk
    import tkMessageBox, tkFileDialog

else:
    import tkinter as tk
    from tkinter import messagebox as tkMessageBox
    from tkinter import filedialog as tkFileDialog

class MainApp(tk.Frame):

    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.parent.title('App')
        # call the widgets
        self.okButton()
        self.quitButton()
        self.readDataButton()
        self.clearDataButton()
        self.velScale()
        self.canvas()

    # print messages on the screen
    def printMessage(self):
        if (self.data):
            print("Data is loaded and accessible from here (printMessage()).")
        else:
            print('No data loaded...')

    ### OK button
    def okButton(self):
        self.okButton = tk.Button(self, text='Test', command=self.printMessage)
        self.okButton.grid(column=1, row=1, sticky="nesw")

    ### Quit button
    def quitButton(self):
        self.quitButton = tk.Button(self, text='Quit', command=self.confirmQuit)
        self.quitButton.grid(column=1, row=2, sticky="nesw")
    # confirm quitting
    def confirmQuit(self):
        answer = tkMessageBox.askyesno(title="App", message="Do you really want to quit?")
        if (answer):
            self.quit()

    # Read data button
    def readDataButton(self):
        self.data = None
        self.readDataButton = tk.Button(self, text='Import data', command=self.readData)
        self.readDataButton.grid(column=1, row=3, sticky="nesw")
    # reading data
    def readData(self):
        import os
        fullPath = dataList = tkFileDialog.askopenfilename(initialdir='path/to/initialdir')
        dataDir = os.path.split(fullPath)[0]+'/'
        self.data = readData(fullPath)

    # Clear data from current session
    def clearDataButton(self):
        self.clearData = tk.Button(self, text='Clear data', command=self.confirmClearData)
        self.clearData.grid(column=1, row=4, sticky="nesw")
    # confirm clearing data
    def confirmClearData(self):
        answer = tkMessageBox.askyesno(title="App", message="Are you sure you want to clear the loaded data?")
        if (answer):
            self.data = None
            tkMessageBox.showwarning(title="App", message="Data has been deleted.")

    # Velocity scale
    def velScale(self):
        self.velVar = tk.StringVar()
        velLabel = tk.Label(self, text="Scale value:", textvariable=self.velVar)
        velLabel.grid(row=4, column=0, columnspan=2, sticky=tk.W+tk.E)
        velScale = tk.Scale(self, from_=-500, to=+500, orient=tk.HORIZONTAL, resolution=20,
                        sliderlength=20, showvalue=0,
                        length=200, width=20,
                        command=self.onVelScale)
        velScale.grid(column=1, row=5, sticky="nesw")
    # update velLabel
    def onVelScale(self, val):
        self.velVar.set("Scale value: {:+0.0f}".format(float(val)))

    # Canvas
    def canvas(self):
        self.f = Figure(figsize=(4,2))
        self.a = self.f.add_subplot(111)
        self.a.plot([1,2,3,4,5,6,7,8],[5,6,1,3,8,9,3,5])

        self.canvas = FigureCanvasTkAgg(self.f, master=self)
        self.canvas.get_tk_widget().grid(column=2, row=1, rowspan=5, sticky="nesw")

        self.toolbar = NavigationToolbar2TkAgg(self.canvas, self.parent)

if __name__ == "__main__":
    root = tk.Tk()
    root.geometry("800x600+10+10")
    root.resizable(0, 0)
    MainApp(root).pack(side=tk.TOP)
    root.mainloop()

为了展示我不调用 show 的意思,我还将您的 matplotlib 导入移到了文件的顶部,并更改了其他导入以适合 python 2/3,这似乎是网格对我来说很好,你能澄清一下它没有做你期望它做的事情吗? (如果这仍然不起作用)

使用 canvas() 函数并以 canvas

调用 FigureCanvasTkAgg 时不会出现错误