I've been using Python for quite some time now and I made a few applications with GUI's. When I was writing my new application I came across some behaviour of tkinter that I think is just strange.
Consider the following code:
from tkinter import *
root = Tk()
root.geometry('200x200')
label = Label(root, bg='green', text='0', font=('arial', 40, 'bold'))
label.place(x=0, y=0, width=200, height=200)
def add():
label['text'] = int(label['text']) + 1
if label['text'] == 10:
button.place_forget()
button = Button(root, command=add, text='+1', font=('arial', 20, 'bold'))
button.place(x=50, y=50, width=100, height=100)
root.mainloop()
As you would expect, when you run this code you will get a small window with a green background and a button. After you pressed the button 10 times, it disappears, and a '10' is show in the window.
The problem I encountered occures when I use a different parent widget for the button widget. For example, when I change this line
button = Button(root, command=add, text='+1', font=('arial', 20, 'bold'))
to
button = Button(label, command=add, text='+1', font=('arial', 20, 'bold'))
replacing 'root' with 'label', my gui just glitches out. Every time I press the button it disappeares. Sometimes it reappears after a few seconds but sometimes it only reappears when I try to click on it.
After some more testing I found out the button disappears every time the parent label updates.
I was wondering; Why is this happening? And is there a way to fix the problem without using 'root' as the parent for button (this would make my application a lot more complicated, or would at least force me to rebuild a lot of the gui stuff)
I tried to find information about this problem, but I couldn't find any.
I don't think there's a good reason for why you are seeing this behavior. I don't see it on OSX, but the, on OSX buttons behave a bit differently than other platforms. It should be perfectly fine to use a label as a parent to another window without seeing this flickering.
One possible workaround might be to call lift on the button widget whenever you reconfigure the label. Perhaps the stacking order is changing on you, causing the label to be above the button.
Finally, are you aware you can use place with relative coordinates, and that widgets can be relative to other widgets even if there is no parent/child relationship? You seem concerned that you'll have to change a lot of code if you can't create the two widgets in a parent/child relationship.
For example, you can create your widget and call place like this:
button = Button(root, command=add, text='+1', font=('arial', 20, 'bold'))
button.place(in_=label, relx=0, rely=0, relwidth=1, relheight=100)
You'll need to make sure the stacking order is correct. The easiest way is to simply make sure the button is created sometime after creating the label.
Related
Here is my code
from tkinter import *
root = Tk()
root.geometry('500x500')
btn = Button(root, text = 'Play', height=5,width=30, command = root.destroy)
btn.place(x=140, y=200)
w = Label(root, text ='Lost', font=("Courier", 50), height=4)
w.pack()
root.mainloop()#
The button is meant to say play on it
This is a side effect of several things. What's happening here is that the text in your label is being centered vertically in a box with room for 4 lines. That text has opaque background, so the bottom of text box is sitting over the top of your button and hiding the text. If you remove height=4, you'll see that it works just fine.
The other problem here is mixing the placement tools. You are mixing place and pack, and that is going to cause trouble. You may need to think about the layout issues some more.
I really need to be able to delete a button onscreen into a label. All I need to do is remove the button, and put the label in place of it. However, I do not know how to remove buttons.
I am running Windows 10, Python 3.9.2.
Are you looking for something like this?:
import tkinter as tk
def remove_button():
global label
# Get the grid parameters passed in button when it was created
button_grid_info = button.grid_info()
button.grid_forget()
label = tk.Label(button_grid_info["in"], text="This is a Label widget")
# Put the label exactly where the button was:
label.grid(**button_grid_info)
root = tk.Tk()
button = tk.Button(root, text="Click me", command=remove_button)
button.grid(row=1, column=1)
root.mainloop()
grid_forget removes the widget without destroying it. If you used <button>.pack, use pack_forget. If you used <button>.place, use place_forget.
I've made a GUI using Tkinter. For the time being, I set the geometry to '962x652' and made it so the user can't resize it by using .resizable(0, 0). I'm now looking for a way to make it so when the GUI is resized, all the elements change size as well along side it, preferably with the aspect ratio locked.
Is there a way to achieve this whilst making it so only the element change size visibly without changing actually changing size? For example, if my GUI contains a scrolledtext box, if the user was to resize the GUI, the scrolledtext will also increase in size, but so will the text and each line of text in it will stay in the same line. I hope that makes sense.
I've add some code down below to show what I'm working with. This should have a similar effect to what I currently have:
import tkinter as tk
from tkinter import *
from tkinter import scrolledtext
TrialGUI = Tk()
TrialGUI.title('Resize GUI')
TrialGUI.geometry('962x652+0+0')
#Remove the following so you can resize the GUI
TrialGUI.resizable(0, 0)
#These are the two frames. I've changed their colour so they are visible when they are resized.
ABC1b = Frame(TrialGUI, bg='lightpink', bd=20, width=900, height=600)
ABC1b.grid(row=0, column=0)
ABC2 = Frame(TrialGUI, bg='lightblue', bd=20, width=452, height=600)
ABC2.grid(row=0, column=1)
#This is the text box
txtQuestion = scrolledtext.ScrolledText(ABC1b, wrap=tk.WORD, width=42, height=10, font=(14))
txtQuestion.grid(row=0, column=0, columnspan=4, pady=3)
#This inserts text into it. I made it insert which line the text should be in so it is easier to check if it is still in the same line when it is resized.
txtQuestion.insert(tk.INSERT, 'This should be in the first line.----------------------------------- This should be in the second line.------------------------------ This should be in the third line.----------------------------------')
TrialGUI.mainloop()
If anyone decides to play around with this code, remember to remove TrialGUI.resizable(0, 0) so it can be resized.
I was testing a method to hide and show frames by pressing a button (the buttons will be a menu bar later). So I created this code:
import tkinter as tk
from tkinter import *
win = Tk()
win.geometry("275x350")
win.title("MyProgram")
win.configure(background="white")
frame1 = Frame(win, width=200, height=200)
frame1.grid_propagate(False)
frame2 = Frame(win, width=200, height=200)
frame2.grid_propagate(False)
Label(frame1, text="Hello world", font="20").grid()
Label(frame2, text="Zweiter frame", font="20").grid()
def runGrid(run, destroy):
run.grid(row=1, column=2)
destroy.grid_forget()
def run2nd(run, destroy):
run.grid(row=1, column=2)
destroy.grid_forget()
Button(win, text="Run", command=lambda:runGrid(frame1, frame2)).grid(row=0, column=0)
Button(win, text="Run 2nd", command=lambda:run2nd(frame2, frame1)).grid(row=0, column=1)
win.mainloop()
So here is the problem...
After pressing the first button, the first frame comes up in his wanted size. After pressing the 2nd button, the 2nd frame comes up in his wanted size. But when you press the first button again (after pressing both buttons once before) then the frame is just showing up like grid_propagate was removed from the code (the frame is just as big as the label in it). Can someone explain me the fault in my code which is causing the problem?
Thanks a lot ;)
I found an other way to get what I want:
By simply using grid_remove() instead of using grid_forget() it is working.
But I still hope someone can answer my question because it really should work with grid_forget() as well I think.
I just made an app using python and tkinter widgets.
There are Labels, Frames, Buttons, etc in the Tk and Toplevel widgets.
However, it includes thousands of codes and its really annoying to resize every widgets when I support multiple resolutions.
Is there any way to expand the resolution ratio for existing Tkinter Tk() and Toplevel() widget and their child widgets? (zooming-in)
If not, what would be the best approach to support multiple resolutions of a python app with the same ratio?
Any help would be much appreciated, sorry for bad English.
Yes, this is possible however it depends on the geometry manager you have used in your program.
For the .pack() method (which is arguably the simplest geometry method for "intelligent" GUI designs) you can use a range of attributes on when you declare .pack() on the widget. These attributes include (but are not limited to) fill, expand, anchor, padx, pady, etc.
The below shows an example of a set of three buttons which will automatically expand to fit the window if it changes or is initialised to a different size than was used during development.
from tkinter import *
root = Tk()
btn1 = Button(root, text="btn1")
btn2 = Button(root, text="btn2")
btn3 = Button(root, text="btn3")
btn1.pack(fill="both", expand=True)
btn2.pack(fill="both", expand=True)
btn3.pack(fill="both", expand=True)
root.mainloop()
For the .grid() method you will need to make use of the functions Grid.columnconfigure() and Grid.rowconfigure. Both of these have the attribute weight which determines which rows and columns should be given priority for assignment of extra space if more becomes available in the window. Setting all rows and columns to have a weight of 1 means they will all be given space equally. You will also need to use the sticky attribute when declaring .grid() on the widgets.
The below shows an example of a set of three buttons which will automatically expand to fit the window if it changes or is initialised to a different size than was used during development.
from tkinter import *
root = Tk()
for column in range(3):
Grid.columnconfigure(root, column, weight=1)
for row in range(1):
Grid.rowconfigure(root, row, weight=1)
btn1 = Button(root, text="btn1")
btn2 = Button(root, text="btn2")
btn3 = Button(root, text="btn3")
btn1.grid(column=0, row=0, sticky=N+S+E+W)
btn2.grid(column=1, row=0, sticky=N+S+E+W)
btn3.grid(column=2, row=0, sticky=N+S+E+W)
root.mainloop()
Using .place() would be a lot more difficult, you would need to have a function setup which would trigger on every window resize event which would calculate the size that the buttons need to expand to.
This would look something like the below:
from tkinter import *
class App:
def __init__(self, root):
self.root = root
self.button = Button(self.root, text="Button")
self.button.place(relx=0.5, rely=0.5, anchor="center")
self.root.bind("<Configure>", self.resize)
def resize(self, *args):
self.button.configure(width=self.root.winfo_width(), height=self.root.winfo_height())
root = Tk()
App(root)
root.mainloop()
Subjectively speaking, .pack() tends to be easier, however this all comes down to how much effort you're willing to put in to implement this with your current program.
Can't comment so I add a short tip to the detailed Ethan answer. You can design most of the GUIs in tkinter with either pack, grid or a combination of both (by placing frames on a window with one of them, and using either grid or pack inside of each frame, to "place" the widgets). You can tune the configurations for proper location and size when the window resizes. Keep placer use for special cases (like overlaying some widget on the top of others)