Items display issue in canvas - python

i'm french so sorry for the translation and i'm beginner on python.
I have a items display problem in canvas. Refering to the following code, the script doesn't work properly if i want generate a high number of items into my canvas.
All items are not displayed properly
2)the script is freezing a little bit
I dont no why...Someone can help me please?
from tkinter import *
from tkinter import ttk
import tkinter as tk
class Main_frame(Frame):
# Init
def __init__(self, fenetre_principale=None):
tk.Frame.__init__(self, fenetre_principale)
self.grid(sticky = "news")
self.main_frame = tk.Frame(self, bg = "blue")
self.main_frame.grid(sticky = "news")
self.frame_canvas = tk.Frame(self.main_frame, bg = "yellow")
self.frame_canvas.grid(row = 2, column = 0, pady = 5, sticky="nw")
self.frame_canvas.grid_rowconfigure(0, weight=1)
self.frame_canvas.grid_columnconfigure(0, weight=1)
self.frame_canvas.grid_propagate(False)
self.canvas = tk.Canvas(self.frame_canvas, bg = "yellow")
self.canvas.grid(row = 0, column = 0, sticky='news')
self.vbar = tk.Scrollbar(self.frame_canvas, orient = "vertical", command = self.canvas.yview)
self.vbar.grid(row = 0, column = 1, sticky = "ns")
self.canvas.configure(yscrollcommand= self.vbar.set)
self.frame_buttons = tk.Frame(self.canvas, bg = "grey")
self.canvas.create_window((0, 0), window=self.frame_buttons, anchor="nw")
self.ajout()
def ajout(self):
self.bu_aj = tk.Button(self.main_frame, text = "Ajout", command = self.update_).grid(row = 0, column= 0, pady = 5, sticky="nw")
def update_(self):
for k in range(2000):
self.bu = tk.Button(self.frame_buttons, text = f"Test{k}")
self.bu.grid(row = k, column= 0, sticky="news")
self.frame_buttons.update_idletasks()
first5columns_width = sum([self.bu.winfo_width() for j in range(0, 15)])
first5rows_height = sum([self.bu.winfo_height() for i in range(0, 5)])
self.frame_canvas.config(width=first5columns_width + self.vbar.winfo_width(),height=first5rows_height)
self.canvas.config(scrollregion=self.canvas.bbox("all"))
print("ok")
if __name__ == "__main__":
root = tk.Tk()
root.grid_rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
root.title("Title")
interface = Main_frame(fenetre_principale=root)
interface.mainloop()

Related

Double Scrollbars in Tkinter Python

