【问题标题】:How do I make tkinter mainloop wait for a matplotlib click event如何让 tkinter mainloop 等待 matplotlib 点击事件
【发布时间】:2020-04-28 05:29:52
【问题描述】:

我正在使用 Tkinter 和 matplotlib 构建一个带有嵌入式绘图的 GUI。我在我的窗口中嵌入了一个图形,现在希望使用 matplotlib 的事件处理程序从图形中获取两组 x,y 坐标,然后使用这些坐标创建一条从图形中的数据中减去的直线。代码的简化版本如下所示:

import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import tkinter as tk

#ideally this uses matplotlib's event handler and also waits for a click before registering the cooridnates
def choose_points():
    points = []
    window.bind("<Button-1>", on_click)
    points.append(graph_xy)
    window.bind("<Button-1>", on_click)
    points.append(graph_xy)
    return points

def on_click(event):
    window.unbind("<Button-1")
    window.config(cursor="arrow")
    graph_xy[0]=event.x
    graph_xy[1]=event.y

def line(x1=0,y1=0,x2=1,y2=1000):
    m=(y2-y1)/(x2-x1)
    c=y2-m*x2
    line_data=[]
    for val in range(0,20):
        line_data.append(val*m + c)
    return line_data

def build_line():
    points = []
    points = choose_points()
    #store line in line_list
    line_list=line(points[0],points[1],points[2],points[3])

#lists needed
line_list=[]
graph_xy=[0,0]

#GUI
window=tk.Tk()
window.title("IPES Graphing Tool")
window.geometry('1150x840')

#Make a frame for the graph
plot_frame = tk.Frame(window)
plot_frame.pack(side = tk.TOP,padx=5,pady=5)

#Button for making the straight line
line_btn = ttk.Button(plot_frame,text="Build line", command = build_line)
line_btn.grid(row=4, column=2,sticky='w')

#make empty figure
fig1=plt.figure(figsize=(9,7))
ax= fig1.add_axes([0.1,0.1,0.65,0.75])

#embed matplotlib figure
canvas = FigureCanvasTkAgg(fig1, plot_frame)
mpl_canvas=canvas.get_tk_widget()
canvas.get_tk_widget().pack(padx=20,side=tk.BOTTOM, fill=tk.BOTH, expand=False)
toolbar = NavigationToolbar2Tk(canvas, plot_frame)
toolbar.update()
canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=False)

window.mainloop()

显然这个例子没有以任何方式绘制或使用线,坐标也不正确,因为它们没有转换为图形的坐标。我尝试将window.bind("&lt;Button-1&gt;",wait_click) 替换为plt.connect('button_press_event',on_click) 但这不会等待点击,因此由于程序尝试访问points 但它是空的而发生错误。

我想使用matplotlib事件处理的功能,这样我就可以使用event.xdataevent.inaxes等方法来避免不必要的额外工作。

谢谢。

