在 MS-Windows 下调整大小导致的 Tkinter 性能问题

Posted

技术标签:

【中文标题】在 MS-Windows 下调整大小导致的 Tkinter 性能问题【英文标题】:Tkinter performance issues by resizing under MS-Windows 【发布时间】:2021-01-11 22:27:07 【问题描述】:

受this 问题的启发,我想为我的根窗口编写自己的调整大小功能。 但我只是注意到我的代码显示了一些性能问题。如果你快速调整它的大小,你会看到窗口没有像我希望的那样找到它的高度提示,它“结结巴巴”。 (更像是摇摆)

有人知道为什么会这样吗?我的最佳猜测是 tkinter 事件处理程序对它来说太慢了,或者我所做的数学运算不是最快的方法。

我确实在不同的位置尝试了update_idletasks(),而且还尝试了几次。我尝试过的另一种方法是使用after 方法,但它使情况变得更糟。

这是一个示例代码:

import tkinter as tk


class FloatingWindow(tk.Tk):
    def __init__(self):
        super().__init__()
        self.overrideredirect(True)
        self.center()

        self.label = tk.Label(self, text="Grab the upper-right corner to resize")
        self.label.pack(side="top", fill="both", expand=True)

        self.grip2 = tk.Label(self,bg='blue')
        self.grip2.place(relx=1.0, rely=0, anchor="ne")
        self.grip2.bind("<B1-Motion>",self.OnMotion)

    def OnMotion(self, event):
        abs_x = self.winfo_pointerx() - self.winfo_rootx()
        abs_y = self.winfo_pointery() - self.winfo_rooty()
        if abs_x >0:
            x = self.winfo_rootx()
            y = self.winfo_rooty()+abs_y
            height = self.winfo_height()-abs_y
            if height >0:
                self.geometry("%dx%d+%d+%d" % (abs_x,height,
                                               x,y))
            
        
    def center(self):
        width = 300
        height = 300
        screen_width = self.winfo_screenwidth()
        screen_height = self.winfo_screenheight()
        x_coordinate = (screen_width/2) - (width/2)
        y_coordinate = (screen_height/2) - (height/2)

        self.geometry("%dx%d+%d+%d" % (width, height,
                                       x_coordinate, y_coordinate))

app=FloatingWindow()
app.mainloop()

full example

更新

性能问题似乎与 Microsoft 相关,well known issue 让大多数 MS 开发人员发疯。

抖动/闪烁/跳跃

黑色边框

双像/快闪/跳跃

我的机器是:

Windows 10 家庭版; x64-base,Intel(R) Core(TM) i3-2120 @ 3.30GHz, 3300 MHz,2 核

Python 3.7.2 和 tkinter 8.6

【问题讨论】:

实际上,您的代码非常好,并且在我的系统上运行良好。我没有遇到任何口吃。 无法重现:我经历了非常流畅的大小调整,在过时的系统上没有任何卡顿。 osx10.12.5 macbook air 2 cores i5 2011 with 4GB RAM, 2 browsers, one large pdf, system monitor, textedit opened, and two screens 使用python 3.8 通过Jupyter notebook - 您的代码有一个错误:在双显示器上,调整边框大小会使y's 双向扩展。 @ReblochonMasque 感谢您的回复。我确实更新了我的答案并修复了另一个错误。我没有双显示器系统,所以谢谢你让我知道。我还添加了我的系统以及 tk 和 python 的版本。 @ReblochonMasque 正如你所说,这似乎是一个 windows only 问题并且是一个well known issue。我将使用这些信息更新此问题。 【参考方案1】:

我能够通过在 OnMotion 方法的开头添加 update() 函数来解决问题

【讨论】:

请详细说明如何添加 update() 函数,特别是在何处添加函数 它为我解决了这个问题,如果我玩一点,我会得到一个RecursionError: maximum recursion depth exceeded while calling a Python object【参考方案2】:

如果注释行(在 OnMotion 方法的开头)被停用 self.update(),我已经快速添加了一些小部件和一些事件,以便每个人都能注意到性能问题。我玩过代码没有收到任何错误。希望这能解决您的问题。

import tkinter as tk

