Issue
I'm working on building a GUI using ttk. One component involves dynamically adjusting the number of rows of Entry objects with a Scale / Spinbox. During the range of acceptable values, the number of fields exceeds the size of the window, so a scroll bar and canvas are used to accommodate. The issue arises with when the frame gets updated from one that had a scrollbar to another that has a scrollbar.
In this version, no matter if there is a scrollbar/canvas or not, the entries are placed in an innerFrame
within the entriesFrame
. The scrollbar is finally updating! But it's updating to size of the previous bbox.
from tkinter import Canvas, Tk, LEFT, BOTH, RIGHT, VERTICAL, X, Y, ALL, IntVar, TOP
from tkinter.ttk import Frame, Scale, Entry, Scrollbar, Spinbox
def validateSpin(P):
if (P.isdigit() or P =="") and int(P) > 0 and int(P) < 11:
return True
else:
return False
def updateScale(event):
def inner():
spnVal = spin.get()
if spnVal != "":
scale.set(float(spnVal))
entries.set(float(spnVal))
refreshEntries()
root.after(1, inner)
def updateSpin(event):
def inner():
reportVal = round(float(scale.get()))
spin.set(reportVal)
entries.set(reportVal)
refreshEntries()
root.after(1, inner)
def scrollbarNeeded():
if entries.get() > 7:
return True
else:
return False
def makeScrollbarAndCanvas():
scrollingCanvas = Canvas(entriesFrame, width = 200, highlightthickness=0)
scrollingCanvas.pack(side=LEFT,fill=X,expand=1)
scrollbar = Scrollbar(entriesFrame, orient=VERTICAL,command=scrollingCanvas.yview)
scrollbar.pack(side=RIGHT,fill=Y)
scrollingCanvas.configure(yscrollcommand=scrollbar.set)
scrollingCanvas.bind("<Configure>",lambda e: scrollingCanvas.config(scrollregion=scrollingCanvas.bbox(ALL)))
innerFrame = Frame(scrollingCanvas)
innerFrame.bind("<Map>", lambda e: scrollingCanvas.config(scrollregion=scrollingCanvas.bbox(ALL)))
scrollingCanvas.create_window((0,0),window= innerFrame, anchor="nw")
return innerFrame
def scrollbarFound():
for child in entriesFrame.pack_slaves():
if isinstance(child, Canvas):
return True
return False
def populateEntries(frm):
for i in range(entries.get()):
E = Entry(frm, width=15)
E.grid(column=0, row = i, pady=2)
def refreshEntries():
def searchAndDestory(obj):
if hasattr(obj, 'winfo_children') and callable(getattr(obj, 'winfo_children')):
for child in obj.winfo_children():
searchAndDestory(child)
obj.destroy()
elif isinstance(obj, list):
for child in obj:
searchAndDestory(child)
else:
obj.destroy()
if scrollbarNeeded():
if scrollbarFound():
print(entriesFrame.winfo_children().reverse())
for child in entriesFrame.winfo_children():
if isinstance(child, Canvas):
frm = child.winfo_children()[0]
searchAndDestory(frm.grid_slaves())
populateEntries(frm)
child.config(scrollregion=child.bbox(ALL))
else:
searchAndDestory(entriesFrame.winfo_children()[0])
populateEntries(makeScrollbarAndCanvas())
else:
searchAndDestory(entriesFrame.winfo_children())
innerFrame = Frame(entriesFrame)
populateEntries(innerFrame)
innerFrame.pack(fill=BOTH)
root = Tk()
root.resizable(False,False)
root.geometry("275x250")
outerFrame = Frame(root, padding = 10)
entries = IntVar()
entries.set(5)
topFrame = Frame(outerFrame, padding = 10)
spin = Spinbox(topFrame, from_=1, to=10, validate="key", validatecommand=(topFrame.register(validateSpin), "%P"))
spin.grid(column=0, row=0)
spin.set(entries.get())
spin.bind("<KeyRelease>", updateScale)
spin.bind("<ButtonRelease>", updateScale)
spin.bind("<MouseWheel>", updateScale)
scale = Scale(topFrame, from_=1, to=10)
scale.grid(column=1, row=0)
scale.set(entries.get())
scale.bind("<Motion>", updateSpin)
scale.bind("<ButtonRelease>", updateSpin)
topFrame.pack(side=TOP, fill=BOTH)
entriesFrame = Frame(outerFrame)
refreshEntries()
entriesFrame.pack(fill=BOTH)
outerFrame.pack(fill=BOTH)
root.mainloop()
The Scale and Spinbox work together fine and I've been able to make them control the number of fields. I've verified that with a constant number of entries, the scroll bar and Entry objects are completely operational. Additionally, the correct number of entries are being created and displayed, (you can verify by changing the root.geometry() term to "275x350").
I think it has something to do with configuring the scrollregion.
Thank you to those who have already helped!
Solution
Thanks to everyone that has helped! The original question has been edited a few times, so check out the edits for the original question. Here's the code that worked.
from tkinter import Canvas, Tk, LEFT, BOTH, RIGHT, VERTICAL, X, Y, ALL, IntVar, TOP
from tkinter.ttk import Frame, Scale, Entry, Scrollbar, Spinbox
def validateSpin(P):
if (P.isdigit() or P =="") and int(P) > 0 and int(P) < 11:
return True
else:
return False
def updateScale(event):
def inner():
spnVal = spin.get()
if spnVal != "":
scale.set(float(spnVal))
entries.set(float(spnVal))
refreshEntries()
root.after(1, inner)
def updateSpin(event):
def inner():
reportVal = round(float(scale.get()))
spin.set(reportVal)
entries.set(reportVal)
refreshEntries()
root.after(1, inner)
def scrollbarNeeded():
if entries.get() > 7:
return True
else:
return False
def makeScrollbarAndCanvas():
scrollingCanvas = Canvas(entriesFrame, width = 200, highlightthickness=0)
scrollingCanvas.pack(side=LEFT,fill=X,expand=1)
scrollbar = Scrollbar(entriesFrame, orient=VERTICAL,command=scrollingCanvas.yview)
scrollbar.pack(side=RIGHT,fill=Y)
scrollingCanvas.configure(yscrollcommand=scrollbar.set)
scrollingCanvas.bind("<Configure>",lambda e: scrollingCanvas.config(scrollregion=scrollingCanvas.bbox(ALL)))
innerFrame = Frame(scrollingCanvas)
scrollingCanvas.create_window((0,0),window= innerFrame, anchor="nw")
return innerFrame
def scrollbarFound():
for child in entriesFrame.pack_slaves():
if isinstance(child, Canvas):
return True
return False
def populateEntries(frm):
for i in range(entries.get()):
E = Entry(frm, width=15)
E.grid(column=0, row = i, pady=2)
def refreshEntries():
def searchAndDestory(obj):
if hasattr(obj, 'winfo_children') and callable(getattr(obj, 'winfo_children')):
for child in obj.winfo_children():
searchAndDestory(child)
obj.destroy()
elif isinstance(obj, list):
for child in obj:
searchAndDestory(child)
else:
obj.destroy()
if scrollbarNeeded():
if scrollbarFound():
for child in entriesFrame.winfo_children():
if isinstance(child, Canvas):
frm = child.winfo_children()[0]
searchAndDestory(frm.grid_slaves())
populateEntries(frm)
child.config(scrollregion=(0, 0, 96, entries.get() * 25))
else:
searchAndDestory(entriesFrame.winfo_children()[0])
populateEntries(makeScrollbarAndCanvas())
else:
searchAndDestory(entriesFrame.winfo_children())
innerFrame = Frame(entriesFrame)
populateEntries(innerFrame)
innerFrame.pack(fill=BOTH)
root = Tk()
root.resizable(False,False)
root.geometry("275x250")
outerFrame = Frame(root, padding = 10)
entries = IntVar()
entries.set(5)
topFrame = Frame(outerFrame, padding = 10)
spin = Spinbox(topFrame, from_=1, to=10, validate="key", validatecommand=(topFrame.register(validateSpin), "%P"))
spin.grid(column=0, row=0)
spin.set(entries.get())
spin.bind("<KeyRelease>", updateScale)
spin.bind("<ButtonRelease>", updateScale)
spin.bind("<MouseWheel>", updateScale)
scale = Scale(topFrame, from_=1, to=10)
scale.grid(column=1, row=0)
scale.set(entries.get())
scale.bind("<Motion>", updateSpin)
scale.bind("<ButtonRelease>", updateSpin)
topFrame.pack(side=TOP, fill=BOTH)
entriesFrame = Frame(outerFrame)
refreshEntries()
entriesFrame.pack(fill=BOTH)
outerFrame.pack(fill=BOTH)
root.mainloop()
Answered By - Max Friedman
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.