How to "activate" certain parts of a code - python

I want to "activate" parts of my code through buttons. I tried something like, when you press a button, the value of a variable is set to an other amount which activates another part of my code:
from tkinter import *
window = Tk()
window.title('Adventure')
c = Canvas(window, height=360, width=640, bg='black')
c.pack()
system = 1
def start():
c.delete(anfang1)
anfangbutton.destroy()
if system == 1:
anfang1 = c.create_text(320, 180, text='Adventure', fill='white', font=('Halvatica', 50))
anfangbutton = Button(window, text='Start', command=start)
anfangbutton.place(x=320, y=250) # I want that if you press the button, start is activated and the value of "system" goes to 2, so the next part begins
if system == 2:
anfang2 = c.create_text(320, 180, text='Adventure', fill='white', font=('Halvatica', 50))
I would appreciate if someone could help me with this or has another way of doing it

Here is the code, I will explain:
def nextStage():
#CODE#
def start():
c.delete(anfang1)
anfangbutton.destroy()
anfang2 = c.create_text(320, 180, text='Adventure', fill='white', font=('Halvatica', 50))
#then button or code to move onto next "stage"
def main():
global anfang1
global anfangbutton
anfang1 = c.create_text(320, 180, text='Adventure', fill='white', font=('Halvatica', 50))
anfangbutton = Button(window, text='Start', command=start)
anfangbutton.place(x=320, y=250) # I want that if you press the button, start is activated and the value of "system" goes to 2, so the next part begins
main()
Basically, what I have done to try and combat the issue is two things.
Rather than trying to use a system variable and If/Else statements, I have put it all into functions. For tkinter, you want to build functions from the first one at the bottom of the page. So the last "stage" or function will be at the top (this is just so it can be properly referenced by the button).
I then globalised the variables so that start() would be able to destroy the buttons.
Not the most effective but works?

Related

stop for loop and wait until user type specific key in tkinter text

