tkinter packs in wrong window - python-3.x

I am creating the simonsays game and separating into 3 files. The table.py file contains:
from tkinter import *
import random
class Table:
def __init__(self, parent):
self.parent = parent
self.canvas = Canvas(self.parent, height=400, width=400) #tk.
self.canvas.pack()
self.dark = {'r':'darkred', 'g':'darkgreen', 'b':'darkblue', 'y':'darkgoldenrod'}
self.light = {'r':'red', 'g':'green', 'b':'blue', 'y':'goldenrod'}
self.squares = {'r':self.canvas.create_rectangle(0, 0, 200, 200,
fill='darkred', outline='darkred'),
'g':self.canvas.create_rectangle(200, 0, 400, 200,
fill='darkgreen', outline='darkgreen'),
'b':self.canvas.create_rectangle(0, 200, 200, 400,
fill='darkblue', outline='darkblue'),
'y':self.canvas.create_rectangle(200, 200, 400, 400,
fill='darkgoldenrod', outline='darkgoldenrod')}
self.status = Label(text='Good luck!')
self.status.pack(side=BOTTOM, fill=X)
The player file basically has some methods on how the game should work. When I start the main_menu file(the one below) everything is running smoothly besides one thing. The status bar which is a label from the table file (which should be in the window of the game) is packed in the main_menu window. What is the reason for that? (I am still a beginner so dont be too harsh if it is stupid mistake ^_^ and thanks in advance)
from tkinter import *
from table import Table
from player import Player
class Window():
def __init__(self, master):
self.master = master
self.master.geometry('300x100')
self.master.title('Simon says')
self.label1 = Label(self.master, text = 'Welcome to the simon says game', fg='red').pack()
self.button1 = Button(self.master, text = 'Start game',fg = 'blue', command =self.start_game).pack()
self.button2 = Button(self.master, text = 'Quit game',fg = 'blue', command =self.quit_game).pack()
def start_game(self):
root2 = Toplevel(self.master)
table = Table(root2)
player = Player(table)
def quit_game(self):
self.master.destroy()
def main():
root = Tk()
my_main_menu= Window(root)
root.mainloop()
if __name__ == '__main__':
main()
Side question is there a simple function in tkinter to use a picture for a background for the main menu?

Related

Game with username entry using tkinter

