On python tkinter, I am using 2 different frames on a Toplevel window, one on the right and another on the left.
The frame which is on right side is not scrollable. I have created a canvas on top of the frame on that frame and one more frame on top of that canvas. I have made that canvas scrollable and pasted the widgets on that canvas but it's not scrollable. I am attaching the code of the scrollable part.
w1 = Canvas(frame2, width=600, height=300,background="white", scrollregion=(1500,1500,3000,3000))
scr_h1 = ttk.Scrollbar(frame2,orient=HORIZONTAL)
scr_h1.pack(side=BOTTOM,fill=X)
scr_h1.config(command=w1.xview)
scr_v1 = ttk.Scrollbar(frame2,orient=VERTICAL)
scr_v1.pack(side=RIGHT,fill=Y)
scr_v1.config(command=w1.yview)
w1.config(xscrollcommand=scr_h1.set,yscrollcommand=scr_v1.set)
w1.pack(fill=BOTH,expand=True)
This code works for me running Python 3.4 - a tkinter window pops up with a red oval (for testing), and the scrollbar allows you to navigate the frame. If you are using Python 2, change tkinter to Tkinter (capital T).
from tkinter import *
root = Tk()
frame2 = Frame(root)
frame2.pack(side=RIGHT)
w1 = Canvas(frame2, width=600, height=300,background="white", scrollregion=(0,0,3000,3000))
scr_h1 = Scrollbar(frame2,orient=HORIZONTAL)
scr_h1.pack(side=BOTTOM,fill=X)
scr_h1.config(command=w1.xview)
scr_v1 = Scrollbar(frame2,orient=VERTICAL)
scr_v1.pack(side=RIGHT,fill=Y)
scr_v1.config(command=w1.yview)
w1.config(xscrollcommand=scr_h1.set,yscrollcommand=scr_v1.set)
w1.pack(fill=BOTH,expand=True)
# inserted to see if it's actually scrolling
w1.create_oval(0,0,50,50,fill='red')
root.mainloop()
Two Possible Issues
Why were you using a ttk ScrollBar? The simple tkinter scroll bar will suffice for your code. When things aren't working, it might help to go back to the simpler model.
Why your starting scroll region was 1500 - any object placed on the canvas in the first 1500 units in either direction were not visible, with this setting, which may have given you the illusion that the scrollbar was not working. See http://effbot.org/zone/tkinter-scrollbar-patterns.htm for more information on using scroll bars.
It should not matter that there are two frames or their orientation, though you may run into problems if you try to mix managers (grid,pack,etc.). These problems are more along the lines of stalled programs, not stationary scrollbars.
Related
I am working on a tkinter project, where I set the background image (not a solid color). I was using .grid() for my widgets, but then I realized I need to display a widget at the very bottom of the tkinter window. To continue using grid for this, I separated my widgets into two frames; 1 frame that contained most of the widgets and one that just contained the widget I want to be displayed at the bottom. Then, I packed my frames, one on the top and the other at the bottom.
However, after I did this, my background image went on top of my existing widgets and blocked them out, so they can't be seen anymore. What can I do to fix this?
Here is the method I used to display the background photo:
from tkinter import *
filename = PhotoImage(file="image.png")
background_label = Label(root, image=filename) # root is the Tk() object
background_label.place(x=0, y=0, relwidth=1, relheight=1)
# Other code here
mainloop()
I have a fairly large size GUI built in tkinter using the grid manager method. I need to add a scrollbar to the entire window and Im having some trouble. Here is a very crude version of a scroll-bar using the grid manager in Tkinter (i dont want to scroll the list box, i want to scroll the entire Tk window).
import Tkinter
from Tkinter import *
Tk = Tkinter.Tk
self=Tk()
listbox = Listbox(self, width = 10, height = 60)
listbox.grid(row =0, column=0)
scrollbar = Scrollbar(self)
scrollbar.grid(sticky=E, row = 0, rowspan = 100, column = 11, ipady = 1000)
mainloop()
Is it possible to fix the Tkinter window size (using grid manager) and add a scrollbar which then allows the user to view additional content? The window is too large and additional content needs to be viewed so the only option i see is a scrollbar. I only see examples using the pack method. As you can probably guess I am new to Tkinter and would appreciate any input.
Thanks to all in advance.
You cannot scroll the entire contents of a root window, a Toplevel window, or a Frame. The solution is to put all of your widgets in a canvas, and then add a scrollbar to the canvas. There are questions on this site that give examples, such as Python Tkinter scrollbar for frame
I just came across a strange behavior of Tkinter when debugging my program. If a Frame object is created before a Canvas object and later inserted into that Canvas, it can't be displayed. However if the creation order is inverted (firstly Canvas and then Frame), contents in the Frame is displayed correctly.
For example, the following code works well:
from Tkinter import *
app = Frame()
canvas = Canvas(app)
frame = Frame(app)
Label(frame, text = 'aaaa').pack()
Label(frame, text = 'bbbb').pack()
canvas.create_window(0, 0, anchor = NW, window = frame)
canvas.grid()
app.grid()
app.mainloop()
But if the initialization order is inverted, like:
frame = Frame(app)
canvas = Canvas(app)
you get nothing but a blank window.
Is this a intentionally designed behavior (If so, why?), or I just found a bug in Tkinter?
It is a feature. Widgets have a stacking order that defaults to the order that they were created. You can adjust this stacking order with the lift and lower methods.
For example, you can create the frame first and then the canvas, so that the canvas has a higher stacking order. As you observe, you don't see the frame because it is behind the canvas. To make it visible, you can lift it:
frame.lift(canvas)
Doing so will give the same visual effect as if you had created the canvas first.
This technique can be useful to hide and show widgets. For example, you can create a notebook-like widget by stacking several frames on top of each other, and then using lift to bring the one you want to be visible to the top of the order.
I am experimenting with Tkinter, as I was trying to figure out is there a way to set the tkinter's window size without using canvas. I came upon this how to set frame size question on SO's Question & Answer. So I went ahead and test it by writing a very small program to display a text label. But I found out it is "missing", or disappear when I use frame.pack_propagate(0)
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root, width=400, height=400)
# Does not work at the moment, textBox is missing
# frame.pack_propagate(0)
frame.pack()
textBox = tk.Label(frame, text="(x,y): ")
textBox.pack()
root.mainloop()
So my question is, can you explain why my textBox (Label) is not appearing when I use the frame.pack_propagate(0) instead of frame.pack() method? And secondly, is there a way to set the window size without using a canvas? I want to know because I am writing a series of small programs to teach my friend about tkinter, before introducing canvas to him. It would be nice if the window size are all the same across my tkinter samples. And I am just wondering as well (curious). Thank you very much.
I am using python 3.2.2 on MAC OS 10.5.8.
pack_propagate only sets a flag, it doesn't cause the frame to be placed in the widget. It is not a substitute for calling pack.
In other words you must do this:
# put the frame in its parent
frame.pack()
# tell frame not to let its children control its size
frame.pack_propagate(0)
# put the textbox in the frame
textBox.pack()
To answer your second question: Yeah, there is a way.
tkinters Tk do have the Tk.geometry function. When you just call it without arguments, you will get the current geometry in form of 'widthxheight+x+y', so for example (on Windows 10) '200x200+26+26' when you create your first Tk window. Using that format you can resize the Tk by, e.g., writing: root.geometry('400x500+60+60') to set the width to 400, the height to 500 and place it at the coordinates (60|60).
This works for Tk alswell as for Toplevel. But Toplevel also takes the arguments height and width when initialized or configured. If you want them to keep their size when packing something inside just use root.pack_propagate(False) on them.
By the way there is something similar for the grid manager: root.grid_propagate(False)
I'm attempting to write a basic Tkinter GUI that has a Text widget at the top, then a Button widget left aligned under it, then another Text widget underneath the button. The problem I'm having is, after packing the Button widget to the left, when I then go to pack the second Text widget, it puts it next to the button on the right, rather than underneath the button. This happens regardless of what I set the side argument to for the second Text widget Here's a simple piece of code that demonstrates this behaviour:
from Tkinter import *
root = Tk()
w = Text(root)
w.pack()
x = Button(root, text="Hi there!")
x.pack(side=LEFT)
y = Text(root)
y.pack(side=BOTTOM)
root.mainloop()
So how would I go about setting up the second Text widget so that it appears below the button, rather than to the right of it?
There are generally two solutions to layout problems:
switch to using grid. It becomes real easy to do layouts like what you are trying to accomplish. Grid can solve probably 95% of all layout issues (it's amazing when you think about it -- Tk does with one manager what most toolkits need half a dozen to accomplish!)
use multiple frames. If some widgets need to be stacked top-to-bottom and some left-to-right you can't always get what you want packing everything in a single frame. Use one frame for the top-to-bottom parts of the layout and additional frames for the left-to-right content.
Also realize that widgets don't have to be children of the widget in which they are packed/gridded. You can use the "in" parameter to put widgets in some other container than their parent.
For example, in your specific example you can create three frames, top, middle, bottom. Pack these top-to-bottom in your toplevel window. Then you can pack the first text widget in the top, the button or buttons horizontally in the middle, and the other text widget in the bottom.
The advantage to such an approach is that it makes it much easier to change the layout in the future (which in my experience always happens at some point). You don't have to re-parent any of your widgets, just pack/place/grid them in some other container.
In your short example it doesn't make much difference, but for complex apps this strategy can be a life saver.
My best advice is this: layout isn't an afterthought. Do a little planning, maybe even spend five minutes drawing on some graph paper. First decide on the major regions of your app and use a frame or some other container for each (paned window, notebook, etc). Once you have those, do the same divide-and-conquer approach for each section. This lets you use different types of layout for different sections of your app. Toolbars get horizontal layout, forms might get vertical layout, etc.
I was initially misunderstanding how packing worked and didn't realise that the entire left side was being "claimed" when i did x.pack(side=LEFT). What I found after reading this and the answer by Alex here is that I was not really after having x packed to the left side at all, but rather having it anchored to the left, using anchor=W (W for West) instead of side=LEFT. My revised code snippet which does what I was after looks like this:
from tkinter import *
root = Tk()
w = Text(root)
w.pack()
x = Button(root, text="Hi there!")
x.pack(anchor=W)
y = Text(root)
y.pack(side=BOTTOM)
root.mainloop()
This way x is not "claiming" the left side anymore, it's just aligned to the left (or West) within its block of space.
Packing happens in the order the .pack methods are called, so once x has "claimed" the left side, that's it -- it will take up the left portion of its parent and everything else within its parent will be to its right. You need a Frame to "mediate", e.g....:
from Tkinter import *
root = Tk()
w = Button(root, text="Mysterious W")
w.pack()
f = Frame(root)
x = Button(f, text="Hi there!")
x.pack()
y = Button(f, text="I be Y")
y.pack(side=BOTTOM)
f.pack(side=LEFT)
root.mainloop()
(changed Texts to Buttons for more immediate visibility of layout only -- the Tkinter on this Mac doesn't show Texts clearly until they have focus, but Buttons are quite clear;-).
Do it the same way that WebView does using the Mosaic Canvas Widget Sets internals(which are very similar to Tk). The trick is that the second identical named Frame Object works as a Block Level Float(inline:block;) for everything placed after it and everything that calls "fr" already will automatically begin over inside of it.
You can have many doing this of TOP aligned widgets and simply add another identical named Frame where you want to break between side=LEFT's. Works after Bottom also.
fr=Frame(root)
fr.pack(fill=X, side=TOP)
block1=Label(fr)
block1.pack(side=LEFT)
block2=Label(fr)
block2.pack(side=LEFT)
block3=Button(fr)
block3.pack(side=LEFT)
# NAME IT THE SAME ID NAME AS THE FIRST MAIN FRAME...
fr=Frame(root)
fr.pack(fill=X, side=TOP)
# These NOW jump into the second Frame breaking the side=LEFT in new Frame
block4=Label(fr)
block4.pack(side=LEFT)
block5=Label(fr)
block5.pack(side=LEFT)
# AND THEY CONTINUE GOING side=LEFT AFTERWARDS.