Python Tkinter 滚动条滑动不稳

3

介绍: 我的Python Tkinter应用程序设计了一个侧边滚动条,以便如果调整窗口大小,应用程序仍然可以通过滚动条查看。我通过将包含所有内容的帧放入画布中,并使用滚动条控制画布来实现此目的。在窗口大小调整时,我有一个名为resizeCanvas的函数来调整画布大小。

问题: 调整窗口大小后,滚动条似乎有些工作但似乎会跳来跳去,就像在抽搐一样。然而,在初始化时,滚动条可以平稳地工作。所以调整窗口大小似乎是问题所在。您对滚动条为什么会出现这种行为有任何建议吗?

应用程序层次结构:

  • Tkinter根
    • 帧(只是画布的容器)
      • 画布(滚动条附加到此画布)
        • 帧(内容在此帧中)

注意:Python 2.7.2

代码片段:

    myframe=Frame(root,width=winx,height=winy)
    myframe.place(x=0,y=0)
    canvas = Tkinter.Canvas(myframe,width=winx,height=winy)
    frame = Frame(canvas,width=winx,height=winy)
    myscrollbar=Scrollbar(myframe,orient="vertical")
    myscrollbar.configure(command=canvas.yview)
    canvas.configure(yscrollcommand=myscrollbar.set)

    myscrollbar.pack(side="left",fill="y")
    canvas.pack(side="left")
    canvas.create_window((0,0),window=frame,anchor='nw')
    frame.bind("<Configure>", initializeCanvas)
    bind("<Configure>", resizeCanvas)

def initializeCanvas(self, event):
   canvas.configure(scrollregion=self.canvas.bbox("all"),width=winx,height=winy)

def resizeCanvas(self, event):
    update()
    cwidth = self.winfo_width()
    cheight = self.winfo_height()
    canvas.config(width=cwidth, height=cheight)

整个代码:

import sys
#external python files are in the includes folder
sys.path.insert(0, 'includes')

import webbrowser
import os
import Tkinter
import tkFileDialog
import tkMessageBox
import ConfigParser
from ttk import *
import tkFont
import Tix

# configurations held in this variable
config = ConfigParser.RawConfigParser()
# pady padding for create buttons
py = 15
# padx padding for create buttons
px = 5
# padding on left side for indenting elements
indentx = 25
winx = 815
winy = 515
# array to hold features
FEATURES =['']