I am working on a typing speed app.
typing speed application GUI
Until now my app can:
read content of a .txt file and insert it in Base text
search for " " in Base Text and save each index in a list
with a for loop and a list of indexes it can change background color of each word from Base text
After changing background of first word from Base text, I want my app to wait until user will type first word in Type here and press space key. When user presses space, my loop must go forward, change background of second word, wait again for typing text in Type here and so on until it reaches the end of Base text.
I can do this if I create a click button and associate it with a command that changes a value when user clicks this button. After that I can use .wait_variable() in for loop. This way, for loop will stop and wait until user types in Type here and presses button, but this is not a good design for my app.
Can someone give me a hint or another solution?
Maybe my for loop is not the best approach.
Thank you!
This is my code:
main.py
from typingspeed import TypingSpeedApp
my_app = TypingSpeedApp()
my_app.read_text_file()
my_app.find_space_intervals()
my_app.type_and_check()
my_app.main_loop()
typingspeed.py
import tkinter
from tkinter import *
import time
import keyboard
class TypingSpeedApp:
def __init__(self):
self.typing_speed_app = Tk()
self.typing_speed_app.title("Typing Speed Application")
self.typing_speed_app.minsize(1200, 700)
self.typing_speed_app.resizable(False, False)
# define background image
self.background_image = PhotoImage(file="background_image.PNG")
# create canvas
self.canvas = Canvas(self.typing_speed_app, width=1200, height=700)
self.canvas.pack(fill="both", expand=True)
# set image in canvas
self.canvas.create_image(0, 0, image=self.background_image, anchor="nw")
# add a label
self.canvas.create_text(590, 50, text="Welcome to typing speed application", font=("Helvetica", 30),
fill="white")
# add a label
self.canvas.create_text(130, 90, text="Base text:", font=("Helvetica", 20), fill="white", state=DISABLED)
# Define Entry Boxes
self.base_text = Text(self.typing_speed_app, font=("Helvetica", 24), width=60, height=2, bd=0,
wrap=tkinter.WORD)
self.base_text_window = self.canvas.create_window(70, 120, anchor="nw", window=self.base_text)
# add a label
self.canvas.create_text(130, 220, text="Type here:", font=("Helvetica", 20), fill="white")
# Define Entry Boxes
self.type_text = Text(self.typing_speed_app, font=("Helvetica", 24), width=20, height=1, bd=0,
wrap=tkinter.WORD)
self.type_text_window = self.canvas.create_window(70, 250, anchor="nw", window=self.type_text)
# add a label
self.canvas.create_text(510, 220, text="Timer:", font=("Helvetica", 20), fill="white")
# Define Entry Boxes
self.time_text = Text(self.typing_speed_app, font=("Helvetica", 24), width=5, height=1, bd=0,
background="green")
self.time_text_window = self.canvas.create_window(470, 250, anchor="nw", window=self.time_text)
# text get from base_text
self.base_text_get_test = ""
# this list contain indexes of spaces
self.space_indexes = ["1.0"]
self.space_is_not_press = tkinter.StringVar()
self.text_from_type_text = ""
# show gui
def main_loop(self):
self.typing_speed_app.mainloop()
def read_text_file(self):
with open('text_1.txt') as text_file:
lines = str(text_file.readlines())
self.base_text.insert(tkinter.END, lines[2:-2] + " ")
self.base_text.configure(state=DISABLED)
self.base_text_get_test = self.base_text.get(0.1, tkinter.END)
def find_space_intervals(self):
idx = '1.0'
while 1:
idx = self.base_text.search(" ", idx, stopindex=END)
if not idx:
break
last_idx = '%s+%dc' % (idx, 1)
self.space_indexes.append(idx)
self.space_indexes.append(last_idx)
idx = last_idx
def wait_until_space_is_pressed(self):
pass
def type_and_check(self):
for index in range(0, len(self.space_indexes) - 1, 2):
self.base_text.tag_add("select", self.space_indexes[index], self.space_indexes[index + 1])
self.base_text.tag_configure("select", background="gray")
self.base_text.see(f"{self.space_indexes[index]}")
self.typing_speed_app.update()
time.sleep(0.02)
#self.typing_speed_app.wait_variable(self.space_is_not_press)
# ???????????
# stop for loop and wait until user press space key
# ???????????
self.base_text.tag_remove("select", self.space_indexes[index], self.space_indexes[index + 1])
and background_image
enter image description here
GUI programs are event-based. That means that you don't normally write application code that waits for something since the GUI framework is always waiting for all events. In tkinter this happens when you call mainloop(). Instead, you configure the GUI to respond to specific events. In your case, the event is when the space key has been pressed.
In tkinter you do this with the bind method. You call it on a widget and you specify an event and a function, and tkinter will wait for that event and then call that function.
So, sometime after creating self.type_text you need to add something like following code:
self.type_text.bind("<space>", self.space_detected)
Tkinter will call self.space_detected whenever the user types a space. You then need to define the space_detected method. It will be passed an event, though you won't need the information in this object.
Within that function, you can put any logic you want. For example, this is where you would want to unhighlight the current word and highlight the next.
Due to how tkinter processes events, note that this function will be called before the space is actually inserted into the widget. This is by design, and is actually a very powerful feature of tkinter. For more information, see this answer which explains it in a bit more detail.

Running a loop inside a Radio button

