Tkinter button default infinitely large - python

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.

Related

Tkinter percentage sizes?

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.

How to place a Tkinter widget at given coordinates in a fixed window?

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.

Python: Having trouble using .bind() on anything not involving the mouse

I can make a basic python application like so:
from tkinter import *
block = None
def moveUp(event):
field.move(block,0,-50)
root = Tk()
field = Canvas(root, width = 300, height = 300, bg = 'light blue')
field.pack()
block = field.create_rectangle(100,100,110,110)
field.bind('<Button-1>',moveUp)
mainloop()
and it will behave just like you would expect. It creates a square on a Canvas and moves that square up 50 pixels every time you click in the Canvas.
However, When I replace
field.bind('<Button-1>',moveUp)
to, for example,
field.bind('<Return>',moveUp)
the square does not move, no matter how many times I press the Enter key. This problem persists for any kind of keyboard input (e.g <space>, etc), but any input involving the mouse is fine.
Any input at all is appreciated. Thanks!
The field does not have focus, and therefore does not capture the keypress. One option is simply to make the binding more general:
field.bind('<Return>',moveUp)
to
root.bind('<Return>',moveUp)
Another option is to set the focus to the field:
field.bind('<Return>',moveUp)
field.focus_set()
Not entirely sure what's the reason, but it seems to work if you use bind_all instead of bind.
field.bind_all('<Return>',moveUp)
My guess is that using the keyboard, the canvas does not have focus and so does no register the event. Using bind_all, the event is registered when any widget of the application has focus.
See here for some information on levels of binding.

Tkinter box issues- Entry, Labelbox, Text all take up space from side to side, grid crashes computer

I'm using a tkinter canvas and trying to make a chat box on the right side of my game. However, I found that when I do...
import turtle
import tkinter as tk
master = tk.Tk()
w = tk.Canvas(master,width=1155,height=600,cursor='cross_reverse', bg='#101010')
shift = 1.000
sc = turtle.TurtleScreen(w)
tu = turtle.RawTurtle(sc)
e = tk.Entry(master, bg = '#000', fg = '#03f', font = 'Courier', justify='right', insertbackground = '#101010',width='115')
lb = tk.Listbox(master,height=3)
#e.grid(row=3,column=3)
sc.bgcolor("#101010")
txt = tk.Text(master,state="disabled")
txt.pack()
lb.pack()
w.pack()
sc.tracer(100)
drawcontinents() #Draws stuff with turtle, works just fine
e.pack()
tk.mainloop()
... a few things go wrong.
1.Text and Entry do not seem to want to coexist. I seem to be only able to have one or the other. My plan was to use entry as a chat entry, and display messages in Text. My backup plan is to append messages to label.
2.Text, entry, and Label box take up the entire window in whatever rows they are in, which blocks out the rest of what I am trying to draw. In other words,it puts the text box in the center, with a big gray stripe from side to side across whatever I've drawn. Is there any way to just display the box, and put it to the right?
3.Whenever I try to use the grid system, my whole computer freezes and I have to restart. Is this because the program is taking up more space than I have available, or is this a known bug or problem with installation?
You cannot use both pack and grid at the same time for the same containing widget (ie: for all widgets inside the same frame, toplevel or root window).
What happens is this: grid lays out all the widgets, potentially changing the size of some widgets based on your options (ie: it may grow a widget to stick to the sides of the cell). pack then notices that some widgets changed size in the containing widget it thinks it is responsible for, so it redoes what it thinks is the proper layout. This may change the size of some widgets based on your options. grid then notices that some widgets it thinks it is responsible for change size so it redoes what it does, potentially changing the size of some widgets. pack notices and re-adjusts, grid notices and re-adjusts, pack notices, ... until the end of time.
The solution is simple: only use grid, or only use pack, for all widgets that have a common parent. In this case, all your widgets share the root window as their parent, so they all need to use grid, or they all need to use pack.

How to pack a tkinter widget underneath an existing widget that has been packed to the left side?

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.

Categories

Resources