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)
Related
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.
I am attempting to create fixed-size canvas widget with scroll bars. The canvas in question could be quite large, and will almost definitely be much larger than the frame containing it. I would like to keep the canvas at its fixed size, but be able to resize the window containing it. The issue I am having is I don't know how to bind the scroll bars to the edge of the window.
I have tried both .pack and .grid. The obvious issue with .grid is that it will simply place the scroll bars next to the canvas. Unfortunately, the canvas must have a fixed size that will always be larger than the window. Whenever I .pack, the canvas appears to resize with the window, even when I explicitly disable expand and set fill to None.
I have made set the background to black for the purpose of clearly seeing the canvas area.
import tkinter as tk
from tkinter import *
class DialogueCreation(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.xbar = tk.Scrollbar(parent, orient=HORIZONTAL)
self.xbar.pack(side=BOTTOM, fill=X)
self.ybar = tk.Scrollbar(parent)
self.ybar.pack(side=RIGHT, fill=Y)
self.item_canvas = tk.Canvas(parent, width=5000, height=5000, xscrollcommand=self.xbar.set, yscrollcommand=self.ybar.set)
self.item_canvas.pack(side=LEFT, expand=FALSE, fill=None)
self.item_canvas.configure(background='black')
if __name__ == '__main__':
root = tk.Tk()
DialogueCreation(root)
root.title("Editor")
root.mainloop()
The canvas is a massive 5000x5000, so I should definitely be able to scroll when the window is small. I want the scrollbars to remain flush with the edges of the window, without resizing my canvas. The scrollbars remain dormant no matter how large or small the window is. I'm assuming the canvas is resizing with the window, which is definitely not the desired result.
Eventually this canvas will have several images displayed on it, and the location of those images must not change on the canvas. I do not believe the issue is with how I bound the scrollbars (I checked several other posts on this website to make sure), but it would not be uncharacteristic if I missed something obvious like that.
When you say you want to create a fixed size canvas, I'm assuming that you mean you want the drawable area to be a fixed size, rather than have a fixed size for the viewable portion of the canvas.
To do that, you need to set the scrollregion attribute to the drawable area. You use the width and height attributes to set the size of the visible portion of the canvas.
Also, hooking up scrollbars is a two way street: you've got to configure the canvas to update the scrollbars, and configure the scrollbars to scroll the canvas.
Note: you made DialogCreation a Frame, yet you put all of the widgets directly in the parent. That's very unusual, and not the best way to do it. I recommend inheriting from Frame like you do, but then all of the widgets should go in self rather than parent.
When you do it this way, you need to make sure you call pack on the instance of DialogCreation, eg:
dr = DialogueCreation(root)
dr.pack(fill="both", expand=True)
Using pack
With pack, you should set the expand option to True if you want the visible portion to grow or shrink when the user resizes the window.
My personal experience is that code is easier to understand and easier to maintain if you separate widget creation from widget layout. The following code shows how I would rewrite your code using pack. Notice the additional lines for configuring xbar and ybar, as well as setting the scrollregion.
class DialogueCreation(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.xbar = tk.Scrollbar(self, orient=HORIZONTAL)
self.ybar = tk.Scrollbar(self)
self.item_canvas = tk.Canvas(self, width=400, height=400,
xscrollcommand=self.xbar.set,
yscrollcommand=self.ybar.set)
self.xbar.configure(command=self.item_canvas.xview)
self.ybar.configure(command=self.item_canvas.yview)
self.item_canvas.configure(scrollregion=(0,0,4999,4999))
self.item_canvas.configure(background='black')
self.xbar.pack(side=BOTTOM, fill=X)
self.ybar.pack(side=RIGHT, fill=Y)
self.item_canvas.pack(side=LEFT, expand=TRUE, fill=BOTH)
Using grid
The obvious issue with .grid is that it will simply place the scroll bars next to the canvas.
I don't see that as obvious at all. grid has no such restriction. You can put the scrollbar anywhere you want.
The important thing to remember with grid is that rows and columns do not automatically grow or shrink when the window as a whole changes size. You have to explicitly tell tkinter which rows and columns to grow and shrink.
To achieve the same effect as with using pack, you need to configure row zero, column zero to be given all extra space. You do that by giving them a weight that is greater than zero.
To use grid instead of pack, replace the last three lines of the above example with the following six lines:
self.item_canvas.grid(row=0, column=0, sticky="nsew")
self.xbar.grid(row=1, column=0, sticky="ew")
self.ybar.grid(row=0, column=1, sticky="ns")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
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)
If I call text_area.pack() before scrollbar.pack() (i.e switch them), the scrollbar doesn't show. Why is that? If I am going to create a larger program, I would have absolutely no chance to find out where the problem is.
from tkinter import *
import tkinter.filedialog
root = Tk()
root.geometry("200x100")
frame = Frame(root,width=150, height=90)
frame.pack()
scrollbar = Scrollbar(frame)
text_area = Text(frame, width=200, height=50,yscrollcommand=scrollbar.set)
scrollbar.config(command=text_area.yview)
scrollbar.pack(side="right", fill="y")
text_area.pack()
root.mainloop()
The reason the scrollbar doesn't show is because there's simply no room for it. You're specifying a window size of 200x100 pixels and an inner frame size of 150x90 pixels, but you are trying to put a much larger text widget in that space. You're specifying a size of 200x50 characters (roughly 1400x750, depending on the fonts you're using) which is much too wide for the available space.
The way pack works is that it looks at the available space, puts the widget in that space, and subtracts the spaced needed for that widget from the space available for the next widget. Because you put the text widget first, and it requested more than the available space, it used up all of the available space. Then, when you call pack on the scrollbar, there's simply nowhere to put it.
When you reverse the order, the scrollbar takes up only a fraction of the available space, so there's room to fit the text widget in.
The best solution is to change the order in which you call pack. In general, it's best to call pack so that the last widget you call pack on is the "hero" -- the one that takes up all remaining space and grows or shrinks as the window grows and shrinks. Usually that's a text widget or a canvas widget, or a frame that itself contains many widgets.
The key to success with tkinter is to not try to force a tkinter widget or window to be a particular size in pixels (except, perhaps, for the canvas). Instead, either let a widget use it's default size (particularly with buttons and scrollbars), or pick a sensible default (number of rows and/or columns). Tkinter will then compute the right size to fit everything in based on the font, the screen resolution, and other factors.
The other 2 answers are really good - I thought I would add an example. Personally, I don't like to use .pack - I like to place things instead like this: self.set_label_logpage.place(x=175, y=100)
Example code:
faultlogframe_logs = tk.Label(self, textvariable=logging_screen_label, font=Roboto_Normal_Font,
height=180, width=400, background='white', foreground='black')
faultlogframe_logs.place(x=605, y=600)
self.scrollbar = Scrollbar(self, orient=VERTICAL, elementborderwidth=4, width=32)
self.scrollbar.pack(pady=60,padx=0, ipady=4, ipadx=4, side=RIGHT, fill=Y)
self.scrollbar_y = Scrollbar(self, orient=HORIZONTAL, width=12, takefocus=1)
self.scrollbar_y.pack(expand=TRUE, ipady=9, ipadx=9, padx=0, pady=0, side=BOTTOM, fill=X, anchor=S)
self.set_label_logpage = tk.Listbox(self, yscrollcommand=self.scrollbar.set, xscrollcommand=self.scrollbar_y.set)
self.set_label_logpage.config(font=Roboto_Normal_Font, background='white', foreground='black', height=16, width=55) #textvariable=self.label_to_display_log
self.set_label_logpage.place(x=175, y=100)
self.scrollbar_y.config(command=self.set_label_logpage.xview)
self.scrollbar.config(command=self.set_label_logpage.yview)
When you switch them, scrollbar.pack() simply is unaware that it needs to go top, instead it goes next place from top to bottom, and right. You can see that when you expand window size.
You can resolve the issue by replacing:
text_area.pack()
with:
text_area.pack(side="left")
When you want to design more complex geometry structures I'd suggest you use grid instead of pack.
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.