Python tkinter scrollbar doesn't attached well to listbox - python

SO, I am doing what everyone else says:
scrollbar = Scrollbar(frame)
scrollbar.pack(side=RIGHT, fill=Y)
listbox = Listbox(frame, yscrollcommand=scrollbar.set)
listbox.pack()
scrollbar.config(command=listbox.yview)
but it seems that the scroll bar is attached to the whole frame and not the listbox itself...
the only thing I am able to do - is use scrollbar.place() instead of pack
and place it next to the listbox, but them its only a 1 size scroll bar and it does not cover the listbox Y-bar only part of it, which is ugly...
can anyone help here?
thanks!

You do need to attach the scrollbar to the frame -- But you can easily create a new frame to hold only the scrollbar and the listbox.
e.g.
myframe=Frame(frame)
myframe.pack(side=RIGHT, fill=Y)
scrollbar = Scrollbar(myframe)
scrollbar.pack(side=RIGHT, fill=Y)
listbox = Listbox(myframe, yscrollcommand=scrollbar.set)
listbox.pack()
scrollbar.config(command=listbox.yview)

widgets don't attach to each other, they only occupy space inside a container. If you pack the scrollbar on the right, and then pack the listbox on the right, they will appear attached. You can also put the listbox on the left and have it fill in the horizontal direction, or you can pack it anywhere if it fills both directions.
You can also use grid instead of pack; just arrange for them to be in adjacent columns.
So, don't think of "attaching" widgets, think about placing them in containers.

Related

the scrollbar is not working in the window of tkinter please suggest me the solution of it