I cannot get x and y scrollbars to work in Tkinter with Python, although I have followed multiple examples:
How to add 2 scrollbars with tkinter in python 3.4
Adding a scrollbar to a group of widgets in tkinter
How to make a proper double scrollbar frame in tkinter
Vertical and Horizontal Scrollbars on Tkinter Widget
Scrolling a Canvas widget horizontally and vertically
The scrollbars appear, but do not activate when the window is smaller than the frame. How can I get this to work (see image below)?
Below is the minimal code that is producing my problem (Python 3.7)
import tkinter as tk
from tkinter import ttk
big_font = ("Arial", 50)
class DoubleScrollbarFrame(ttk.Frame):
def __init__(self,
parent,
*args,
**kwargs):
self.parent = parent
super().__init__(self.parent,
*args,
**kwargs)
#Set widgets to fill main window such that they are
#all the same size
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.create_widgets()
self.position_widgets()
def create_widgets(self):
self.canvas = tk.Canvas(self)
self.frame = ttk.Frame(self.canvas)
self.scroll_x = ttk.Scrollbar(self,
orient = tk.HORIZONTAL,
command = self.canvas.xview)
self.scroll_y = ttk.Scrollbar(self,
orient = tk.VERTICAL,
command = self.canvas.yview)
self.canvas.config(xscrollcommand = self.scroll_x.set,
yscrollcommand = self.scroll_y.set)
self.canvas.create_window((0,0),
window = self.frame,
anchor = 'nw')
self.frame.bind('<Configure>',
self.set_scrollregion)
self.sizegrip = ttk.Sizegrip(self)
#Test
self.test1 = tk.Label(self.frame,
text = 'Test 1',
font = big_font)
self.test2 = tk.Label(self.frame,
text = 'Test 2',
font = big_font)
self.test3 = tk.Label(self.frame,
text = 'Test 3',
font = big_font)
def position_widgets(self,
**kwargs):
self.test1.grid(row = 0,
column = 0,
sticky = 'w')
self.test2.grid(row = 1,
column = 0,
sticky = 'w')
self.test3.grid(row = 2,
column = 0,
sticky = 'w')
self.scroll_x.grid(row = 1,
column = 0,
sticky = 'ew')
self.scroll_y.grid(row = 0,
column = 1,
sticky = 'ns')
self.canvas.grid(row = 0,
column = 0,
sticky = 'nsew')
self.frame.grid(row = 0,
column = 0,
sticky = 'nsew')
self.sizegrip.grid(row = 1,
column = 1,
sticky = 'se')
def set_scrollregion(self,
event):
print('Frame Dimensions: {} x {}'.format(self.frame.winfo_width(),
self.frame.winfo_height()))
print('Canvas Dimensions: {} x {}'.format(self.canvas.winfo_width(),
self.canvas.winfo_height()))
self.canvas.configure(scrollregion = self.canvas.bbox('all'))
class MainApp(tk.Tk):
def __init__(self,
*args,
**kwargs):
super().__init__(*args,
**kwargs)
#Set widgets to fill main window such that they are
#all the same size
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.create_widgets()
self.position_widgets()
def create_widgets(self):
self.frame = DoubleScrollbarFrame(self)
def position_widgets(self):
self.frame.grid(row = 0,
column = 0,
sticky = 'nsew')
def exit(self):
self.destroy()
if __name__ == '__main__':
#Create GUI
root = MainApp()
#Run program
root.mainloop()
The problem is in these lines of code inside DoubleScrollbarFrame.position_widgets:
self.frame.grid(row = 0,
column = 0,
sticky = 'nsew')
This removes control of the widget from the canvas and gives control to grid. It is no longer a canvas object, so self.canvas.bbox("all") is returning (0, 0, 1, 1). If the scrollregion is set incorrectly, the scrollbars don't know how much to scroll.
The solution is simple: don't call grid on self.frame.

Centering widgets on frames

