Tkinter Scrollbar - python

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.

Related

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.

Tkinter: How to make a fixed canvas size with scroll bars that resize to the window

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)

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)

How do I achieve the following Tkinter GUI layout with either pack or grid?

Here's my current GUI layout for my checkers game:
As you can see, it consists of a Menu along the top, a Canvas on the left where I draw the checkerboard, a toolbar (Frame) on the top right where I have various formatting/navigation buttons, and a Text widget that is used to annotate moves. Currently, I am using a grid layout for the widgets.
Here's what I need to do:
Be able to show/hide a scrollbar in the Text widget when the amount of text grows larger than the widget size. (This seems to require the grid layout, according to this article.)
Change the font and/or size of the text in the Text widget [via a Preferences dialog] and not leave weird gaps around the Text widget. (This seems to require a pack layout because the Text widget can only be given a width and height in characters not pixels ... that means the Text widget grows or shrinks when I change the font or size, and the window won't adjust to fit with a grid layout. I've been trying to use Font.measure to adjust the Text widget size according to the font selected, but I still get gaps because I can't resize the widget down to the exact pixel.)
My final solution needs to be cross-platform (both Windows & Linux, and hopefully Mac).
Which layout can I use to meet my requirements? If neither will work completely, which layout (grid or pack) will get me closest to my goal? Thanks!
For this simple layout you could use grid, pack or both. Neither has a clear advantage in this particular case. Both have the resize behavior you desire.
Off the top of my head I would use a horizontal frame to hold the buttons, and pack the buttons in it. I would then probably use grid to place the toolbar, text widget and scrollbar inside a frame. Pack can be used too, either would work. That takes care of the right side.
If you want the menubar the way it is in the picture (ie: non-standard, only over the chessboard) I would use a similar technique: another frame for the left side with the menubar packed on the top, chessboard on the bottom.
i would then use pack in the main window, with statusbar on the bottom, the chessboard on the left, and then the text area on the right.
However, it's better to use a standard menubar which means you don't need a containing frame for the chessboard/menubar combination
Here's a quick hack at one solution using a standard menubar. This uses the technique of putting most widgets as children of the parent, then using the in_ parameter to put them in a container. This makes it much easier to change the layout later since you don't have to change a whole hierarchy, but only placement of widgets in containers.
import Tkinter as tk
import random
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
size = 40
menubar = tk.Menu(self)
menubar.add_cascade(label="Game")
menubar.add_cascade(label="Options")
menubar.add_cascade(label="Help")
chessboard = tk.Canvas(width=8*size, height=8*size, borderwidth = 0,
highlightthickness=0)
statusbar = tk.Label(self, borderwidth=1, relief="sunken")
right_panel = tk.Frame(self, borderwidth = 1, relief="sunken")
scrollbar = tk.Scrollbar(orient="vertical", borderwidth=1)
# N.B. height is irrelevant; it will be as high as it needs to be
text = tk.Text(background="white",width=40, height=1, borderwidth=0, yscrollcommand=scrollbar.set)
scrollbar.config(command=text.yview)
toolbar = tk.Frame(self)
for i in range(10):
b = tk.Button(self, text="B%s" % i, borderwidth=1)
b.pack(in_=toolbar, side="left")
self.config(menu=menubar)
statusbar.pack(side="bottom", fill="x")
chessboard.pack(side="left", fill="both", expand=False)
toolbar.grid(in_=right_panel, row=0, column=0, sticky="ew")
right_panel.pack(side="right", fill="both", expand=True)
text.grid(in_=right_panel, row=1, column=0, sticky="nsew")
scrollbar.grid(in_=right_panel, row=1, column=1, sticky="ns")
right_panel.grid_rowconfigure(1, weight=1)
right_panel.grid_columnconfigure(0, weight=1)
if __name__ == "__main__":
app = App()
app.mainloop()

Categories