Python self.after() changes running order - python

I am (still!) writing a Noughts and Crosses program using Tkinter in Python 3.
I wanted to implement a short pause between the player's move and the computer's move, so in the add_move_comp() method implemented self.after() function.
I expected this to pause the program for 1000ms then continue by 'drawing' on the computer's move.
However when I run my code, the program only recognises the computer's move the next move after, causing problems with identifying a 3-in-a-row.
I think this is due to line 62 running after 1000ms while the rest of the program continues running, but I don't know how to stop the whole program and continue running with line 62 after the pause.
from tkinter import *
from functools import partial
from itertools import *
import random
class Window(Frame):
def __init__(self, master = None): # init Window class
Frame.__init__(self, master) # init Frame class
self.master = master # allows us to refer to root as master
self.rows = 3
self.columns = 3
self.guiGrid = [[None for x in range(self.rows)] for y in range(self.columns)] # use this for the computer's moves
self.noText = StringVar(value = '')
self.cross = StringVar(value = 'X')
self.nought = StringVar(value = 'O')
self.button_ij = None
self.myMove = True
self.create_window()
self.add_buttons()
def create_window(self):
self.master.title('Tic Tac Toe')
self.pack(fill = BOTH, expand = 1)
for i in range(0,3): # allows buttons to expand to frame size
self.grid_columnconfigure(i, weight = 1)
self.grid_rowconfigure(i, weight = 1)
def add_buttons(self):
rows = 3
columns = 3
for i in range (rows):
for j in range(columns):
self.button_ij = Button(self, textvariable = self.noText, command = lambda i=i, j=j: self.add_move(i,j))
self.guiGrid[i][j] = self.button_ij
self.button_ij.grid(row = i,column = j, sticky =E+W+S+N)
def add_move(self, i,j):
# player's move
while self.myMove:
pressedButton = self.guiGrid[i][j]
self.guiGrid[i][j].config(textvariable = self.cross)
# computer's turn
self.myMove = False
while (self.myMove == False):
self.add_move_comp()
def add_move_comp(self):
repeat = True
while repeat:
i = random.randint(0,0) # row - only allow O to be drawn on 1st row for testing purposes
j = random.randint(0,2) # column
testText = self.guiGrid[i][j].cget('text')
if testText == '':
self.after(1000,lambda i = i, j = j :self.guiGrid[i][j].config(textvariable = self.nought)) # search up rules with returning values using lambda
print('Should plot O at row: ',i,' column: ',j)
print('TEXT ACTUALLY PLOTTED IS: ',self.guiGrid[i][j].cget('text'))
self.myMove = True
repeat = False
print(self.guiGrid[0][0].cget('text'), self.guiGrid[0][1].cget('text'), self.guiGrid[0][2].cget('text')+'THIS IS PRINTING')
root = Tk() # creating Tk instance
rootWidth = '500'
rootHeight = '500'
root.geometry(rootWidth+'x'+rootHeight)
ticTacToe = Window(root) # creating Window object with root as master
root.mainloop() # keeps program running

This is due to the fact that self.after imeadiately returns and starts the given function after passing trough a queque to prevent the actual Window from beeing unable to responde because there is some code running. A way to work around this is to create a BooleanVariable for validation and using self.wait_variable as follows:
from tkinter import *
from functools import partial
from itertools import *
import random
class Window(Frame):
def __init__(self, master = None): # init Window class
Frame.__init__(self, master) # init Frame class
self.master = master # allows us to refer to root as master
self.rows = 3
self.columns = 3
self.guiGrid = [[None for x in range(self.rows)] for y in range(self.columns)] # use this for the computer's moves
self.noText = StringVar(value = '')
self.cross = StringVar(value = 'X')
self.nought = StringVar(value = 'O')
self.button_ij = None
self.myMove = True
self.validationVar = BooleanVar(self)
self.create_window()
self.add_buttons()
def create_window(self):
self.master.title('Tic Tac Toe')
self.pack(fill = BOTH, expand = 1)
for i in range(0,3): # allows buttons to expand to frame size
self.grid_columnconfigure(i, weight = 1)
self.grid_rowconfigure(i, weight = 1)
def add_buttons(self):
rows = 3
columns = 3
for i in range (rows):
for j in range(columns):
self.button_ij = Button(self, textvariable = self.noText, command = lambda i=i, j=j: self.add_move(i,j))
self.guiGrid[i][j] = self.button_ij
self.button_ij.grid(row = i,column = j, sticky =E+W+S+N)
def add_move(self, i,j):
# player's move
while self.myMove:
pressedButton = self.guiGrid[i][j]
self.guiGrid[i][j].config(textvariable = self.cross)
# computer's turn
self.myMove = False
while (self.myMove == False):
self.add_move_comp()
def add_move_comp(self):
repeat = True
while repeat:
i = random.randint(0,0) # row - only allow O to be drawn on 1st row for testing purposes
j = random.randint(0,2) # column
testText = self.guiGrid[i][j].cget('text')
if testText == '':
self.after(1000, self.validationVar.set, True)
self.wait_variable(self.validationVar)
self.guiGrid[i][j].config(textvariable = self.nought) # search up rules with returning values using lambda
print('Should plot O at row: ',i,' column: ',j)
print('TEXT ACTUALLY PLOTTED IS: ',self.guiGrid[i][j].cget('text'))
self.myMove = True
repeat = False
print(self.guiGrid[0][0].cget('text'), self.guiGrid[0][1].cget('text'), self.guiGrid[0][2].cget('text')+'THIS IS PRINTING')
root = Tk() # creating Tk instance
rootWidth = '500'
rootHeight = '500'
root.geometry(rootWidth+'x'+rootHeight)
ticTacToe = Window(root) # creating Window object with root as master
root.mainloop() # keeps program running