I've downloaded a sample snake game from this github and I am desperately trying to modify the code to have an additional username entry window which then displays the username as the game is being played. I'd also hoping to code a leaderboard once I have the current issue sorted out.
My problem is that while the game itself works great as soon as I added the additional code to open the username entry window at the start the window dimension are applied to the game window as well.
Can anyone point out the likely blindingly obvious thing I'm missing here? My code is everything after the comment line.
import tkinter as tk
from random import randint
from PIL import Image, ImageTk
from tkinter import messagebox
MOVE_INCREMENT = 20
MOVES_PER_SECOND = 15
GAME_SPEED = 1000 // MOVES_PER_SECOND
def StartGame():
class Snake(tk.Canvas):
def __init__(self):
super().__init__(
width=600, height=620, background="black", highlightthickness=0
)
self.snake_positions = [(100, 100), (80, 100), (60, 100)]
self.food_position = self.set_new_food_position()
self.direction = "Right"
self.score = 0
self.load_assets()
self.create_objects()
self.bind_all("<Key>", self.on_key_press)
self.pack()
self.after(GAME_SPEED, self.perform_actions)
def load_assets(self):
try:
self.snake_body_image = Image.open("./assets/snake.png")
self.snake_body = ImageTk.PhotoImage(self.snake_body_image)
self.food_image = Image.open("./assets/food.png")
self.food = ImageTk.PhotoImage(self.food_image)
except IOError as error:
print(error)
root.destroy()
def create_objects(self):
self.create_text(
45, 12, text=f"Score: {self.score}", tag="score", fill="#fff", font=(10)
)
self.create_text(
150, 12, text=f"Username: {username}", tag="username", fill="#fff", font=(10)
)
for x_position, y_position in self.snake_positions:
self.create_image(
x_position, y_position, image=self.snake_body, tag="snake"
)
self.create_image(*self.food_position, image=self.food, tag="food")
self.create_rectangle(7, 27, 593, 613, outline="#525d69")
def check_collisions(self):
head_x_position, head_y_position = self.snake_positions[0]
return (
head_x_position in (0, 600)
or head_y_position in (20, 620)
or (head_x_position, head_y_position) in self.snake_positions[1:]
)
def check_food_collision(self):
if self.snake_positions[0] == self.food_position:
self.score += 1
self.snake_positions.append(self.snake_positions[-1])
self.create_image(
*self.snake_positions[-1], image=self.snake_body, tag="snake"
)
self.food_position = self.set_new_food_position()
self.coords(self.find_withtag("food"), *self.food_position)
score = self.find_withtag("score")
self.itemconfigure(score, text=f"Score: {self.score}", tag="score")
def end_game(self):
self.delete(tk.ALL)
self.create_text(
self.winfo_width() / 2,
self.winfo_height() / 2,
text=f"Game over! You scored {self.score}!",
fill="#fff",
font=("", 10)
)
def move_snake(self):
head_x_position, head_y_position = self.snake_positions[0]
if self.direction == "Left":
new_head_position = (head_x_position - MOVE_INCREMENT, head_y_position)
elif self.direction == "Right":
new_head_position = (head_x_position + MOVE_INCREMENT, head_y_position)
elif self.direction == "Down":
new_head_position = (head_x_position, head_y_position + MOVE_INCREMENT)
elif self.direction == "Up":
new_head_position = (head_x_position, head_y_position - MOVE_INCREMENT)
self.snake_positions = [new_head_position] + self.snake_positions[:-1]
for segment, position in zip(self.find_withtag("snake"), self.snake_positions):
self.coords(segment, position)
def on_key_press(self, e):
new_direction = e.keysym
all_directions = ("Up", "Down", "Left", "Right")
opposites = ({"Up", "Down"}, {"Left", "Right"})
if (
new_direction in all_directions
and {new_direction, self.direction} not in opposites
):
self.direction = new_direction
def perform_actions(self):
if self.check_collisions():
self.end_game()
self.check_food_collision()
self.move_snake()
self.after(GAME_SPEED, self.perform_actions)
def set_new_food_position(self):
while True:
x_position = randint(1, 29) * MOVE_INCREMENT
y_position = randint(3, 30) * MOVE_INCREMENT
food_position = (x_position, y_position)
if food_position not in self.snake_positions:
return food_position
root = tk.Tk()
root.title("Snake")
root.resizable(False, False)
root.tk.call("tk", "scaling", 4.0)
board = Snake()
root.mainloop()
# My code
window = tk.Tk()
window.title("Snake game")
window.geometry('250x120')
Usern_label= tk.Label(window, text="Username: ", font=("Arial Bold", 10))
Usern_label.place(x=10, y=10)
UsernInput = tk.Text(window, bg='White', bd=5, width=15, height=1)
UsernInput.pack
UsernInput.place(x=90, y=10)
username = UsernInput.get("1.0", "end-1c")
if len(username) > 8:
messagebox.showwarning("Invalid username", "Please enter a valid username")
usern_button = tk.Button(window, text='Start Game', bd='5', command=StartGame)
usern_button.place(x=20, y=75)
window.mainloop()
The cause of your problems is just copying the snake code and putting it in a function. You should take the Snake class out of the function. You should only have one instance of Tk, the root window. Therefore this needs to be created first. I've used .withdraw() to hide it until we need it. Then I've used Toplevel to create another window. This is like Tk but doesn't cause problems. Your current user validation does not work as you get and check the value of UsernInput immediately after creating it. Therefore it will always pass as the length of an empty string is less than 8. Instead you need to do the validation in the StartGame function. I've replaced the Text with an Entry because whilst Text would work fine, Entry is much more well suited to what you need (a single line of text).
When the user presses the button it calls StartGame. This validates the input and if it's valid it destroys the username window and shows the root, then does the same setup stuff as before. Although it isn't necessary in this case it's always a good idea to pass a parent widget to every child. In this case the snake canvas will automatically go to the root window but in your original code this is what caused the window size to be wrong as it automatically went in the username window. I've done this by adding the parent parameter to Snake and passing root. Assuming the Snake code all works fine this code should all work. If you want to use the value of username in Snake then you'll want to pass it as a parameter by changing it to def __init__(self, parent, username):. Then after adding something like self.username = username you can use it anywhere in the class.
import tkinter as tk
from random import randint
from PIL import Image, ImageTk
from tkinter import messagebox
MOVE_INCREMENT = 20
MOVES_PER_SECOND = 15
GAME_SPEED = 1000 // MOVES_PER_SECOND
class Snake(tk.Canvas):
def __init__(self, parent):
super().__init__(
parent, width=600, height=620, background="black", highlightthickness=0
)
# Rest of the Snake class code goes here
root = tk.Tk()
root.title("Snake")
root.withdraw() # Hide root window for now
def StartGame():
# Do the validation after the user has pressed the button
# The input has no value if you call it immediately after
username = UsernInput.get() # The get is easier with an Entry
if len(username) > 8:
messagebox.showwarning("Invalid username", "Please enter a valid username")
window.focus() # Go back to the window after showing the warning
else:
window.destroy() # Get rid of the username input
root.deiconify() # Show the root window again
root.tk.call("tk", "scaling", 4.0)
root.resizable(False, False)
board = Snake(root)
window = tk.Toplevel() # Don't use multiple Tk instances
window.title("Snake game")
window.geometry('250x120')
Usern_label= tk.Label(window, text="Username: ", font=("Arial Bold", 10))
Usern_label.place(x=10, y=10)
# It makes more sense to use an entry for what you are doing
UsernInput = tk.Entry(window, bg='White', bd=5, width=15)
UsernInput.place(x=90, y=10)
usern_button = tk.Button(window, text='Start Game', bd='5', command=StartGame)
usern_button.place(x=20, y=75)
root.mainloop()

