Can not move widgets in Tkinter - python-3.x

I am trying to learn tkinter, but I got a problem and I can`t move forward from this point. I wanted to make just a simple GUI with one button, unfortunately, I am not able to move that button ( being always displayed in the most left upper corner).
This is the code that I used :
class App(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.grid()
self.master.title('GUI')
quitbttn = Button(self, text='quit')
quitbttn.grid(row=3, column=5)
root = Tk()
app = App(root)
app.mainloop()
Although, I found this snippet of code on the iternet, and it is working perfectly, the only difference being that pack() is used instead of grid() :
class Window(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def init_window(self):
self.master.title("GUI")
self.pack(fill=BOTH, expand=1)
quit_button = Button(self, text='quit')
quit_button.pack(side=BOTTOM)
root = Tk()
app = Window(root)
root.mainloop()
I would like to be able to use grid as well.
Any advice is being apreciated. Thank you!

The reason you cannot move the button is because you only have one element.
quitbttn.grid(row=3, column=5)
This part of the script basically says that the button should be placed a third row down and in the fifth space along. Since you have no other elements in the window it does not move the button at all. This is because all the 2 rows and 4 columns are all equal to 0 so the first place it packs is in the top left corner.
Using the .pack() function allows you move the button without the need of any other button in the window.
If you added another button you would then be able to move around the first button in three different places.
Note that you cannot use the .pack() and .grid() functions in the same window.

Related

How to switch between frames in tkinter

Currently resisting the temptation to throw my laptop out of the window and smash it with a bat at this point.
Currently, I'm trying to create a simple GUI for what used to be a nice simple text based RPG game. But trying to work with a GUI makes me want to die.
I just want to have a scaleable way to swap between frames in the game. (Currently there exists the main menu and the Work in progress character creation screen because I can't even manage to get even just that to work.)
I've tried most things that I can find on this website and on discord servers and I seem to just get a new error every time.
I just want to know how to swap between these since trying anything that I can find online just creates more errors.
There are more "screens" to come since it's a game so a scaleable solution would be perfect thanks.
import tkinter
from tkinter import *
from tkinter import ttk
from PIL import ImageTk, Image
root = Tk()
content = ttk.Frame(root)
root.geometry("600x600")
class CharacterCreate(tkinter.Frame):
def __init__(self, parent):
tkinter.Frame.__init__(self)
self.parent = parent
backgroundchar = ImageTk.PhotoImage(Image.open("plont2.png"))
backgroundlabelchar = tkinter.Label(content, image = backgroundchar)
backgroundlabelchar.image = backgroundchar
backgroundlabelchar.grid(row=1,column=1)
Charname = tkinter.Label(content, text = "Enter your character name here:").grid(row=0)
e1 = tkinter.Entry(content)
e1.grid(row=0, column=1)
e1.lift()
CharBtn1 = Button(content, text="Return to main menu", width = 15, height = 1)
CharBtn1.grid(row=2, column=2)
CharBtn1.lift()
class MainMenu(tkinter.Frame):
def __init__(self, parent):
tkinter.Frame.__init__(self)
self.parent = parent
background = ImageTk.PhotoImage(Image.open("bred.png"))
content.grid(column=1, row=1)
Btn1 = Button(content, text="Play", width=5, height=1, command = CharacterCreate.lift(1))
Btn2 = Button(content, text="Quit", width=5, height=1, command = root.quit)
backgroundlabel = tkinter.Label(content, image=background)
backgroundlabel.image = background
backgroundlabel.grid(row=1, column=1)
Btn1.grid(row=1, column=1, padx=(50), pady=(50))
Btn1.columnconfigure(1, weight=1)
Btn1.rowconfigure(1, weight=1)
Btn1.lift()
Btn2.grid(row=1, column=2, padx=(50), pady=(50))
Btn2.columnconfigure(2, weight=1)
Btn2.rowconfigure(1, weight=1)
Btn2.lift()
MainMenu(1)
root.mainloop()
You have five major problems:
you are calling a function immediately (command=CharacterCreate.lift(1)) rather than at the time the button is clicked (command=CharacterCreate.lift),
you are passing an invalid argument to lift - you are passing 1, but the argument to lift must be another widget,
you are calling lift on a class rather than an instance of a class.
you never create an instance of CharacterCreate
your classes inherit from Frame but you never use the classes as frames -- they each place their widgets directly in container
Switching between pages usually involves one of two techniques: create all the frames at startup and then lift the active frame above the others, or destroy the current frame and recreate the active frame. You seem to be attempting to do the latter, so this answer will show you how to do that.
Since fixing your program is going to require many changes, I am instead going to show you a template that you can use to start over.
Let's start with an import, and then the definition of your pages. To keep the example short, each class will have a single label so that you can distinguish between them (note: importing tkinter "as tk" is done simply to make the code a bit easier to read and type):
import tkinter as tk
class CharacterCreate(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="I am CharacterCreate")
label.pack(padx=20, pady=20)
class MainMenu(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="I am MainMenu")
label.pack(padx=20, pady=20)
Your original code created a container, so we'll do that next. We need to create the root window, too:
root = tk.Tk()
container = tk.Frame(root)
container.pack(fill="both", expand=True)
Now we need to create an instance of each page, giving them the container as the parent. As a rule of thumb, the code that creates a widget should be the code that calls pack, place, or grid on the widget, so we have to do that too. We need to make sure that grid is configured to give all weight to row 0 column 0.
main = MainMenu(container)
cc = CharacterCreate(container)
main.grid(row=0, column=0, sticky="nsew")
cc.grid(row=0, column=0, sticky="nsew")
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
We need a way to lift one of the classes above the other. That's best handled by a function. To make the code easier to understand we'll save the pages in a dictionary so we can reference them by name. This name will be the argument to the function.
pages = {"cc": cc, "main": main}
def switch(name):
page = pages[name]
page.lift()
Finally, we need to start with the main menu on top, and we need to start the event loop:
switch('main')
root.mainloop()
With that, you have a program that runs and displays the main menu. To finish the example lets add a button to the menu to switch to the create page, and create a button in the create page to switch back to the menu.
First, inside the __init__ of MainMenu add the following after the code that creates the label. Notice that because we need to pass an argument to switch, we use lambda:
button = tk.Button(self, text="Go to creater", command=lambda: switch('cc'))
button.pack()
And next, inside the __init__ of CharacterCreate add the following after the code that creates the label:
button = tk.Button(self, text="Go to main menu", command=lambda: switch('main'))
button.pack()
With that, you now have the basic structure to create as many pages as you want, and easily switch to them by name.

How to fix 'Cannot use geometry manager grid inside . which already has slaves managed by pack'

I am new to Tkinter, and am trying to create a window that has a button that opens another window when clicked. When trying to open the new window, I get the above error. I understand that pack() and grid() cannot be used together, but that leads to more confusion.
In my main window, I tried removing the self.pack line, but nothing would show up in my main window. After removing it, I tried calling self.grid, and everything showed up but was not in the proper positions (they were all jammed together in the top left corner). What I think is happening is the self.pack line is causing issues in the new window, but I'm not sure if that is what is causing the issue. If it is, I don't understand why.
Here is the main window
class Window(Frame):
#constructor
def __init__(self,master = None):
Frame.__init__(self,master)
self.master = master
self.init_window()
#create the window
def init_window(self):
self.master.title("Japanese Word A Day")
#configure the grid
self.columnconfigure(1,weight=1)
self.rowconfigure(1,weight=1)
#configure the view history button
view_button = Button(self, text="View\nSaved Words", command=self.view_history)
view_button.grid(column=3,row=5)
#fill the window - without this line, nothing shows up
self.pack(fill=BOTH, expand=1)
def view_history(self):
self.h_window = Toplevel(self.master)
history = History(self.h_window)
Here is the new window
class History(Frame):
def __init__(self,master):
Frame.__init__(self,master)
self.master = master
self.init_window()
def init_window(self):
self.master.title("Japanese Word A Day - History")
self.master.geometry("600x600")
headers = ("English","Kanji","Kana","Romaji","Pronunciation")
self.tree = ttk.Treeview(columns=headers,show="headings")
self.y_scroll = ttk.Scrollbar(orient=VERTICAL,command=self.tree.yview)
self.tree['yscroll'] = self.y_scroll.set
self.rowconfigure(0,weight=1)
self.columnconfigure(0,weight=1)
self.tree.grid(row=0,column=0,sticky=NSEW)
self.y_scroll.grid(row=0, column=1,sticky=NS)
Here is the code that instantiates the main window
root = Tk()
root.geometry("400x300")
app = Window(root)
root.mainloop()
This is in Python 3.6.8. Looking at examples of the grid manager online, I have seen some programs include the pack, and others that do not.

Python 3 Tkinter button command not working (very specific scenario)

I am using these calendar modules found in this post for my program, with some slight modifications to the imports to make it work for the latest python version.
I'll just show the snippets of my code that I feel does matter to this problem.
So I have this pop up window that I made that I use for alerts:
#class for pop-up windows for alerts, errors etc.
class PopUpAlert():
def __init__(self, alert='Alert!'):
self.root = tk.Tk()
tk.Label(self.root,
text=alert,
font="Verdana 15",
fg='red',
padx=10,
pady=5).pack(side=tk.TOP)
self.root.bind('<Return>', (lambda event: self.ok()))
tk.Button(self.root,
text='ok',
pady=10,
command=self.ok).pack(side=tk.TOP)
def ok(self):
print('ok clicked')
self.root.destroy()
The function ok was made just for me to test if the function is even being called. This window works completely fine in my code, except when I try to implement with the calendar, where the "ok" button of my PopUpAlert (which is supposed to destroy the window) stops working:
class CalendarDialog(tkSimpleDialog.Dialog):
"""Dialog box that displays a calendar and returns the selected date"""
def body(self, master):
self.calendar = ttkcalendar.Calendar(master)
self.calendar.pack()
def apply(self):
self.result = self.calendar.selection
def validate(self):
if self.calendar.selection == None:
PopUpAlert(alert='Please select a date or click cancel!')
return False
return True
The calendar has an "ok" button that is used to confirm selection of the date and close the calendar window. What I was trying to do is make it such that the user cannot click "ok" to close the window if he/she has not picked a date. For that, I used the function validate which is pre-defined in the class tkSimpleDialog.Dialog which my CalendarDialog inherits from. I overwrote the function in my CalendarDialog class to call up PopUpAlert, then returned False to the parent function ok (which is called when the "Ok" button is pressed on the calendar window):
def ok(self, event=None):
if not self.validate():
self.initial_focus.focus_set() # put focus back
return
self.withdraw()
self.update_idletasks()
self.apply()
self.cancel()
def cancel(self, event=None):
# put focus back to the parent window
self.parent.focus_set()
self.destroy()
(The whole thing can be found in the tkSimpleDialog file that's linked in the other SO page that I linked above.)
After commenting out lines one by one I found that the "ok" button on my PopUpAlert only didn't work when self.root.destroy() isn't called on the calendar. Why? How do I fix this?
I already tried changing my PopUpAlert to be a Toplevel window, which also didn't work.
It would be a lot nicer of you to provide a mcve instead of asking us to make it.
The problem is that a dialog by default disables clicks to other windows, including windows it spawns. To fix this you need to use a Toplevel instead of Tk (as mentioned) AND add this line of code to the end of PopUpAlert.__init__:
self.root.grab_set()
It would be a lot neater if you subclassed Toplevel rather than that weird wrapper. Here's a mcve:
try:
import Tkinter as tk
import tkSimpleDialog as sd
except:
import tkinter as tk
from tkinter import simpledialog as sd
#class for pop-up windows for alerts, errors etc.
class PopUpAlert(tk.Toplevel):
def __init__(self, master, alert='Alert!', **kwargs):
tk.Toplevel.__init__(self, master, **kwargs)
tk.Label(self,
text=alert,
font="Verdana 15",
fg='red',
padx=10,
pady=5).pack(side=tk.TOP)
self.bind('<Return>', self.ok)
tk.Button(self,
text='ok',
pady=10,
command=self.ok).pack(side=tk.TOP)
self.grab_set() # this window only gets commands
def ok(self, *args):
print('ok clicked')
self.destroy()
class CalendarDialog(sd.Dialog):
"""Dialog box that displays a calendar and returns the selected date"""
def body(self, master):
self.calendar = tk.Label(master, text="Whatever you do, don't click 'OK'!")
self.calendar.pack()
def validate(self):
PopUpAlert(self, alert='Please select a date or click cancel!')
def display():
CalendarDialog(root)
root = tk.Tk()
tk.Button(root, text='data data data', command=display).pack()
root.mainloop()
Note I also got rid of that useless lambda, which happens to be a pet peeve of mine. lambda is great in some cases, but it's very rarely needed.

Issue with tkinter Root Window and Radio Button GUI Generation

As someone new to tkinter for Python 3, I am having a couple of little issues navigating how to generate a GUI (in context of a Python script) despite reading the documentation and Stack Overflow answers. My objective is to create a frame with 7 radio button choices each corresponding to a screen resolution size which when selected and the submit button is pressed, the selected radio button will pass its value to a variable. However when I implement my GUI, I get two issues.
The first is that my frame opens correctly with the radio buttons, but another frame, which is blank and is titled "tk" appears. Regardless of what I do (i.e. use root.withdraw() etc. as others have mentioned), this blank window still appears.
The second and more baffling issue I am having is that when generated, all but the first radio button is selected, not normally with a dot in the center, but with a hyphen. Now the user can press on the option he/she wants and it will all unselect except for the choice, but it doesn't look normal and would probably confuse the user. I read about setting tristatevariable to none yet that didn't work (or at least in my trial). I also tried to force a deselect() function on all of the radio buttons before they generate and that didn't work either. Also, keep in mind that the radio buttons' variable must handle a string and not an int. What is happening here and how can I fix it?
The code snippet that pertains to both of these seemingly related issues is as follows:
if urldata == None:
class ResolutionInputGUI:
def __init__(self, master):
self.master = master
master.title("My GUI")
self.label = tk.Label(master, text="Your Screen Resolution Is: " + screenres + "\n")
self.label.pack()
MODES = [
("500×500", "500×500"),
("1280×800", "1280×800"),
("1280×1024", "1280×1024"),
("1440×900", "1440×900"),
("1680×1050", "1680×1050"),
("1920×1080", "1920×1080"),
("1920×1200", "1920×1200")
]
resolution = tk.StringVar()
resolution.set("500×500")
for text, mode in MODES:
self.radiobutton = tk.Radiobutton(master, text=text, variable=resolution, value=mode)
self.radiobutton.pack(anchor=tk.W)
self.submit_button = tk.Button(master, text="Submit", command=self.submit)
self.submit_button.pack()
self.cancel_button = tk.Button(master, text="Cancel", command=self.cancelbutton)
self.cancel_button.pack()
def submit(self):
global screenres
screenres = self.radiobutton.get()
root.quit()
self.master.destroy()
print(screenres)
def cancelbutton(self):
raise SystemExit
root = tk.Tk()
my_gui = ResolutionInputGUI(root)
root.mainloop()
Any help would be greatly appreciated as I cant seem to solve this issue and tkinter seems to be much more complicated than originally thought. Also, is there anything else that I am doing inefficiently here or to make the end user experience more "friendly?" Thank you so much!
The first is that my frame opens correctly with the radio buttons, but another frame, which is blank and is titled "tk" appears
This is because you are calling Tk() twice. I see one of them near the end, and you must have another elsewhere in your code.
all but the first radio button is selected, not normally with a dot in the center, but with a hyphen.
This is because you are using a local variable. Change "resolution" to "self.resolution".
when selected and the submit button is pressed, the selected radio button will pass its value to a variable
To do this you need to return the value from the variable, not from the button.
Also, you should put the class definition at the global level.
import tkinter as tk
class ResolutionInputGUI:
def __init__(self, master):
self.master = master
master.title("My GUI")
self.label = tk.Label(master, text="Your Screen Resolution Is: " + screenres + "\n")
self.label.pack()
MODES = [
("500×500", "500×500"),
("1280×800", "1280×800"),
("1280×1024", "1280×1024"),
("1440×900", "1440×900"),
("1680×1050", "1680×1050"),
("1920×1080", "1920×1080"),
("1920×1200", "1920×1200")
]
self.resolution = tk.StringVar(master, value="500×500")
for text, mode in MODES:
self.radiobutton = tk.Radiobutton(master, text=text, variable=self.resolution, value=mode)
self.radiobutton.pack(anchor=tk.W)
self.submit_button = tk.Button(master, text="Submit", command=self.submit)
self.submit_button.pack()
self.cancel_button = tk.Button(master, text="Cancel", command=self.cancelbutton)
self.cancel_button.pack()
def submit(self):
global screenres
screenres = self.resolution.get()
root.quit()
self.master.destroy()
print(screenres)
def cancelbutton(self):
raise SystemExit
if urldata == None:
root = tk.Tk()
my_gui = ResolutionInputGUI(root)
root.mainloop()

Python3 class function definition confusion about NameError

I'm new to programming and this is my first post on the site. I'm sure I'm making a dumb mistake, but I'd really appreciate a push in the right direction. I'm trying to make a calculator, and want to make a function that produces a Button object for numbers. When I try to run this I get the error:
'NameError: name 'num_but_gen' is not defined'
Here is the code:
from tkinter import *
WINDOW_HEIGHT = 300
WINDOW_WIDTH = 325
class Window(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def num_but_gen(self, disp, xloc=0, yloc=0, wid=0, hei=0):
self.Button(text='{}'.format(disp),height=hei, width=wid)
self.place(x=xloc, y=yloc)
def init_window(self):
self.master.title('Calculator')
self.pack(fill=BOTH, expand=1)
Button1 = num_but_gen('1', xloc=0, yloc=200, wid=40, hei=40)
root = Tk()
app = Window(root)
root.geometry("{}x{}".format(WINDOW_WIDTH,WINDOW_HEIGHT))
root.mainloop()
Any help would be greatly appreciated! Also bonus points to anyone with suggestions on how to better phrase my question titles in future posts.
jasonharper is right, you need to add self in front of num_but_gen, but there are other problems in your code.
In num_but_gen:
your window class does not have a Button attribute, so you need to remove self. in front of Button
it is not the Window instance but the button that you want to place
you don't need to use text='{}'.format(disp), text=disp does the same.
In init_window:
you store the result of num_but_gen in a variable, but this function returns nothing so that's useless (and capitalized names should not be used for variables, but for class names only)
the width option of a button displaying text is in letters, not in pixels and its height option is in text lines, so wid=40, hei=40 will create a very big button. If you want to set the button size in pixels, you can do it through the place method instead.
Here is the corresponding code:
import tkinter as tk
WINDOW_HEIGHT = 300
WINDOW_WIDTH = 325
class Window(tk.Frame):
def __init__(self, master = None):
tk.Frame.__init__(self, master)
self.master = master
self.init_window()
def num_but_gen(self, disp, xloc=0, yloc=0, wid=0, hei=0):
button = tk.Button(self, text=disp)
button.place(x=xloc, y=yloc, height=hei, width=wid)
def init_window(self):
self.master.title('Calculator')
self.pack(fill=tk.BOTH, expand=1)
self.num_but_gen('1', xloc=0, yloc=200, wid=40, hei=40)
root = tk.Tk()
app = Window(root)
root.geometry("{}x{}".format(WINDOW_WIDTH,WINDOW_HEIGHT))
root.mainloop()

Resources