I'm currently trying to implement a scrollbar on a canvas, since I learned that I can't do it instantly on a frame. I can make it appear, but I cannot actually make it work. I'm still a beginner when it comes to python and tkinter and the previous posts in this matter haven't helped me that much. Here's my code(I'm open to advice on anything else I've done that's considered bad practice as well):
from tkinter import *
class myApp():
def __init__(self,root):
myApp.f2=Frame(root)
myApp.f2.pack()
myApp.canv=Canvas(self.f2)
myApp.canv.pack()
myApp.f1=Frame(self.canv)
myApp.f1.pack(side=LEFT, fill=BOTH, expand=TRUE)
myApp.scroll=Scrollbar(self.f1,orient=VERTICAL,
command=myApp.canv.yview)
myApp.scroll.grid(row=0,column=6)
myApp.canv.config(yscrollcommand=myApp.scroll.set)
I have to use grid for the rest of the widgets, that I haven't included here.
i don't really understand how the bind works, but here's the code i use for a scrollbar in a Toplevel, it's not from me but i don't remember where i found it (i think it's on stackoverflow, you should search more, i'm sure you will find something). it should work but you can scroll the bar only if you hover it with the mouse though
Toplevel = tk.Toplevel(self)
#create canvas to make a scrollbar
canvas = tk.Canvas(Toplevel, borderwidth=0)
#create frame which will contains your widgets
frame = tk.Frame(canvas)
#create and pack your vsb to the Toplevel and link it to the canvas yview
vsb = tk.Scrollbar(Toplevel, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=vsb.set)
vsb.pack(side="right", fill="y")
canvas.pack(side="left", fill="both", expand=True)
canvas.create_window((5,5), window=frame, anchor="nw")
#i don't understand this line
frame.bind("<Configure>", lambda event, canvas=canvas: canvas.configure(scrollregion=canvas.bbox("all")))
#add your widgets to the frame
...
PS : Don't use from tkinter import * you can(and will) have name collisions, use import tkinter or import tkinter as tk
Edit : this question is my source.
Related
I have been trying to find a way to size a frame inside of a canvas window for quite a while to no avail. I finally came across some posts that helped me begin to understand the problem, and eventually dug up a post that gave the solution below:
import tkinter as tk
def onCanvasConfigure(e):
canvas.itemconfig('frame', height=canvas.winfo_height(), width=canvas.winfo_width())
root=tk.Tk()
canvas = tk.Canvas(root, background="blue")
frame = tk.Frame(canvas, background="red")
canvas.pack(expand=True, fill="both")
canvas.create_window((0,0), window=frame, anchor="nw", tags="frame")
canvas.bind("<Configure>", onCanvasConfigure)
root.mainloop()
This completely solves my problem....if I don't have the GUI in a function, which I need to. I have multiple different GUI's that would need to implement this solution. I have come across other solutions that use OOP, but I haven't yet wrapped my head around OOP. I've also found a way to make the above code work inside of a program myself:
import tkinter as tk
def onCanvasConfigure(e):
canvas.itemconfig('frame', height=canvas.winfo_height(), width=canvas.winfo_width())
def test():
window=tk.Tk()
global canvas
canvas = tk.Canvas(master=window)
frame=tk.Frame(master=canvas, background='red')
canvas.pack(expand=True, fill=tk.BOTH)
canvas.create_window((0,0), window=frame, anchor=tk.NW, tags = 'frame')
canvas.bind("<Configure>", onCanvasConfigure)
root.mainloop()
test()
However, this requires the use of a global variable, which I would rather avoid. Is there anything I'm missing that would help me resize the frame inside of the canvas window? If you have any pointers to where I might even find this information that would also be helpful.
The event object that is passed in has a reference to the widget that received the event. So, just replace your global canvas with e.widget, or initialize a local variable named canvas:
def onCanvasConfigure(e):
canvas = e.widget
canvas.itemconfig('frame', height=canvas.winfo_height(), width=canvas.winfo_width())
If it helps, here's an object-oriented version of your application code. Other than the implementation differences, it should behave the same way as the functional version.
import tkinter as tk
class App(tk.Tk): # create a base app class that inherits from Tk
def __init__(self):
super().__init__() # initialize Tk
self.canvas = tk.Canvas(master=self)
self.frame = tk.Frame(master=self.canvas, background='red')
self.canvas.pack(expand=True, fill=tk.BOTH)
self.canvas.create_window(
(0,0),
window=self.frame,
anchor=tk.NW,
tags='frame',
)
self.canvas.bind('<Configure>', self.on_canvas_configure)
def on_canvas_configure(self, event):
self.canvas.itemconfig(
'frame',
height=self.canvas.winfo_height(),
width=self.canvas.winfo_width(),
)
if __name__ == '__main__':
root = App() # instantiate the App class
root.mainloop() # start the app
Since everything here is contained within the App class, you can avoid globals (thanks to self!)
I am trying to capture a keypress after a canvas object is displayed in order to delete the latter, and it's not working. The canvas is displayed properly, but the keypress event is not getting captured. I am a total newbie at Python and this is just a test-code to check my understanding of Tkinter so far. I'm sure it's something fairly basic that I'm missing, so thank you for your patience.
from tkinter import *
class Main:
def __init__(self,master):
# create splash screen
splash = Canvas(master, bg='white')
splash.bind("<Key>",self.splash_key)
splash.pack(fill="both", expand=True)
def splash_key(event):
print('key captured!')
splash.delete("all")
root = Tk()
root.wm_title('Test')
root.attributes('-zoomed',True)
app = Main(root)
root.mainloop()
I know you can align a button to the left with pack(side="left"), and you can also do right top and bottom, but how would i align stuff on a new line just below a set of buttons above it?
I tried using grid but no matter how much i googled it I couldn't find out how to fix it.
What you'd want to do is create different frames, each with their own positioning logic. Take a look at this:
import tkinter
root = tkinter.Tk()
frame = tkinter.Frame(root)
frame.pack()
bottomframe = tkinter.Frame(root)
bottomframe.pack(side=tkinter.BOTTOM)
tkinter.Button(frame, text="left").pack(side=tkinter.LEFT)
tkinter.Button(frame, text="middle").pack(side=tkinter.LEFT)
tkinter.Button(frame, text="right").pack(side=tkinter.LEFT)
# what you want
tkinter.Button(bottomframe, text="top").pack(side=tkinter.TOP)
tkinter.Button(bottomframe, text="under").pack(side=tkinter.TOP)
root.mainloop()
I'm in the beginning stages of building a program where I can take items from a TreeView and move them into a listbox. I have added scrollbars to both the Treeview and listbox so that as they get longer, I can scroll to see the contents. Currently, I have populated the Treeview with more items than can fit in the window. I have the ability to scroll the Treeview with the mouse wheel, but when I click and drag the box on the scrollbar, the scrollbar will not move. I'm using Python 3.7. I could use some help figuring out what's going on.
import tkinter as tk
from tkinter import ttk
import l5x
root = tk.Tk()
root.geometry("600x400")
root.resizable(False, False)
root.title("TreeView Example")
tree_frame = ttk.Frame(root)
list_frame = ttk.Frame(root)
tree = ttk.Treeview(tree_frame)
tree_scroll = ttk.Scrollbar(tree_frame,orient="vertical",command=tree.yview())
tree.configure(yscrollcommand=tree_scroll.set)
listbox = tk.Listbox(tree_frame)
list_scroll = ttk.Scrollbar(tree_frame,orient="vertical",command=listbox.yview())
listbox.configure(yscrollcommand=list_scroll.set)
tree_frame.columnconfigure(0, weight=1)
tree_frame.columnconfigure(1, weight=1)
tree_frame.columnconfigure(2, weight=1)
tree_frame.columnconfigure(3, weight=1)
for i in range(0,20):
tree.insert('','end',i,text="blah")
tree_frame.grid(row=0,column=0,sticky="NSEW")
tree.grid(row=0,column=0,sticky="NSEW")
tree_scroll.grid(row=0,column=1,sticky="NS")
listbox.grid(row=0,column=2,sticky="NSEW")
list_scroll.grid(row=0,column=3,sticky="NS")
root.mainloop()
The command attribute requires you pass a reference to a callable function. Instead, you're calling the yview() method and giving the results to the command attribute.
Your scrollbar definition needs to look like this:
tree_scroll = ttk.Scrollbar(tree_frame,orient="vertical",command=tree.yview)
list_scroll = ttk.Scrollbar(tree_frame,orient="vertical",command=listbox.yview)
I use the same format of frame but it doesn't show in the interface, hope someone could tell me the solution, thanks.
class Interface(Frame):
def __init__(self,parent=None):
Frame.__init__(self,parent)
self.master.title("measurement")
self.grid()
# fix the size and parameters of widget
self.master.geometry("700x400+100+50")
self.master.Frame1 = Frame(self,relief=GROOVE,bg='white')
self.master.Frame1.grid(column=1,row=9)
self.can =Canvas(self, bg="ivory", width =200, height =150)
self.master.canvas = Canvas(self.master, width=150, height=120, background='snow')
ligne1=self.master.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
window = Tk()
window.resizable(False, False)
Interface(window).mainloop()
I can't figure out why you have 2 Canvas's, but the problem is that you aren't placing them on their respective parents. I cut out a lot of the code that seemed unnecessary and restructured your code to make it more logical:
class Interface(Frame):
def __init__(self, parent):
self.parent = parent
super().__init__(self.parent)
self.Frame1 = Frame(self, relief=GROOVE)
self.Frame1.grid()
self.canvas = Canvas(self.Frame1, bg="ivory", width=200, height=150)
self.canvas.grid()
self.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
root = Tk()
# Tk configurations are not relevant to
# the Interface and should be done out here
root.title('Measurement')
root.geometry('700x400+100+50')
root.resizable(False, False)
Interface(root).pack()
root.mainloop()
i think I don't really understand your problem, you don't see your frame because you don't have any widget in it, that's all
import tkinter as tk
class Interface(tk.Frame):
def __init__(self,parent=None):
tk.Frame.__init__(self,parent)
self.master.title("measurement")
self.grid(row=0, column=0)
# fix the size and parameters of widget
self.master.geometry("700x400+100+50")
self.master.Frame1 = tk.Frame(self,relief='groove',bg='white')
self.master.Frame1.grid(column=1,row=9)
labelExemple =tk.Label(self.master.Frame1, text="Exemple")
labelExemple.grid(row=0,column=0)
self.can = tk.Canvas(self, bg="ivory", width =200, height =150)
self.master.canvas = tk.Canvas(self.master, width=150, height=120, background='snow')
self.ligne1=self.master.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
window = tk.Tk()
window.resizable(False, False)
Interface(window).mainloop()
PS : use import tkinter as tk instead of from tkinter import *
There are several problems with those few lines of code, almost all having to do with the way you're using grid:
you aren't using the sticky option, so widgets won't expand to fill the space they are given
you aren't setting the weight for any rows or columns, so tkinter doesn't know how to allocate unused space
you aren't using grid or pack to put the canvases inside of frames, so the frames stay their default size of 1x1
The biggest problem is that you're trying to solve all of those problems at once. Layout problems are usually pretty simple to solve as long as you're only trying to solve one problem at a time.
Start by removing all of the widgets from Interface. Then, give that frame a distinctive background color and then try to make it fill the window (assuming that's ultimately what you want it to do). Also, remove the root.resizable(False, False). It's rarely something a user would want (they like to be able to control their windows), plus it makes your job of debugging layout problems harder.
Once you get your instance of Interface to appear, add a single widget and make sure it appears too. Then add the next, and the next, adding one widget at a time and observing how it behaves.