Related

My python tkinter buttons are updating too quickly after the wrong comparison is made

I'm trying to make a memory game for fun and as a learning experience, and I've run into the issue where even with something like time.sleep(.5) I still can't get buttons to update correctly with a delay. In fact the second button seems to update to hidden as it's about to show the proper image. I'm assuming the issue lies somewhere in the buttonClicked() function.
I'm trying to figure out how I can make it show one button, then the second, then wait half a second and hide both. And if someone understands why this is happening or where I could look into the issue and read up on my own, that would be helpful.
Thanks.
from re import A
import time
import tkinter as tk
from tkinter import *
from typing_extensions import Self
from PIL import Image, ImageTk
import glob
import os, os.path
import numpy as np
from sqlalchemy import null
#resize images and button
imgButtonWidth = 100
imgButtonHeight = 100
imgButtonSize = (imgButtonWidth,imgButtonHeight)
#Set the height and width of the game by number of items.
width = 6
height = 6
#buttons = [[Button]*width]*height
#Total number of items 36 (0-35)
count = width*height-1
buttonList = []
#Will be a 2d array of [button, id]
answersList = []
clickedCount = 0
imgs = []
hiddenImg = null
# Create frame, set default size of frame and background color.
root = Tk()
root.title('Memory Game')
root.geometry(str(imgButtonWidth * (width+1)) + "x" + str(imgButtonHeight * (height+1)))
root.config(bg='darkblue')
frame = Frame(root, bg='darkblue')
# Fetch images from location and create a list of Image objects, then return.
def getImages():
imgs = []
path = "/home/paul/Programming/Python/MyMiniProjects/Mid/MemoryGame/"
valid_images = [".jpg",".gif",".png",".tga"]
for f in os.listdir(path):
ext = os.path.splitext(f)[1]
if ext.lower() not in valid_images:
continue
imgs.append([Image.open(os.path.join(path,f)).resize(imgButtonSize), f])
return imgs + imgs
#Shuffle images for the game
imgs = getImages()
random.shuffle(imgs)
#Simple image to cover the tiles
hiddenImg = ImageTk.PhotoImage(Image.new('RGB', (imgButtonWidth, imgButtonHeight), (0,0,105)))
#Disable buttons after a match
def disable():
global clickedCount, answersList
clickedCount = 0
for a in answersList:
a[0]["state"] = "disabled"
a[0]["bg"] = "green"
answersList = []
#Hide buttons again
def hide():
global clickedCount, answersList
clickedCount = 0
for a in answersList:
#a[0].config(image = hiddenImg)
a[0]["image"] = hiddenImg
a[0]["state"] = "normal"
a[0]["bg"] = "white"
answersList = []
def wrong():
for a in answersList:
a[0]["bg"] = "red"
def buttonClicked(picture, id, button):
global clickedCount, answersList
print(clickedCount, len(answersList))
#print(button.image, "1", hiddenImg, picture)
if button.image is hiddenImg and clickedCount < 2:
button["image"] = picture
button["state"] = "disabled"
clickedCount += 1
answersList.append([button, id])
if len(answersList) == 2:
#Check id but make sure it's not the same button pressed twice
if answersList[0][1] is answersList[1][1]:#and answersList[0][0] is not answersList[1][0]:
disable()
else:
wrong()
hide()
#Create the actual buttons with their respective image
for h in range(height): #print(buttons[w][::],"\n")
newList = []
for w in range(width):
tempImage = imgs.pop(count)
picture = ImageTk.PhotoImage(tempImage[0])
id = tempImage[1]
button = Button(frame, image=hiddenImg, state=NORMAL, height=imgButtonHeight, width=imgButtonWidth)
#Need to split this up because of how python handles closures
button["command"] = lambda pic_temp=picture, id_temp=id, button_temp = button: buttonClicked(pic_temp, id_temp, button_temp)
button.image = hiddenImg
#buttons[w][h].name = str(w + h)
#buttons[w][h].grid(row=w, column=h, ipadx=random.randint(0,40), ipady=random.randint(0,40), padx=random.randint(0,5), pady=random.randint(0,5))
button.grid(row=h, column=w, padx=1, pady=1)
#Button(frame, image=picture).grid(row=w, column=h, ipadx=random.randint(0,40), ipady=random.randint(0,40), padx=random.randint(0,5), pady=random.randint(0,5))
count -= 1
# buttonList.append(buttons[h][w])
newList.append(button)
buttonList.append(newList)
# for y in range(height):
# for x in range(width):
# print(ButtonList[y][x])
# print("")
frame.pack(expand=True)
root.mainloop()```

Could somebody help me figure out the error in my code? (Python basic GUI)

I've only recently dipped my toes into python (only ever coded in java and JS) and thought I would try my hand at making a simple number pad GUI. I'm still getting used to the indentation format.
I've applied proper indenting and fixed all typos and syntax errors but still receive the following error
"Traceback (most recent call last):
File "/home/pi/Desktop/Lab Work/Lab 1/Number Pad test GUI.py", line 4, in
class BaseWindow(tkinter.Tk):
File "/home/pi/Desktop/Lab Work/Lab 1/Number Pad test GUI.py", line 8, in BaseWindow
self.minsize (x,y)
NameError: name 'self' is not defined"
I have updated the code below to my latest version also
import tkinter, tkinter.ttk as ttk
import random
class BaseWindow(tkinter.Tk):
def _Change (self):
x,y = self.winfo_width(), self.winfo_height()
self.minsize (x, y); self.maxsize(x, y)
#This locks window size when called
def FgridFormatButtons (self, ButtonList, NewLineAmount = 3):
self.Row = 0
self.Col = 0
for Button in ButtonList:
Button.grid(row = self.Row, column = self.Col)
self.Col += 1
if self.Col == NewLineAmount:
self.Row += 1
self.Col = 0
continue
class Window (BaseWindow):
def __init__(self, **args):
super(Window, self).__init__()
#Main method code
self.EntryFrame = ttk.Frame(self)
self.PadFrame = ttk.Frame(self)
self.EntryFrame.pack(padx = 5, pady = 5)
self.PadFrame.pack(padx = 5, pady = 5)
self.AllButtons = []
self.CanWrite = true
self.Cod = args.get("Code") or random.randrange(9999)
self.Timer = args.get("Timer") or 2000
print ("debug %d"% self.Code)
for x in range (1,10):
self.AllButtons.append(ttk.Button(self.PadFrame, width = 4, text = x, command = lambda y = x: self.Update(x)))
self.bind(str(x), lambda CatchEvent, y = x: self.Update(y))
self.FGridFormatButtons(self.AllButtons)
self.ZeroButton = ttk.Button (self.PadFrame, width = 4, text = 0, command = lambda: self.Update(0))
self.SubmitButton = ttk.Button(self.PadFrame, width = 4, text = "Ent", command = self.CheckCode)
self.ClearButton = ttk.Button(self.PadFrame, width = 4, text = "C", command = lambda: self.Update(-1))
self.ClearButton.grid(row = self.Row, column = 0)
self.ZeroButton.grid(row = self.Row, column = 1)
self.SubmitButton.grid(row = self.Row, column = 2)
self.bind ("0", lambda CatchEvent: self.Update(0))
self.bind("<return>", lambda CatchEvent: self.CheckCode())
self.KeyEnter = ttk.Entry(self,EntryFrame, state ="disabled")
self.KeyEnter.pack
#--
self.after (5, self._Change)
#This will wait 5 miliseconds and then lock the window
#If your computer takes longer than 5 miliseconds to load then kill yourself. Or alternatively, change the last value to match how long it takes
def Update (self, x):
if self.CanWrite:
self.KeyEnter["state"] = "normal"
if x == 1:
self.KeyEnter.delete(0, tkinter.END)
else:
self.KeyEnter.insert(tkinter.END, x)
self.KeyEnter["state"] = 'disabled'
def CheckCode(self):
Key = self.KeyEnter.get()
self.Update(-1)
if Key == str(self.Code):
self.Update("Correct Code!")
self.after(self.Timer, self.destroy)
else:
self.Update("Incorrect code")
self.ChangeWritePerms()
self.after(self.Timer, self.ChangeWritePerms)
def ChangeWritePerms(self):
if self.CanWrite:
self.CanWrite = False
else:
self.CanWrite = True
self.Update(-1)
window().mainloop()
class BaseWindow(tkinter.Tk):
def _change (self):
x,y = self.winfo_width(), self.winfo_height()
self.minimize (x,y)
#This locks window size when called
def FgridFormatButtons (self, ButtonList, NewLineAmount = 3):
self.Row = 0
self.Col = 0
for Button in ButtonList:
Button.grid(row = self.Row, column = self.Col)
self.Col += 1
if self.Col == NewLineAmount:
self.Row += 1
self.Col = 0
continue
class Window (BaseWindow):
def __init__(self, **args):
super(Window, self).__init__()
#Main method code
self.EntryFrame = ttk.Frame(self)
self.PadFrame = ttk.Frame(self)
self.EntryFrame.pack(padx = 5, pady = 5)
self.PadFrame.pack(padx = 5, pady = 5)
self.AllButtons = []
self.CanWrite = true
self.Cod = args.get("Code") or random.randrange(9999)
self.Timer = args.get("Timer") or 2000
print ("debug %d"% self.Code)
for x in range (1,10):
self.AllButtons.append(ttk.Button(self.PadFrame, width = 4, text = x, command = lambda y = x: self.Update(x)))
self.bind(str(x), lambda CatchEvent, y = x: self.Update(y))
self.FGridFormatButtons(self.AllButtons)
self.ZeroButton = ttk.Button (self.PadFrame, width = 4, text = 0, command = lambda: self.Update(0))
self.SubmitButton = ttk.Button(self.PadFrame, width = 4, text = "Ent", command = self.CheckCode)
self.ClearButton = ttk.Button(self.PadFrame, width = 4, text = "C", command = lambda: self.Update(-1))
self.ClearButton.grid(row = self.Row, column = 0)
self.ZeroButton.grid(row = self.Row, column = 1)
self.SubmitButton.grid(row = self.Row, column = 2)
self.bind ("0", lambda CatchEvent: self.Update(0))
self.bind("<return>", lambda CatchEvent: self.CheckCode())
self.KeyEnter = ttk.Entry(self,EntryFrame, state ="disabled")
self.KeyEnter.pack
#--
self.after (5, self._Change)
#This will wait 5 miliseconds and then lock the window
#If your computer takes longer than 5 miliseconds to load then kill yourself. Or alternatively, change the last value to match how long it takes
def Update (self, x):
if self.CanWrite:
self.KeyEnter["state"] = "normal"
if x == 1:
self.KeyEnter.delete(0, tkinter.END)
else:
self.KeyEnter.insert(tkinter.END, x)
self.KeyEnter["state"] = 'disabled'
def CheckCode(self):
Key = self.KeyEnter.get()
self.Update(-1)
if Key == str(self.Code):
self.Update("Correct Code!")
self.after(self.Timer, self.destroy)
else:
self.Update("Incorrect code")
self.ChangeWritePerms()
self.after(self.Timer, self.ChangeWritePerms)
def ChangeWritePerms(self):
if self.CanWrite:
self.CanWrite = False
else:
self.CanWrite = True
self.Update(-1)
window().mainloop()
It seems like you change your question.
Try to change tkinter.tk into tkinter.Tk with big T.
This error shows that you forgot to put comma before lambda function.
"line 60 self.bind("" lambda CatchEvent: self.CheckCode()) ^SyntaxError: invalid syntax"
Have you tried to add comma ?
Check line 5 and 110 you forget space after def and colon at the end function argument.
Check line 86 too, you defined key as Key in line 82, so you have to call it Key with big K in line 86.
Check line 88, it shows self.;after. Please delete the semicolon to avoid syntax error too.
What is the text editor you are using ? If you use vscode, it will show the error with red underline.
include "self.minsize (x, y); self.maxsize(x, y)" inside the function
any statement inside the class must be inside a function
Ex: import tkinter, tkinter.ttk as ttk
import random
class BaseWindow(tkinter.Tk):
def _Change (self):
x,y = self.winfo_width(), self.winfo_height()
self.minsize (x, y); self.maxsize(x, y)

Python tkinter Doubling Checkboxes

I'm currently working on a Synthesizer inside Python for a school project and currently have a really troublesome issue. I have a Stack of Checkboxes which mark when a note is played and on which pitch inside a sequencer. My Problem is that whenever I open two Oscillators inside my synthesizer and put in the Values inside the checkboxes, the checkboxes duplicate their value over the multiple windows, but don't do it for the actual sequence, as in I have two lists with correct values, but the values aren't properly displayed inside the window.
To Replicate the Problem hit "new Oscillator" then click on "Arpeggio", do this a second time and click any Checkbox on the Arppeggio Windows
I know this might be a confusing explanation, but I'm going to link the complete code in the bottom so you can try it out and might know what I'm talking about.
The problem occurs inside the "Arpeggio" Class
import numpy # used for Waveform Calculation
from functools import partial # used for Command Combining
from tkinter import * # used for GUI
from tkinter import ttk # used for GUI
from tkinter import filedialog # used for GUI
np = numpy # Simplifying Libraries
tk = ttk # Simplifying Libraries
fd = filedialog # Simplifying Libraries
root = Tk()
#StartupFunction
def StartUp():
print("")
print("Starting Startup")
app = App(root) # Initializing GUI
print("Finished Startup")
main()
("Exiting Startup")
#Main Program Function
def main():
print("Executing Main")
root.mainloop()
print("Finished Main")
return 0
class Oscillator():
pass
class App(tk.Frame):
OscillatorWindowList = []
OscillatorList = []
SoundInputArrayList = []
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
root.title("PySynth")
root.geometry("984x300")
root.resizable(False, True)
root.maxsize(984,720)
btnNewOscillator = Button(root,text="New Oscillator",command=self.NewOscillator,relief=RIDGE,bg="#2d2d2d",fg="white")
btnNewOscillator.place(x = 8, y = 8+128)
def NewOscillator(self):
print("AddingOscillator")
self.OscillatorList.append(Oscillator())
self.SoundInputArrayList.append(SoundInputArray(root,len(self.OscillatorList)-1,len(self.OscillatorList)-1))
print(self.OscillatorList)
self.OscillatorWindowList.append(OscillatorGUI(root,self.OscillatorList[len(self.OscillatorList)-1],len(self.OscillatorList)))
def EXIT(self):
root.destroy()
#$SoundInputArray
class SoundInputArray():
actv = []
CheckbuttonList = []
CheckButtonFreq = []
i=0
ButtonCount = 32
VolumeSlider = None
btnArpeggio = None
Arpeggio = None
hasArpeggio = False
LFO = None
ArpeggioList = [(0,0)]
def __init__(self,master,oscillatorCount,number):
btnArpeggio = Button(master,text="Arpeggio",command=self.OpenArpeggio,relief=RIDGE,bg="#2d2d2d",fg="white")
btnArpeggio.place(x = 8, y = (1+oscillatorCount)*48 +128 )
def OpenArpeggio(self):
if self.Arpeggio == None:
self.Arpeggio = Arpeggio()
def GetArpeggio(self):
return self.Arpeggio
#$Arpeggio
class Arpeggio():
SoundValueList = None
def __init__(self):
GUI = Toplevel(root)
GUI.title("Arpeggio")
GUI.geometry("480x320")
GUI.resizable(False, False)
GUI.configure(bg="#171717")
self.SoundValueList = np.arange(0,16)
self.DrawUI(GUI)
self.ClearList()
def DrawUI(self,frame):
Button(frame,text="display", command= self.PrintSound, width=11,bg="#171717",).place(x = 4, y = 4)
Button(frame,text="empty", command= self.ClearList).place(x = 96, y = 4)
y = 1
x = 1
checkbuttonList = []
for y in range(1,13):
for x in range(0,16):
updatecommand = partial(self.UpdateList,x,y)
checkbuttonList.append(Checkbutton(frame, variable=self.SoundValueList[x], onvalue=y, offvalue=0,command = updatecommand))
checkbuttonList[len(checkbuttonList)-1].place(x = x*24 + 96, y= y*24 + 8)
def ClearList(self):
for i in range(0,16):
self.SoundValueList[i] = 0
def UpdateList(self,x,value):
if (self.SoundValueList[x] == value):
self.SoundValueList[x] = 0
else:
self.SoundValueList[x] = value
self.PrintSound()
def PrintSound(self):
print(self.SoundValueList)
def GetList(self):
print(self.SoundValueList)
return self.SoundValueList
StartUp() # Initiate Program

Widgets become invisible when i shrink my window - Tkinter - Python

I have been creating a GUI application for university using Tkinter and python.
I am however having a problem where when the application first loads, or when i make the window smaller, the only widget visible is the Plotter (extends canvas) widget. If i expand the window however, the others become visible.
This is my code:
from assign2_support import *
import tkinter as tk
from tkinter import *
from tkinter.messagebox import *
import random
from tkinter.filedialog import askopenfilename
def get_station_name(filename):
temp1 = list(filename.split("/"))
temp = list((temp1[len(temp1) - 1]).split("."))
return temp[0]
def isInDict(value, dic):
if value in dic:
return True
else:
return False
#TemperaturePlotApp class
class TemperaturePlotApp(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.stations = TemperatureData()
self.color = ['#f90909', '#ffa405', '#c0c203', '#1abd04', '#058096', '#042ee1',
'#d30af1','#ec06b3']
self.selected = dict()
self.usedColors = dict()
self.master.title("Max Temperature")
self.button = tk.Button(self, text="File", command=self.load_file, width=10)
self.button.pack(side = 'top', anchor = tk.W)
self.plotter = Plotter(self,width=850, height=400, bg="white", highlightthickness=0)
self.plotter.pack(fill='both', expand=tk.YES)
self.plotter.bind("<B1-Motion>", self.onPlotClicked)
self.plotter.bind("<Button 1>", self.onPlotClicked)
# tag all of the drawn widgets TODO delete
self.plotter.addtag_all("all")
self.df = DataFrame(self)
self.df.pack(fill = tk.X, anchor = tk.N, pady = 10)
self.sf = SelectionFrame(self)
self.sf.pack(fill = tk.X, anchor = tk.N)
self.pack(fill = 'both', side = 'left', expand = tk.YES)
def loadStation(self, stationName):
self.stations.load_data(stationName + ".txt")
def onPlotClicked(self, event):
x = event.x
year = self.ct.get_year(x)
self.df.setYear(year)
try:
self.plotter.delete(self.l)
except:
pass
self.l = self.plotter.create_line(x, 0, x, self.plotter.winfo_height(), fill = "black")
for s in self.stations.get_stations():
if self.selected[s] == True:
temp = self.stations.get_data()[s].get_temp(int(year))
print(temp)
self.df.setDatumText(s, temp)
def plotData(self):
self.plotter.delete(tk.ALL)
minY, maxY, minT, maxT = self.stations.get_ranges()
self.ct = CoordinateTranslator(self.plotter.winfo_width(),self.plotter.winfo_height(), minY, maxY, minT, maxT)
self.i = 0
data = self.stations.get_data()
for s in self.stations.get_stations():
firstRun = True
if s in self.usedColors:
pass
else:
self.usedColors[s] = random.choice(self.color)
if self.sf.isCheckButton(s) == False:
self.sf.addCheckButton(s, self.usedColors[s], lambda: self.toggleCheckButton(s))
self.selected[s] = self.stations.is_selected(self.i)
if self.selected[s] == True:
if self.df.isInDataFrameLabels(s) == False:
self.df.addDatum("", self.usedColors[s], s)
if self.df.isHidden(s) == True:
self.df.showDatum(s)
for d in data[s].get_data_points():
if firstRun:
self.lastX, self.lastY = self.ct.temperature_coords(d[0], d[1])
firstRun = False
else:
x, y = self.ct.temperature_coords(d[0], d[1])
self.plotter.create_line(self.lastX, self.lastY, x, y, fill = self.usedColors[s])
self.lastX = x
self.lastY = y
else:
self.df.hideDatum(s)
self.i = self.i + 1
def toggleCheckButton(self, stationName):
if self.selected[stationName] == True:
self.selected[stationName] = False
else:
self.selected[stationName] = True
self.plotData()
def load_file(self):
fname = askopenfilename(filetypes=([("Text files","*.txt")]))
if fname:
fn = get_station_name(fname)
self.loadStation(fn)
self.plotData()
try:
print(fname) # TODO Delete
except:
showinfo("Failed to read file", "failed to read file: " + fname)
return
# Start DataFrame class
class DataFrame(tk.Frame):
def __init__(self,parent, *args,**kwargs):
tk.Frame.__init__(self, parent,*args,**kwargs)
self.lb = dict()
self.l = tk.Label(self, text="Data for ")
self.l.pack(side = 'left')
self.year = tk.Label(self, text="")
self.year.pack(side = 'left')
self.hidden = dict()
def addDatum(self, txt, color, stationName):
l1 = tk.Label(self, text=txt, fg = color)
self.lb[stationName] = l1
l1.pack(side = 'left')
self.hidden[stationName] = False
def setDatumText(self, stationName, txt):
self.lb[stationName].configure(text = txt)
def hideDatum(self, stationName):
self.lb[stationName].pack_forget()
self.hidden[stationName] = True
def showDatum(self, stationName):
self.lb[stationName].pack(side = 'left')
self.hidden[stationName] = False
def isHidden(self, stationName):
return self.hidden[stationName]
def setYear(self, year):
self.year.configure(text = str(year) + ":")
def getDataFrameLabels(self):
return self.lb
def isInDataFrameLabels(self,stationName):
return isInDict(stationName, self.lb)
# Start SelectionFrame Class
class SelectionFrame(tk.Frame):
def __init__(self,parent,*args,**kwargs):
tk.Frame.__init__(self, parent,*args,**kwargs)
self.cb = dict()
self.l = tk.Label(self, text="Station Selection: ").pack(side = 'left')
def addCheckButton(self, text, color, com):
c = tk.Checkbutton(self, text = text, fg = color, activeforeground = color, command = com)
self.cb[text] = c
c.select()
c.pack(side = 'left')
def getCheckButtons(self):
return self.cb
def isCheckButton(self, stationName):
if stationName in self.cb:
return True
else:
return False
# Start Plotter Class
class Plotter(tk.Canvas):
def __init__(self, parent,*args,**kwargs):
Canvas.__init__(self,parent,**kwargs)
self.bind("<Configure>", self.on_resize)
self.height = self.winfo_reqheight()
self.width = self.winfo_reqwidth()
def on_resize(self,event):
# determine the ratio of old width/height to new width/height
wscale = float(event.width)/self.width
hscale = float(event.height)/self.height
self.width = event.width
self.height = event.height
# resize the canvas
self.config(width=self.width, height=self.height)
# rescale all the objects tagged with the "all" tag
self.scale("all",0,0,wscale,hscale)
#Begin TemperatureData class
class TemperatureData:
def __init__(self):
self._data = dict()
self._stationNames = list()
self._stationsSelected = list()
def load_data(self, filename):
station_name = get_station_name(filename)
self._stationNames.append(station_name)
self._stationsSelected.append(True)
station = Station(filename)
self._data[station_name] = station
def get_data(self):
return self._data
def toggle_selected(self, i):
if self._stationsSelected[i] == True:
self._stationsSelected[i] = False
else:
self._stationsSelected[i] = True
def is_selected(self, i):
return self._stationsSelected[i]
def get_stations(self):
return self._stationNames
def get_ranges(self):
min_year = None
max_year = None
min_temp = None
max_temp = None
for k, v in self._data.items():
if min_year == None or max_year == None or min_temp == None or max_temp == None:
min_year, max_year = v.get_year_range()
min_temp, max_temp = v.get_temp_range()
else:
t_min_year, t_max_year = v.get_year_range()
t_min_temp, t_max_temp = v.get_temp_range()
min_year = min(min_year, t_min_year)
max_year = max(max_year, t_max_year)
min_temp = min(min_temp, t_min_temp)
max_temp = max(max_temp, t_max_temp)
return (min_year, max_year, min_temp, max_temp)
#End TemperatureData class
# My support
def load_stations(stations_file):
"""Return the list of station names
load_stations() -> list(str)
"""
fd = open(stations_file, "r")
stations = []
for line in fd:
line = line.strip()
if not line:
continue
stations.append(line)
fd.close()
return stations
##################################################
# !!!!!! Do not change (or add to) the code below !!!!!
###################################################
def main():
root = tk.Tk()
app = TemperaturePlotApp(root)
app.pack()
root.geometry("800x400")
root.mainloop()
if __name__ == '__main__':
main()
If someone wouldnt mind pointing out to me why this is happening, i would much appreciate it, as the assignment is due in 4 hours, and i have no idea what to do.
EDIT:
assign2_support.py file code:
#
# Support for assignment 2
#
# Imports for use in your assignment
import tkinter as tk
import os.path
from tkinter import filedialog
from tkinter import messagebox
# colours for drawing lines and text
COLOURS = ['#f90909', '#ffa405', '#c0c203', '#1abd04', '#058096', '#042ee1',
'#d30af1','#ec06b3']
def load_data_points(filename):
"""Return the data contained in the given file.
load_data_points(str) -> dict(int:float)
"""
fd = open(filename, 'r')
data = {}
for line in fd:
parts = line.split(',')
data[int(parts[0])] = float(parts[1])
return data
class FileExtensionException(Exception):
pass
class Station(object):
"""A class for storing yearly average temperature data for a given station
"""
def __init__(self, stationfile):
""" Constructor: Station(str)"""
self._data = load_data_points(stationfile)
keys = self._data.keys()
self._min_year = min(keys)
self._max_year = max(keys)
temps = self._data.values()
self._min_temp = min(temps)
self._max_temp = max(temps)
base = os.path.basename(stationfile)
if not base.endswith('.txt'):
raise(FileExtensionException())
self._name = base.replace(".txt", "")
def get_temp(self, year):
"""Return the temperature average for the given year.
get_temp(int) -> float
"""
return self._data.get(year)
def get_data_points(self):
"""Return the data as a list of points in year order
get_data_points() -> list((int, float))
"""
return [(year, self._data[year]) for year in sorted(self._data.keys())]
def get_year_range(self):
""" Return the range of years in the data
get_year_range() -> (int, int)
"""
return (self._min_year, self._max_year)
def get_temp_range(self):
"""Return the range of temperatures in the data
get_temp_range() -> (float, float)
"""
return (self._min_temp, self._max_temp)
def get_name(self):
return self._name
def __repr__(self):
return "Station({0})".format(self._name)
class CoordinateTranslator(object):
"""A class which manages translation of data values into (x, y) coordinates.
The application manages real-world data (year, temp), but the Canvas
drawings require (x, y) coordinates. This class
converts between the two.
"""
def __init__(self, width, height, min_year, max_year, min_temp, max_temp):
"""
Create a CoordinateTranslator with the given canvas width/height,
the smallest and largest years and
the smallest and largest temperatures
Constructor: CoordinateTranslator(int, int, int, int, float, float)
"""
self._min_year = min_year
self._max_year = max_year
self._min_temp = min_temp
self._max_temp = max_temp
self.resize(width, height)
def resize(self, width, height):
"""Adjust the scaling factors to account for a new width/height.
After the Canvas resizes, call this method to fix the scaling.
"""
self._xscale = (self._max_year - self._min_year) / width
self._yscale = (self._max_temp - self._min_temp) / height
self._width = width
self._height = height
def temperature_coords(self, year, temperature):
"""Given a year and a temperature,
return (x, y) coordinates to plot.
temperature_coords(int, float) -> (float, float)
"""
return ((year - self._min_year)/ self._xscale,
self._height - (temperature - self._min_temp) / self._yscale)
def get_year(self, x):
"""Given an x coordinate on the Canvas, return the year that it
corresponds to.
get_year(float) -> int
"""
return int(x * self._xscale + 0.5) + self._min_year
## CSSE7030
def best_fit(points):
"""Given points are a list of (x,y) points ordered by x
this function computes the best line fit over that range and
returns the coords of end points of the line.
best_fit(list((floatt, float)) -> ((float, float), (float, float))
"""
count = len(points)
if count == 0:
# needed to avoid division by zero
# return something that will not appear on screen if drawn
return ((-1,-1), (-1, -1))
x_values = [x for x, _ in points]
y_values = [y for _, y in points]
sum_x = sum(x_values)
sum_y = sum(y_values)
sum_x2 = sum(x**2 for x in x_values)
sum_y2 = sum(y**2 for y in y_values)
sum_xy = sum(x*y for x,y in points)
x_mean = sum_x/count
y_mean = sum_y/count
slope = (sum_xy - sum_x * y_mean) / (sum_x2 - sum_x * x_mean)
y_inter = y_mean - slope * x_mean
return ((x_values[0], slope * x_values[0] + y_inter),
(x_values[-1], slope * x_values[-1] + y_inter))
Thanks heaps
Corey :)
You are creating a canvas with a requested size of 850x400. You are fixing the window size to be 800x400. Because there's not enough room in the window to fit everything in, Tkinter has to start reducing widgets, or removing widgets from view. It won't attempt to reduce a widget below its requested size, so it's not going to shrink your canvas. So, it's next option is to start hiding widgets from view.
When tkinter has to start hiding part or all of a widget from view, it starts with the widget last in the "packing list" -- the last widget to have called pack(...). Thus, if you pack the canvas last, before the bottom frame, it will be the one that starts getting shrunk below its requested size.
A simple fix is to remove the width and height attributes of the canvas, and also remove the binding on <Configure>. This lets tkinter decide the size of the canvas, which when set up properly, means that it will grow and shrink to fit the available space.
You can also save packing of the canvas to the very last, which makes it the first widget to start getting "chopped off" when there isn't enough room.
For the complete description of the packing algorithm see http://tcl.tk/man/tcl8.5/TkCmd/pack.htm#M26

Tkinter: Identifying button by row and column

I want to be able to select a button based on what row and column it is in on a grid and the button and control its Text and Relief. I haven't been able to find anything on widgets or cells used in this manner.
Edit:
I changed where root is placed and now it says that I can't use a tuple that I recieved for 'relief' which makes sense, I need to access the widget itself. Any reccomendations
import tkinter
import functools
import random
from time import sleep
width = input('Enter the grid width. ')
height = input('Enter the grid height. ')
numb = input('Enter the number of bombs. ')
Matrix = [[0 for lp in range(int(width))] for fg in range(int(height))]
def ranintx():
return random.randint(0,int(width))
def raninty():
return random.randint(0,int(height))
def placemines():
y = ranintx()
x = raninty()
for ranintformine in range(int(numb)):
x = ranintx()
y = raninty()
Matrix[y-1][x-1] = 1
placemines()
def sunken(event, self, x, y):
button = event.widget
button['relief'] = 'sunken'
if x - 1 < 0 :
return
if x > int(width) + 1 :
return
if y - 1 < 0 :
return
if y > int(height) + 1 :
return
if Matrix[x][y] == 1 :
top = tkinter.Toplevel()
top.title("About this application...")
msg = tkinter.Message(top, text="You Lose")
msg.pack()
button = tkinter.Button(top, text="Dismiss", command=top.destroy)
button.pack()
print('Column = {}\nRow = {}'.format(x, y))
else:
n1 = x - 1
n2 = y - 1
for lp in range(3):
for lp2 in range(3):
abutton = root.grid_location(n1, n2)
abutton['relief'] = ['sunken']
# I want to be able to change and select the button here. This was one of my poor attempt
n2 =+ 1
n1 =+ 1
def push(event, self, x, y):
button = event.widget
if Matrix[x][y] == 1 :
print('Column = {}\nRow = {}'.format(x, y))
class MineSweep(tkinter.Frame):
#classmethod
def main(cls, width, height):
window = cls(root, width, height)
'''placemine()'''
root.mainloop()
def __init__(self, master, width, height):
super().__init__(master)
self.__width = width
self.__height = height
self.__build_buttons()
self.grid()
#def sunken(event):
# button = event.widget
# button['relief'] = 'sunken'
def __build_buttons(self):
self.__buttons = []
for y in range(self.__height):
row = []
for x in range(self.__width):
button = tkinter.Button(self, state='disabled')
button.grid(column=x, row=y)
button['text'] = ' '
print(grid.slaves)
self.checked = True
#button['command'] = functools.partial(self.__push, x, y)
button.bind("<Button-3>",
lambda event, arg=x, brg=y: push(event, self, arg, brg))
button['relief'] = 'raised'
button.bind("<Button-1>",
lambda event, arg=x, brg=y: sunken(event, self, arg, brg))
#button['command'] = sunken
row.append(button)
self.__buttons.append(row)
root = tkinter.Tk()
if __name__ == '__main__':
MineSweep.main(int(width), int(height))
You have a few things wrong with your program. First, sunken should be a method on the class. It's very weird to have it outside the class, and then you pass in self as some other argument. It works, but it makes the code very confusing.
That being said, you're actually very close to making this work. You're already saving a reference to each button in a list of lists, so you should be able to get the widget with self.__buttons[y][x]. However, because sunken is not part of the class, and because you named the variable with two underscores, the variable is not accessible to the sunken function.
If you change the variable to have a single underscore instead of a double, your code should work more-or-less exactly as it is (once you fix the syntax and indentation errors). The other solution is to make sunken a method on the class and fix how you call it (remove the self argument, call it as self.sunken), it will work with two underscores.
Frankly, using two underscores has zero practical benefit. Avoid the temptation to use it. At the very least, don't use it until you have your basic logic working, then you can go back and hide attributes you don't want to be exposed.

Categories