i am just trying to set the scrollbar for the canvas window which have many of the elements as checkbuttons of tkinter for the selection of multiple items at a time. i was just created it using canvas method create_window and create new window and put Frame into it. then for every element (for every Checkbutton) I am just creating new frame every time. in the window frame of canvas. and attach the canvas to the scrollbar but it does not work. so if anyone have the solution of it please let me know also.
Note: please do not change the width and the height of any frame and canvas and please do not use pack method of widget where i use place method for widgets. it will change my desired result which i am getting using this method the only problem is that the scrollbar is not working.
def Item_List(self):
list_heading=Label(self.root,text="what would you prefer to eat? ",font=("open sans condensed",13,"bold"),fg="aqua",bg="purple",padx=4)
list_heading.place(x=350,y=85)
Main_Frame=Frame(self.root,width=218,height=370)
canvas=Canvas(Main_Frame,highlightthickness=0)
canvas.place(x=0,y=0,width=218,height=366)
Main_Frame.place(x=351,y=115)
scroll = Scrollbar(self.root, orient=VERTICAL, command=canvas.yview)
scroll.place(x=552,y=115,height=370,width=15)
canvas.configure(yscrollcommand=scroll.set,highlightthickness=0)
canvas.bind('<Configure>',lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
Content_Frame=Frame(canvas)
canvas.create_window((0,0),window=Content_Frame,anchor=NW)
self.Items=self.Item_Des_Data.keys()
for item in range(1,101):
food=Frame(Content_Frame,width=200,height=30,bg="white")
foodItem = StringVar()
foodItem.set("0")
checkbox = Checkbutton(food, text=f"item{item}", variable=foodItem, onvalue=f"item{item}", border=2, bg="white")
checkbox.place(x=10,y=10)
food.pack()

Scrollbar for TreeView - Python

I have been trying to attach a vertical scrollbar for the tkinter treeview in Python.
For some reason it shows up under TreeView not on the right side.
screenshot
TreeView is in the frame
fFetchActivity = LabelFrame(root, text="Spis Twoich Aktywności", padx=50, pady=15)
fFetchActivity.place(x=20, y=200)
and TreeView and Scrollbar code:
tv1 = ttk.Treeview(fFetchActivity)
tv1.pack()
scrollbar_object = Scrollbar(fFetchActivity, orient="vertical")
scrollbar_object.pack(side=RIGHT, fill=Y)
scrollbar_object.config(command=tv1.yview)
tv1.configure(yscrollcommand=scrollbar_object.set)
Does anyone have any ideas on how to improve this?
Thanks!
The default for pack is to place the widget along the top of the unallocated space. So, when you do tv1.pack(), tv1 is placed at the top of the window. Another aspect of pack is that when a widget is placed along an edge, it is allocated the entire edge. For example, once a widget is placed along the top, it is not possible to put something to the right or left side because the widget is allocated the entire top edge.
If you want a widget to be to the right of another widget, you should call pack on that item first so that it is allocated the entire right side. You can then pack the other widget however you want in the remaining space.
In your case, this is typically how you would do it with pack to get the treeview to take up as much space as possible, and for the scrollbar to be on the right.
scrollbar_object.pack(side="right", fill="y")
tv1.pack(side='left', fill="both", expand=True)

tkinter - understand pack and grid - simple layout. (always confused...)

I'm trying to configure a text box and below to the text box 3 buttons in a row centered
I don't want to expand them to fill all the area. just be at the center to stay in their original size.
I was trying to do it with pack or grid. but I'm really get confused. I also trying to to put the text box and the buttons on different frame so maybe it will separate the widgets and let me configure it without messing up things (because everything is relative to the other..) ... but I came with nothing that looks good.
I also want to learn how to use the grid in the correct way if I have all kinds of widgets and buttons one below the other without "columnspan" or adjust the text length inside the buttons as well to match the widgets above them...
In this example. How I can center the buttons? I have to use side=tkinter.LEFT in order to put them one after one in a row. but the problem that they also stick to the left...
import tkinter
window = tkinter.Tk()
frame1 = tkinter.Frame(window).pack()
textbox1 = tkinter.Text(frame1, width=70, height=15).pack(side=tkinter.TOP)
button1 = tkinter.Button(frame1, text="button1").pack(side=tkinter.LEFT)
button2 = tkinter.Button(frame1, text="button2").pack(side=tkinter.LEFT)
button3 = tkinter.Button(frame1, text="button3").pack(side=tkinter.LEFT)
window.mainloop()
in this example if I set another frame to do separation between the widgets ...
It's not get to the center either....
import tkinter
window = tkinter.Tk()
frame1 = tkinter.Frame(window).pack(side=tkinter.TOP)
textbox1 = tkinter.Text(frame1, width=70, height=15).pack(side=tkinter.TOP)
frame2 = tkinter.Frame(window).pack(side=tkinter.TOP)
button1 = tkinter.Button(frame2, text="button1").pack(side=tkinter.LEFT)
button2 = tkinter.Button(frame2, text="button2").pack(side=tkinter.LEFT)
button3 = tkinter.Button(frame2, text="button3").pack(side=tkinter.LEFT)
window.mainloop()
And in this example. with grid, if I'm using different frames the button just jump on the text box and messed up everything....
import tkinter
window = tkinter.Tk()
frame0 = tkinter.Frame(window).grid(row=0, column=0)
frame1 = tkinter.Frame(window).grid(row=1, column=0)
textbox = tkinter.Text(frame0, width=70, height=15).grid(row=0, column=0)
button1 = tkinter.Button(frame1, text="button1").grid(row=0, column=0)
button2 = tkinter.Button(frame1, text="button2").grid(row=0, column=1)
button3 = tkinter.Button(frame1, text="button3").grid(row=0, column=2)
window.mainloop()
Can someone explain to me please in which way it's better to use and how to understand it better...?
it's always confusing me...
thanks in advance,
eliran
I was trying to do it with pack or grid. but I'm really get confused.
I also trying to to put the text box and the buttons on different
frame so maybe it will separate the widgets and let me configure it
without messing up things (because everything is relative to the
other..) ... but I came with nothing that looks good.
Your second example is fairly close to working, but it has a fatal flaw. If you add some debugging statements you'll see that frame1 and frame2 are None. Thus, any widgets with those as a parent actually end up in the root window.
This is because foo().bar() always returns the result of .bar(). In tkinter, .grid(...) always returns None, so Frame(...).grid(...) will always return None.
The best practice is to always separate widget creation from widget layout. For example:
frame1 = tkinter.Frame(window)
frame2 = tkinter.Frame(window)
frame1.pack(side="top")
frame2.pack(side="top")
With that, frame1 and frame2 are properly set to the frames. And when that happens, the rest of the code in your second example works as you expect and the buttons are centered.
And in this example. with grid, if I'm using different frames the button just jump on the text box and messed up everything....
That happens for the same reason as mentioned above: you think you're using separate frames, but everything is going in the root window. Because they are all in the root window, and you put the text widget and a button in the same row and column, they overlap.
I also want to learn how to use the grid in the correct way if I have
all kinds of widgets and buttons one below the other without
"columnspan" or adjust the text length inside the buttons as well to
match the widgets above them...
grid is not the right choice in this specific case, since you aren't actually creating a grid. You can use it, but it requires more code than using pack. grid is the right choice if you're creating an actual grid. In this case you aren't.
Using grid in this case requires a little creativity. While it's not the only solution, I would recommend that you divide the bottom frame into five columns - an empty column on the left and right, and three columns in the middle for the buttons. The empty columns can be used to take up all extra space, forcing the middle columns to all be centered.
A best practice for using grid is that every window that uses grid to manage its children needs at least one row and one column with a non-zero weight. That lets tkinter know where to allocate any extra space, such as when the user resizes the window.
Here's a complete solution using grid:
import tkinter
window = tkinter.Tk()
frame0 = tkinter.Frame(window)
frame1 = tkinter.Frame(window)
window.grid_rowconfigure(0, weight=1)
window.grid_columnconfigure(0, weight=1)
frame0.grid(row=0, column=0, sticky="nsew")
frame1.grid(row=1, column=0, sticky="nsew")
frame0.grid_rowconfigure(0, weight=1)
frame0.grid_columnconfigure(0, weight=1)
textbox = tkinter.Text(frame0, width=70, height=15)
textbox.grid(row=0, column=0, sticky="nsew")
button1 = tkinter.Button(frame1, text="button1")
button2 = tkinter.Button(frame1, text="button2")
button3 = tkinter.Button(frame1, text="button3")
frame1.grid_rowconfigure(0, weight=1)
frame1.grid_columnconfigure((0,4), weight=1)
button1.grid(row=0, column=1)
button2.grid(row=0, column=2)
button3.grid(row=0, column=3)
window.mainloop()
Can someone explain to me please in which way it's better to use and how to understand it better...?
In summary, your instinct to use separate frames is the right place to start. You should divide your UI into logical groups, and use separate frames for each group. Then, you are free to pick either grid or pack for each group separately. However, you need to be diligent with grid to make sure that the sticky option is used correctly, and that you've set weights for all of the right columns.
And finally, you have to start with the proper practice of separating widget creation from widget layout.
I have had these kinds of problems before. Even though the .pack() and .grid() systems are excellent, when things are getting hectic you can use the .place() system. .place() allows you to exactly pin-point your tkinter and ttk widgets using x-y axis coordinates.
The coordinates (0,0) are not at the center but at the topmost left corner of your tkinter window.
Eg:
some_widget_name = Button(root, text="Click me!"....)
some_widget_name.place(x=100, y=50)
This will make your widget move right 100 pixels and move down 50 pixels from the topmost left corner.
However, sometimes when you really want to make the location of the widgets precise, you may have to do some trial-and-error to make it visually pleasing.

How to keep a widget visible while resizing/shrinking the tkinter app window

I have this text box on the bottom of my app, and I need it to stay there no matter what (much like a sticky positioning in css). But as I resize the window, the textbox kinda gets hidden by the Frame on top(that consists of another textbox and a scrollbar)
top=Frame(self.root)
top.pack(side=TOP, fill=BOTH, expand=True)
text1=Text(top)
text1.pack(side=LEFT, fill=BOTH, expand=True)
scroll=Scrollbar(top)
scroll.pack(side=RIGHT, fill=Y)
scroll.config(command=text1.yview)
text1.config(yscrollcommand=scroll.set)
text1.config(state="disabled")
text2=Text(self.root, height=1)
text2.pack(side=BOTTOM, fill=BOTH, expand=False)
self.root.mainloop()
When you resize a window to make it smaller than the preferred size, tkinter has no choice but to start reducing the size of the interior widgets. Since you're using pack, pack will will start by reducing the size of the widget that was packed last. Once it disappears it will pick the next-to-last widget, and so on.
In your case, the bottom text widget is packed last, so it is the first one to be reduced. In your case you want the top text widget to be the one that grows and shrinks, so it should be the one you pack last.
Personally, I find the code much easier to read if you group widgets together that have the same parent or master, and separate layout commands from widget creation commands. It makes it much easier to visualize the relationships between widgets. In your case I would rewrite the code to look like the following.
Notice that I create all of the widgets that are directly in root first, and then all the widgets that are inside top second, and that I grouped the creation of the widgets together, and then grouped the layout of the widgets together. Also pay attention to the order that top and text2 are packed.
top=Frame(self.root)
text2=Text(self.root, height=1, background="pink")
text2.pack(side=BOTTOM, fill=BOTH, expand=False)
top.pack(side=TOP, fill=BOTH, expand=True)
text1=Text(top)
scroll=Scrollbar(top)
text1.config(yscrollcommand=scroll.set)
text1.config(state="disabled")
scroll.config(command=text1.yview)
text1.pack(side=LEFT, fill=BOTH, expand=True)
scroll.pack(side=RIGHT, fill=Y)
Note:
The order in which the widgets are managed is called the packing list. The normal way to change the packing list is to pack items in a different order, as in the above example. You can, however, explicitly request that items be placed in a different order. For example, you could continue to pack the top widget first, but when you pack the text2 you can use before=top to tell pack that you want the bottom text widget to be before the top widget in the packing list.
top.pack(side=TOP, fill=BOTH, expand=True)
text2.pack(side=BOTTOM, fill=BOTH, expand=False, before=top)

Resize Tkinter Listbox widget when window resizes

I'm new to Tkinter, and I've got a Listbox widget that I'd like to automatically-resize when changing the main window's size.
Essentially I would like to have a fluid height/width Listbox. If someone can point me to some documentation or provide a bit a code / insight, I'd appreciate it.
You want to read up on the geometry managers pack and grid, which lets you place widgets in a window and specify whether they grow and shrink or not. There's a third geometry manager, place, but it's not used very often.
Here's a simple example:
import tkinter as tk
root = tk.Tk()
scrollbar = tk.Scrollbar(root, orient="vertical")
lb = tk.Listbox(root, width=50, height=20, yscrollcommand=scrollbar.set)
scrollbar.config(command=lb.yview)
scrollbar.pack(side="right", fill="y")
lb.pack(side="left",fill="both", expand=True)
for i in range(0,100):
lb.insert("end", "item #%s" % i)
root.mainloop()
If you wish to use grid instead of pack, remove the two lines that call pack and replace them with these four lines:
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
scrollbar.grid(row=0, column=1, sticky="ns")
lb.grid(row=0, column=0, sticky="nsew")
Note that with grid you have to take the extra step to configure the weight for the row and column that contains the listbox, otherwise tkinter won't allocate any extra space to the widget.
The two main ways to allow a listbox to stretch when the window is resized are using the .pack() or .grid() methods.
SPECS:
Windows 7, Python 3.8.1, tkinter version: 8.6
.pack()
I found the easiest way to do this is by using the .pack() method, and utilizing the fill= & expand=True options.
import tkinter as tk
root=tk.Tk() #Creates the main window
listbox=tk.Listbox(root) #Create a listbox widget
listbox.pack(padx=10,pady=10,fill=tk.BOTH,expand=True) #fill=tk.BOTH, stretch vertically and horizontally
#fill=tk.Y, stretch vertically
#fill=tk.X, stretch horizontally
If your listbox is placed in a frame, the frame will also need to use the fill= & expand=True options.
import tkinter as tk
root=tk.Tk()
frame1=tk.Frame(root)
frame1.pack(fill=tk.BOTH, expand=True)
listbox=tk.Listbox(frame1)
listbox.pack(padx=10,pady=10,fill=tk.BOTH,expand=True)
.grid()
The alternative technique is to use the .grid() method and utilize thesticky= option. In addition, you will need to configure the row and column that the listbox resides in.
import tkinter as tk
root=tk.Tk() #create window
root.columnconfigure(0,weight=1) #confiugures column 0 to stretch with a scaler of 1.
root.rowconfigure(0,weight=1) #confiugures row 0 to stretch with a scaler of 1.
listbox=tk.Listbox(root)
listbox.grid(row=0,column=0,padx=5,pady=5,sticky='nsew')
The sticky option causes the listbox to stick to the "North" (Top), "South" (Bottom), "East" (Right), and "West" (Left) sides of the cell as it is stretched.
If your listbox is placed within a frame, you will need to configure the column and row that the frame is in, along with configure the column and row that the listbox is in.
import tkinter as tk
root=tk.Tk() #create window
root.columnconfigure(0,weight=1)
root.rowconfigure(0,weight=1)
frame1=tk.Frame(root)
frame1.grid(row=0,column=0,sticky='nsew')
frame1.columnconfigure(0,weight=1)
frame1.rowconfigure(0,weight=1)
listbox=tk.Listbox(frame1)
listbox.grid(row=0,column=0,padx=5,pady=5,sticky='nsew')
.pack() & .grid()
Now there is one other technique, but some people frown on it. The third technique is to utilize the .pack() method and .grid() method in the same script. You can mix different geometry management method in the same script as long as only a one management type is used per container. You can see an example of this below.
import tkinter as tk
root=tk.Tk() #create window
frame1=tk.Frame(root) #container: root
frame1.pack(fill=tk.BOTH,expand=True)
frame1.columnconfigure(0,weight=1)
frame1.rowconfigure(0,weight=1)
frame1.rowconfigure(1,weight=1)
listbox=tk.Listbox(frame1) #container: frame1
listbox.grid(row=0,rowspan=2,column=0,padx=5,pady=5,sticky='nsew')
btn1=tk.Button(frame1,text='Demo1') #container: frame1
btn1.grid(row=0,column=1, padx=5, pady=5)
btn2=tk.Button(frame1,text='Demo2') #container: frame1
btn2.grid(row=1,column=1, padx=5, pady=5)
frame2=tk.Frame(root) #container: root
frame2.pack()
btn3=tk.Button(frame2,text='Demo3') #container: frame2
btn3.grid(row=0,column=0)
You can see above that the frames used .pack() while the listbox and buttons used .grid(). This was possible because the frames resided within the root container, while the listbox and buttons resided within their respective frames.
To check you tkinter version use:
import tkinter as tk
print(tk.TkVersion)
If you would like to learn about the differences between fill and expand, please see the following link.
https://effbot.org/tkinterbook/pack.htm

Categories