How would I adjust my code to run the following loop while a radio button is selected? The idea is to create a program that clicks the left mouse button every 20 seconds or so to prevent idle. I think I'm so close I just don't quite understand how the mainloop() event stuff works. Does my loop code need to go in the main loop or something? Also, what is value=val).pack(anchor=tk.W)? I have a feeling that is a piece I don't get as well. Here is my current code:
import tkinter as tk, pyautogui, time # This is the prefered way to call tkinter, don't use wildcards.
my_ui_window = tk.Tk() # TK
my_ui_window.title('Radio Button Example')
v = tk.IntVar()
v.set(1) # initializing the choice
on_or_off = [
("Enabled"),
("Disabled")
]
def ExecuteChoice():
choice = (v.get())
while choice == 0:
time.sleep(20)
pyautogui.click()
else:
print ('waiting...')
time.sleep(3)
for val, i in enumerate(on_or_off):
tk.Radiobutton(my_ui_window,
text=i,
borderwidth = 2,
indicatoron= 0,
width = 20,
padx = 50,
variable=v,
command=ExecuteChoice(),
value=val).pack(anchor=tk.W)
my_ui_window.mainloop()
Here is my code re-written appropriate to tkinter. I was making several mistakes. The main one is you typically do not run loops inside tkinter and you definitely don't use sleep. Instead we use the .after class. Here is a better way. It's now heavily commented for anyone lost.
import tkinter as tk # This is the prefered way to call tkinter, don't use wildcards.
import pyautogui # This allows mouse stuff
import time # This allows sleep commands
my_ui_window = tk.Tk() # make tk.Tk() into just a single object.
my_ui_window.title('Radio Button Example')
v = tk.IntVar() # This becomes the index of sorts for our radio elements.
v.set(1) # initializing radio button to off
on_or_off = [ # Creates an array to represent the radio buttons needed.
("Enabled"),
("Disabled")
]
def every_20_seconds(): # Calls this function which clicks if the radio button is set to index 0. It then tells my_ui_window to wait 20 seconds using the .after method before calling itself again. In the meantime, it gives control back to the mainloop() which is always searching for an event change on the UI.
if v.get() == 0:
pyautogui.click()
my_ui_window.after(20000, every_20_seconds)
for val, i in enumerate(on_or_off): # This builds the UI for us.
tk.Radiobutton(my_ui_window,
text=i,
borderwidth = 2,
indicatoron= 0,
width = 20,
padx = 50,
variable=v,
value=val).pack(anchor=tk.W)
every_20_seconds()
my_ui_window.mainloop()
This should do pretty much what you want, even though it's not perfect yet:
import tkinter as tk,pyautogui,time
import threading
my_ui_window = tk.Tk() # TK
my_ui_window.title('Radio Button Example')
v = tk.IntVar()
v.set(0) # initializing the choice
on_or_off = [
(1, "Enabled"),
(0, "Disabled")
]
def ExecuteChoice():
choice = v.get()
if choice == 1:
print("CLICK")
threading.Timer(5.0, ExecuteChoice).start()
else:
print ('waiting...')
pass
for val, name in on_or_off:
tk.Radiobutton(my_ui_window,
text=name,
borderwidth = 2,
indicatoron= 0,
width = 20,
padx = 50,
variable=v,
command=ExecuteChoice,
value=val).pack(anchor=tk.W)
my_ui_window.mainloop()
There were two issues with your code:
You used command=ExecuteChoice() instead of command=ExecuteChoice. Thus, you call the function when initializing your RadioButtons instead of setting this function as a parameter
Yourwhile loop in ExecuteChoice was blocking, i.e. it is the only thing running. The GUI will not update anymore. Hence, you need to call my_ui_window.update() and choice = v.get() in the loop so to update the GUI and check whether the user has changed his choice of Radio-Buttons Thus, we exchange it with an if and an asynchronous timer instead of sleep()
/e: Comment is right. As mentioned, this is not best practice but the closest to the code of the poster to still make it work. I've made a little additional adjustment, to not block anymore. That doesn't mean its best practice. This would include rewriting the code more.

How to add a reset button that clears my code?