My widgets are not centered on the MainFrame which uses a grid layout. So I've decided to put them on 3 different frames and add them into the MainFrame. Still, they are not centered. How to make them be on the squares that I've drawn?
from Tkinter import *
class MainFrame:
def __init__(self, master):
self.frame = Frame(master)#, width = 300, height = 250)
self.frame.pack()
self.createFrames()
self.createCheckBoxes()
self.createButtons()
def createButtons(self):
self.printButton = Button(self.frame0, text = "Print msg", command = self.printMsg)
self.printButton.grid()
self.printButton2 = Button(self.frame0, text = "Print msg 2", command = self.printMsg)
self.printButton2.grid()
self.quitButton = Button(self.frame2, text = "QUIT", command = self.frame.quit, fg = "red")
self.quitButton.grid(columnspan = 10)
def createCheckBoxes(self):
self.cb1var = IntVar()
self.cb1 = Checkbutton(self.frame1, text = "Checkbox 1", variable = self.cb1var, command = self.printMsgCb)
self.cb1.grid()
def createFrames(self):
self.frame0 = Frame(self.frame)
self.frame0.grid(row = 0, column = 0)
self.frame0.grid_rowconfigure(0, weight=1)
self.frame0.grid_columnconfigure(0, weight=1)
self.frame1 = Frame(self.frame)
self.frame1.grid(row = 0, column = 10)
self.frame1.grid_rowconfigure(0, weight=1)
self.frame1.grid_columnconfigure(0, weight=1)
self.frame2 = Frame(self.frame)
self.frame2.grid(row = 0, column = 20)
self.frame2.grid_rowconfigure(0, weight=1)
self.frame2.grid_columnconfigure(0, weight=1)
#button commands ----------------------------------
def printMsg(self):
print "Clicou no botao"
def printMsgCb(self):
print "Check box value = " + str(self.cb1var.get())
#--------------------------------------------------
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
frame = MainFrame(root)
root.geometry('1100x600+200+150')
root.mainloop()
First of all, let's lose some of the redundant parts that do nothing for the current code. Remove:
self.frame0.grid_rowconfigure(0, weight=1)
self.frame0.grid_columnconfigure(0, weight=1)
...
self.frame1.grid_rowconfigure(0, weight=1)
self.frame1.grid_columnconfigure(0, weight=1)
...
self.frame2.grid_rowconfigure(0, weight=1)
self.frame2.grid_columnconfigure(0, weight=1)
Above lines are configuring the inner layout of the subframes(self.frame0, self.frame1, self.frame2) which needs no configuration currently.
Then replace:
self.frame1.grid(row = 0, column = 10)
...
self.frame2.grid(row = 0, column = 20)
with:
self.frame1.grid(row = 0, column = 1)
...
self.frame2.grid(row = 0, column = 2)
As you shouldn't be needing that large of a grid currently.
Then replace:
self.frame.pack()
with:
self.frame.pack(fill='x', expand=True)
to bring your self.frame to the vertical center while being expanded horizontally.
Finally uniformly distribute the subframes of self.frame horizontally by configuring the columns they're in, by adding:
self.frame.grid_columnconfigure(0, weight=1, uniform=True)
self.frame.grid_columnconfigure(1, weight=1, uniform=True)
self.frame.grid_columnconfigure(2, weight=1, uniform=True)
anywhere after self.frame is defined.
Entire code with the changes have been made:
from Tkinter import *
class MainFrame:
def __init__(self, master):
self.frame = Frame(master)#, width = 300, height = 250)
self.frame.grid_columnconfigure(0, weight=1, uniform=True)
self.frame.grid_columnconfigure(1, weight=1, uniform=True)
self.frame.grid_columnconfigure(2, weight=1, uniform=True)
self.frame.pack(fill='x', expand=True)
self.createFrames()
self.createCheckBoxes()
self.createButtons()
def createButtons(self):
self.printButton = Button(self.frame0, text = "Print msg", command = self.printMsg)
self.printButton.grid() # add sticky='nsew' optionally
self.printButton2 = Button(self.frame0, text = "Print msg 2", command = self.printMsg)
self.printButton2.grid()
self.quitButton = Button(self.frame2, text = "QUIT", command = self.frame.quit, fg = "red")
self.quitButton.grid(columnspan = 10)
def createCheckBoxes(self):
self.cb1var = IntVar()
self.cb1 = Checkbutton(self.frame1, text = "Checkbox 1", variable = self.cb1var, command = self.printMsgCb)
self.cb1.grid()
def createFrames(self):
self.frame0 = Frame(self.frame)
self.frame0.grid(row = 0, column = 0)
self.frame1 = Frame(self.frame)
self.frame1.grid(row = 0, column = 1)
self.frame2 = Frame(self.frame)
self.frame2.grid(row = 0, column = 2)
#button commands ----------------------------------
def printMsg(self):
print "Clicou no botao"
def printMsgCb(self):
print "Check box value = " + str(self.cb1var.get())
#--------------------------------------------------
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
frame = MainFrame(root)
root.geometry('1100x600+200+150')
root.mainloop()

Tkinter, canvas unable to scroll