Tkinter buttons not changing back to the correct color after state changing to active

I am making this PDF tool, and I want the buttons to be disabled until a file or files are successfully imported. This is what the app looks like at the launch:
Right after running the callback for the import files button, the active state looks like this:
I want the colors of the buttons to turn maroon instead of the original grey. They only turn back to maroon once you hover the mouse over them. Any thoughts for how to fix this? Here is the callback for the import button:
def import_callback():
no_files_selected = False
global files
files = []
try:
ocr_button['state'] = DISABLED
merge_button['state'] = DISABLED
status_label.pack_forget()
frame.pack_forget()
files = filedialog.askopenfilenames()
for f in files:
name, extension = os.path.splitext(f)
if extension != '.pdf':
raise
if not files:
no_files_selected = True
raise
if frame.winfo_children():
for label in frame.winfo_children():
label.destroy()
make_import_file_labels(files)
frame.pack()
ocr_button['state'] = ACTIVE
merge_button['state'] = ACTIVE
except:
if no_files_selected:
status_label.config(text='No files selected.', fg='blue')
else:
status_label.config(text='Error: One or more files is not a PDF.', fg='red')
status_label.pack(expand='yes')
import_button = Button(root, text='Import Files', width=scaled(20), bg='#5D1725', bd=0, fg='white', relief='groove',
command=import_callback)
import_button.pack(pady=scaled(50))
I know this was asked quite a while ago, so probably already solved for the user. But since I had the exact same problem and do not see the "simplest" answer here, I thought I would post:
Just change the state from "active" to "normal"
ocr_button['state'] = NORMAL
merge_button['state'] = NORMAL
I hope this helps future users!
As I understand you right you want something like:
...
ocr_button['state'] = DISABLED
ocr_button['background'] = '#*disabled background*'
ocr_button.bind('<Enter>', lambda e:ocr_button.configure(background='#...'))
ocr_button.bind('<Leave>', lambda e:ocr_button.configure(background='#...'))
merge_button['state'] = DISABLED
merge_button['background'] = '#*disabled background*'
merge_button.bind('<Enter>', lambda e:ocr_button.configure(background='#...'))
merge_button.bind('<Leave>', lambda e:ocr_button.configure(background='#...'))
...
...
ocr_button['state'] = ACTIVE
ocr_button['background'] = '#*active background*'
ocr_button.unbind('<Enter>')
ocr_button.unbind('<Leave>')
merge_button['state'] = ACTIVE
merge_button['background'] = '#*active background*'
merge_button.unbind('<Enter>')
merge_button.unbind('<Leave>')
...
If there are any errors, since I wrote it out of my mind or something isnt clear, let me know.
Update
the following code reproduces the behavior as you stated. The reason why this happens is how tkinter designed the standart behavior. You will have a better understanding of it if you consider style of ttk widgets. So I would recommand to dont use the automatically design by state rather write a few lines of code to configure your buttons how you like, add and delete the commands and change the background how you like. If you dont want to write this few lines you would be forced to use ttk.Button and map a behavior you do like
import tkinter as tk
root = tk.Tk()
def func_b1():
print('func of b1 is running')
def disable_b1():
b1.configure(bg='grey', command='')
def activate_b1():
b1.configure(bg='red', command=func_b1)
b1 = tk.Button(root,text='B1', bg='red',command=func_b1)
b2 = tk.Button(root,text='disable', command=disable_b1)
b3 = tk.Button(root,text='activate',command=activate_b1)
b1.pack()
b2.pack()
b3.pack()
root.mainloop()
I've wrote this simple app that I think could help all to reproduce the problem.
Notice that the state of the button when you click is Active.
#!/usr/bin/python3
import sys
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
class Main(ttk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__()
self.parent = parent
self.init_ui()
def cols_configure(self, w):
w.columnconfigure(0, weight=0, minsize=100)
w.columnconfigure(1, weight=0)
w.rowconfigure(0, weight=0, minsize=50)
w.rowconfigure(1, weight=0,)
def get_init_ui(self, container):
w = ttk.Frame(container, padding=5)
self.cols_configure(w)
w.grid(row=0, column=0, sticky=tk.N+tk.W+tk.S+tk.E)
return w
def init_ui(self):
w = self.get_init_ui(self.parent)
r = 0
c = 0
b = ttk.LabelFrame(self.parent, text="", relief=tk.GROOVE, padding=5)
self.btn_import = tk.Button(b,
text="Import Files",
underline=1,
command = self.on_import,
bg='#5D1725',
bd=0,
fg='white')
self.btn_import.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
self.parent.bind("<Alt-i>", self.switch)
r +=1
self.btn_ocr = tk.Button(b,
text="OCR FIles",
underline=0,
command = self.on_ocr,
bg='#5D1725',
bd=0,
fg='white')
self.btn_ocr["state"] = tk.DISABLED
self.btn_ocr.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
r +=1
self.btn_merge = tk.Button(b,
text="Merge Files",
underline=0,
command = self.on_merge,
bg='#5D1725',
bd=0,
fg='white')
self.btn_merge["state"] = tk.DISABLED
self.btn_merge.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
r +=1
self.btn_reset = tk.Button(b,
text="Reset",
underline=0,
command = self.switch,
bg='#5D1725',
bd=0,
fg='white')
self.btn_reset.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
b.grid(row=0, column=1, sticky=tk.N+tk.W+tk.S+tk.E)
def on_import(self, evt=None):
self.switch()
#simulate some import
self.after(5000, self.switch())
def switch(self,):
state = self.btn_import["state"]
if state == tk.ACTIVE:
self.btn_import["state"] = tk.DISABLED
self.btn_ocr["state"] = tk.NORMAL
self.btn_merge["state"] = tk.NORMAL
else:
self.btn_import["state"] = tk.NORMAL
self.btn_ocr["state"] = tk.DISABLED
self.btn_merge["state"] = tk.DISABLED
def on_ocr(self, evt=None):
state = self.btn_ocr["state"]
print ("ocr button state is {0}".format(state))
def on_merge(self, evt=None):
state = self.btn_merge["state"]
print ("merge button state is {0}".format(state))
def on_close(self, evt=None):
self.parent.on_exit()
class App(tk.Tk):
"""Main Application start here"""
def __init__(self, *args, **kwargs):
super().__init__()
self.protocol("WM_DELETE_WINDOW", self.on_exit)
self.set_style()
self.set_title(kwargs['title'])
Main(self, *args, **kwargs)
def set_style(self):
self.style = ttk.Style()
#('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')
self.style.theme_use("clam")
def set_title(self, title):
s = "{0}".format('Simple App')
self.title(s)
def on_exit(self):
"""Close all"""
if messagebox.askokcancel(self.title(), "Do you want to quit?", parent=self):
self.destroy()
def main():
args = []
for i in sys.argv:
args.append(i)
kwargs = {"style":"clam", "title":"Simple App",}
app = App(*args, **kwargs)
app.mainloop()
if __name__ == '__main__':
main()

How to make a button change colors by binding in a class

I tried to use bind to bind the mouse clicks to change colors based on the foreground and background of the buttons
from tkinter import *
class Clicks():
def __init__(self, master):
frame=Frame(master)
frame.pack()
#trying to bind the mouse clicks to change the color of the button
self.button1= Button(frame, text="Click Me!", fg='red', bg='black')
self.button1.bind("<Button-1>", fg='black')
self.button1.bind("<Button-3>", bg='red')
self.button1.grid(row = 0, column = 1, sticky = W)
root = Tk()
b = Clicks(root)
root.mainloop()
TypeError: bind() got an unexpected keyword argument 'fg'
Please check the snippet. You can use 2 approaches here.
First you can bind using lambda function
from tkinter import *
class Clicks():
def __init__(self, master):
frame=Frame(master)
frame.pack()
self.button1= Button(frame, text="Click Me!", fg='red', bg='black')
self.button1.bind("<Button-1>", lambda event, b=self.button1: b.configure(bg="green",fg="blue"))
self.button1.grid(row = 0, column = 1, sticky = W)
root = Tk()
b = Clicks(root)
root.mainloop()
Second you can do via passing command to access function
from tkinter import *
class Clicks():
def __init__(self, master):
frame=Frame(master)
frame.pack()
self.button1= Button(frame, text="Click Me!",command=self.color, fg='red', bg='black')
self.button1.grid(row = 0, column = 1, sticky = W)
def color(self):
self.button1.configure(bg = "green",fg="blue")
root = Tk()
b = Clicks(root)
root.mainloop()

I need help making a tkinter program that variably opens a picture

I'm trying to make a program that has a button that will open a picture if a variable is in a certain state, and change how the button looks (or maybe show a different picture) if it's not. I have been trying to work through the bugs I've been getting.
This is honestly intermediary code so I can understand how to make what I'm actually trying to do, make a network-enabled GUI for some physical buttons.
I've tried passing blueButton in as a variable, but that didn't work.
import tkinter as tk
weather = "sunny"
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.create_widgets()
def create_widgets(self):
self.blueButton = tk.Button(self, fg = "blue")
self.blueButton["text"] = "I'm Blue"
self.blueButton["command"] = self.change
self.blueButton.pack(anchor="nw")
self.quit = tk.Button(self, text = "QUIT", fg = "red",
command = self.master.destroy)
self.quit.pack(side="bottom")
self.pack(fill = "both", expand = 1)
def change(self):
global weather
if weather == "sunny":
w = tk.Canvas(root, width=400, height=750)
img = tk.PhotoImage(file = "haunter.gif")
w.create_image((200, 200), image = img)
w.pack()
else:
self.blueButton["bitmap"] = "error"
root = tk.Tk()
root.geometry("400x300")
app = Application(master = root)
app.mainloop()
The canvas gets made, but the picture doesn't show up, the "quit" button just moves.
I've also gotten the error "name blueButton is not defined".
You could keep the image as an attribute of your App, put it on a canvas, then show or hide the canvas depending on the weather.
import random
import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.master = master
self.pack()
self.create_widgets()
def create_widgets(self):
self.blueButton = tk.Button(self, fg = "blue")
self.blueButton["text"] = "I'm Blue"
self.blueButton["command"] = self.change
self.blueButton.pack()
self.quit = tk.Button(self, text = "QUIT", fg = "red",
command = self.master.destroy)
self.quit.pack()
self.pack(fill = "both", expand = 1)
self.canvas = tk.Canvas(root, width=400, height=750)
# self.sunny_img = tk.PhotoImage(file="haunter.gif")
self.sunny_img = tk.PhotoImage(file="rapporteur.gif")
self.canvas.create_image((200, 200), image=self.sunny_img)
def change(self):
weather = ['sunny', 'rainy']
current_weather = random.choice(weather)
if current_weather == 'sunny':
self.canvas.pack()
self.blueButton["bitmap"] = ''
else:
self.canvas.pack_forget()
self.blueButton["bitmap"] = "error"
root = tk.Tk()
root.geometry("400x300")
app = Application(master = root)
app.mainloop()

How to open a second ptinker Window separately rather than as a tab

I am trying to open a second window in tkinter but it always appears as a tab rather than separately. Code showing the problem is as follows:
import tkinter as tk
class MainWindow:
def __init__(self, master):
self.master = master
frame = tk.Frame(self.master)
button = tk.Button(frame, text = 'New Window', width = 25, command = self.new_window)
button.pack()
frame.pack()
def new_window(self):
newWindow = tk.Toplevel(self.master)
SecondWindow(newWindow)
class SecondWindow:
def __init__(self, master):
frame = tk.Frame(master)
quitButton = tk.Label(frame, text = 'Second Window')
quitButton.pack()
frame.pack()
def main():
root = tk.Tk()
app = MainWindow(root)
root.mainloop()
if __name__ == '__main__':
main()
When I run this I get the following output:
Clicking the button gives:
(Ignoring the secondary issue of the size) if we expand it we get the following:
I can get a separate window by dragging the tab. How to I code this so that I get the window displaying as a separate window when I click the button ?

Resources