How to allow Tkinter GUI to detect key presses when not selected - python

I am trying to make a full GUI, for A game borderlands 2, however it only detects my key presses when the tkinter box is selected (in focus). In a game this is not happening, so I need a way for tkinter to detect my key presses while not selected.
This is the code so far. It is not finished, ignore the "Auto Exec" and "Auto Exit" the problem is "Auto Reload". Clicking once will turn it ON and again will turn it OFF. Just choose any number (1-4) for the weapon slot, does not affect the error.
from easygui import *
from pyautogui import *
from time import *
import os
from tkinter import *
count = 1
slot = "0"
allowReload, allowExit = False, False
def poop():
sleep(3)
print("poop")
def countdown(count):
autoexecB.config(text = count)
if count == 2:
autoexecB.config(bg = "orange")
if count == 1:
autoexecB.config(bg = "yellow")
if count == 0:
autoexecB.config(text = "Running...", bg = "#44ff00")
if count > -1:
root.after(1000, countdown, count-1)
else:
for i in range(1, 3):
sleep(1)
press("'")
sleep(0.5)
type("exec Patch.txt")
press("enter")
press("esc")
press("enter")
sleep(4)
press("esc")
pressMult("down", 4)
press("enter")
press("up")
press("enter")
sleep(1)
press("'")
sleep(0.5)
type("exec STV.txt")
press("enter")
press("esc")
autoexecB.config(text = "Auto Exec", bg = "red")
def type(text):
typewrite(text)
def pressMult(key, amount):
for i in range(1, amount+1):
press(key)
def autoexec():
countdown(3)
def info():
msgbox("Auto Exec: Runs Mods automaticly\nAuto Exit: Exits the game automaticly to the main menu using INSERT\nAuto Reload: Automaticly reloads your gun using LEFT SHIFT")
def exit():
global count
count = 1
press("esc")
pressMult("down", 4)
press("enter")
press("up")
press("enter")
sleep(2.1)
press("enter")
if choose == "FARM at Hero's Pass":
sleep(3)
keyDown("w")
sleep(0.7)
keyUp("w")
keyDown("d")
sleep(1)
keyUp("d")
keyDown("ctrl")
sleep(0.5)
press("e")
keyUp("ctrl")
count += 1
def reloadslot():
global allowReload, slot
while True:
if allowReload == True:
break
slot = str(integerbox("Enter in the weapon's slot to be reloaded"))
if slot not in ("1","2","3","4"):
msgbox("A weapon can only be in slot 1, 2, 3 or 4")
else:
break
if allowReload == True:
allowReload = False
else:
allowReload = True
def on_press(event):
print(event.keysym)
if event.keysym == "Insert" and allowExit == True:
print("exit")
exit()
if event.keysym == "Shift_L" and allowReload == True:
print("running reload")
press(";")
press("e")
press(slot)
print("done")
root = Tk()
root.bind('<KeyPress>', on_press)
root.geometry("378x134")
root.config(bg = "blue")
autoexecB = Button(text = "Auto Exec", bg = "red", font = ("calibri","13"), height = 3, width = 13, command = lambda: autoexec())
autoexitB = Button(text = "Auto Exit", bg = "red", font = ("calibri","13"), height = 3, width = 13)
autoreloadB = Button(text = "Auto Reload", bg = "red", font = ("calibri","13"), height = 3, width = 13, command = lambda: reloadslot())
infoB = Button(text = "INFO", bg = "blue", width = 26, height = 3, command = lambda: info())
exitB = Button(text = "EXIT", bg = "blue", width = 25, height = 3, command = lambda: root.destroy())
autoexecB.place(x = 0, y = 0)
autoexitB.place(x = 126, y = 0)
autoreloadB.place(x = 252, y = 0)
infoB.place(x = 0, y = 78)
exitB.place(x = 193, y = 78)
root.mainloop()
root.mainloop()

I need a way for tkinter to detect my key presses while not selected
You can't use tkinter for that. It can only detect keyboard events when it has the keyboard focus. You will have to use some platform-specific library to do what you want.

Related

After() method in tkinter for timer