I'm trying to make a Tkinter window class that contains a canvas with a scrollbar based on the tkinter Toplevel class. When I run my code I don't receive any errors but the scrollbar in the window is disabled. The Frame or canvas that has the information wont stretch with the window when I stretch it manually after the program is running. Here is the bugged code:
class new_window(Toplevel):
def __init__(self,master):
Toplevel.__init__(self,master)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
s = Scrollbar(self, orient = VERTICAL)
s.grid(row = 0, column = 1, sticky = NS)
self.can = Canvas(self, yscrollcommand=s.set)
self.can.grid(row = 0, column = 0, sticky = N+S+E+W)
self.win = Frame(self.can)
self.can.create_window(0,0, window = self.win, anchor = NW)
s.config(command = self.can.yview)
size = (self.win.winfo_reqwidth(), self.win.winfo_reqheight())
self.can.config(scrollregion="0 0 %s %s" % size)
self.win.update_idletasks()
self.ca.configure(scrollregion = (1,1,win.winfo_width(),win.winfo_height()))
def create(self):
for i in range (100):
i = Label(self.win, text = str(i))
i.grid()
root = Tk()
win = new_window(root)
win.create()
root.mainloop()
It was working fine before I decided to implement classes:
from Tkinter import *
root = Tk()
window = Toplevel()
window.grid_rowconfigure(0, weight=1)
window.grid_columnconfigure(0, weight=1)
s = Scrollbar(window, orient = VERTICAL)
s.grid(row = 6, column = 1, sticky = NS)
can = Canvas(window, width = 1600, height = 700, yscrollcommand=s.set)
can.grid(row = 6, column = 0, sticky = NSEW)
win = Frame(can)
can.create_window(0,0, window = win, anchor = NW)
s.config(command = can.yview)
for i in range(100):
lbl = Label(win, text = str(i))
lbl.grid()
win.update_idletasks()
can.configure(scrollregion = (1,1,win.winfo_width(),win.winfo_height()))
root.mainloop()
Im not sure where I went wrong in the transition, any help would be greatly appreciated.
I think the issue is coming from here:
self.win.update_idletasks()
self.ca.configure(scrollregion = (1,1,win.winfo_width(),win.winfo_height()))
This is inside the initialization function, when it should be updating after the create function is called. There's still probably a more efficient way to structure this, but this should work in the meantime:
from Tkinter import *
class new_window(Toplevel):
def __init__(self,master):
Toplevel.__init__(self,master)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
s = Scrollbar(self, orient = VERTICAL)
s.grid(row = 0, column = 1, sticky = NS)
self.can = Canvas(self, yscrollcommand=s.set)
self.can.grid(row = 0, column = 0, sticky = N+S+E+W)
self.win = Frame(self.can)
self.can.create_window(0,0, window = self.win, anchor = NW)
s.config(command = self.can.yview)
size = (self.win.winfo_reqwidth(), self.win.winfo_reqheight())
self.can.config(scrollregion="0 0 %s %s" % size)
def create(self):
for i in range (100):
i = Label(self.win, text = str(i))
i.grid()
self.win.update_idletasks()
self.can.configure(scrollregion = (1,1,self.win.winfo_width(),self.win.winfo_height()))
root = Tk()
win = new_window(root)
win.create()
root.mainloop()

Tkinter - Text widget shrinks when Scrollbar is added

