Why does tkinter sometimes work without mainloop in Python 3? - python-3.x

Here's code for a little game. You're supposed to hit buttons that say 'click' to score points and avoid hitting buttons that say 'clack' or 'cluck' which cause you to lose points. The first weird thing is that the program works fine under IDLE even though it doesn't include a call to mainloop. The second is that it stops working if we add the following line at the bottom:
input('Hit enter to continue')
No game window appears, even after we hit enter. Adding a different line at the end like
print('hello')
does not have this effect.
Can anyone explain to me what's going on? I know that GUI programs used to run under IDLE's own mainloop, but that was a long time ago in Python 2 and definitely wasn't generally true under Python 3, at least last time I checked.
from tkinter import *
import random
score = 0
root = Tk()
scoreFrame = Frame(root)
scoreFrame.pack(expand = YES, fill = BOTH)
scoreLabel = Label(scoreFrame)
scoreLabel.pack()
def showScore():
scoreLabel['text'] = 'Score: {0}'.format(score)
scoreLabel.pack()
clickFrame = Frame(root)
clickFrame.pack(side = BOTTOM, expand = YES, fill = BOTH)
def changeLabels():
for button in buttons:
button['text'] = random.choice(['click', 'clack', 'cluck'])
button['bg'] = buttonDefaultColor
root.after(1500, changeLabels)
def makeButton():
button = Button(clickFrame)
def cmd():
global score
if button['bg'] == buttonDefaultColor:
if button['text'] == 'click':
score += 10
button['bg'] = 'light green'
else:
score -= 10
button['bg'] = 'light yellow'
button['command'] = cmd
button.pack(side = LEFT, expand = YES, fill = BOTH)
return button
buttons = [makeButton() for i in range(5)]
buttonDefaultColor = buttons[0]['bg']
changeLabels()
showScore()
Added: The first three comments all suggest that IDLE is running the event loop for me, making mainloop unnecessary but (1) I remember clearly when this suddenly stopped being true some years back (not that IDLE stopped running mainloop, but that GUI programs running under it did not need to run mainloop themselves) and (2) no one has explained why the input statement at the end breaks the program.

Related

Python 3.x - Multi mouse button activated auto clicker

