Python Tkinter threading - python

I need help with a program which uses tkinter. So I try to use a button to cycle trough some pictures, but there are two cycles and when i press one button either disappears or goes back to the first one (it's kinda hard to explain). I was asking myself if I could use some threading here and I need help since I've never used it before.
This is the part of the code:
square1 = Image.open("Square1.jpg")
square1r = ImageTk.PhotoImage(square1)
self.square1img = Label(self.windowSq, image=square1r)
self.square1img.image = square1r
self.square1img.place(x=30, y=100)
square1 = Image.open("Square1a.jpg")
square1r = ImageTk.PhotoImage(square1)
self.square1img = Label(self.windowSq, image=square1r)
self.square1img.image = square1r
self.square1img.place(x=435, y=100)
next = Button(self.windowSq, text="Next", font=self.customFont, relief=GROOVE, command=self.next, cursor='hand2')
next.place(x=185, y=380)
self.num = 0
next1 = Button(self.windowSq, text="Next", font=self.customFont, relief=GROOVE, command=self.next1, cursor='hand2')
next1.place(x=600, y=380)
self.num1 = 0
def next(self):
self.num = self.num + 1
if self.num == 1:
self.square1img.destroy()
square2 = Image.open("Square2.jpg")
square2r = ImageTk.PhotoImage(square2)
self.square2img = Label(self.windowSq, image=square2r)
self.square2img.image = square2r
self.square2img.place(x=30, y=100)
elif self.num == 2:
self.square2img.destroy()
square3 = Image.open("Square3.jpg")
square3r = ImageTk.PhotoImage(square3)
self.square3img = Label(self.windowSq, image=square3r)
self.square3img.image = square3r
self.square3img.place(x=30, y=100)
elif self.num == 3:
self.square3img.destroy()
square4 = Image.open("Square4.jpg")
square4r = ImageTk.PhotoImage(square4)
self.square4img = Label(self.windowSq, image=square4r)
self.square4img.image = square4r
self.square4img.place(x=30, y=100)
elif self.num == 4:
self.square4img.destroy()
square5 = Image.open("Square5.jpg")
square5r = ImageTk.PhotoImage(square5)
self.square5img = Label(self.windowSq, image=square5r)
self.square5img.image = square5r
self.square5img.place(x=30, y=100)
elif self.num == 5:
self.square5img.destroy()
square1 = Image.open("Square1.jpg")
square1r = ImageTk.PhotoImage(square1)
self.square1img = Label(self.windowSq, image=square1r)
self.square1img.image = square1r
self.square1img.place(x=30, y=100)
self.num = 0
self.windowSq.after(50000, self.next)
def next1(self):
self.num1 = self.num1 + 1
if self.num1 == 1:
self.square1img.destroy()
square2 = Image.open("Square2a.jpg")
square2r = ImageTk.PhotoImage(square2)
self.square2img = Label(self.windowSq, image=square2r)
self.square2img.image = square2r
self.square2img.place(x=435, y=100)
elif self.num1 == 2:
self.square2img.destroy()
square3 = Image.open("Square3a.jpg")
square3r = ImageTk.PhotoImage(square3)
self.square3img = Label(self.windowSq, image=square3r)
self.square3img.image = square3r
self.square3img.place(x=435, y=100)
elif self.num1 == 3:
self.square3img.destroy()
square4 = Image.open("Square4a.jpg")
square4r = ImageTk.PhotoImage(square4)
self.square4img = Label(self.windowSq, image=square4r)
self.square4img.image = square4r
self.square4img.place(x=435, y=100)
elif self.num1 == 4:
self.square4img.destroy()
square5 = Image.open("Square5a.jpg")
square5r = ImageTk.PhotoImage(square5)
self.square5img = Label(self.windowSq, image=square5r)
self.square5img.image = square5r
self.square5img.place(x=435, y=100)
elif self.num1 == 5:
self.square5img.destroy()
square1 = Image.open("Square1a.jpg")
square1r = ImageTk.PhotoImage(square1)
self.square1img = Label(self.windowSq, image=square1r)
self.square1img.image = square1r
self.square1img.place(x=435, y=100)
self.num1 = 0
self.windowSq.after(50000, self.next1)
The whole program is in a class (if you are wondering...)
class Window(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.master = master
self.master.resizable(0, 0)
master.title("Arcade Games")
master.geometry("800x600+560+240")

You have a lot of redundant code above. The idea is to first store the images in a list and then cycle through the list. You can have 2 buttons that call 2 different functions (one for each list), 2 lists of images, and 2 counters to keep your place in each list, and then change the image on a Label from whichever list you want (I use a button as below, and note that only one button is created-never destroyed, and just the image is changed each time). Or you can use one class, and 2 instances of the class, passing a different list of images to be loaded to each instance. This is just a simple proof of concept program. Click on the button/image to change it.
import sys
if sys.version_info[0] < 3:
import Tkinter as tk ## Python 2.x
else:
import tkinter as tk ## Python 3.x
class ChangeImage():
def __init__(self, root):
self.photos=[]
self.load_images()
self.image_num=0
self.btn = tk.Button(root, image=self.photos[self.image_num], command=self.next_image)
self.btn.grid(row=0)
tk.Button(root, text="Exit", bg="orange", command=root.quit).grid(row=1)
def next_image(self):
self.image_num += 1
if self.image_num >= len(self.photos):
self.image_num=0
## pipe the next image to be displayed to the button
self.btn["image"]=self.photos[self.image_num]
def load_images(self):
""" copy data images to a list that is an instance variable
"""
ladybug_gif_b64='''\
R0lGODlhIAAgALMAAP///wAAADAwMP99Hf8AAP+lAP//AMopUwAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAACH5BAAAAAAALAAAAAAgACAAAwTHEMhJq714hp3lDh0GiqH2UWOVAt96pUIsBLKglWg87Dwv
4xMBj0Asxgg+XKxwLBJrxUGsI5TKnARoVHoLDp5XbNP5cwmNAqwa/aOc13ByrfKOw2UGw1SSxrb+
AWIxeXsAaX92UDQ1Nh6BdneMhQIHkHGSjYaVlmt4epmacp19YAKEoJRspKWrjBapWWGqcm1uB5tj
ok4HZa+3m5wEt5kuhpTAcb+FVL/NzspAfDHPysslMJjEIS0oLygnOMVd0SwcHeDk6errEQA7
'''
grape_gif='''\
R0lGODlhIAAgALMAAAAAAAAAgHCAkC6LV76+vvXeswD/ANzc3DLNMubm+v/6zS9PT6Ai8P8A////
/////yH5BAEAAAkALAAAAAAgACAAAAS00MlJq7046803AF3ofAYYfh8GIEvpoUZcmtOKAO5rLMva
0rYVKqX5IEq3XDAZo1GGiOhw5rtJc09cVGo7orYwYtYo3d4+DBxJWuSCAQ30+vNTGcxnOIARj3eT
YhJDQ3woDGl7foNiKBV7aYeEkHEignKFkk4ciYaImJqbkZ+PjZUjaJOElKanqJyRrJyZgSKkokOs
NYa2q7mcirC5I5FofsK6hcHHgsSgx4a9yzXK0rrV19gRADs=
'''
house='''R0lGODdhFQAVAPMAAAQ2PESapISCBASCBMTCxPxmNCQiJJya/ISChGRmzPz+/PxmzDQyZDQyZDQy
ZDQyZCwAAAAAFQAVAAAElJDISau9Vh2WMD0gqHHelJwnsXVloqDd2hrMm8pYYiSHYfMMRm53ULlQ
HGFFx1MZCciUiVOsPmEkKNVp3UBhJ4Ohy1UxerSgJGZMMBbcBACQlVhRiHvaUsXHgywTdycLdxyB
gm1vcTyIZW4MeU6NgQEBXEGRcQcIlwQIAwEHoioCAgWmCZ0Iq5+hA6wIpqislgGhthEAOw==
'''
## could also be a list of files passed to the class
for photo in (ladybug_gif_b64, grape_gif, house):
self.photos.append(tk.PhotoImage(data=photo))
root=tk.Tk()
CI=ChangeImage(root)
root.mainloop()

Related

.delete("1.0",END) not clearing textbox

Ive been coding a maths game but stumbled across a problem where I cant clear a text widget. SO after the users get shown a list of incorrect questions and to play the game again, but after playing the game a second time the list doesn't reset. I've added a comment above the text widget I want to clear if that helps.
from tkinter import *
import random
from tkinter import messagebox
class Timestable:
def __init__(self, parent):
Timestable.f1 = Frame(parent,bg='#66bcb9')
Timestable.f1.grid()
Timestable.f2 = Frame(parent,bg='#66bcb9')
Timestable.f2.grid()
Timestable.f2.grid_forget()
Timestable.f3 = Frame(parent,bg='#66bcb9')
Timestable.f3.grid()
Timestable.f3.grid_forget()
Timestable.f4 = Frame(parent,bg='#66bcb9')
Timestable.f4.grid()
Timestable.f4.grid_forget()
#frame 1 ========================================================
Label(self.f1,text="Multiplication Practice",bg='#508484',fg='white').grid(columnspan=2,column=0,row=0)
Label(self.f1,text="Name:",bg='#508484',fg='white').grid(row=1,column=0)
Timestable.name = Entry (self.f1)
Timestable.name.grid(row=1,column=1)
Label(self.f1,text="Age",bg='#508484',fg='white').grid(row=2,column=0)
Timestable.age = Entry (self.f1)
Timestable.age.grid(row=2,column=1)
Timestable.incorrect=[]
Timestable.user = []
Timestable.checked = []
Label(self.f1,text="Select Timestable",bg='#508484',fg='white').grid(columnspan=2,row=3,column=0)
frame = Frame(self.f1)
frame.grid(row=5,column=0, columnspan=2)
for i in range(12):
Timestable.checked.append(IntVar()) # store the tkinter variable for the checkbox
Checkbutton(frame, text=i+1, variable=self.checked[-1], onvalue=i+1, offvalue=0, anchor='w',bg='#66bcb9').grid(row=i//3, column=i%3, sticky='ew')
Label(self.f1,text="Total Questions",bg='#508484',fg='white').grid(row=4,column=0)
Timestable.w = Spinbox(self.f1, from_=1, to=5)
Timestable.w.grid(row=4,column=1)
Button(self.f1,text="Start", command=lambda: data.start(self),bg='#88c43c').grid(columnspan=2, column=0,row=6)
#frame 2 ========================================================
Label(self.f2,text="Multiplication Practice",bg='#508484',fg='white').grid(columnspan=2,column=0,row=0)
self.x=0
self.correct=0
Timestable.questions=Label(Timestable.f2,text="")
Timestable.questions.grid(row=1,column=0)
Timestable.entryWidget = Entry (self.f2)
Timestable.entryWidget.grid(row=1,column=1)
Button(self.f2,text="submit", command=lambda: data.Submit(self),bg='#88c43c').grid(columnspan=2,column=0,row=3)
#frame 3 ========================================================
Label(self.f3,text="Multiplication Practice",bg='#508484',fg='white').grid(columnspan=2,column=0,row=0)
Timestable.congrats=Label(Timestable.f3,text="")
Timestable.congrats.grid(columnspan=2,column=0,row=1)
#this is the widget I want to clear
Timestable.incdisplay = Text(Timestable.f3, width=15,height=10,bg='#d0e4f4')
Timestable.incdisplay.grid(columnspan=2,column=0,row=2)
Button(self.f3,text="next", command=lambda:data.next(self),bg='#88c43c').grid(columnspan=2,column=0,row=3)
#fram 4 ========================================================
Label(self.f4,text="Multiplication Practice",bg='#508484',fg='white').grid(columnspan=2,column=0,row=0)
Label(self.f4,text="Results",bg='#508484',fg='white').grid(columnspan=2,column=0,row=1)
Timestable.my_text=Text(self.f4,width=20,height=10,bg='#d0e4f4')
Timestable.my_text.grid(columnspan=2,column=1,row=2)
Button(self.f4,text="again", command=lambda:data.again(self),bg='#88c43c').grid(columnspan=2,column=0,row=3)
class data:
def saveleader(self):
file=open('Leaderboard.txt',"a")
file.write(Timestable.name.get()+","+Timestable.age.get()+","+str(data.percent)+"%""\n")
file.close
file=open("Leaderboard.txt","r").read()
headers = " ".join(["Name", "Age", "Score"])
output = [i.split(",") for i in file.split("\n") if i]
out_sort = sorted(output, key=lambda x: int(x[2][:-1]), reverse=True)
final = "\n".join([headers, "-"*len(headers),*(" ".join(i) for i in out_sort)])
Timestable.my_text.insert(END, final)
def clear_text(self):
Timestable.entryWidget.delete(0, 'end')
def Questions(self):
number1 = random.choice(Timestable.user)
number2 = random.randrange(1,12)
self.answer = number1 * number2
self.prompt = (str(number1) + " X " + str(number2))
Timestable.questions.configure(text=self.prompt, width=len(self.prompt),bg='#66bcb9')
return self.answer
def start(self):
Timestable.user = [v.get() for v in Timestable.checked if v.get()]
if Timestable.user:
Timestable.f1.grid_forget()
Timestable.f2.grid()
data.Questions(self)
def results(self):
Timestable.f2.grid_forget()
Timestable.f3.grid()
def next(self):
Timestable.f3.grid_forget()
Timestable.f4.grid()
data.saveleader(self)
self.correct=0
data.deletold(self)
def again(self):
Timestable.f4.grid_forget()
Timestable.f1.grid()
def deletold(self):
Timestable.incdisplay.configure(state=NORMAL)
Timestable.incdisplay.delete("1.0",END)
Timestable.incdisplay.configure(state=DISABLED)
def Submit(self):
if Timestable.entryWidget.get() == "":
messagebox.showerror("Error", "Please enter a number.")
else:
if self.answer != int(Timestable.entryWidget.get().strip()):
messagebox.showinfo("Answer", "INCORRECT! Answer: " + str(self.answer))
Timestable.incorrect.append(self.prompt+"\n")
else:
messagebox.showinfo("Answer", "CORRECT!")
self.correct = self.correct +1
self.x=self.x+1
if self.x < int(Timestable.w.get()):
data.Questions(self)
data.clear_text(self)
else:
data.clear_text(self)
data.results(self)
data.percent = round(self.correct/self.x*100)
Timestable.congrats.configure(text="Congrats, you got "+ str(data.percent) +"% of the questions correct",bg='#66bcb9')
self.x=0
for i in Timestable.incorrect:
Timestable.incdisplay.configure(state=NORMAL)
Timestable.incdisplay.insert(END,i)
Timestable.incdisplay.configure(state=DISABLED)
root = Tk()
root.geometry("300x300")
root.configure(bg='#66bcb9')
Timestable(root)
root.mainloop()

Why is my function not affecting a button in tkinter?

I'm trying to make Tic Tac Toe in python using tkinter, but I'm running into an issue. I'm trying to make it so when you click on one of the squares, it displays whichever player's symbol on that square. However, with my current function playerCheck, nothing happens when I press them. I can't figure out why this is happening, so I would appreciate any help.
Keep in mind I am in no way finished.
import tkinter as tk
Player1 = "X"
Player2 = "O"
turn = None
playerNumber = None
def playerCheck(function):
if turn == Player1:
playerNumber = Player1
function("white", playerNumber)
elif turn == Player2:
playerNumber = Player2
function("white", playerNumber)
def topLeft(color, player):
topL = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(topL)).grid(row=0,column=0)
def topMiddle(color, player):
topM = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(topMiddle)).grid(row=0,column=1)
def topRight(color, player):
topR = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(topRight)).grid(row=0,column=2)
def middleLeft(color, player):
midL = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(middleLeft)).grid(row=1,column=0)
def middleMiddle(color, player):
midM = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(middleMiddle)).grid(row=1,column=1)
def middleRight(color, player):
midR = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(middleRight)).grid(row=1,column=2)
def bottomLeft(color, player):
botL = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(bottomLeft)).grid(row=2,column=0)
def bottomMiddle(color, player):
botM = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(bottomMiddle)).grid(row=2,column=1)
def bottomRight(color, player):
botR = tk.Button(text=player, fg="black", bg=color, height=5, width=10, command=lambda: playerCheck(bottomRight)).grid(row=2,column=2)
def gameStart():
topLeft("white", "")
topMiddle("white", "")
topRight("white", "")
middleLeft("white", "")
middleMiddle("white", "")
middleRight("white", "")
bottomLeft("white", "")
bottomMiddle("white", "")
bottomRight("white", "")
turn = Player1
def Main():
a = tk.Tk()
a.title("Tick Tack Toe")
a.geometry("250x250")
gameStart()
a.mainloop()
Main()
The argument to playerCheck() is a button, not a function. You should change the button's text and color, not try to call it.
def playerCheck(button):
global turn
button['text'] = turn
button['fg'] = "white"
if turn == Player1:
turn = Player2
else:
turn = Player1
Here is an example of a completely working tic-tac-toe made with tkinter. I used your Button approach. If you are going to make anything with tkinter it should look a lot more like the below code. I commented it all over the place to give you a bit of a guide to what everything is doing, but I did not dumb down the code. Some parts may be very hard for you to understand (like the logic in win_or_block()). That's a good thing. It will force you to research and get better.
tips:
Learn how to create and use classes, so you can separate and organize your code into meaningful chunks.
Your approach is to manually define each individual thing, even if they are the exact same thing. You should consider a more dynamic approach where you create something once and let a loop create and position a bunch of copies/instances of it.
If your code was written properly, according to the design that you laid out, you would technically have a Button that recreates itself when it is clicked. This type of circular logic is bad.
Worry less about Buttons and more about game logic. Your graphics should simply express a condition. You're on your way to making the graphics serve as the logic behind your conditions, and that is a terrible approach.
#python 3.8+
from tkinter import Tk, Frame, Button, Entry, StringVar, IntVar, Checkbutton
from statistics import mean
from random import choice
class Splash(Frame):
WIDTH = 300
HEIGHT = 190
def __init__(self, master, callback, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
self.callback = callback
self.message = StringVar()
self.tricky = IntVar()
#just to make some lines shorter
e = dict(font='calibri 18 bold', bg=self['bg'], border=0, justify='center', textvariable=self.message)
b = dict(font='calibri 18 bold', bg='black', fg='white')
c = dict(font='calibri 16 bold', bg=self['bg'])
Entry(self, **e).place(width=280, height=40, x=10, y=5)
Checkbutton(self, text="tricky", variable=self.tricky, **c).place(width=200, height=30, x=50, y=50)
Button(self, text='1 Player' , command=lambda: self.remove(True) , **b).place(width=200, height=45, x=50, y=90)
Button(self, text='2 Players', command=lambda: self.remove(False), **b).place(width=200, height=45, x=50, y=140)
def remove(self, auto):
self.place_forget()
self.callback(auto)
class Board(Frame):
#if you change this you need to change the font size in self.default_cell and vise-versa
WIDTH = 450
HEIGHT = 393
def __init__(self, master, callback, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
#cell default properties
self.default_cell = dict(
foreground = 'black',
background = 'black',
activebackground = 'black',
disabledforeground = 'black',
text = "",
font = "consolas 48 bold",
relief = 'sunken',
state = 'normal',
width = 4,
height = 1,
)
#make board
for i in range(9):
cell = Button(self, **self.default_cell)
cell.config(command=lambda c=cell, i=i: callback(c, i))
cell.grid(row=int(i//3), column=i%3, sticky='nswe')
def reset(self):
for cell in self.winfo_children():
cell.config(**self.default_cell)
#used to stop cell clicks while splash screen is visible
def lock(self):
for cell in self.winfo_children():
cell.config(state='disabled')
class Game(Tk):
def __init__(self, *args, **kwargs):
Tk.__init__(self, *args, **kwargs)
#current player
self.player = 0
#True: against computer - False: against 2nd human
self.auto = False
#stores moves
self.moves = [0]*9
#player colors
self.color = ['red', 'green']
# [crossed swords, flag outline]
self.marks = [chr(9876), chr(9872)]
#positions
self.edge = {1, 7, 3, 5}
self.corner = {0, 6, 2, 8}
#True = hard | False = easy
self.tricky = True
'''
numbers that can't average together with 0 to equal each other
where [n, n, n] is any given winning combination in tic-tac-toe
consider the mean of: [1, 2, 0] vs [1, 1, 1]
against the mean of: [1, 4, 0] vs [1, 1, 1]
'''
self.pids = [1, 4]
#init board
self.board = Board(self, self.move)
self.board.grid(row=0, column=0, sticky='nswe')
#init "game over" screen and place() properties
self.splash = Splash(self, callback=self.reset, bg='gray92')
self.splashprops = dict(
x = (Board.WIDTH-Splash.WIDTH)/2,
y = (Board.HEIGHT-Splash.HEIGHT)/2,
width = Splash.WIDTH,
height = Splash.HEIGHT
)
self.show_splash("Connect 3 And Conquer")
'''
This method is tricky. It is used to check:
1: if the current player has won (id=current_id, n=4)
2: if the computer can win (id=player2_id, n=3)
3: if the computer can block a win (id=player1_id, n=3)
'''
def win_or_block(self, id, n=3):
best = -1
try:
for a in range(3):
#vertical
m = self.moves[a::3]
l = list(filter(lambda i: i>0, m))+[id]
if len(l) == n and mean(l) == id:
best = (m.index(0) * 3) + a
break
#horizontal
m = self.moves[a*3:a*3+3]
l = list(filter(lambda i: i>0, m))+[id]
if len(l) == n and mean(l) == id:
best = m.index(0) + (a * 3)
break
#back slash
if best < 0:
m = self.moves[0::4]
l = list(filter(lambda i: i>0, m))+[id]
if len(l) == n and mean(l) == id:
best = m.index(0) * 4
#forward slash
if best < 0:
m = self.moves[2::2][0:3]
l = list(filter(lambda i: i>0, m))+[id]
if len(l) == n and mean(l) == id:
best = (m.index(0) + 1) * 2
except ValueError:
#m.index(0) does not exist, current player has won
best = 0
return best
def best(self, index):
best = -1
#computer checks if it can win, and if not, then if it can block
for i in range(1, -1, -1):
best = self.win_or_block(self.pids[i])
if best > -1:
break
#if the computer cannot win or there is nothing to block
if best < 0:
avail = {i for i, v in enumerate(self.moves) if v == 0}
c = list(self.corner.intersection(avail)) #corners
e = list(self.edge.intersection(avail)) #edges
#choose the middle or mirror opposite of the last move
if self.tricky:
b = 4 if 4 in avail else abs(index - 8)
if b in avail:
best = b
#attempt a random choice in this order: corner, center, edge
if best < 0:
if len(c):
best = choice(c)
elif 4 in avail:
best = 4
else:
best = choice(e)
return best
def move(self, button, index):
#mark the square
button.config(bg=self.color[self.player], text=self.marks[self.player], state='disabled')
#record the move
self.moves[index] = self.pids[self.player]
#game over screens
if self.win_or_block(self.pids[self.player], 4) > -1: #check win
self.show_splash(f'Player {self.player+1} Has Conquered!')
return
elif self.moves.count(0) == 0: #check "no more moves"
self.show_splash('Stalemate!')
return
#derive next player ~ Player1 = 0, Player2 = 1
self.player = (self.player + 1)%2
if self.auto and self.player == 1:
#invoke the button with the child index that corresponds to the "best" move
self.board.winfo_children()[self.best(index)].invoke()
def show_splash(self, msg):
self.board.lock()
self.splash.message.set(msg)
self.splash.place(**self.splashprops)
def reset(self, auto):
self.tricky = bool(self.splash.tricky.get())
self.auto = auto
self.player = 0
self.moves = [0]*9
self.board.reset()
if __name__ == '__main__':
app = Game()
app.title("Connect 3 And Conquer")
#lock down the window size
app.minsize(Board.WIDTH, Board.HEIGHT)
app.maxsize(Board.WIDTH, Board.HEIGHT)
app.mainloop()
aside:
I understand you are new. Don't take my criticisms to heart. The point is to make you better. Anybody could come in here and tell you how to make poor code work. My agenda is to stop you from writing poor code in the first place - by giving you an excellent example and explaining where you went wrong.
Cheers

how do i allow a function to be used anywhere in python tkinter?

i have 2 classes and when i run the first one, it works fine but when it gets to the second class i get an error saying AttributeError: 'Question2' object has no attribute 'correct'. how do i make it so that the functions work in both of the class? is there something wrong with my indent? please help me fix this code, thanks:
Edit: i have a problem with using self, if i remove self from the functions it wouldnt work, if i indent it to not be a part of the class, it still wont work, the self gets turns white
class Question1:
def __init__ (self, master):
x = random.randint(5, 12)
y = random.randint(5, 12)
self.master = master
self.user_choice = StringVar()
self.user_choice.set("")
self.frame = Frame(master, padx=200, pady=200)
self.frame.grid()
self.q = Label(self.frame, text="What is {} + {} ?".format(x, y))
self.q.grid(row=0)
self.ans = Entry(self.frame, width=50, textvariable=self.user_choice)
self.ans.grid(row=1)
self.answer = x+y
self.sub = Button(self.frame, text="submit", command=self.correct)
self.sub.grid(row=3)
def correct(self):
global p
if int(self.user_choice.get()) == self.answer:
cor = Label(self.frame,text="Correct!")
cor.grid(row=5, pady=20)
p += 1
if p >= 3:
Question2(self.master)
else:
self.sub.destroy()
nex = Button(self.frame, text="Next", command=self.necs)
nex.grid(row=4)
else:
inc = Label(self.frame,text="incorrect")
inc.grid(row=5, pady=20)
self.sub.destroy()
nex = Button(self.frame, text="Next", command=self.necs)
nex.grid(row=4)
self.frame.destroy()
Question1(self.master)
def necs(self):
self.frame.destroy()
Question1(self.master)
class Question2:
def __init__(self, master):
x = random.randint(2, 2)
y = random.randint(2, 3)
self.master = master
self.user_choice = StringVar()
self.user_choice.set("")
self.frame = Frame(master, padx=200, pady=200)
self.frame.grid()
self.q = Label(self.frame, text="What is {} x {} ?".format(x, y))
self.q.grid(row=0)
self.ans = Entry(self.frame, width=50, textvariable=self.user_choice)
self.ans.grid(row=1)
self.answer = x * y
self.sub = Button(self.frame, text="submit", command=self.correct)
self.sub.grid(row=3)
You can do that by inheriting the properties of Question1 to Question2:
That can be:
class Question2(Question1):
#you can access that by:
self.correct()
Other way is you can define a global function outside both the classes and you can easily access it.
Example:
#somewhere globally:
def correct():
#some code
class Question1():
correct()
class Question2():
correct()
I think you can develop more such ideas of using a function which will be required by multiple classes.
As #JenilDave answered, you need to define function outside class, inherit from other class.explicitly call class.
i.e. for last case:
class Question1:
def correct():
<codes>
class Question2:
q1 = Question1()
q1.correct()
or
Question1.correct(<Question1 instance>)
But since your 'correct' function are heavily dependent to Question1, you can't use either way, and reconstruct your codes.
Working example below:
Instead of generating question Class per questions, send lists of questions to one class.
Every time you succeed third time, you'll move on to next questions by poping list you've provided before.
When Lists are empty, pop() causes IndexError and program closes.
...
Since I can't get what variable 'p' stands for, I'm guessing it's number of successes(passes).
Full Code:
import random
from tkinter import Frame, Label, Entry, Button, StringVar, Tk
class QuestionFrame(Frame):
def __init__(self, master, question_answer):
super().__init__(master)
self.x, self.y = 0, 0
self.master = master
self.entries = question_answer
self.question, self.answer = self.entries.pop(0)
self.success = 0
self.user_choice = StringVar()
self.frame = Frame(master)
self.frame.grid()
self.quest_label = Label(self.frame)
self.ans = Entry(self.frame, width=50, textvariable=self.user_choice)
self.sub = Button(self.frame)
self.quest_label.grid(row=0)
self.ans.grid(row=1)
self.sub.grid(row=3)
self.reload_question()
def reload_question(self):
self.x, self.y = random.sample(range(5, 12), 2)
next_quest = f"What is {self.question.format(self.x, self.y)} ?"
self.quest_label.configure(text=next_quest)
self.ans.delete(0, 'end')
self.sub.configure(text="submit", command=self.correct)
def next(self):
print(self.success)
if self.success == 3:
# loads next entry
try:
self.question, self.answer = self.entries.pop(0)
except IndexError:
self.master.destroy()
else:
self.success = 0
self.reload_question()
else:
self.reload_question()
def correct(self):
self.sub.configure(text="Next", command=self.next)
if int(self.user_choice.get()) == self.answer(self.x, self.y):
self.quest_label['text'] = "Correct!"
self.success += 1
else:
self.quest_label['text'] = "Incorrect!"
if __name__ == '__main__':
# Passing questions with list of (question, answer) tuples.
tests = [("{} x {}", lambda x, y: x*y),
("{} - {}", lambda x, y: x-y)]
root = Tk()
root.title(f'Some fancy title')
window = QuestionFrame(root, tests)
window.mainloop()

How to set up multiple frames

This is a simple math game which is currently in progress. The loop starts off in mainGame() which then proceeds to mainMenu(). I am trying to create 2 frames; mframe and gframe in order to .destroy() the frames later on, essentially clearing the previous interface for the next one (similar to changing pages).
error:
Label(gframe, textvariable=self.question_var).pack() #gframe stands
for game frame NameError: name 'gframe' is not defined
from tkinter import *
from random import randint
root = Tk()
mframe = Frame(root).pack()
gframe = Frame(root).pack()
frame.pack()
start = True
class mainMenu:
def __init__(self):
gframe.destroy() #gets rid of previous interface
title = Label(mframe, text = "main menu").pack() #mfame stands for menu frame
class mainGame:
def __init__(self):
if start == False:
mframe.destroy() #gets rid of previous interface
#question
self.question_var = StringVar()
Label(gframe, textvariable=self.question_var).pack() #gframe stands for game frame
#answer
self.user_answer_var = StringVar()
entry = Entry(gframe, textvariable=self.user_answer_var)
entry.pack()
submit = Button(gframe, text = "submit", command = self.check_answer).pack()
#response output
self.response_var = StringVar()
self.count = 0
self.score = 0
Label(gframe, textvariable=self.response_var).pack()
#starts loop
self.ask_question()
root.mainloop()
def ask_question(self):
if self.count == 1:
self.endGame()
num1 = randint(1, 10)
num2 = randint(1, 10)
self.question_var.set("What is "+str(num1)+" + " +str(num2)+"?")
self.true_answer = num1 + num2
#print(self.true_answer) #testing purposes
def check_answer(self):
self.count += 1
user_answer = self.user_answer_var.get()
#print(user_answer) #testing purposes
if int(user_answer) == self.true_answer:
text = "Good job"
self.score += 1
else:
text = "Oh no"
self.response_var.set(text)
#clear answer for next loop
self.user_answer_var.set("")
self.ask_question()
def endGame(self):
print("endGame")
mainMenu()
mainGame()
As said in the comments above, the pack() method returns None. What you need to do is first create the two frames and assign them to variables, then pack them later. This way, the variables still point to the frame instances and not None.
You should change;
root = Tk()
mframe = Frame(root).pack()
gframe = Frame(root).pack()
frame.pack()
start = True
to;
root = Tk()
mframe = Frame(root)
gframe = Frame(root)
mframe.pack()
gframe.pack()
start = True

OptionMenu changes on second function pass Tkinter Python 2.7

The problem I am having is that the function below works fine on the first pass with regards to displaying the chosen value at the top of the OptionMenu before the 'Enter' button is clicked:
First pass
However on a second pass and any future passes to the function the chosen value is not displayed at the top of the OptionMenu (although if selected it will be used) before the 'Enter' Button is pressed:
Second etc. passes
The code is:
def load_create_list(self):
self.app = Toplevel(self.parent)
self.parent.withdraw()
self.app.title("Rgs2")
self.student = []
self.student1=[]
self.gs1=0
self.ng1=0
options = ["2", "3", "4", "5", "6", "7"]
self.results=[]
self.ngg=StringVar() #for the option menu widget the variable must be a stringVar not a normal string, int etc.
self.ng = Label(self.app, text="Number of groups")
self.ng.grid(row=2, column=0)
self.ngg.set("3")
self.ng1 = OptionMenu(self.app, self.ngg, *options)
self.ng1.grid(row=2, column=1, ipadx=10)
for i in range(0,len(self.x3)):
self.L1 = Label(self.app,text="Student")
self.L1.grid(row=i+4, column=0)
self.en = Entry(self.app)
self.en.insert(END, self.x3[i])
self.en.grid(row=i+4, column=1)
self.student.append(self.en)
self.el = int(len(self.student)+1)
for h in range(self.el,self.el+5):
self.L2 = Label(self.app,text="Student")
self.L2.grid(row=h+4, column=0)
self.em = Entry(self.app)
self.em.grid(row=h+4, column=1)
self.student.append(self.em)
button=Button(self.app,text="enter",command=lambda : self.hallo2()).grid(row=h+7,column=0)
button=Button(self.app,text="Save List",command=lambda : self.save_list2()).grid(row=h+7,column=1)
I have tried everything but can't understand what might be causing this issues. Any help would be gladly appreciated. Seems like very odd behaviour could it be something outside of the function that is causing the issue?
Full code is:
from Tkinter import *
from tkFileDialog import askopenfile
class main_menu:
def __init__(self, parent):
self.myParent = parent
self.myContainer1 = Frame(parent)
parent.title("Random Group Sorter")
self.myContainer1.pack()
self.button1 = Button(self.myContainer1, command = lambda : self.hope())
self.button1.configure(text="Create List", height = 1, width = 20, font=("Arial", 16))
self.button1.pack(side=TOP)
self.button1.focus_force()
self.button3 = Button(self.myContainer1, command = lambda : self.hope1())
self.button3.configure(text="Load List", height = 1, width = 20, font=("Arial", 16))
self.button3.pack(side=TOP)
self.button2 = Button(self.myContainer1, command = lambda : exit(), )
self.button2.configure(text="Exit",height = 1, width = 20, font=("Arial", 16))
self.button2.pack(side=TOP)
def hope(self):
self.myParent.destroy()
create_list()
def hope1(self):
self.myParent.destroy()
load_list()
class create_list():
def __init__(self):
parent = Tk()
self.parent=parent
self.student = []
self.student1 =[]
self.student2 =[]
self.fake_student = []
self.results=[]
self.ngg = StringVar()# for the option menu widget the variable must be a stringVar not a normal string, int etc.
self.ng = Label(text="Number of groups")
self.ng.grid(row=2, column=0)
self.ng = OptionMenu(parent, self.ngg, "2", "3", "4", "5","6")
self.ng.grid(row=2, column=1)
for i in range(3,21):
self.L1 = Label(text="Student")
self.L1.grid(sticky=E)
self.en = Entry(parent)
self.en.grid(row=i, column=1)
self.student.append(self.en)
button=Button(parent,text="Enter",command=lambda : self.hallo()).grid(row=23,column=0)
button=Button(parent,text="Save List",command=lambda : self.save_list()).grid(row=23,column=1)
parent.mainloop()
def hallo(self):
self.gs1 = int(len(self.student))
self.ng1 = int(self.ngg.get())# still need to use .get even though a stringvar
for entry in self.student:
self.student1.append(entry.get())
self.student1 = filter(None, self.student1)
for i in self.student1:# this is added as there are duplicate entries in the student list if saved
if i not in self.student2:
self.student2.append(i)
self.parent.destroy()
lis_an(self.student2,self.ng1)
def save_list(self):
for entry in self.student:
self.student1.append(entry.get())
self.student1 = filter(None, self.student1)
import tkFileDialog
root = Tk()
root.withdraw()
file_name = tkFileDialog.asksaveasfile(parent=root)
root.destroy()
print >>file_name, "\n"
print >>file_name, "\n".join(self.student1)
file_name.close()
class load_list:
def __init__(self):
self.x = []
self.x3 = []
self.load_clean()
self.root = Tk()
def load_clean(self):#option to load an already created file, cleans unwanted info from the list
import tkFileDialog
root = Tk()
root.withdraw()
file_name = tkFileDialog.askopenfile(parent=root)
root.destroy()
self.x = file_name.readlines()
file_name.close()
x2 =self.x[:]
for z in self.x:
if z [0:6]== "Random" or z == '\n' or z[0:5] == "Group":
x2.remove(z)
for c in range (0,len(x2)):
v = x2[c].rstrip()# this strip spaces and \n from each list item and returns the cleaned up string
self.x3.append(v)
self.load_create_list()
def load_create_list(self):
parent = Tk()
self.parent=parent
self.student = []
self.student1=[]
self.gs1=0
self.ng1=0
self.results=[]
self.ngg = StringVar()# for the option menu widget the variable must be a stringVar not a normal string, int etc.
self.ng = Label(text="Number of groups")
self.ng.grid(row=2, column=0)
self.ng = OptionMenu(parent, self.ngg, "2", "3", "4", "5", "6")
self.ng.grid(row=2, column=1)
for i in range(0,len(self.x3)):
self.L1 = Label(text="Student")
self.L1.grid(row=i+3, column=0)
self.en = Entry(parent)
self.en.insert(END, self.x3[i])
self.en.grid(row=i+3, column=1)
self.student.append(self.en)
self.el = int(len(self.student)+1)
for h in range(self.el,self.el+5):
self.L2 = Label(text="Student")
self.L2.grid(row=h+3, column=0)
self.em = Entry(parent)
self.em.grid(row=h+3, column=1)
self.student.append(self.em)
button=Button(parent,text="enter",command=lambda : self.hallo2()).grid(row=h+6,column=0)
button=Button(parent,text="Save List",command=lambda : self.save_list2()).grid(row=h+6,column=1)
parent.mainloop()
def hallo2(self):
self.student2= []
self.gs1 = int(len(self.student))
self.ng1 = int(self.ngg.get())# still need to use .get even though a stringvar
for entry in self.student:
self.student1.append(entry.get())
self.student1 = filter(None, self.student1)
for i in self.student1:# this is added as there are duplicate entries in the student list if saved
if i not in self.student2:
self.student2.append(i)
self.parent.destroy()
lis_an(self.student2,self.ng1)
def save_list2(self):
for entry in self.student:
self.student1.append(entry.get())
self.student1 = filter(None, self.student1)
import tkFileDialog
root = Tk()
root.withdraw()
file_name = tkFileDialog.asksaveasfile(parent=root)
root.destroy()
print >>file_name, "\n"
print >>file_name, "\n".join(self.student1)
file_name.close()
class lis_an:
def __init__(self,student1,ng1):
self.student1 = student1
self.ng1=ng1
self.results = []
self.randomList()
def randomList(self): # this creates a random list of students on the course
import random
studentb = self.student1[:]# this is added as otherwise the student list is overwritten
studentc = []
for i in range(len(studentb)):
element = random.choice(studentb)
studentb.remove(element)
studentc.append(element)
self.student1 = studentc
self.partition()
def partition(self): # this creates sub list of the student list containing the groups of students
increment = len(self.student1) / float(self.ng1)
last = 0
i = 1
while last < len(self.student1):
idx = int(round(increment * i))
self.results.append(self.student1[last:idx])
last = idx
i += 1
output(self.results, self.ng1)
class output:
def __init__(self, student, ng1):
self.ng1 = ng1
self.student = student
self.parent = Tk()
for item1 in range (0,len(self.student)):
test1 = "Group " + str(item1+1)+ ":"
v = Label(self.parent, text=test1, font=("Arial", 13))
test = "\n".join(self.student[item1])
w = Label(self.parent, text=test, justify = LEFT, font=("Arial", 12))
v.pack(side="top", anchor="w")
w.pack(side="top", anchor="w")
button=Button(self.parent,text="Repeat",command=lambda : self.join_list()).pack(side="top", anchor="w")
button=Button(self.parent,text="Main Menu",command=lambda : self.menu_link()).pack(side="top", anchor="w")
mainloop()
def join_list(self):#this function creates a new undivided version of student to feed back to lis_an
self.parent.destroy()
self.student = [j for i in self.student for j in i]
lis_an(self.student,self.ng1)
def menu_link(self):#destroys the parent frame and returns back to main menu
self.parent.destroy()
main()
def main():
parent = Tk()
myapp = main_menu(parent)
parent.mainloop()
main()
Very Simple solution to this problem. I was not defining self.ngg as a StringVar in the parent widget so:
self.ngg = StringVar()
Would cause the error, and
self.ngg = StringVar(self.app)
Solves the problem. Not quite sure why this would occur on the second and subsequent uses of the function but not the first.

Categories

Resources