I am trying to build a small software with the Tkinter module and python but I can't figure out how to set a widget size with percentages instead of pixels. I could of course do it by grabbing the size of the entire window at every moment and divide by 2 to get a size of 50%, but is there a better way to do it with the Tkinter module?
EDIT 1: To answer your questions I put my code here:
class Navbar:
def __init__(self, master):
self.maxsizeFrame = Frame(master, height = 50) #maxsize handler, prevent self.frame to grow too big
self.maxsizeFrame.pack(fill = X, expand = NO)
self.frame = Frame(self.maxsizeFrame)
self.frame.pack(side = TOP, fill = BOTH, expand = YES)
self.masteries = Button(self.frame, text = "Masteries")
self.masteries.pack(side = LEFT, fill = BOTH, expand = YES)
self.runes = Button(self.frame, text = "Runes")
self.runes.pack(side = RIGHT, fill = BOTH, expand = YES)
So I want to create a navigation bar with two button handled in "self.frame". But I want this design to be responsive, that's why I set expand to "YES". However I wanted to set a maximum size for "self.frame" but the only way I found was to pack this frame in an other one ("self.maxsizeFrame") and set expand to "NO" on this one. And finally, I would like to set the maximum expand size to half the main window, so it could be great if the height of "self.maxsizeframe" could be in percentage. Thanks for reading.
EDIT 2:
Actually it seems more efficient to build the software with the grid layout and the weight's option would be accurate.
If you would like to make it half the size of the window, use width=root.winfo_width / 2, height=winfo_height
In the maximum size. (I had to post it late due to the limit of posting per 30Mins
Ok, I wonder how no one responded yet.
So instead of sizing the component at the moment of creation, you can instead use the widget.place() method, define relwidth and relheight (0=0%,0.5=50%,1=100%,etc), this will give the widget x% of its parent width/height. Using place() you can also define relative starting positions for the widgets using relx and rely, and even apply width,height,x, and y by specifying screen units as you would do normally.
Now the fun stuff, which I don't see talked around, is that you can apply relwidth and width at the same time,by playing with negative values and joining relx and x properties, you can get really responsive sites.
Here is an example where I played a bit with this fields to get the responsiveness I've desired:(don't mind 'yposition' comes from the application context)
self.label.place(rely=yposition,x=0.025,relheight=0.04,width=150)
self.entry.place(rely=yposition,x=150,relheight=0.04,relwidth=0.95,width=-190)
self.button.place(rely=yposition,relx=0.98,x=-40,relheight=0.04,width=40)
Although it depends on the widget, you should be able to do width=30% and same with height. Can you please edit your post saying which widget you are using. I would not recommend percents though as they get funky some times.
Related
I am trying to set up a basic GUI with two buttons and some number of labels (maybe 4 or so).
I was under the impression that Tkinter buttons had a default size that was relative to the amount of text contained in the button. When my buttons appear on the screen however, they seem to take up as much of the screen as possible depending on where I place them. Here is my code:
from tkinter import *
root = Tk()
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
screen_width2 = screen_width/1.3
screen_height2 = screen_height/1.3
a = str(int(screen_width2)) + "x" + str(int(screen_height2))
root.geometry (a)
root.title("Tester")
welcomeUser = Label(root, text ="Welcome User")
upload = Button(root, text="Upload")
run = Button(root, text="Run")
welcomeUser.place(bordermode=INSIDE, relheight= 0.25, relwidth= 1.0)
upload.place(bordermode=INSIDE, relheight= 1.0, relwidth= 0.25)
run.place(bordermode=INSIDE, relheight= 1.0, relwidth= 1.75)
root.mainloop()
For example, when I run this code, the text for the "run" button is in the correct place, but the actual button takes up the entire window. Whichever button is run last eclipses everything else in the window.
I tried to resize the button with config() and just by changing the size within its own parameters like run = Button(root, text="Run", height=100, width=100).
I know it has something to do with the fact that I'm using place(), because when I use pack() or grid(), the button size is default (size of text).
For the people who are going to say "Use grid instead of place()" - I can't figure out how grid() would better. It seems much less intuitive and much less effective, especially for screen resolution dependent application window sizes (as is part of my code). In fact the whole format of grid() is much weaker than just using floats measures relative to window (I.E. how is done using place). I come from an OS background where this is standard practice, and people who use pixels etc are derided. The script seems to be flipped on StackOverflow with Tkinter, though. Feel free to change my mind.
My question is, how can I can keep the buttons at their default size (encapsulating text) while still being able to place them effectively?
When you use place and specify relwith and/or relheight, you are requesting that the buttons width or height be relative to its parent. In this case the parent is the root window.
Thus, if you set the relheight to 1, you are requesting that the button be exactly as tall as the whole window. Similarly, when you set relwidth to .25, you are asking it to be 1/4 as wide as the whole window.
You can easily see this by simply removing the relwidth and relheight properties and the buttons will be their natural size.
Is it actually possible to place widgets at specific coordinates in a Tkinter window? For example, if i set up a window like so...
class LogInWindow(object):
def __init__(self):
#create variables
self.currentUser = StringVar()
#create the window and frame
self.LW = Toplevel()
self.LW.title('Login')
self.LW.geometry('310x100-500+300')
self.LW.resizable(width=False, height=False)
self.LWFrame = ttk.Frame(self.LW)
Creating a fixed window 310 pixels wide and 100 pixels high. How would I then place a button at say x=120,y=62?
I've explored the pack and grid documentation but cannot seem to find anything useful.
There's the less well known place geometry manager.
In your case you'd simply create the button and place it at the coordinates you want.
b = tk.Button(self.LW,text='Button')
b.place(x=120,y=62)
The reason people typical avoid 'place' is that it isn't automatically responsive to things like design changes or window resizes in the same way that pack and grid are.
You might be better off using relx and rely and the anchor options to express the position in terms of fractions of the window rather than specifying an absolute position to avoid some of these disadvantages.
This is my first post on stackoverflow. I am finally posting because I can not find this anywhere and have been searching for nearly 4 hours, but I am stuck.
Here is my code example:
import tkinter as tk
from tkinter import *
root = tk.Tk()
root.geometry("600x100+200+200")
leftverticalFrame = Frame(root)
leftverticalFrame.pack(side=LEFT)
middleverticlFrame = Frame(root)
middleverticlFrame.pack(expand=TRUE)
rightverticalFrame = Frame(root)
rightverticalFrame.pack(side=RIGHT)
right = tk.Label(rightverticalFrame, text="Right Vertical Status Frame", bg="yellow")
right.pack(side=tk.RIGHT, fill=BOTH)
left = tk.Label(leftverticalFrame, text = "Left Vertical Navigation Frame", bg="orange")
left.pack(side=tk.LEFT, fill=BOTH)
bottom = tk.Label(middleverticlFrame, text="Middle Vertical Frame", bg="blue")
bottom.pack(side=tk.BOTTOM, expand=True, fill=tk.BOTH)
root.mainloop()
What I am doing is merely trying to layout the frames individually within the root because the frames will use different managers. The left frame is functioning exactly as I want it to, as is the middle frame. The problem is with the frame on the right.
Notice when you re-size the window making it more narrow, the right frame comes into the "middle frame's territory". Now the strange thing is the middle frame does not replicate the same behavior when it comes to the boundary of the left frame. I want the right frame to behave the same as the middle frame. Essentially I am trying to make the Left and Right fairly static, but the middle frame more dynamic. Can anyone tell me what I am doing wrong please?
An important thing to remember about pack is that the side attribute doesn't refer to the side of the window, it refers to the side of the remaining available space. The causes the order in which you pack things and the side that you pack them to be significant, because each time you pack something you change the location and amount of remaining available space.
In this case, the problem is that you didn't specify the side attribute for the middle frame, so it defaults to "top" (as in, "top of the remaining space", not "top of the window"). Since there's already something on the left, this puts it at the top of the remaining space on the right. Then, when you put the next item on the right, it's on the right but below the thing that is on the top.
There are at least a couple ways to solve this. The first is to pack the left and right sides first, and then pack the middle. In this case it doesn't matter which side you put the middle frame:
leftverticalFrame.pack(side=LEFT)
rightverticalFrame.pack(side=RIGHT)
middleverticlFrame.pack(expand=TRUE, side=TOP)
The second solution is to leave them in the original order, but pack the middle frame on the left or right instead of the top:
leftverticalFrame.pack(side=LEFT)
middleverticlFrame.pack(expand=TRUE, side=LEFT)
rightverticalFrame.pack(side=RIGHT)
These two variations will initially look identical, or perhaps nearly identical depending on what else might be in the frames or in the window. However, the behavior is different when you start to make the window too small to fit all of the frames.
In such a case, tkinter must eventually start reducing the size of a widget. It does this in the reverse order that they were packed (read: the last one to be packed is the first one to be shrunk). That means that if you want the left and right to be fixed as much as possible, you should pack the middle section last.
pro tip: it makes your code easier to read and maintain if you group all of your layout code together. Consider this code:
f1 = Frame(...)
f1.pack(...)
f2 = Frame(...)
f2.pack(...)
I think you'll find over time that your code is easier to read and maintain if you write it like this:
f1 = Frame(...)
f2 = Frame(...)
...
f1.pack(...)
f2.pack(...)
...
I think it makes the code much easier to visualize, since all of the layout for a given parent window is in one place rather than sprinkled throughout the code.
I cannot get my widgets fit in my computer screen and the layout is not forming as expected.
The two Text widgets should expand and occupy rest of frame available to them and the second frame which contains response2Field should fit into screen but it's not happening.
How can I achieve these goals?
# -*- coding: utf-8 -*-
from Tkinter import Tk,Label,Entry,Text,Button,Frame
text = """Python was created in the early 1990s by Guido van Rossum at Stichting
Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands
as a successor of a language called ABC. Guido remains Python's
principal author, although it includes many contributions from others."""
root = Tk()
request = Frame(root)
response = Frame(root)
response1 = Frame(response)
response2 = Frame(response)
request.grid(row=0,column=0,sticky='news')
response.grid(row=0,column=1)
response1.grid(row=0,column=0,sticky='w')
response2.grid(row=1,column=0,sticky='news')
InputText = Text(request)
InputText.pack(expand='true')
Button(response1,text='Submit',bg='brown',fg='yellow').pack()
response2Field = Text(response2,bg='black',fg='green')
response2Field.pack(expand='true')
InputText.insert("1.0",text)
response2Field.insert("1.0",text)
root.mainloop()
Output:
The default behaviors of tkinter geometry managers pack and grid is to grow containers and windows to display everything you put inside. If you want to constrain this, you can add at the end of your gui building code (copied from this other answer)
root.geometry('{}x{}'.format(<widthpixels>, <heightpixels>))
To have this to work, you have to handle correctly resizing in your layout. First of all, your use of grid is overelaborate, you do not have to use all these intermediate frames and can put your widgets directly in the grid. Second, grid need to be instructed of which row and columns should grow. This is done through the weight parameter. It describes the growth rate of an element (relative to the sum of all weights at the same level) and default to 0. This is configured on the container side. For instance, to have your request and response fill the entire height of the window, you have to add
root.grid_rowconfigure(0, weight=1)
On pack side, you have to specify both parameter expand and fill to have your widgets fill the entire available space pack(expand='true', fill='both').
To visualize re-size behavior of your Frame containers, you might consider to add borders borderwidth=2, relief='sunken' or background background='magenta' (it hurts the eyes, but helps to understand).
You can see that indeed InputText is not resizing, neither magenta request. response2Field occupies its whole green frame (misses fill='both' for proper resize handling, but not visible since part of the path which determine window original size).
I have a treeview in the left side of an hpaned but when I try to move the bar to the left to make the treeview smaller than its automatic size instead of resizing the treeview it expands the entire program window to the right. Any ideas on how to fix this?
The relevant portions of the source are the following:
For the hpaned.
self.vpan = gtk.VPaned()
self.hpan = gtk.HPaned()
self.vpan.show()
self.hpan.show()
self.vBox1.pack_end(self.hpan, True, True, 0)
self.hpan.pack2(self.vpan,True, True)
And for the tree View.
self.ftree = gtk.TreeStore(str,str,str)
self.treefill(None, os.path.abspath(os.path.dirname(__file__)))
self.tree = gtk.TreeView(self.ftree)
self.tvcolumn = gtk.TreeViewColumn('Project')
self.tree.append_column(self.tvcolumn)
self.cellpb = gtk.CellRendererPixbuf()
self.celltxt = gtk.CellRendererText()
self.tvcolumn.pack_start(self.cellpb,False)
self.tvcolumn.pack_start(self.celltxt,True)
self.tvcolumn.set_attributes(self.cellpb, stock_id=0)
self.tvcolumn.set_attributes(self.celltxt, text=1)
self.tvcolumn.set_resizable(True)
self.hpan.pack1(self.tree,True,True)
self.tree.show()
I was going to answer this question, and then I realized that my answer was a repeat of the user's answer. But, as he hasn't posted his resolution as an answer, I will anyway. (Below is what I would have posted if he hadn't figured it out.)
Expand and Fill seem to be causing a bit of a fight for space between the various objects in this box (as I'm assuming that all of your objects do this. I used to have the habit, too, but no problem.) In the PyGTK documentation, "Expand" is defined as
True if child is to be given extra space allocated to box. The extra
space will be divided evenly between all children of box that use this
option.
and "Fill" as:
True if space given to child by the expand option is actually
allocated to child, rather than just padding it. This parameter has no
effect if expand is set to False. A child is always allocated the full
height of a gtk.HBox and the full width of a gtk.VBox. This option
affects the other dimension.
So, essentially, the box is giving everything the same space...you give more to one, it gives it to the other, and the objects all take it and get bigger. The window then has to expand to compensate.
To fix this, set one or both of those options to "False". Setting "Expand" to "False" will also shut off "Fill", but only setting "Fill" to "False" will cause the box to give the extra space to "Fill-False" objects as padding instead.