# wrapper class for GUI
class AppTk(Tkinter.Tk):
    def __init__(self, parent):
        Tkinter.Tk.__init__(self, parent)
        self.parent = parent
        self.settings = "./settings/settings.cfg"
        self.initialize_gui()
        self.minsize(winx, 100)
        sizex = winx
        sizey = winy
        posx  = 100
        posy  = 100
        self.wm_geometry("%dx%d+%d+%d" % (sizex, sizey, posx, posy))

        try:
            self.iconbitmap('./imgs/favicon.ico')
        except Exception, e:
            print "\n****Error occurred (GUI_MBD_File_Creator.init):  favicon not found!"

    # Setup grid of elements in GUI
    # action: on start of application
    def initialize_gui(self):

        # ----------------------------------------------------
        # START Settings frame initialization
        # ----------------------------------------------------
        self.myframe=Frame(self,width=winx,height=winy)
        self.myframe.place(x=0,y=0)
        self.canvas = Tkinter.Canvas(self.myframe,width=winx,height=winy)
        self.frame = Frame(self.canvas,width=winx,height=winy)
        self.myscrollbar=Scrollbar(self.myframe,orient="vertical")
        self.myscrollbar.configure(command=self.canvas.yview)
        self.canvas.configure(yscrollcommand=self.myscrollbar.set)

        self.myscrollbar.pack(side="left",fill="y")
        self.canvas.pack(side="left")
        self.canvas.create_window((0,0),window=self.frame,anchor='nw')
        self.frame.bind("<Configure>",self.initializeCanvas)
        self.bind("<Configure>",self.resizeCanvas)


        frameFont = tkFont.Font(size=13, weight=tkFont.BOLD)

        self.frameSettings = Tkinter.LabelFrame(self.frame, text="Settings: fill these out first", relief="groove", borderwidth="3", font=frameFont)
        self.frameSettings.grid(sticky="EW", padx=px, pady=(5,15))

        labelSpreadsheet = Label(self.frameSettings, text="1. Spreadsheet Path:")
        labelSpreadsheet.grid(row=0, column=0, sticky='W', padx=(indentx,0))
        variableSpreadsheet = Tkinter.StringVar()
        self.entrySpreadsheet = Entry(self.frameSettings, textvariable=variableSpreadsheet, width=90, state=Tkinter.DISABLED)
        self.entrySpreadsheet.grid(row=0, column=1, sticky='W', padx=px, pady=5)
        self.entrySpreadsheet.svar = variableSpreadsheet
        buttonSpreadsheet = Button(self.frameSettings, text="Browse...")
        buttonSpreadsheet.grid(row=0, column=2, sticky='W', padx=(0,10))

        labelPath = Label(self.frameSettings, text="2. Root Save Path:")
        labelPath.grid(row=1, column=0, sticky='W', padx=(indentx,0))
        variablePath = Tkinter.StringVar()
        self.entryPath = Entry(self.frameSettings, textvariable=variablePath, width=90, state=Tkinter.DISABLED)
        self.entryPath.grid(row=1, column=1, sticky='W', padx=px, pady=(5,10))
        self.entryPath.svar = variablePath
        buttonPath = Button(self.frameSettings, text="Browse...")
        buttonPath.grid(row=1, column=2, sticky='W', padx=(0,10), pady=(0,5))

        # ----------------------------------------------------
        # START Creation Menu frame initialization
        # ----------------------------------------------------

        self.frameCreationIndividual = Tkinter.LabelFrame(self.frame, text="Feature Files Creation Menu", relief="groove", borderwidth="3", font=frameFont)
        self.frameCreationIndividual.grid(sticky="EW", padx=px, pady=(5,15))

        labelReq = Label(self.frameCreationIndividual, text="3. Feature(s):")
        labelReq.grid(row=0, column=0, sticky='NW', pady=(5,0), padx=(indentx,15))
        self.scrollbarReq = Scrollbar(self.frameCreationIndividual)
        self.scrollbarReq.grid(row=1, column=3, rowspan=16, sticky="NSW", pady=(0,15),padx=(0,20))

        variableSelectAll = Tkinter.IntVar()
        self.checkSelectAll = Checkbutton(self.frameCreationIndividual, text = "Select All", variable = variableSelectAll, onvalue = 1, offvalue = 0)
        self.checkSelectAll.grid(row=0, column=1, columnspan=2, sticky='NE', padx=px, pady=(5,0))
        self.checkSelectAll.svar = variableSelectAll

        labelReq = Label(self.frameCreationIndividual, text="4. Files:")
        labelReq.grid(row=0, column=5, sticky='NW', pady=(5,0), padx=15)

        variableIndividualFFS = Tkinter.IntVar()
        self.checkIndividualFFS = Checkbutton(self.frameCreationIndividual, text = "Create Feature File", variable = variableIndividualFFS, onvalue = 1, offvalue = 0)
        self.checkIndividualFFS.grid(row=1, column=5, sticky='NW', padx=15)
        self.checkIndividualFFS.svar = variableIndividualFFS

        variableIndividualSFS = Tkinter.IntVar()
        self.checkIndividualSFS = Checkbutton(self.frameCreationIndividual, text = "Create SubFeature Files", variable = variableIndividualSFS, onvalue = 1, offvalue = 0)
        self.checkIndividualSFS.grid(row=2, column=5, sticky='NW', padx=15)
        self.checkIndividualSFS.svar = variableIndividualSFS

        variableIndividualDO = Tkinter.IntVar()
        self.checkIndividualDO = Checkbutton(self.frameCreationIndividual, text = "Create Doc Outline", variable = variableIndividualDO, onvalue = 1, offvalue = 0)
        self.checkIndividualDO.grid(row=3, column=5, sticky='NW', padx=15)
        self.checkIndividualDO.svar = variableIndividualDO

        variableIndividualDWR = Tkinter.IntVar()
        self.checkIndividualDWR = Checkbutton(self.frameCreationIndividual, text = "Create Doc With Requirements", variable = variableIndividualDWR, onvalue = 1, offvalue = 0)
        self.checkIndividualDWR.grid(row=4, column=5, sticky='NW', padx=(15,30))
        self.checkIndividualDWR.svar = variableIndividualDWR

        self.buttonIndividualAll = Button(self.frameCreationIndividual, text="Create...", width=43)
        self.buttonIndividualAll.grid(row=1, column=6, rowspan=4, sticky='NESW', padx=px)

        # ----------------------------------------------------
        # START Entire System Creation frame initialization
        # ----------------------------------------------------

        self.frameCreationSystem = Tkinter.LabelFrame(self.frame, text="System Creation Menu", relief="groove", borderwidth="3", font=frameFont)
        self.frameCreationSystem.grid(sticky="EW", padx=px, pady=15)

        self.buttonLAIF = Button(self.frameCreationSystem, text="Create Layers/App Integration Files", width=35)
        self.buttonLAIF.grid(row=11, column=0, sticky='NESW', ipady=5, padx=(indentx,0), pady=(16,8))

        self.buttonDO = Button(self.frameCreationSystem, text="Create Entire Doc Outline")
        self.buttonDO.grid(row=12, column=0, sticky='NESW', ipady=5, padx=(indentx,0), pady=(8,10))

        # ----------------------------------------------------
        # START Feature Tab Creation Frame initialization
        # ----------------------------------------------------

        self.frameCreationNew = Tkinter.LabelFrame(self.frame, text="Feature Tab Creation Menu", relief="groove", borderwidth="3", font=frameFont)
        self.frameCreationNew.grid(sticky="EW", padx=px, pady=(15,5))

        labelIssueSpreadsheet = Label(self.frameCreationNew, text="2. Feature Spreadsheet Path:")
        labelIssueSpreadsheet.grid(row=0, column=0, sticky='W', padx=(indentx,0))
        variableIssueSpreadsheet = Tkinter.StringVar()
        self.entryIssueSpreadsheet = Entry(self.frameCreationNew, textvariable=variableIssueSpreadsheet, width=83, state=Tkinter.DISABLED)
        self.entryIssueSpreadsheet.grid(row=0, column=1, sticky='W', padx=px, pady=5)
        self.entryIssueSpreadsheet.svar = variableIssueSpreadsheet
        buttonIssueSpreadsheet = Button(self.frameCreationNew, text="Browse...")
        buttonIssueSpreadsheet.grid(row=0, column=2, sticky='W')

        labelFeatureTab = Label(self.frameCreationNew, text="3. Feature Name:")
        labelFeatureTab.grid(row=1, column=0, sticky='W', padx=(indentx,0))
        variableFeatureTab = Tkinter.StringVar()
        self.entryFeatureTab = Entry(self.frameCreationNew, textvariable=variableFeatureTab, width=83)
        self.entryFeatureTab.grid(row=1, column=1, sticky='W', padx=px, pady=5)
        self.entryFeatureTab.svar = variableFeatureTab

        labelFeatureAbbrv = Label(self.frameCreationNew, text="4. Feature Abbreviation:")
        labelFeatureAbbrv.grid(row=2, column=0, sticky='W', padx=(indentx,0))
        variableFeatureAbbrv = Tkinter.StringVar()
        self.entryFeatureAbbrv = Entry(self.frameCreationNew, textvariable=variableFeatureAbbrv, width=83)
        self.entryFeatureAbbrv.grid(row=2, column=1, sticky='W', padx=px, pady=5)
        self.entryFeatureAbbrv.svar = variableFeatureAbbrv

        self.buttonNewFeature = Button(self.frameCreationNew, text="Create Feature Tab", width=35)
        self.buttonNewFeature.grid(row=3, column=0, columnspan =2, sticky='NWS', ipady=5, pady=(8,10), padx=(indentx,0))

    # ----------------------------------------------------
    # START general purpose methods
    # ----------------------------------------------------

    def initializeCanvas(self, event):
        self.canvas.configure(scrollregion=self.canvas.bbox("all"),width=winx,height=winy)

    def resizeCanvas(self, event):
        self.update()
        cwidth = self.winfo_width()
        cheight = self.winfo_height()
        self.canvas.config(scrollregion=self.canvas.bbox("all"), width=cwidth, height=cheight)