I want to start a timer when the user clicks a button for the first time in my number click game. I tried to use the after method for this, but when I click a button, the timer stays at 0. The rest of the code works fine without any error messages.
Here's the code:
import tkinter as tk
from random import randint
# create window
window = tk.Tk()
window.title('Clicker')
# create list for random numbers
new_list = []
# define time count:
def time_event():
global current_time, after_id
if clock_started:
current_time += 1
clock["text"] = str(current_time)
after_id = clock.after(1000, time_event)
# define click event
def click_event(event):
global clock_started
new_button = event.widget
clicked_val = int(new_button["text"])
clock_started = True
if not clock_started:
clock_started = True
clock.after(1000, time_event)
if clicked_val == new_list[0]:
del new_list[0]
new_button["state"] = tk.DISABLED
if len(new_list) == 0:
clock.started = False
clock.after_cancel(after_id)
# create buttons
for i in range(25):
new_num = randint(1, 999)
while i in new_list:
new_num = randint(1, 999)
new_list.append(new_num)
new_list.sort()
new_button = tk.Button(window, text=new_num)
new_button.grid(column=i // 5, row=i % 5)
new_button.bind("<Button-1>", click_event)
# create clock
current_time = 0
clock = tk.Label(window, text=str(current_time))
clock.grid(column=2, row=6)
clock_started = False
# run game
window.mainloop()
In your code, clock_started has been initialized to True which implies that this condition if not clock_started: will not be satisfied to begin with and hence the timer doesn't work without giving an error. Your final click_event(event) should look like this:
def click_event(event):
global clock_started
new_button = event.widget
clicked_val = int(new_button["text"])
clock_started = False
if not clock_started:
clock_started = True
clock.after(1000, time_event)
if clicked_val == new_list[0]:
del new_list[0]
new_button["state"] = tk.DISABLED
if len(new_list) == 0:
clock.started = False
clock.after_cancel(after_id)

time.sleep skips multiple lines in my script

def fctCountdown():
t = 3
countdown = Label(Pong, text = "3", font = ("Fixedsys", 30), bg = "#CB997E", fg = 'black')
countdown.place(x = 387, y = 300)
while t >= 0:
if t == 2:
countdown.config(text = "2")
if t == 1:
countdown.config(text = "1")
if t == 0:
countdown.config(text = "Start !")
sleep(1)
t -= 1
countdown.place_forget()
def fctGoDown():
global x1, y1
def fctGameMenu():
background_image = PhotoImage(master = Pong, file = "fondpong0.png")
background_label = Label(Pong, image = background_image)
background_label.place(x = 0, y = 0)
background_label.image = background_image
def fctStartGame(evt):
global state
if state == 0:
state = 1
fctGameMenu()
sleep(1)
fctCountdown()
Hi guys ! I'm asking you for help because the sleep(1) line in fctStartGame is executed before the fctGameMenu() and i don't understand why. Because of it my countdown doesn't work.

tkinter countdown app stop and reset button not working

I have written a first attempt at a tkinter countdown app and I'm struggling with the stop and reset buttons as well as parts of the interface design.
My stop button does nothing at all whereas my reset button resets the values, but then continues the countdown immediately. I suspect it's because I have set self.running to True in the init method and that my app loops through those lines again and again. How do I prevent that from happening?
The other issue is that my width argument in all of my entry fields gets seemingly ignored. How can I assign the same width to all three fields?
Thanks for your help.
from tkinter import *
import time
from tkinter import messagebox
class Application(Frame):
def __init__(self,master):
super(Application,self).__init__(master)
self.place()
self.widgets()
self.running = True
def widgets(self):
self.hour = StringVar()
self.hour.set("00")
# Why is the width ignored?
self.hour_entry = Entry(width = 7, font=("Calibri",14,""), textvariable = self.hour)
self.hour_entry.place(x = 10, y = 10)
self.minute = StringVar()
self.minute.set("00")
self.minute_entry = Entry(width = 7, font=("Calibri",14,""), textvariable = self.minute)
self.minute_entry.place(x = 30, y = 10)
self.second = StringVar()
self.second.set("00")
self.seconds_entry = Entry(width = 7, font=("Calibri",14,""), textvariable = self.second)
self.seconds_entry.place(x = 50, y = 10)
self.start_btn = Button(text = "Start", command=self.clock)
self.start_btn.place(x = 30, y = 100)
self.stop_btn = Button(text = "Stop", command=self.stop)
self.stop_btn.place(x = 70, y = 100)
self.reset_btn = Button(text = "Reset", command=self.reset)
self.reset_btn.place(x = 110, y = 100)
def clock(self):
if self.running == True:
self.time_total = int(self.hour_entry.get())*3600 + int(self.minute_entry.get())*60 + int(self.seconds_entry.get())
while self.time_total > -1:
# returns 3600/60 = 60 with 0 left: so that's 60 min, 0 seconds
self.mins, self.secs = divmod(self.time_total,60)
self.hours = 0
if self.mins > 60:
self.hours, self.mins = divmod(self.mins, 60)
self.hour.set("{0:02d}".format(self.hours))
self.minute.set("{0:02d}".format(self.mins))
self.second.set("{0:02d}".format(self.secs))
self.time_total -= 1
root.update()
time.sleep(1)
if self.time_total == 0:
messagebox.showinfo("Time Countdown", "Time's up!")
def start(self):
self.running = True
self.clock()
def stop(self):
self.running = False
def reset(self):
self.running = False
self.hour.set("00")
self.minute.set("00")
self.second.set("00")
if __name__ == '__main__':
root = Tk()
app = Application(root)
mainloop()
For the timer, use the after method instead of sleep.
The entry widths are being used, but the place method puts one entry on top of another.
Also - Call the start method from the button click to reset the running flag.
Try this code:
from tkinter import *
import time
from tkinter import messagebox
class Application(Frame):
def __init__(self,master):
super(Application,self).__init__(master)
self.place()
self.widgets()
self.running = False
def widgets(self):
self.hour = StringVar()
self.hour.set("00")
# Why is the width ignored?
self.hour_entry = Entry(width = 4, font=("Calibri",14,""), textvariable = self.hour)
self.hour_entry.place(x = 10, y = 10)
self.minute = StringVar()
self.minute.set("00")
self.minute_entry = Entry(width = 4, font=("Calibri",14,""), textvariable = self.minute)
self.minute_entry.place(x = 60, y = 10)
self.second = StringVar()
self.second.set("00")
self.seconds_entry = Entry(width = 4, font=("Calibri",14,""), textvariable = self.second)
self.seconds_entry.place(x = 110, y = 10)
self.start_btn = Button(text = "Start", command=self.start)
self.start_btn.place(x = 30, y = 100)
self.stop_btn = Button(text = "Stop", command=self.stop)
self.stop_btn.place(x = 70, y = 100)
self.reset_btn = Button(text = "Reset", command=self.reset)
self.reset_btn.place(x = 110, y = 100)
def clock(self):
if self.running == True:
# returns 3600/60 = 60 with 0 left: so that's 60 min, 0 seconds
self.mins, self.secs = divmod(self.time_total,60)
self.hours = 0
if self.mins > 60:
self.hours, self.mins = divmod(self.mins, 60)
self.hour.set("{0:02d}".format(self.hours))
self.minute.set("{0:02d}".format(self.mins))
self.second.set("{0:02d}".format(self.secs))
root.update()
#time.sleep(1)
root.after(1000, self.clock) # wait 1 second, re-call clock
if self.time_total == 0:
self.running = False
messagebox.showinfo("Time Countdown", "Time's up!")
self.time_total -= 1
def start(self):
self.time_total = int(self.hour_entry.get())*3600 + int(self.minute_entry.get())*60 + int(self.seconds_entry.get())
self.running = True
self.clock()
def stop(self):
self.running = False
def reset(self):
self.running = False
self.hour.set("00")
self.minute.set("00")
self.second.set("00")
if __name__ == '__main__':
root = Tk()
app = Application(root)
mainloop()

Maximum recursion error depth causing crash?

I'm making Minesweeper with tkinter. I want to have it so that when a button with 0 mines surrounding it is clicked, all the buttons around that one are automatically clicked. However, when I run this and click on a button with 0, the program crashes. (originally it gave me the error of "maximum recursion depth exceeded"). How do I fix this? Here is the full code:
import tkinter as tk
import sys
import random
from tkinter import messagebox
from PIL import Image, ImageTk
from types import FunctionType
from copy import copy
app = tk.Tk()
app.geometry("432x468")
app.title("Minesweeper")
sys.setrecursionlimit(999999999)
colors = ['#FFFFFF', '#0000FF', '#008200', '#FF0000', '#000084', '#840000', '#008284', '#840084', '#000000']
# create lists
def createlists():
global buttonlist
global mylist
global numberlist
global panelist
global rightlist
global flaglist
global rowlist
global columnlist
global abomb
global adic
global secondlist
secondlist = []
abomb = []
adic = {}
buttonlist = []
mylist = [0]
numberlist = []
panelist = []
rightlist = []
flaglist = []
rowlist = []
columnlist = []
for a in range(1,18):
mylist.append(18*a)
for i in range(1,325):
button = "b" + str(i)
flag = 'flag' + str(i)
row = 'row' + str(i)
column = 'column' + str(i)
buttonlist.append(button)
numberlist.append(i)
secondlist.append(i)
flaglist.append(flag)
rowlist.append(row)
columnlist.append(column)
# randomly select bombs
def selectbombs():
for i in range(0,50):
x = "panel" + str(bomblist[i])
panelist.append(x)
for i in bomblist:
n = "a" + str(i)
abomb.append(n)
secondlist.remove(i)
# create function for when a bomb is clicked
def mine():
global bomblist
for i in range(0,50):
panelist[i] = tk.Label(app, image = bomb)
for x in mylist:
for i in range(x,x+18):
if i+1 in bomblist:
thing = bomblist.index(i+1)
panelist[thing].grid(row=mylist.index(x)+1, column=i-x+1, columnspan=1)
MsgBox = tk.messagebox.askquestion ('Game Over', 'You clicked on a bomb! Game Over. Would you like to play again?')
if MsgBox == 'no':
app.destroy()
else:
createlists()
bomblist = random.sample(numberlist, 50)
selectbombs()
buttons()
numbers()
flags()
# create grid of buttons
def buttons():
for i in range(0,324):
if i+1 in bomblist:
buttonlist[i] = tk.Button(app, text="", width=2, height=1, command=mine)
else:
buttonlist[i] = tk.Button(app, text="", width=2, height=1)
for x in mylist:
for i in range(x,x+18):
buttonlist[i].grid(row=mylist.index(x)+1, column=i-x+1, columnspan=1)
rowlist[i] = mylist.index(x)+1
columnlist[i] = i-x+1
# determine the number of bombs adjacent to each button
def numbers():
for i in range(1,325):
adic.update({"a"+str(i) : 0})
alist = list(adic)
for i in bomblist:
for x in numberlist:
if rowlist[x-1] in range(rowlist[numberlist.index(i)]-1, rowlist[numberlist.index(i)]+2) and columnlist[x-1] in range(columnlist[numberlist.index(i)]-1, columnlist[numberlist.index(i)]+2):
adic[alist[x-1]] = adic[alist[x-1]] + 1
for i in bomblist:
del adic[alist[numberlist.index(i)]]
alist = list(adic)
for i in adic:
buttonlist[secondlist[alist.index(i)]-1].bind("<Button-1>", lambda event, x=secondlist[alist.index(i)]-1, y=adic[i] : num(x, y))
# number functions
def num(x, y):
a = rowlist[x]
b = columnlist[x]
if y==0:
buttonlist[x].config(bg = '#FFFFFF')
buttonlist[x]["state"] = "disabled"
if a != 1 and b != 1:
num(x-19, adic["a"+str(x-18)])
if a != 1:
num(x-18, adic["a"+str(x-17)])
if a != 1 and b != 18:
num(x-17, adic["a"+str(x-16)])
if b != 1:
num(x-1, adic["a"+str(x)])
if b != 18:
num(x+1, adic["a"+str(x+2)])
if a != 18 and b != 1:
num(x+17, adic["a"+str(x+18)])
if a != 18:
num(x+18, adic["a"+str(x+19)])
if a != 18 and b != 18:
num(x+19, adic["a"+str(x+20)])
else:
buttonlist[x].config(text = y, disabledforeground = colors[y], bg = '#FFFFFF')
buttonlist[x]["state"] = "disabled"
# create function to place a flag
im = Image.open("flag.png")
im = im.resize((20,20), Image.ANTIALIAS)
flag = ImageTk.PhotoImage(im)
def flags():
for i in range(0,324):
buttonlist[i].bind("<Button-3>", lambda event, x=i : right(event, x))
def right(event, x):
if buttonlist[x]["state"] == "normal":
flaglist[x] = tk.Button(app, text = "", width=18, height=19, image = flag)
flaglist[x].grid(row=rowlist[x], column=columnlist[x], columnspan=1)
flaglist[x].bind("<Button-1>", lambda event: flaglist[x].destroy())
# check if the game has been won
def checkwin():
disnum = 0
for i in secondlist:
if buttonlist[i-1]["state"] == "disabled":
disnum = disnum + 1
if disnum == 274:
MsgBox = tk.messagebox.askquestion ('Game Won', 'You have won the game! Would you like to play again?')
if MsgBox == 'no':
app.destroy()
else:
createlists()
bomblist = random.sample(numberlist, 50)
selectbombs()
buttons()
numbers()
flags()
# open images
img = Image.open("bomb.png")
img = img.resize((20,20), Image.ANTIALIAS)
bomb = ImageTk.PhotoImage(img)
createlists()
bomblist = random.sample(numberlist, 50)
selectbombs()
buttons()
numbers()
flags()
app.mainloop()
the specific part which is causing the program to crash:
def num(x, y):
a = rowlist[x]
b = columnlist[x]
if y==0:
buttonlist[x].config(bg = '#FFFFFF')
buttonlist[x]["state"] = "disabled"
if a != 1 and b != 1:
num(x-19, adic["a"+str(x-18)])
if a != 1:
num(x-18, adic["a"+str(x-17)])
if a != 1 and b != 18:
num(x-17, adic["a"+str(x-16)])
if b != 1:
num(x-1, adic["a"+str(x)])
if b != 18:
num(x+1, adic["a"+str(x+2)])
if a != 18 and b != 1:
num(x+17, adic["a"+str(x+18)])
if a != 18:
num(x+18, adic["a"+str(x+19)])
if a != 18 and b != 18:
num(x+19, adic["a"+str(x+20)])
else:
buttonlist[x].config(text = y, disabledforeground = colors[y], bg = '#FFFFFF')
buttonlist[x]["state"] = "disabled"
The issue is that in the recursion process, the code is backtracking on previous squares. When checking a new square, be sure it has not already be checked.
In the num function, add a line of code to skip squares that have already been disabled:
def num(x, y):
if buttonlist[x]["state"] == "disabled": return # add this line
a = rowlist[x]
b = columnlist[x]

How do you disable a button when it is clicked?

I am new to python, and self taught. I am trying to make a simplifed minesweeper game called "Mines". This game has a 5 by 5 grid of buttons, with a random number of mines. Everything is working fine, except for one issue. After you click a safe space, the button should be disabled so you cannot gain any more points. With my code, I have no idea how to do this.
I did already try to call a method to disable the button, as well as using the DISABLED command. Neither worked. I want the "white" buttons to be disabled after click, as those are the safe spaces. In a perfect world, I will call the disable() method when the button is clicked and use that method to disable that button.
##Create the canvas of the board.
window = Tk ()
window.title("Mines Game")
window.geometry('819x655')
##Scoring function. The first box clicked awards 1 point, and after that each box is worth double your total points.
def score():
global scores
if (scores == 0):
scores = scores + 1
print ("Safe space hit! You have " + str(scores) + " point.")
else:
scores = scores * 2
print ("Safe space hit! You have " + str(scores) + " points.")
def squares():
##Main method of creating a 5x5 square of buttons.
global scores
scores = 0
less_bombs = True
r = -1
c = 0
x = 0
if less_bombs:
for i in range(5):
r = r + 1
for l in range(5):
y = randint(0,25)
if x == 5:
y = 10
if y < 6:
btn = Button(window, bg = "red", command=end)
btn.grid(column = c,row = r)
#btn.grid(sticky="nesw")
btn.config(height = 8, width = 22)
x = x + 1
else:
btn = Button(window, bg = "white", command=lambda:[score(),DISABLED])
btn.grid(column = c,row = r)
#btn.grid(sticky="nesw")
btn.config(height = 8, width = 22)
c = c + 1
c = 0
def end():
##This method creates the popup after you click a mine.
end = Tk ()
end.title ('Game Over!')
end.geometry ('300x161')
btn = Button(end, text ="Close game", command=close)
btn.grid(column = 0,row = 0)
#btn.grid(sticky="nesw")
btn.config(height = 10, width = 20)
btn = Button(end, text ="Reset game", command=lambda:[reset(),end.destroy()])
btn.grid(column = 1,row = 0)
#btn.grid(sticky="nesw"
btn.config(height = 10, width = 20)
if (scores == 1):
print ("Game over! You hit a mine :(")
print ("Your score for that game was " + str(scores) + " point.")
if (scores != 1):
print ("Game over! You hit a mine :(")
print ("Your score for that game was " + str(scores) + " points.")
def disable():
pass
def close():
sys.exit()
def reset():
squares()
squares()
window.mainloop()
I want the button to be disabled after being clicked, however currently the player can click the button as many times as he or she wants.
You can fix it by changing creation of white Buttonss as indicated below in ALL CAPS. This gives the lambda function a default value for btn which changes each iteration of loop (otherwise the function would always refer to the last one created in the for loop).
def squares():
""" Main method of creating a 5x5 square of buttons. """
global scores
scores = 0
less_bombs = True
r = -1
c = 0
x = 0
if less_bombs:
for i in range(5):
r = r + 1
for l in range(5):
y = randint(0,25)
if x == 5:
y = 10
if y < 6:
btn = Button(window, bg="red", command=end)
btn.grid(column=c, row=r)
#btn.grid(sticky="nesw")
btn.config(height=8, width=22)
x = x + 1
else:
btn = Button(window, bg="white") ## REMOVE command= OPTION. ##
btn.grid(column=c, row=r)
#btn.grid(sticky="nesw")
btn.config(height=8, width=22, # USE command SHOWN BELOW. ##
command=lambda btn=btn: [score(), btn.config(state=DISABLED)])
c = c + 1
c = 0

Categories