【问题讨论】:

    标签: python matplotlib tkinter click wait


    【解决方案1】:

    您应该使用canvas.mpl_connect 触发您的events,然后检索xdataydata 来绘制线。示例见下:

    import matplotlib
    matplotlib.use("TkAgg")
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
    from matplotlib.figure import Figure
    import tkinter as tk
    
    window=tk.Tk()
    window.title("IPES Graphing Tool")
    window.geometry('1150x840')
    
    plot_frame = tk.Frame(window)
    plot_frame.pack(side = tk.TOP,padx=5,pady=5)
    
    fig1=Figure(figsize=(9,7))
    ax= fig1.add_axes([0.1,0.1,0.65,0.75])
    
    canvas = FigureCanvasTkAgg(fig1, window)
    canvas.get_tk_widget().pack(padx=20,side=tk.TOP, fill=tk.BOTH, expand=False)
    toolbar = NavigationToolbar2Tk(canvas, window)
    toolbar.update()
    
    class DrawLine: # a simple class to store previous cords
        def __init__(self):
            self.x = None
            self.y = None
    
        def get_cords(self, event):
            if self.x and self.y:
                ax.plot([self.x, event.xdata], [self.y, event.ydata])
                canvas.draw_idle()
            self.x, self.y = event.xdata, event.ydata
    
    draw = DrawLine()
    canvas.mpl_connect('button_press_event', draw.get_cords)
    
    window.mainloop()
    

    【讨论】:

    • 感谢您的回答 Henry Yik,但这不是我想要的。我提出的例子过于简单化了这个问题。我的目标是让用户单击一个按钮并让程序进入一个例程,该例程从图形中获取两个点(由单击事件指定),然后使用它们找到一条直线,然后从中减去图中的数据。我有减法例程工作,但我不能让程序在进入这个减法例程之前等待用户的点击事件。希望这是有道理的。将编辑问题
    • 这个想法是一样的——你等待两根绳子被拾起,然后执行你喜欢的任何功能。您可以使用 about 类来存储初始值并仅在第二次选择后执行您的函数。
    • 我很确定我明白你的意思,但我的问题有点不同。我不希望它一直执行。我想要的是只有在我按下某个按钮时才会发生这种情况。我一直在尝试使用您的想法,但我没有任何运气。
    • 我想我越来越近了。我创建了一个按钮,该按钮发出一个函数choose_coords(draw),该函数采用DrawLine 对象,随后调用canvas.mpl_connect('button_press_event',draw.get_cords) 开始监听图形上的点击(函数存储连接的ID)。我调整了 get_cords 方法以从点击中获取坐标,使用它们,然后执行 canvas.mpl_disconnect(cid) 并重新初始化 DrawLine 对象。现在可以了!
    【解决方案2】:

    所以,我设法通过调整@Henry Yik 的答案获得了我想要的功能。只需使用canvas.mpl_disconnect(cid) 即可解决此问题。

    我基本上使用一个按钮来使用一个名为choose_cords() 的函数,该函数将接收一个名为draw 的对象,该对象类型为DrawLine,包含x 和y 坐标来构建线。然后该函数将发出命令canvas.mpl_connect('button_press_event', draw.get_cords) 开始监听图表上的点击。一旦注册了两次点击,matplotlib 事件处理程序将与 draw 对象断开连接。代码是这样的

    
    import matplotlib
    matplotlib.use("TkAgg")
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
    from matplotlib.figure import Figure
    import tkinter as tk
    
    window=tk.Tk()
    window.title("IPES Graphing Tool")
    window.geometry('1150x840')
    
    plot_frame = tk.Frame(window)
    plot_frame.pack(side = tk.TOP,padx=5,pady=5)
    
    fig1=Figure(figsize=(9,7))
    ax= fig1.add_axes([0.1,0.1,0.65,0.75])
    
    canvas = FigureCanvasTkAgg(fig1, window)
    canvas.get_tk_widget().pack(padx=20,side=tk.TOP, fill=tk.BOTH, expand=False)
    toolbar = NavigationToolbar2Tk(canvas, window)
    toolbar.update()
    
    def choose_cords(draw):
        draw.cid=canvas.mpl_connect('button_press_event', draw.get_cords)
    
    class DrawLine: # a simple class to store previous cords
        def __init__(self):
            self.x = None
            self.y = None
            self.cid = None
    
        def get_cords(self, event):
            if self.x and self.y:
                ax.plot([self.x, event.xdata], [self.y, event.ydata])
                canvas.draw_idle()
                canvas.mpl_disconnect(self.cid)
                self.__init__()
                return
            self.x, self.y = event.xdata, event.ydata
    
    draw = DrawLine()
    draw_btn = tk.Button(window, text="Draw the Line", command=lambda:choose_cords(draw)).pack(side=tk.TOP,padx=5,pady=5)
    
    window.mainloop()
    

    我在绘制线后添加了一个返回,因为我每次都想要一个新线,而不是继续。

    再次感谢亨利的回答,它产生了这个

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-10-27
      • 1970-01-01
      • 2023-03-09
      • 2017-11-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多