class FloatingWindow(tk.Tk):
    def __init__(self):
        super().__init__()
        self.overrideredirect(True)
        self.geometry("800x400+300+100")
        self.minsize(200, 200)
        self.config(bg="green")
        self.grid_columnconfigure(0, weight=3)
        self.grid_rowconfigure(1, weight=3)

        self.menu()
        self.textbox()        

        self.grip_se = tk.Label(self,bg='blue')
        self.grip_se.place(relx=1.0, rely=1.0, anchor="se")
        self.grip_se.bind("<B1-Motion>",lambda e, mode='se':self.OnMotion(e,mode))

        self.grip_e = tk.Label(self,bg='green')
        self.grip_e.place(relx=1.0, rely=0.5, anchor="e")
        self.grip_e.bind("<B1-Motion>",lambda e, mode='e':self.OnMotion(e,mode))
        
        self.grip_ne = tk.Label(self,bg='blue')
        self.grip_ne.place(relx=1.0, rely=0, anchor="ne")
        self.grip_ne.bind("<B1-Motion>",lambda e, mode='ne':self.OnMotion(e,mode))

        self.grip_n = tk.Label(self,bg='green')
        self.grip_n.place(relx=0.5, rely=0, anchor="n")
        self.grip_n.bind("<B1-Motion>",lambda e, mode='n':self.OnMotion(e,mode))

        self.grip_nw = tk.Label(self,bg='blue')
        self.grip_nw.place(relx=0, rely=0, anchor="nw")
        self.grip_nw.bind("<B1-Motion>",lambda e, mode='nw':self.OnMotion(e,mode))

        self.grip_w = tk.Label(self,bg='green')
        self.grip_w.place(relx=0, rely=0.5, anchor="w")
        self.grip_w.bind("<B1-Motion>",lambda e, mode='w':self.OnMotion(e,mode))

        self.grip_sw = tk.Label(self,bg='blue')
        self.grip_sw.place(relx=0, rely=1, anchor="sw")
        self.grip_sw.bind("<B1-Motion>",lambda e, mode='sw':self.OnMotion(e,mode))

        self.grip_s = tk.Label(self,bg='green')
        self.grip_s.place(relx=0.5, rely=1, anchor="s")
        self.grip_s.bind("<B1-Motion>",lambda e, mode='s':self.OnMotion(e,mode))

    def menu(self):
        self.frame = tk.Frame(self, height=25, bg='black')
        self.frame.grid(row=0, column=0, sticky="new")
        color = ['#FEF3B3','#FFF9DC', "#341C09"]

        for i in range(3):
            self.button = tk.Button(self.frame, text="Can you see", font=('calibri',12), bg=color[i-1], fg="red", relief="flat", bd=0)
            self.button.pack(side="left", fill="both", padx=3)
            self.lbl_space  = tk.Label(self.frame ,text="",bd=0,bg="black")
            self.lbl_space.pack(side="left", padx=5)

            self.button = tk.Button(self.frame, text="Can you see", font=('calibri',12), bg=color[i-1], fg="red", relief="flat", bd=0)
            self.button.pack(side="right", fill="both", padx=3)

            
    def textbox(self):
        self.frame2 = tk.Frame(self, bg='white')
        self.frame2.grid(row=1, column=0, sticky="wens")
        self.text_editor = tk.Text(self.frame2, wrap='word', font='calibri 12',undo = True, relief=tk.FLAT,bg="white")
        self.yscrollbar = tk.Scrollbar(self.frame2, command=self.text_editor.yview)
        self.yscrollbar.grid(row=0, column=1, sticky="ns")#ns

        self.text_editor.config(yscrollcommand=self.yscrollbar.set)
        self.text_editor.grid(row=0, column=0, sticky="wens", padx=3)

        self.frame2.grid_columnconfigure(0, weight=3)
        self.frame2.grid_rowconfigure(0, weight=3)
        self.text_editor.insert("1.0", 'Bed sincerity yet therefore forfeited his certainty neglected questions. Pursuit chamber as elderly amongst on. Distant however warrant farther to of. My justice wishing prudent waiting in be. Comparison age not pianoforte increasing delightful now. Insipidity sufficient dispatched any reasonably led ask. Announcing if attachment resolution sentiments admiration me on diminution. ')

        # insert a widget inside the text box
        options = ["choice 1","choice 2"]
        clicked = tk.StringVar()
        clicked.set(options[0])
        self.drop = tk.OptionMenu(self.text_editor, clicked, *options)
        self.text_editor.window_create("1.0", window=self.drop)
        self.drop.config(bg="#474747", relief='flat', font=('calibri',11, 'bold'))

    def OnMotion(self, event, mode):
        self.update() # <==== if you deactivate this line you can see the performance issues
        
        abs_x = self.winfo_pointerx() - self.winfo_rootx()
        abs_y = self.winfo_pointery() - self.winfo_rooty()
        width = self.winfo_width()
        height= self.winfo_height()
        x = self.winfo_rootx()
        y = self.winfo_rooty()
        
        if mode == 'se' and abs_x >0 and abs_y >0:
                self.geometry("%sx%s" % (abs_x,abs_y)
                              )
                
        if mode == 'e':
            self.geometry("%sx%s" % (abs_x,height)
                          )
        if mode == 'ne' and abs_x >0:
                y = y+abs_y
                height = height-abs_y
                if height >0:
                    self.geometry("%dx%d+%d+%d" % (abs_x,height,
                                                   x,y))
        if mode == 'n':
            height=height-abs_y
            y = y+abs_y
            if height >0 and width >0:
                self.geometry("%dx%d+%d+%d" % (width,height,
                                               x,y))
            
        if mode == 'nw':
            width = width-abs_x
            height=height-abs_y
            x = x+abs_x
            y = y+abs_y
            if height >0 and width >0:
                self.geometry("%dx%d+%d+%d" % (width,height,
                                               x,y))
        if mode == 'w':
            width = width-abs_x
            x = x+abs_x
            if height >0 and width >0:
                self.geometry("%dx%d+%d+%d" % (width,height,
                                               x,y))
        if mode == 'sw':
            width = width-abs_x
            height=height-(height-abs_y)
            x = x+abs_x
            if height >0 and width >0:
                self.geometry("%dx%d+%d+%d" % (width,height,
                                               x,y))
        if mode == 's':
            height=height-(height-abs_y)
            if height >0 and width >0:
                self.geometry("%dx%d+%d+%d" % (width,height,
                                               x,y))
            


app=FloatingWindow()
app.mainloop()

【讨论】:

我仍然遇到这些问题。尝试南/西锚并展开窗口。同样在几秒钟后通过在事件中摇动鼠标,您仍然会收到错误。

以上是关于在 MS-Windows 下调整大小导致的 Tkinter 性能问题的主要内容,如果未能解决你的问题,请参考以下文章

Centos/Linux下调整分区大小(以home和根分区为例)

调整窗口大小会导致位图被覆盖

iOS - 调整图像大小导致内存泄漏

调整home分区和根分区的大小

部分卷曲过渡导致控件调整大小

CentOS下调整home和根分区大小