I am having troubles with the python code i am trying to write. I am attempting to create an auto-clicker that when I hold down left click and one of the side buttons, left click gets triggered every 0.1 seconds. If I let go of either button, the auto-clicker stops. I have searched through the PyAutoGUI, PyNput, and the PyGame libraries, as well as many of stack overflow posts on the topic of mouse detection. Here is the code I have put together so far. (note, a majority of this code is not mine, I simply do not remember where I grabbed the code snippits otherwise they would be linked below.
from pynput import mouse
from pynput.mouse import Button, Controller
import threading
import time
control = Controller()
running = False
leftPressed=False
rightPressed=False
def process():
print('start')
count = 0
while running:
print(count)
count += 1
control.click(Button.left,1)
print("test")
time.sleep(.05)
print('stop')
def on_click(*args):
global running, leftPressed, rightPressed
if args[-1]:
# mouse key pressed
if args[-2].name == "left":
leftPressed = True
print("Left click")
elif args[-2].name == "x2":
rightPressed = True
if leftPressed and rightPressed:
# if both left and right are pressed
running = True
threading.Thread(target=process).start()
elif not args[-1]:
# mouse key released
if args[-2].name == "x1":
leftPressed = False
elif args[-2].name == "x2":
rightPressed = False
# as one key has been released, both are no longer pressed
running = False
with mouse.Listener(on_click=on_click) as listener:
listener.join()
Any help with this project would be greatly appreciated, I've come to realize the biggest issue is that since the "on click" event is expecting a left click to run the thread, left clicking inside of the thread is more than not ideal, however if it is possible to program it in such a way where this isn't an issue it would be greatly appreciated.
The pseudocode for what I am looking for would be:
if Button.x2 == pressed and Button.left == pressed:
bothPressed = True
else:
bothPressed = False
while bothPressed:
Button.left.click()
I have tried using multiple methods of obtaining the mouse inputs through PyGame and PyNput. However I've found that I understand PyNput the best and would prefer to stick with it.

Calling a function within a function causing problems in execution

I am building a snake game using Tkinter only, and I managed to make a working game. The code works perfectly, until I added a start menu to show up before the game. To do this, I encased all my functions within one function called play(), and when I run the code, a menu shows up as expected, and when clicked play the window closes and the game itself shows up.
However, the keybinds for left/right/up/down to control the snake no longer works. I have checked that I have called the correct functions within my mainloop, and I have also tried moving it outside of the mainloop but it throws a referenced before assigned error.
Is there a logic error that I am missing? I am a beginner to Python and the Tkinter module so that is quite likely. Here is my code (where I suspect the problem is):
def play():
#some other functions including moveSnake and placeFood
def leftKey(event):
global direction
direction = "left"
def rightKey(event):
global direction
direction = "right"
def upKey(event):
global direction
direction = "up"
def downKey(event):
global direction
direction = "down"
snake = []
snakeSize = 15
snake.append(canvas.create_rectangle(snakeSize,snakeSize, snakeSize * 2, snakeSize * 2, fill="white", outline = ""))
score = 0
txt = "Score:" + str(score)
scoreText = canvas.create_text(width/2, 20, fill="white", font="Helvetica 20 bold", text=txt)
canvas.bind("<Left>", leftKey)
canvas.bind("<Right>", rightKey)
canvas.bind("<Up>", upKey)
canvas.bind("<Down>", downKey)
canvas.focus_set()
direction = "right"
placeFood()
moveSnake()
window.mainloop()
#new window/start menu
window1 = Tk()
window1.geometry('600x800')
window1.title("Snake")
#play button
playButton = Button(window1,text="Play", command = play)
playButton.pack(side=TOP)
#leaderboard button
leaderboardButton = Button(window1,text="Leaderboard", command = viewleaderboard)
leaderboardButton.pack(side=TOP)
#settings button
playButton = Button(window1,text="Settings", command = viewsettings)
playButton.pack(side=TOP)
window1.mainloop() ```
I managed to fix it without needing to create a class. I moved my keybinds to my main script, right below I created window1. Then I reassigned the bind to the 'window', opposed to 'canvas'. The purpose being that the keybinds were not being called to the correct place. I also add global score and global direction, to avoid it being unrecognised. This fixed my code, though I'm sure it can be optimised much further. Hope it helps out another newbie like me!
** To clarify, 'window' is my original window in which the game lies in and is defined in a function which I have not listed, it is separate to window1 **
global score
score = 0
txt = "Score:" + str(score)
scoreText = canvas.create_text(width/2, 20, fill="white", font="Helvetica 20 bold", text=txt)
window.bind("<Left>", leftKey)
window.bind("<Right>", rightKey)
window.bind("<Up>", upKey)
window.bind("<Down>", downKey)
window.focus_set()
global direction
direction = "right"

Stopping, restarting and changing variables in a thread from the main program (Python 3.5)

I'm very new to threading am and still trying to get my head around how to code most of it. I am trying to make what is effectively a text editor-type input box and so, like every text editor I know, I need a cursor-bar thing to indicate the location at which the text is being typed to. Thus I also want to be able to flicker/blink the cursor, which i thought would also prove good practice for threading.
I have a class cursor that creates a rectangle on the canvas based on the bounding box of my canvas text, but I then need to change it's location as more characters are typed; stop the thread and instantaneously hide the cursor rectangle when the user clicks outside of the input box; and lastly restart the thread/a loop within the thread (once again, sharing a variable) - the idea here being that the cursor blinks 250 times and after then, disappears (though not necessary, I thought it would make a good learning exercise).
So assuming that I have captured the events needed to trigger these, what would be the best way to go about them? I have some code, but I really don't think it will work, and just keeps getting messier. My idea being that the blinking method itself was the thread. Would it be better to make the whole class a thread instead? Please don't feel restricted by the ideas in my code and feel free to improve it. I don't think that the stopping is working correctly because every time I alt+tab out of the window (which i have programmed to disengage from the input box) the Python shell and tkinter GUI stop responding.
from tkinter import *
import threading, time
class Cursor:
def __init__(self, parent, xy):
self.parent = parent
#xy is a tuple of 4 integers based on a text object's .bbox()
coords = [xy[2]] + list(xy[1:])
self.obj = self.parent.create_rectangle(coords)
self.parent.itemconfig(self.obj, state='hidden')
def blink(self):
blinks = 0
while not self.stop blinks <= 250:
self.parent.itemconfig(self.obj, state='normal')
for i in range(8):
time.sleep(0.1)
if self.stop: break
self.parent.itemconfig(self.obj, state='hidden')
time.sleep(0.2)
blinks += 1
self.parent.itemconfig(self.obj, state='hidden')
def startThread(self):
self.stop = False
self.blinking = threading.Thread(target=self.blink, args=[])
self.blinking.start()
def stopThread(self):
self.stop = True
self.blinking.join()
def adjustPos(self, xy):
#I am not overly sure if this will work because of the thread...
coords = [xy[2]] + list(xy[1:])
self.parent.coords(self.obj, coords)
#Below this comment, I have extracted relevant parts of classes to global
#and therefore, it may not be completely syntactically correct nor
#specifically how I initially wrote the code.
def keyPress(e):
text = canvas.itemcget(textObj, text)
if focused:
if '\\x' not in repr(e.char) and len(e.char)>0:
text += e.char
elif e.keysym == 'BackSpace':
text = text[:-1]
canvas.itemconfig(textObj, text=text)
cursor.adjustPos(canvas.bbox(textObj))
def toggle(e):
if cursor.blinking.isAlive(): #<< I'm not sure if that is right?
cursor.stopThread()
else:
cursor.startThread()
if __name__=="__main__":
root = Tk()
canvas = Canvas(root, width=600, height=400, borderwidth=0, hightlightthickness=0)
canvas.pack()
textObj = canvas.create_text(50, 50, text='', anchor=NW)
root.bind('<Key>', keyPress)
cursor = Cursor(canvas, canvas.bbox(textObj))
#Using left-click event to toggle thread start and stop
root.bind('<ButtonPress-1', toggle)
#Using right-click event to somehow restart thread or set blinks=0
#root.bind('<ButtonPress-3', cursor.dosomething_butimnotsurewhat)
root.mainloop()
If there is a better way to do something written above, please also tell me.
Thanks.

How to create a Button Listener in TKinter

I am trying to make a simon says game and am having trouble collecting the users choice to compare against the computer's pattern. I am using four buttons to select the colors that flash on the screen. The problem I am having is my program continues to chug along even if my user hasn't selected a color yet, despite my best efforts to stop it, what can I do to stop the program? Here is my code...
Sequence[]
PosColors = ["Yellow","Red","Blue","Green"]
PlayerChoice[]
Score = 0
def Game(Sequence, PlayerChoice,Score):
Score += 1
PlayerChoice = []
Number = randint(0,3)
Sequence.append(PosColors[Number])
for i in Sequence:
RunTime = Score * 1000
Gui.after(1000, blink, rect, SquareCanvas , i)
RunTime = (Score + 1) * 1000
Gui.after(2000, blink, rect, SquareCanvas , White)
X = len(Sequence)
Index = 0
Color = " "
while Color != " ":
BlueButton.config(command = partial(setSelection,NeonBlue))
RedButton.config(command = partial(setSelection,NeonRed))
YellowButton.config(command = partial(setSelection,NeonYellow))
GreenButton.config(command = partial(setSelection,NeonGreen))
Color = getSelection()
print(Color)
while Color != " ":
PlayerTurn(Sequence,PlayerChoice,Score,Index)
X -= 1
Index +=1
def setSelection(Color):
if Color == NeonBlue:
return "NeonBlue"
elif Color == NeonRed:
return "NeonRed"
elif Color == NeonGreen:
return "NeonGreen"
elif Color == NeonYellow:
return "NeonYellow"
def getSelection():
return TheColor
def PlayerTurn(Sequence, PlayerChoice,Score,Index):
PlayerChoice.append(Color)
print(PlayerChoice)
Label1.config(text = 'Well done! \nYou advance to the next round!')
I am planning on passing this through a checker and to loop it until there is an error but I need to get past this front loop first... I know my logic works as I created it using just lists and the command line before moving on the the graphical portion I just need to know how to get the program to stop to collect the user guess, especially as the length of the pattern gets larger. Originally I had the command function included later where I build my Buttons but this placement seems to get as close as possible to what I am looking for. Any help is appreciated thank you
You're going to need to rethink your main program logic. Tkinter is designed such that you should never have your own infinite loop. Tkinter already has an infinite loop -- the mainloop() method of the root window. You can't write logic that waits for input from the user. You have to think of the GUI as in a perpetual state of waiting, and you need set up handlers (button callbacks or event bindings) to react to events.
You also need to understand that a button command can't "return" anything. "Can't" is a bit strong since it obviously can, but there's nothing waiting for the result -- no place to return it to. You need to design your button function such that it sets a global or instance variable.

Continually running functions in tkinter after mainloop in Python 3.4

What I want to do is colour in a single pixel in the centre of the screen, then at random choose an adjacent pixel and colour it in, and then repeat until some condition is met - anything such as time, or the screen is full, or after a certain number of pixels are full. This ending isn't too important, I haven't got that far yet, and I think I could manage to work that out myself.
I have no experience with tkinter, but I decided it was the best way to display this, since I don't really no any other way. Some of this code (mainly the tkinter functions like Canvas, PhotoImage etc) is therefore copy-pasted (and slightly edited) from examples I found here.
What my code does when run is hard to tell - it uses the CPU as much as it can seemingly indefinitely, and slowly increases its memory usage, but doesn't appear to do anything. No window opens, and the IDLE interpreter goes to a blank line, as usual when calculating something. When killed, the window opens, and displays a white page with a little black blob in the bottom right corner - as if the program had done what it was meant to, but without showing it happening, and starting in the wrong place.
So:
Why does it do this?
What should I do to make my program work?
What would be a better way of coding this, changing as many things as you like (ie. no tkinter, a different algorithm etc)?
from tkinter import Tk, Canvas, PhotoImage, mainloop
from random import randrange
from time import sleep
def choose_pixel(pixel_list):
possible_pixels = []
for x in pixel_list:
#adjacent pixels to existing ones
a = [x[0] + 1, x[1]]
b = [x[0] - 1, x[1]]
c = [x[0], x[1] + 1]
d = [x[0], x[1] - 1]
#if a not in pixel_list:
possible_pixels.append(a)
#if b not in pixel_list:
possible_pixels.append(b)
#if c not in pixel_list:
possible_pixels.append(c)
#if d not in pixel_list:
possible_pixels.append(d)
pixel_choosing = randrange(len(possible_pixels))
final_choice = possible_pixels[pixel_choosing]
return final_choice
def update_image(img_name, pixel):
img.put("#000000", (pixel[0], pixel[1]))
WIDTH, HEIGHT = 320, 240
window = Tk()
#create white background image
canvas = Canvas(window, width=WIDTH, height=HEIGHT, bg="#ffffff")
canvas.pack()
img = PhotoImage(width=WIDTH, height=HEIGHT)
canvas.create_image((WIDTH, HEIGHT), image=img, state="normal")
first_pixel = [int(WIDTH/2), int(HEIGHT/2)]
pixel_list = [first_pixel]
img.put("#000000", (first_pixel[0], first_pixel[1]))
canvas.pack()
runs = 0
while True:
next_pixel = choose_pixel(pixel_list)
pixel_list.append(next_pixel)
window.after(0, update_image, img, next_pixel)
canvas.pack()
runs+=1
window.mainloop()
The pattern for running something periodically in tkinter is to write a function that does whatever you want it to do, and then the last thing it does is use after to call itself again in the future. It looks something like this:
import tkinter as tk
...
class Example(...):
def __init__(self, ...):
...
self.canvas = tk.Canvas(...)
self.delay = 100 # 100ms equals ten times a second
...
# draw the first thing
self.draw_something()
def draw_something(self):
<put your code to draw one thing here>
self.canvas.after(self.delay, self.draw_something)
After the function draws something, it schedules itself to run again in the future. The delay defines approximately how long to wait before the next call. The smaller the number, the faster it runs but the more CPU it uses. This works, because between the time after is called and the time elapses, the event loop (mainloop) is free to handle other events such as screen redraws.
While you may think this looks like recursion, it isn't since it's not making a recursive call. It's merely adding a job to a queue that the mainloop periodically checks.

Resources