# ----------------------------------------------------
# Initialize application
# ----------------------------------------------------
if __name__ == "__main__":
    app = AppTk(None)
    app.title("MBD File Creator")
    app.mainloop()

有趣的问题,但是我认为如果没有一个完整的代码示例来展示这个问题,我无法对其进行诊断。到目前为止,我只是在运行你提供的内容时遇到了“IndentationError”。 - Kevin
我的应用程序相当大,我可以看看如何将其精简并在此处发布。 - Jason W
已添加修剪过的代码,应该可以运行了,谢谢。 - Jason W
当我运行你的代码并尝试调整窗口大小时,会出现无尽的错误级联。 - Bryan Oakley
嗯,我测试了修剪后的代码,它对我有效。你使用的是Python 2.7.x吗? - Jason W
2个回答

1
虽然可能存在其他错误,但你有一个非常严重的缺陷:你正在为<Configure>事件创建与self绑定。因为self是根窗口的实例,所以每个你创建的小部件都会继承这个绑定。你的resizeCanvas方法在启动时被调用了数百次,在窗口大小调整时也被调用了数百次。
你还有一个问题,就是在事件处理程序中调用了update。一般来说,这是不好的。实际上,这就像调用mainloop一样,直到所有事件都被处理完才会返回。如果你的代码导致更多的事件被处理(例如重新配置窗口并导致<configure>事件触发),你就会进入递归循环。
你可能需要删除对self.update()的调用和/或将其替换为更安全的self.update_idletasks()。你还需要删除self上的<Configure>绑定,或者在方法中需要检查哪个小部件引起了事件触发(即检查事件小部件是否为根窗口)。

正如你可能已经注意到的那样,我是Python和Tkinter的新手。感谢你的建议。 - Jason W
所有的建议都很好。仅仅“删除对self.update()的调用”似乎就可以解决卡顿滚动的问题。 - Kevin
你是个奇迹般的工作者。移除self.update()修复了滚动问题。谢谢! - Jason W

-1

试着先将 canvas.create_window 保存到一个变量中,如下所示:

self.windows_item = canvas.create_window((0,0),window=frame,anchor='nw')

然后在 resizeCanvas 的末尾调用以下方法:

def update(self):
    "Update the canvas and the scrollregion"
    self.update_idletasks()
    canvas.config(scrollregion=canvas.bbox(self.windows_item))

希望这能有所帮助!
大部分内容可以在这里找到:https://dev59.com/SnA75IYBdhLWcg3w0sp9#47985165

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接