I am trying to create a function where if I press "1" my image changes, but if I press 1 again the image reverts back to how it was. This process should go on indefinitely.
I am able to do this if the user clicks on a button. For example, the below code simply uses config to change the function that should be run each time button is clicked.
# run this "thing" each time seat is activated
def activate(name):
name.config(image=active)
name.config(command=lambda: deactivate(name))
# run this "thing" each time seat is deactivated
def deactivate(name):
name.config(image=deactive)
name.config(command=lambda: activate(name))
x = Button(root, image=deactive, command=lambda: thing(x))
But I am unable to achieve the same if I bind a key instead:
from tkinter import *
# window config
root = Tk()
root.title("Example - on / off")
root.geometry("1080x600")
# on and off images
inactive = PhotoImage(file='green.png')
active = PhotoImage(file='red.png')
# functions to change the image using config
def something(event):
my_label.config(image=active)
def something2():
my_label.config(image=inactive)
# label which stores the image
my_label = Label(root, image=inactive)
my_label.pack()
# invisible button bind to key "1"
my_button = Button(root)
my_button.bind_all(1, something)
my_button.pack()
root.mainloop()
I would welcome any thoughts on this or an indication if I am approaching this completely wrong.
Thanks
You can simplify the logic by using itertools.cycle() and next() functions:
from tkinter import *
from itertools import cycle
root = Tk()
root.title("Example - on / off")
root.geometry("1080x600")
# on and off images
images = cycle((PhotoImage(file='green.png'), PhotoImage(file='red.png')))
def something(event):
my_label.config(image=next(images))
my_label = Label(root, image=next(images))
my_label.pack()
root.bind('1', something)
root.mainloop()
Related
So my aim is to use a single function to show a text message upon a button click. Then there should be a delay and then another text message should be displayed.
The game is a dice game that should show 'Rolling...' upon a button click. And then after a while, it should display a random number.
I tried both .sleep() and .after() and both of them resulted in my program not showing the before delay text. Here's my code:
# Imports
import tkinter as tk
from random import randrange
import time
# Global variables
# SIDES is a constant
SIDES = 12
# Functions
def func():
display["text"] = "Rolling..."
window.after(2000)
display["text"] = str(randrange(SIDES) + 1)
# Main program loop
window = tk.Tk()
display = tk.Label(window, text="Press the button \nto roll the dice.", width=20, height=3)
button = tk.Button(window, text="Roll", command=func)
display.pack()
button.pack(pady=10)
window.mainloop()
Any help would be much appreciated!
Try:
window.after(2000, lambda: display.config(text=randrange(SIDES) + 1))
instead of the:
window.after(2000)
display["text"] = str(randrange(SIDES) + 1)
The problem is that when you sleep in the function, the tkinter main loop is interrupted and the screen isn't updated. (window.after() is just a gloified sleep here). The correct solution is to pass a callback to after, which will make it immediately return and call the callback later:
def func():
display["text"] = "Rolling..."
window.after(2000, lambda: display.__setitem__("text", str(randrange(SIDES) + 1)))
(Note that the call to __setitem__ is a direct one-liner lambda translation. This is not good design.)
I have a small script which is organized in 3 frames:
1 in the first row
1 in the second row Left
1 in the second row right
I press the button in the first row frame and hand over the input value to the Label in the second row in the left.
Here my code:
import tkinter as tk
# Create Window
root = tk.Tk()
# Define String Variable
Name = tk.StringVar()
# Organize root window in 3 frames
EntryFrame = tk.Frame(root)
MainLeftFrame = tk.Frame(root)
MainRightFrame = tk.Frame(root)
# Create Buttons, Entry and Labels
NameLabel = tk.Label(MainLeftFrame, textvariable=Name)
InputName = tk.Entry(EntryFrame, width=20,bg='yellow')
SubmitButton = tk.Button(EntryFrame, text='Submit', command=lambda:action())
# Define what happens when press button reset
def reset():
MainLeftFrame.forget()
MainRightFrame.forget()
EntryFrame.pack()
# Define what happens when button is pressed
def action():
Name.set(InputName.get())
ResetButton = tk.Button(MainRightFrame, text='Reset', command=lambda: reset())
ResetButton.pack()
Placeholder = tk.Label(MainRightFrame, text="place holder")
Placeholder.pack(side="top")
EntryFrame.forget()
# Pack Widgets
EntryFrame.pack(side='top')
MainLeftFrame.pack(side='left')
MainRightFrame.pack(side='right')
InputName.pack()
SubmitButton.pack()
NameLabel.pack()
#mainloop
root.mainloop()
Now to my question:
When I press the "Submit" Button for the Second time (after pressing Reset Button) nothing is happening :(
Thanks in advance!
The reason of your program not working is that, after using forget on the MainLeftFrame and MainRightFrame you aren't packing them again when the action function is called. Adding these 2 lines of code in action function should make it work. BUT
MainLeftFrame.pack()
MainRightFrame.pack()
That's not the only issue, defining new widgets every time the function is called and packing them will additively increase the same widget set over and over again. To avoid this, you would have to predefine them and them perform forget and repacking. But a better thing to do would be to have a dedicated frame for them, so that it becomes easy for you to toggle. I have tried rewriting your script, let me know if this is what you wanted.
from tkinter import *
def reset():
entry_frame.pack()
main_frame.pack_forget()
def submit():
entry_frame.pack_forget()
main_frame.pack()
name.set(name_entry.get())
root=Tk()
entry_frame=Frame(root)
entry_frame.pack()
name_entry=Entry(entry_frame)
name_entry.pack(side='top')
submit_button=Button(entry_frame,text='Submit',command=submit)
submit_button.pack(side='top')
main_frame=Frame(root)
reset_button=Button(main_frame,text='Reset',command=reset)
reset_button.pack(side='top')
name=StringVar()
name_label=Label(main_frame,textvariable=name)
name_label.pack(side='left')
placeholer_label=Label(main_frame,text='placeholer')
placeholer_label.pack(side='right')
root.mainloop()
I have three widgets (shown below) - a label, an entry, and a button.
I'm hoping to accept user input of an integer when the button is clicked.
I get the following error: ERROR RESOLVED!
I was making this too complicated. Success is Mine!!!
I believe that my problem is related to this:
Stack Overflow Post on get()
I chopped it down to the minimum amount of code possible to achieve this before posting.
Any Ideas or obvious failures you see would be greatly appreciated.
Screenshot of screen #1 (should eventually progress to screen #2 after all the players are entered. Screen #2 is where the game will begin.):
Working Code:
from tkinter import *
import tkinter
from PIL import Image, ImageTk
# The next 2 imports override the basic Tk widgets
# widget options that use fg and bg will no longer work.
# Must use, instead, ttk.Style class
from tkinter import ttk
from tkinter.ttk import *
# See docs.python.org/8/library/tkinter.ttk.html
# Class Based Windows
# youtube.com/watch?v=RkaekNkIKNY
# Tkinter - GUI Eaxample Multiple Display Frames
# youtube.com/watch?v=KdoOm3xo8X0
def main():
root = tkinter.Tk()
window1 = Window(root, "Play_a_game_of_Pig", "969x690", "someMessage",
"pig.ico", 0)
return None
class Window:
number_of_players = 0
# int player_names[] = new player_names[number_of_players]
def __init__(self, root, title, geometry, message, iconbitmap,
playerNumber):
self.root = root # this is the instance variable for the entire window
self.root.title(title)
self.root.geometry(geometry)
self.root.iconbitmap(iconbitmap)
self.number_of_players = playerNumber
# Make the window show up in center of screen and not be ccovered up by anything else.
# self.root.eval('tk::PlaceWindow %s center' % self.root.wininfo_toplevel())
# this is an important line.
# self.root.mainloop()
def addPlayers():
"""Allows user to input the number of and names of all game players"""
# stackoverflow.com/questions/12169258/should-i-use-entrys-get-or-its-textvariables-for-tkinter-in-python
print("\n initial # of players = " + str(self.number_of_players))
# Collects user input from the entry and turns it into an int
# user_input_number_of_players.set(int(str(entry_player_number.get("1.0", 'end-1c'))))
try:
user_input_number_of_players = int(entry_player_number.get())
print("Inside try block, user_input = ")
print(user_input_number_of_players)
self.number_of_players = user_input_number_of_players
except ValueError:
tkinter.messagebox.showerror('Non-Integer Input', 'User MUST enter a player # greater than 1.', icon = 'error')
# tkinter.messagebox.deiconify()
# tkinter.messagebox.quit()
# tkinter.messagebox.destroy()
#user_input_number_of_players.set(int(str(entry_player_number.get("1.0", 'end-1c'))))
# Set class instance value to this input from the user
# self.number_of_players = user_input_number_of_players
print("# of players after click = " + str(self.number_of_players))
return self.number_of_players
print("# of players after click = " + str(self.number_of_players))
# Add a label
myLabel1 = tkinter.Label(self.root, text="Please Enter # of Players",
width=25)
myLabel1.config(font="Courier 14 bold")
myLabel1.grid(row=2, column=1)
# bind user input to a variable from the entry box.
# Specifies a name whose value is linked to the widget value.
user_input_number_of_players = tkinter.StringVar()
# add an entry box
entry_player_number = tkinter.Entry(self.root, width=5, borderwidth=5,
textvariable=user_input_number_of_players)
# number.set(int("0"))
entry_player_number.grid(row=2, column=3, rowspan=2)
# specify a default value inside the entry box
# entry_player_number.insert(0,int("2"))
# Add a button for adding players to the game
addPlayerButton = tkinter.ttk.Button(self.root,
text="Enter",
command=addPlayers)
addPlayerButton.grid(row=2, column=4)
self.root.mainloop()
pass
pass
main()
In Summary, I first made two screens. I'm hoping to make these into classes. The first screen (pictured) should get input from the user. Then, the first screen should progress to the second screen.
To do this, I have used the following resources:
Resources for merging this code together into Multiple class based windows and the backend:
Python: Tkinter Class Based Windows
Tkinter - GUI Example Multiple Display Frames
from tkinter import *
from random import *
root = Tk()
#A function to create the turn for the current player. The current player isnt in this code as it is not important
def turn():
window = Tk()
dice = Button(window, text="Roll the dice!", bg= "white", command=lambda:diceAction(window))
dice.pack()
window.mainloop()
#a function to simulate a dice. It kills the function turn.
def diceAction(window):
result = Tk()
y = randint(1, 6)
quitButton = Button(result, text="Ok!", bg="white", command=result.destroy)
quitButton.pack()
window.destroy()
result.mainloop()
#A function to create the playing field and to start the game
def main():
label1 = Button(root, text="hi", bg="black")
label1.pack()
while 1:
turn()
print("Hi")
turn()
main()
root.mainloop()
My problem is that the code in the while function after the first turn() the code isnt executed until i close the root window(which i dont want because it represents the playing field). You can copy this code and execute it yourself if you want.
I have no idea what causes this and havent found anything online. Sorry for the long code but i wrote it so that it is executeable.
I don't know why this particular problem is occurring, but there are a couple of things in your code that are considered bad practice.
Instead of creating multiple instances of Tk(), you should use Toplevel widgets for any pop-up windows needed. Also, it's better to use root.mainloop() to run the program rather than a while loop.
I've made some edits to your code so that it uses a Toplevel widget and discards of the while loop.
from tkinter import *
from random import *
#A function to create the turn for the current player. The current player isnt in this code as it is not important
def turn(prev=None):
# destroy the previous turn
if prev:
prev.destroy()
# pop up with dice
window = Toplevel()
dice = Button(window, text="Roll the dice!", bg= "white")
dice.config(command=lambda b=dice, w=window:diceAction(b, w))
dice.pack()
#a function to simulate a dice, reconfigures the pop-up
def diceAction(button, window):
# roll dice
y = randint(1, 6)
# use dice result here?
print(y)
# reconfigure button, the command now starts a new turn
button.config(text='ok', command=lambda w=window:turn(prev=w))
root = Tk()
# I hijacked this button to use as a start button
label1 = Button(root, text="hi", bg="black", command=turn)
label1.pack()
root.mainloop()
I don't know if this is what you need, but it functions as the code in the question would if it worked.
Sorry I couldn't help with the cause of the error.
I've searched and found a few things on parent windows in python but that is not what I was looking for. I am trying make a simple program that opens a window and another window after that when the previous one is closed. I was also trying to implement some kind of loop or sleep time to destroy the window by default if the user does not. This is what I have (I'm new please don't laugh)
from tkinter import *
import time
root = Tk()
i = 0
if i < 1:
root.title("title")
logo = PhotoImage(file="burger.gif")
w1 = Label(root, image=logo).pack()
time.sleep(3)
root.destroy()
i = i + 1
if i == 1:
root.title("title")
photoTwo = PhotoImage(file="freedom.gif")
labelTwo = Label(root, image=photoTwo).pack()
time.sleep(3)
root.destroy()
i = i + 1
mainloop.()
Perhaps you're looking for something like this:
from tkinter import *
import time
def openNewWindow():
firstWindow.destroy()
secondWindow = Tk()
secondWindow.title("Second Window")
photoTwo = PhotoImage(file="freedom.gif")
labelTwo = Label(secondWindow, image=photoTwo).pack()
secondWindow.mainloop()
firstWindow = Tk()
firstWindow.title("First Window")
logo = PhotoImage(file="burger.gif")
w1 = Label(firstWindow, image=logo).pack()
closeBttn = Button(firstWindow, text="Close!", command=openNewWindow)
closeBttn.pack()
firstWindow.mainloop()
This creates a button in the first window, which the user clicks. This then calls the openNewWindow function, which destroys that window, and opens the second window. I'm not sure there's a way to do this using the window exit button.
To get create a more sustainable window creation, use this:
from tkinter import *
import time
def openThirdWindow(previouswindow):
previouswindow.destroy()
thirdWindow = Tk()
thirdWindow.title("Third Window")
photoTwo = PhotoImage(file="freedom.gif")
labelTwo = Label(thirdWindow, image=photoTwo).pack()
thirdWindow.mainloop()
def openSecondWindow(previouswindow):
previouswindow.destroy()
secondWindow = Tk()
secondWindow.title("Second Window")
photoTwo = PhotoImage(file="freedom.gif")
labelTwo = Label(secondWindow, image=photoTwo).pack()
closeBttn = Button(secondWindow, text="Close!", command= lambda: openThirdWindow(secondWindow))
closeBttn.pack()
secondWindow.mainloop()
def openFirstWindow():
firstWindow = Tk()
firstWindow.title("First Window")
logo = PhotoImage(file="burger.gif")
w1 = Label(firstWindow, image=logo).pack()
closeBttn = Button(firstWindow, text="Close!", command= lambda: openSecondWindow(firstWindow))
closeBttn.pack()
firstWindow.mainloop()
openFirstWindow()
This places the opening of each window in a seperate function, and passes the name of the window through the button presses into the next function. Another method would be setting the window names as global, but this is messy.
The function "lambda:" calls the function, in tkinter you must type this if you want to pass something through a command.
We initiate the whole process first first called "openFirstWindow()"