I'm working on the GUI for a simple quiz app using Tkinter in Python 2.7.
Thus far, I have begun to set up my frame. I've put a scrollbar inside of a Text widget named results_txtbx to scroll up and down a list noting the player's performance on each question. I've been using grid since it's easier for me to manage.
from Tkinter import *
class Q_and_A:
def __init__(self, master):
frame = Frame(master)
Label(master).grid(row = 4)
results_txtbx = Text(master)
results_scrbr = Scrollbar(results_txtbx)
results_scrbr.grid(sticky = NS + E)
results_txtbx.config(width = 20, height = 4, wrap = NONE, yscrollcommand = results_scrbr.set)
results_txtbx.grid(row = 3, column = 1, padx = 12, sticky = W)
root = Tk()
root.wm_title("Question and Answer")
root.resizable(0, 0)
app = Q_and_A(root)
root.mainloop()
What happens is that when it runs, results_txtbx resizes to fit the scrollbar. Is there any way to make it keep its original size using grid?
You don't want to use a text widget as the master for a scrollbar. Like any other widget, if you pack or grid the scrollbar in the text widget, the text widget will shrink or expand to fit the scrollbar. That is the crux of your problem.
Instead, create a separate frame (which you're already doing), and use that frame as the parent for both the text widget and the scrollbars. If you want the appearance that the scrollbars are inside, set the borderwidth of the text widget to zero, and then give the containing frame a small border.
As a final usability hint, I recommend not making the window non-resizable. Your users probably know better what size of window they want than you do. Don't take that control away from your users.
Here's (roughly) how I would implement your code:
I would use import Tkinter as tk rather than from Tkinter import * since global imports are generally a bad idea.
I would make Q_and_A a subclass of tk.Frame so that it can be treated as a widget.
I would make the whole window resizable
I would separate widget creation from widget layout, so all my layout options are in one place. This makes it easier to write and maintain, IMO.
As mentioned in my answer, I would put the text and scrollbar widgets inside a frame
Here's the final result:
import Tkinter as tk
class Q_and_A(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, borderwidth=1, relief="sunken")
self.label = tk.Label(self)
self.results_txtbx = tk.Text(self, width=20, height=4, wrap="none",
borderwidth=0, highlightthickness=0)
self.results_scrbr = tk.Scrollbar(self, orient="vertical",
command=self.results_txtbx.yview)
self.results_txtbx.configure(yscrollcommand=self.results_scrbr.set)
self.label.grid(row=1, columnspan=2)
self.results_scrbr.grid(row=0, column=1, sticky="ns")
self.results_txtbx.grid(row=0, column=0, sticky="nsew")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
root = tk.Tk()
root.wm_title("Question And Answer")
app = Q_and_A(root)
app.pack(side="top", fill="both", expand=True)
root.mainloop()
Set results_scrbr.grid(row = 3, column = 2) next to results_txtbx.grid(row = 3,column = 1, padx = 4), sticky is not needed because window is not resizable, and i lowered the padx so scrollbar is closer to text.
Also to make the results_txtbx vertically scrollable, add results_scrbr.config(command=results_txtbx.yview)
Here is a working code...
from Tkinter import *
class Q_and_A:
def __init__(self, master):
frame = Frame(master)
Label(master).grid(row = 4)
results_txtbx = Text(master)
results_scrbr = Scrollbar(master)
results_scrbr.grid(row = 3, column = 2)
results_scrbr.config(command=results_txtbx.yview)
results_txtbx.config(width = 20, height = 4,
wrap = NONE, yscrollcommand = results_scrbr.set)
results_txtbx.grid(row = 3, column = 1, padx = 4)
root = Tk()
root.wm_title("Question and Answer")
root.resizable(0, 0)
app = Q_and_A(root)
root.mainloop()
My implemented solution:
I needed to add more widgets to the app, so I bound the Scrollbar and Text widgets to another label and put that in the proper column the code (trimmed for readability) is below:
import Tkinter as tk
class Q_and_A(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.label = tk.Label(self)
#Set up menu strip
self.main_menu = tk.Menu(self)
self.file_menu = tk.Menu(self.main_menu, tearoff = 0)
self.file_menu.add_command(label = "Exit", command = self.quit)
self.main_menu.add_cascade(label = "File", menu = self.file_menu)
self.master.config(menu = self.main_menu)
#Set up labels
self.question_lbl = tk.Label(self, text = "Question #: ", padx = 12, pady = 6)
self.question_lbl.grid(row = 0, sticky = "w")
tk.Label(self, text = "Hint: ").grid(row = 1, sticky = "w", padx = 12, pady = 6)
tk.Label(self, text = "Answer: ").grid(row = 2, sticky = "w", padx = 12, pady = 6)
tk.Label(self, text = "Results: ").grid(row = 3, sticky = "nw", padx = 12, pady = 6)
tk.Label(self).grid(row = 4)
#Set up textboxes
self.question_txtbx = tk.Entry(self)
self.question_txtbx.config(width = 60)
self.question_txtbx.grid(row = 0, column = 1, padx = 12, columnspan = 3, sticky = "w")
self.help_txtbx = tk.Entry(self)
self.help_txtbx.config(width = 40)
self.help_txtbx.grid(row = 1, column = 1, columnspan = 2, padx = 12, sticky = "w")
self.answer_txtbx = tk.Entry(self)
self.answer_txtbx.config(width = 40)
self.answer_txtbx.grid(row = 2, column = 1, columnspan = 2, padx = 12, sticky = "w")
self.results_label = tk.Label(self)
self.results_txtbx = tk.Text(self.results_label, width = 10, height = 4, wrap = "none", borderwidth = 1, highlightthickness = 1)
self.results_scrbr = tk.Scrollbar(self.results_label, orient = "vertical", command = self.results_txtbx.yview)
self.results_txtbx.configure(yscrollcommand = self.results_scrbr.set)
self.label.grid(row = 1)
self.results_label.grid(row = 3, column = 1, padx = 11, sticky = "w")
self.results_scrbr.grid(row = 0, column = 1, sticky = "nse")
self.results_txtbx.grid(row = 0, column = 0, sticky = "w")
root = tk.Tk()
root.wm_title("Question and Answer")
#A note: The window is non-resizable due to project specifications.
root.resizable(0, 0)
app = Q_and_A(root)
app.pack(side = "top", fill = "both")
root.mainloop()
I'll keep storage in nested labels as a reference for myself for when I need to group things close together, unless there's some reason it should be avoided. Worked very well here. Thanks to Bryan for the advice.

Tkinter: Getting an image above buttons in grid layout

i'm new to tkinter and I was trying to make a GUI where there was an image at the top with an area of 4 buttons underneath that image which would be a method of selecting answers. However with the code I have so far the buttons that I create just seem to stay in the top left corner and will not move under the image at all, does anybody know a solution to this please?
import Tkinter as tk
from Tkinter import *
from Tkinter import PhotoImage
root = Tk()
class Class1(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.grid()
self.master = master
self.question1_UI()
def question1_UI(self):
self.master.title("GUI")
gif1 = PhotoImage(file = 'Image.gif')
label1 = Label(image=gif1)
label1.image = gif1
label1.grid(row=1, column = 0, columnspan = 2, sticky=NW)
questionAButton = Button(self, text='Submit',font=('MS', 8,'bold'))
questionAButton.grid(row = 2, column = 1, sticky = S)
questionBButton = Button(self, text='Submit',font=('MS', 8,'bold'))
questionBButton.grid(row = 2, column = 2, sticky = S)
questionCButton = Button(self, text='Submit',font=('MS', 8,'bold'))
questionCButton.grid(row = 3, column = 3, sticky = S)
questionDButton = Button(self, text='Submit',font=('MS', 8,'bold'))
questionDButton.grid(row = 3, column = 4, sticky = S)
def main():
ex = Class1(root)
root.geometry("{0}x{1}+0+0".format(root.winfo_screenwidth(),
root.winfo_screenheight()))
root.mainloop()
if __name__ == '__main__':
main()
You are not using self as the parent of label1. Besides, the grid manager starts at row 0:
def question1_UI(self):
# ...
label1 = Label(self, image=gif1)
label1.image = gif1
label1.grid(row = 0, column = 0, columnspan = 2, sticky=NW)
questionAButton = Button(self, text='Submit',font=('MS', 8,'bold'))
questionAButton.grid(row = 1, column = 0, sticky = S)
questionBButton = Button(self, text='Submit',font=('MS', 8,'bold'))
questionBButton.grid(row = 1, column = 1, sticky = S)
questionCButton = Button(self, text='Submit',font=('MS', 8,'bold'))
questionCButton.grid(row = 2, column = 0, sticky = S)
questionDButton = Button(self, text='Submit',font=('MS', 8,'bold'))
questionDButton.grid(row = 2, column = 1, sticky = S)

Categories

Resources