I'm making a basic TicTacToe UI in python, and I believe that a fundamental item to this code is a reset button which resets your codes back to the default. is there any other way to do this?
I've Tried to define a function which resets the text of the button back to " " but I don't think that's a great idea because of a lot of other complexities within the cod.
from tkinter import *
root = Tk()
def changetext():
BTN1["text"] = "X"
BTN1 = Button(root, text=" ", command=changetext)
BTN1.pack()
root.mainloop()
So I want to add a button here that says "Reset Text" and it resets all the codes to defaults.
The easiest way to reset the game would be to
Reset the UI as you suggest, with a single dedicated reset_UI() function
Reset the board state by creating a new game board object, and discarding the old one
This of course means that you'll need to wrap all your variables and functions in a board class Board, so that there aren't a billion global variables you have to worry about resetting. The only thing that should persist between resets are your UI buttons, which can be created in your main() function before initializing the game board.
Here's code demonstrating how something like that could be done (plus a few other things):
import tkinter as tk
def toggle(btn):
if btn["text"] == "X":
btn["text"] = " "
else:
btn["text"] = "X"
def reset(buttons):
for btn in buttons.values():
btn["text"] = " "
root = tk.Tk()
buttons = {}
for row in range(3):
for col in range(3):
button = tk.Button(root, text=" ", width=1, height=1)
button.config(command=lambda btn=button: toggle(btn))
button.grid(row=row, column=col)
buttons[row, col] = button
reset_button = tk.Button(root, text="Reset", command=lambda: reset(buttons))
reset_button.grid(columnspan=3)
root.mainloop()

Problem with calling functions with tkinter

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.

python + tkinter: How to use Canvas for the main function and for a subfunction both?

It should be a easy question but I just didnot find the answer.
I have made a main function, in which a button and a line are drawn. I have also a subfunction, which connects to the button in the main function.
By clicking the button I want to have an another line drawn into the same window, and the original line should be erased.
Here is the code I wrote. I really dont know how to finish it. Any kind of help is highly appreciated. Thanks.
import Tkinter as tk
def DrawFunc():
x1 = 20 ; y1 = 20
x2 = 60 ; y2 = 80
S_Canvas = tk.Canvas(root)
S_Canvas.pack()
S_Canvas.create_line(x1, y1, x2, y2, fill="black") # When the button is clicked, this line should be shown. And the old line should be deleted.
def Window():
global root
root = tk.Tk()
root.geometry("400x400")
S_Canvas = tk.Canvas(root)
S_Canvas.pack()
S_Canvas.create_line(50, 250, 250, 70, fill="red") # When the program is run, this line is shown.
Frame_1 = tk.Frame(root)
Frame_1.place(width=120, height=80, x=0, y=0)
Button_1 = tk.Button(Frame_1, text = u"Draw", width = 20, height=10, bg= "green" ,command = DrawFunc())
Button_1.pack()
root.mainloop()
Window()
To draw in the same canvas, your DrawFunc function must know that canvas.
The quickest solution is to add a targetCanvas parameter to your DrawFunc function, and to pass it S_Canvas.
I might remember bad, but I think that tkinter does not support directly passing arguments to callback functions. As a workaround, you can use a lambda function:
Button_1 = tk.Button(Frame_1,
text = u"Draw",
width = 20,
height=10,
bg= "green",
command = lambda: DrawFunc(targetCanvas))
If however you want to reuse this command, you must define a draw_func_command function, that takes no argument:
def draw_func_command():
global S_Canvas
DrawFunc(S_Canvas)
But then, you need to declare S_Canvas as global...
To avoid this, the general way to implement UI elements is to declare them as classes. I am not going to develop this here though, because it's a broad topic and it's a bit beyond the question.
To delete the first line, it's a bit tricky. Actually, the Canvas.create_line returns an object, that is the line drawn. To delete it, you must store it in a variable, then call the delete function on it.
The problem with your code is that the function are not in the same class, so the variables must be passed from each other...
As for the erasing canvas problem, add a oldLine argument to your DrawFunc function, then call oldLine.delete() in its body.
currentLine = S_Canvas.create_line(50, 250, 250, 70, fill="red")
Button1 = Button(..., command = lambda: DrawFunc(targetCanvas, currentLine))

Categories