<p>多亏了j_4321的回答,在深入研究了<code>FigureCanvasTk</code>对象的源代码之后,我想出了下面的解决方案,它完成了我所需要的一切</p>
<p>看起来canvas对象所做的就是在每次调整canvas小部件的大小时使用<code>tkinter.Canvas.create_image</code>生成图形的新图像,保持图形的DPI不变,并设置图形的宽度和高度以保持其与canvas小部件相同的大小。由于我希望画布小部件根据图形大小调整大小,因此我从图形属性计算宽度和高度,然后将自定义事件对象传递给<code>FigureCanvasTk.resize</code>,即画布小部件的<code><Configure></code>事件的回调函数</p>
<p>最后一个技巧是将画布的滚动区域设置为仅创建的最后一个画布项的大小。看起来没有从画布中删除图形的先前迭代(似乎是内存泄漏?),因此,如果尝试将scrollregion设置为<code>Canvas.bbox('all')</code>,它会将scrollregion设置为图形最大版本的大小,而不是图形的当前版本</p>
<p>以下是完整的示例代码:</p>
<pre class="lang-py prettyprint-override"><code>import tkinter as tk
from tkinter import ttk
from tkinter.simpledialog import askfloat
from matplotlib.figure import Figure
from matplotlib.axes import Axes
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class InteractivePlot(tk.Frame):
def __init__(self,master,**kwargs):
super().__init__(master,**kwargs)
self._figure = Figure(dpi=150)
self._canvas = FigureCanvasTkAgg(self._figure, master=self)
buttonframe = tk.Frame(self)
self._sizebutton = tk.Button(buttonframe,text="Size (in.)", command=self._change_size)
self._dpibutton = tk.Button(buttonframe,text="DPI", command=self._change_dpi)
self._axis = self._figure.add_subplot(111)
# Plot some data just to have something to look at.
self._axis.plot([0,1,2,3,4,5],[1,1,3,3,5,5],label='Dummy Data')
self._cwidg = self._canvas.get_tk_widget()
self._scrx = ttk.Scrollbar(self,orient="horizontal", command=self._cwidg.xview)
self._scry = ttk.Scrollbar(self,orient="vertical", command=self._cwidg.yview)
self._cwidg.configure(yscrollcommand=self._scry.set, xscrollcommand=self._scrx.set)
self._cwidg.bind("<Configure>",self._refresh)
self._sizebutton.grid(row=0,column=0,sticky='w')
self._dpibutton.grid(row=0,column=1,sticky='w')
buttonframe.grid(row=0,column=0,columnspan=2,sticky='W')
self._cwidg. grid(row=1,column=0,sticky='news')
self._scrx. grid(row=2,column=0,sticky='ew')
self._scry. grid(row=1,column=1,sticky='ns')
self.rowconfigure(1,weight=1)
self.columnconfigure(0,weight=1)
# Refresh the canvas to show the new plot
self._canvas.draw()
# Figure size change button callback
def _change_size(self):
newsize = askfloat('Size','Input new size in inches')
if newsize is None:
return
w = newsize
h = newsize/1.8
self._figure.set_figwidth(w)
self._figure.set_figheight(h)
self._refresh()
# Figure DPI change button callback
def _change_dpi(self):
newdpi = askfloat('DPI', 'Input a new DPI for the figure')
if newdpi is None:
return
self._figure.set_dpi(newdpi)
self._refresh()
# Refresh function to make the figure canvas widget display the entire figure
def _refresh(self,event=None):
# Get the width and height of the *figure* in pixels
w = self._figure.get_figwidth()*self._figure.get_dpi()
h = self._figure.get_figheight()*self._figure.get_dpi()
# Generate a blank tkinter Event object
evnt = tk.Event()
# Set the "width" and "height" values of the event
evnt.width = w
evnt.height = h
# Set the width and height of the canvas widget
self._cwidg.configure(width=w,height=h)
self._cwidg.update_idletasks()
# Pass the generated event object to the FigureCanvasTk.resize() function
self._canvas.resize(evnt)
# Set the scroll region to *only* the area of the last canvas item created.
# Otherwise, the scrollregion will always be the size of the largest iteration
# of the figure.
self._cwidg.configure(scrollregion=self._cwidg.bbox(self._cwidg.find_all()[-1]))
root = tk.Tk()
plt = InteractivePlot(root,width=400,height=400)
plt.pack(fill=tk.BOTH,expand=True)
root.mainloop()
</code></pre>