Прокрутка холста Tkinter медленная визуализация

#python-3.x #tkinter #tkinter-canvas

Вопрос:

Виджет canvas в Tkinter очень медленно рисует, что приводит к большим искажениям визуальных элементов приложений при прокрутке, даже при использовании ограниченных виджетов.

Я провел поиск, но, похоже, получил ответы только от людей, которые рисуют несколько вещей на холсте, а не на эффектах полосы прокрутки.

Есть ли какие-либо проблемы с моим кодом, которые могли бы вызвать эту проблему, или есть какие-либо методы, позволяющие исправить время отрисовки, чтобы оно было более плавным визуально. В приложении это означает, что каждая строка имеет свой цвет, что может сделать ее чрезвычайно уродливой на вид и затруднить поиск данных, которые ищет пользователь.

MVCE:

 #python 3.8.6
from tkinter import *
import random
class test:
    def __init__(self):
        self.words = ["troop","relieve","exact","appeal","shortage","familiar","comfortable","sniff","mold","clay","rack","square","color","book","velvet","address","elaborate","grip","neutral","pupil"]
    def scrollable_area2(self, holder):
        base_frame = Frame(holder, padx=5, pady=5)
        base_frame.pack(fill=BOTH, expand=1)
        base_frame.rowconfigure(0, weight=0) 
        base_frame.columnconfigure(0, weight=1)
        can = Canvas(base_frame, bg="white")
        can.pack(side=LEFT, expand=1, fill=BOTH)
        scrollArea = Frame(base_frame, bg="white", )
        scrollArea.pack(side=LEFT, expand=1, fill=BOTH)
        can.create_window(0, 0, window=scrollArea, anchor='nw')
        Scroll = Scrollbar(base_frame, orient=VERTICAL)
        Scroll.config(command=can.yview)
        Scroll.pack(side=RIGHT, fill=Y)
        can.config(yscrollcommand=Scroll.set)
        scrollArea.bind("<Configure>", lambda e=Event(), c=can: self.update_scrollregion(e, c))
        return scrollArea, can
    def update_scrollregion(self, event, can):
        if can.winfo_exists():
            can.configure(scrollregion=can.bbox("all"))
    def generate(self, count): #generates the rows
        for i in range(int(count.get())):
            row = Frame(self.holder)
            row.pack(side=TOP)
            for i in range(9):
                a = Label(row, text=self.words[random.randint(0, len(self.words)-1)])
                a.pack(side=LEFT)
            b = Button(row, text=self.words[random.randint(0, len(self.words)-1)])
            b.pack(side=LEFT)
    def main(self):
        opts = Frame(self.root)
        opts.pack(side=TOP)
        v= StringVar()
        e = Entry(opts, textvariable=v)
        e.pack(side=LEFT)
        b=Button(opts, text="Run", command=lambda e=Event(), v=v:self.generate(v))
        b.pack(side=LEFT)
        main_frame=Frame(self.root)
        main_frame.pack(side=TOP, fill=BOTH, expand=1)
        self.holder, can = self.scrollable_area2(main_frame)
    def run(self):
        self.root = Tk()
        self.main()
        self.root.mainloop()
if __name__ == "__main__":
    app = test()
    app.run()
 

Я оставил поле, в котором вы можете ввести количество строк. Я пробовал от 30 строк до более чем 300 строк, и хотя начальное время рендеринга меняется, проблема прокрутки всегда одна и та же.

ПРИМЕЧАНИЕ: извините за странный способ, которым я создаю область прокрутки, это из более сложного фрагмента кода, который я изменил, чтобы он соответствовал здесь, если это окажется фактором.

Комментарии:

1. Если вы создаете вертикальный стек виджетов, использование текстового виджета может быть более эффективным, чем использование холста и встроенной рамки.

2. Мой код состоит в том, чтобы создать таблицу, в каждой строке которой будет 10-15 столбцов и кнопка. Не уверен, что текстовый виджет может делать то же самое, что комбинация холста и рамки.

3. Ну, честно говоря, ваш пример не создает таблицу. Вы, конечно, можете упорядочить все в табличной форме в текстовом виджете.

4. Вы можете добавлять виджеты в текстовый виджет так же, как и в рамку

Ответ №1:

Поскольку вы просто создаете вертикальную стопку кадров, скорее всего, будет более эффективно использовать текстовый виджет в качестве контейнера, а не холст и встроенную рамку.

Вот простой пример, который создает 1000 строк, похожих на то, как вы делаете это с холстом. На моей машине OSX он работает намного лучше, чем на холсте.

 def scrollable_area2(self, parent):
    base_frame = Frame(parent, padx=5, pady=5)
    base_frame.pack(fill="both", expand=True)

    holder = Text(base_frame)
    vsb = Scrollbar(base_frame, orient="vertical", command=holder.yview)
    holder.configure(yscrollcommand=vsb.set)
    holder.pack(side="left", fill="both", expand=True)
    vsb.pack(side="right", fill="y")
    return holder
...
def generate(self, count): #generates the rows
    for i in range(int(count.get())):
        row = Frame(self.holder)
        self.holder.window_create("end", window=row)
        self.holder.insert("end", "n")
        ...
def main(self):
    ...
    self.holder = self.scrollable_area2(main_frame)
 

Приведенный выше пример сохраняет внутренние рамки, но на самом деле вам это не нужно. Вы можете вставить текст непосредственно в текстовый виджет, что сделает код еще более эффективным.

В комментарии вы сказали, что на самом деле создаете не стопку кадров, а скорее таблицу значений. Вы можете создать таблицу в текстовом виджете, используя вкладки для создания столбцов. Вставляя текст непосредственно в виджет, вы создаете гораздо меньше виджетов, что определенно повысит производительность.

Вот пример использования жестко закодированных вкладок, но вы можете легко вычислить их на основе самого длинного слова в списке.

 def scrollable_area2(self, parent):
    base_frame = Frame(parent, padx=5, pady=5)
    base_frame.pack(fill="both", expand=True)

    self.holder = Text(base_frame, wrap="none", tabs=100)
    vsb = Scrollbar(base_frame, orient="vertical", command=self.holder.yview)
    self.holder.configure(yscrollcommand=vsb.set)
    self.holder.pack(side="left", fill="both", expand=True)
    vsb.pack(side="right", fill="y")
 

generate Тогда ваша функция может выглядеть примерно так:

 def generate(self, count): #generates the rows
    for i in range(int(count.get())):
        for i in range(9):
            text = "t".join([random.choice(self.words) for x in range(9)])
            self.holder.insert("end", text   "t")
            button = Button(self.holder, text=random.choice(self.words))
            self.holder.window_create("end", window=button)
            self.holder.insert("end", "n")
 

скриншот

Комментарии:

1. Привет. Спасибо за ваш ответ. Я вижу, как использование текстового виджета для замены сложенных кадров намного эффективнее и потенциально может удвоить количество строк, которые я могу добавить, однако проблема прокрутки, похоже, все еще существует. У меня установлено приложение на нескольких машинах, поэтому я знаю, что эта проблема возникает не только у меня. Вот скриншот с тем, как это выглядит, когда я прокручиваю prnt.sc/16ft1yi Как вы можете видеть, повторная визуализация происходит очень медленно (это происходит при медленном движении, цвет отображается только при быстрой прокрутке) Медленная прокрутка = 3 строки в секунду, быстрая прокрутка = 10 строк в секунду для справки