program crashes when press button - Number guess game - Tkinter - python-3.x

I have no idea why this is not working, I have been looking and I cant see whats wrong I have messed with it for a while all I want is for it to work.
gets input outputs numbers equal to randomly generated if full number equals the random then you win but it just crashes when I press the button
from tkinter import *
from tkinter import ttk
import random
master = Tk()
master.title('Guess The Number!')
global answer
global guess_entry
global guess_display
answer = str(random.randint(1000,9999))
guess_counter = 0
def callback():
print('Button pressed')
counter = 0
correct = []
while counter < 4:
if guess_entry.get() == answer:
correct.append('Well done, that is correct')
break
elif guess_entry.get()[counter] == answer[counter]:
correct.append(guess_entry.get[counter])
counter += 1
guess_display['text'] = ' '.join(str(correct))
def Help():
win = Toplevel()
win.title('Help')
l = Label(win, text="Guess a 4 digit number and I will tell you\n what you got right,\n keep trying until you get it in the \ncorrect order with correct numbers")
l.grid(row=0, column=0)
b = Button(win, text="Okay", command=win.destroy)
b.grid(row=1, column=0)
guess_entry = Entry(master)
guess_check = Button(master, text='Guess', command=callback)
guess_display = Label(master,text='_ _ _ _')
help_button = ttk.Button(master, text="?", command=Help,width=3)
guess_entry.grid(row=0,column=2)
guess_check.grid(row=1,column=2)
guess_display.grid(row=2,column=1)
help_button.grid(row=0,column=4)
master.mainloop()

If the numbers aren't equal the first time through the loop, they won't be equal any other time through the loop since the user won't have a chance to change their answer while the loop is running.
You can see this by adding a print statement:
while counter < 4:
print("counter:", counter, "guess:", guess_entry.get())
...

Related

Fluid design in Tkinter

I'm trying to create a somewhat "responsive" design with ttk (tkinter). The basic placement of widgets is no problem at all, but making it fluid with the width of the program is something I cannot achieve. In CSS I know it's possible to say something along the lines of '"float: left" for all containers' and the page would adapt to the screen size. I haven't found something similar to that in Tkinter and frames.
My basic test program:
#!/usr/bin/python3
import tkinter
from tkinter import ttk
from ttkthemes import ThemedTk, THEMES
class quick_ui(ThemedTk):
def __init__(self):
ThemedTk.__init__(self, themebg=True)
self.geometry('{}x{}'.format(900, 150))
self.buttons = {}
self.frame1 = ttk.Frame(self)
self.frame1.pack(side="left")
self.frame2 = ttk.Frame(self)
self.frame2.pack(side="left")
#------------------------------------------------------- BUTTONS
i = 0
while (i < 5):
i += 1
self.buttons[i]= ttk.Button(self.frame1,
text='List 1 All ' + str(i),
command=self.dump)
self.buttons[i].pack(side="left")
while (i < 10):
i += 1
self.buttons[i]= ttk.Button(self.frame2,
text='List 2 All ' + str(i),
command=self.dump)
self.buttons[i].pack(side="left")
def dump(self):
print("dump called")
quick = quick_ui()
quick.mainloop()
This creates a window with 10 buttons all besides each other.
When I shrink the window to the point that the buttons no longer fit on the screen, I would like the buttons to appear below each other
So what I did was add a resize listener and setup the following method:
def resize(self, event):
w=self.winfo_width()
h=self.winfo_height()
# print("width: " + str(w) + ", height: " + str(h))
if(w < 830):
self.frame1.config(side="top")
self.frame2.config(side="top")
But Frame doesn't has the property side, which is a parameter given to the method pack. So that didn't work either.
And now I'm lost. I've spend way to long on this, trying grids and other solutions, but I've got the feeling that I'm missing out on one simple, but very important setting.
I've created a (hackish) solution, which is fine due to it being a very internal program. Since no answer is given in the mean time, I'll provide my solution here. It probably has a lot of room for improvements, but I hope that this can give somebody in the future some pointers on how to tackle this problem by him(or her) self.
#!/usr/bin/python3
import re
import sys
import tkinter
from tkinter import filedialog
from tkinter import ttk
from ttkthemes import ThemedTk, THEMES
import subprocess
import os
from tkinter.constants import UNITS
import json
from functools import partial
class quick_ui(ThemedTk):
def __init__(self):
ThemedTk.__init__(self, themebg=True)
self.minsize(600, 250)
self.elems = {}
self.resize_after_id = None
#------------------------------------------------------- Window menu bar contents
self.menubar = tkinter.Menu(self)
self.menubar.add_command(label="Open", command = self.dump)
self.menubar.add_command(label="Refresh", command = self.dump)
self.config(menu=self.menubar)
# Theme menu
self.themeMenu = tkinter.Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label="Theme", menu=self.themeMenu)
self.themeMenu.add_command(label="DEFAULT", command=partial(self.dump, "default"))
#---------------------------------------------------------------------- top_frame
self.top_frame = ttk.Frame(self)
self.top_frame.pack( side = tkinter.TOP, expand='YES', fill='both', padx=10)
self.top_top_frame = ttk.Frame(self.top_frame)
self.top_top_frame.pack(side=tkinter.TOP, expand='YES', fill='both')
self.top_bottom_frame = ttk.Frame(self.top_frame)
self.top_bottom_frame.pack(side=tkinter.BOTTOM)
self.top_bottom_top_frame = ttk.Frame(self.top_frame)
self.top_bottom_top_frame.pack(side=tkinter.TOP)
self.top_bottom_bottom_frame = ttk.Frame(self.top_frame)
self.top_bottom_bottom_frame.pack(side=tkinter.BOTTOM)
#------------------------------------------------------------------- bottom_frame
self.bottom_frame = ttk.Frame(self, relief="sunken")
self.bottom_frame.pack( side = tkinter.BOTTOM,
expand='YES',
fill='both',
padx=10,
pady=10 )
#------------------------------------------------------- BUTTONS
i = 0
while (i < 15):
self.elems[i]=ttk.Button(self.top_bottom_top_frame,
text='List All ' + str(i),
command=self.dump)
i += 1
self.label_test_strings1 = ttk.Label(self.top_top_frame, text='Test strings1')
self.label_test_strings2 = ttk.Label(self.top_bottom_frame, text='Test strings2')
self.label_test_strings4 = ttk.Label(self.top_bottom_bottom_frame, text='Test strings4')
self.label_test_strings1.pack(side = tkinter.TOP)
self.label_test_strings2.pack(side = tkinter.TOP)
self.label_test_strings4.pack(side = tkinter.TOP)
self.placeElems()
# Setup a hook triggered when the configuration (size of window) changes
self.bind('<Configure>', self.resize)
def placeElems(self):
for index in self.elems:
self.elems[index].grid(row=0, column=index, padx=5, pady=5)
# ------------------------------------------------------ Resize event handler
def resize(self, event):
# Set a low "time-out" for resizing, to limit the change of "fighting" for growing and shrinking
if self.resize_after_id is not None:
self.after_cancel(self.resize_after_id)
self.resize_after_id = self.after(200, self.resize_callback)
# ------------------------------------------------------ Callback for the resize event handler
def resize_callback(self):
# The max right position of the program
windowMaxRight = self.winfo_rootx() + self.winfo_width()
# Some basic declarations
found = False
willAdd = False
maxColumn = 0
currIndex = 0
currColumn = 0
currRow = 0
counter = 0
last_rootx = 0
last_maxRight = 0
# Program is still starting up, so ignore this one
if(windowMaxRight < 10):
return
# Loop through all the middle bar elements
for child in self.top_bottom_frame.children.values():
# Calculate the max right position of this element
elemMaxRight = child.winfo_rootx() + child.winfo_width() + 10
# If we already found the first 'changable' child, we need to remove the following child's also
if(found == True):
# Is the window growing?
if(willAdd == True):
# Check to see if we have room for one more object
calcMaxRight = last_maxRight + child.winfo_width() + 20
if(calcMaxRight < windowMaxRight):
maxColumn = counter + 1
# Remove this child from the view, to add it again later
child.grid_forget()
# If this child doesn't fit on the screen anymore
elif(elemMaxRight >= windowMaxRight):
# Remove this child from the view, to add it again later
child.grid_forget()
currIndex = counter
maxColumn = counter
currRow = 1
found = True
else:
# If this child's x position is lower than the last child
# we can asume it's on the next row
if(child.winfo_rootx() < last_rootx):
# Check to see if we have room for one more object on the first row
calcMaxRight = last_maxRight + child.winfo_width() + 20
if(calcMaxRight < windowMaxRight):
child.grid_forget()
currIndex = counter
currColumn = counter
maxColumn = counter + 1
found = True
willAdd = True
# Save some calculation data for the next run
last_rootx = child.winfo_rootx()
last_maxRight = elemMaxRight
counter += 1
# If we removed some elements from the UI
if(found == True):
counter = 0
# Loop through all the middle bar elements (including removed ones)
for child in self.top_bottom_frame.children.values():
# Ignore the elements still in place
if(counter < currIndex):
counter += 1
continue
# If we hit our maxColumn count, move to the next row
if(currColumn == maxColumn):
currColumn = 0
currRow += 1
# Place this element on the UI again
child.grid(row=currRow, column=currColumn, padx=5, pady=5)
currColumn += 1
counter += 1
def dump(self):
print("dump called")
quick = quick_ui()
quick.mainloop()

How do I get the value from an option in tkinter after it has passed through a function?

I'm using tkinter to create an option menu for a user to interact with, when the selection has been made it will pass through a function to return an integer based on the input. I want to be able to return the integer back to a variable outside of the function so it can be received by another file. My problem is that python will keep returning the button as the command has not yet been processed.
from tkinter import *
Final = [] ##List containing options
master = Tk()
variable = StringVar(master)
variable.set(Final[0]) # default value
w = OptionMenu(master, variable, *Final)
w.pack()
def ok():
global choice
choice = variable.get()
global x
x = 0
for i in Final:
if str(i) == str(choice):
break
x += 1
button = Button(master, text="Choose", command=ok)
button.pack()
values = x

displaying in a label the number of times an item is randomly selected

My label2 is supposed to display how many times each item(here the apple) appears
That is to say if apple is selected my counter(label2) should display 1 then 2, three..
But it doesnt seem to work.
from tkinter import*
import random, time
wn=Tk()
wn.geometry("300x300")
mytext=Text(wn,bg="pink",width=10,height=200)
mytext.pack()
label1=Label(wn,text="",bg="yellow",bd=3)
label1.place(x=200,y=20)
label2=Label(wn,text="",bg="lightgreen",bd=3)
label2.place(x=200,y=50)
def update(c=0):
numx = 0
list1=["apple","orange","melon","carrot"]
fruit = random.choice(list1)
label1["text"]=fruit
if label1["text"]=="apple":
numx+=1
label2["text"]=numx
mytext.insert('end', str(fruit) + '\n')
wn.after(1000, update, c+1)
update()
wn.mainloop()
You defined numx to be zero everytime update is called.
First move numx outside your update function, and then declare global inside update:
from tkinter import*
import random, time
wn=Tk()
...
numx = 0
def update(c=0):
global numx
...
update()
wn.mainloop()
As #HenryYik pointed out, you code requires to first declare numx = 0 in the global space, then declare it global inside your update function.
An alternate approach to solving your problem is to use a collections.Counter; it has the advantage to simplify the code logic in update, and, in case you needed it, it also keeps a tally of the number of times each fruit was selected.
The list of fruits is also declared outside the function update, making the code more general.
Something like this:
import tkinter as tk
import random
from collections import Counter
def update(delay=1000):
"""picks a fruit at random, and updates the display and the tally
calls itself again after a time delay
"""
fruit = random.choice(fruit_list)
counter[fruit] += 1
label1['text'] = fruit
label2['text'] = counter['apple']
mytext.insert('end', str(fruit) + '\n')
wn.after(delay, update)
fruit_list = ['apple', 'orange', 'melon', 'carrot']
counter = Counter()
wn = tk.Tk()
wn.geometry('300x300')
mytext = tk.Text(wn, bg='pink', width=10, height=200)
mytext.pack()
label1 = tk.Label(wn, text='', bg='yellow', bd=3)
label1.place(x=200, y=20)
label2 = tk.Label(wn, text='', bg='lightgreen', bd=3)
label2.place(x=200, y=50)
update()
wn.mainloop()

Timed Availability of controls in tkinter GUI

I'm working on a program that will stop users from changing a label after a random amount of time. There are two buttons, start and next, when the user presses start the start button is destroyed but is supposed to come back after a randomly selected amount of time. I tried to have the start button trigger a flag that starts a timer. When the timer reaches a certain value (count_to+1) the flag should go to zero, the start button should reappear, and the label should read end. The flag never seems to switch and the timer never initiates though. Can anyone tell me what I did wrong? and maybe point me towards a solution? Hear is the code:
import sys
from tkinter import *
import random
import time
mGui = Tk()
mGui.geometry('450x450+200+200')
mGui.title('Letters')
stored = ['A', 'b', 'c', 'd']
count_down = [10,20,30,40,50,60]
global count_to
global countFlag
count_to = IntVar()
countFlag = 0
Sec = 0
def run_counter():
count_to = random.choice(count_down)
while countFlag == 1:
Sec+=1
print(sec)
if Sec == count_to+1:
countFlag = 0
newbutton.destroy()
startbutton.grid(row=2,column=1)
phrase.configure(text='End')
return
def change_phrase():
fish = StringVar()
fish = random.choice(stored)
stored.remove(fish)
phrase.configure(text=fish)
#to help with debug
print(countFlag)
print(Sec)
print(count_to)
return
def start_count():
countFlag = True
count_to = random.choice(count_down)
print(countFlag)
startbutton.destroy()
run_counter
return
phrase = Label(mGui,text='Letter',fg='red',bg='blue')
phrase.grid(row=0,column=0, sticky=S,columnspan=2)
startbutton =Button(mGui, text='start',fg='black',bg='green',command=start_count)
startbutton.grid(row=2,column=1)
newbutton = Button(mGui,text='NEXT',fg='black',bg='red',command=change_phrase)
newbutton.grid(row=2,column=0)
#mEntry = Entry(mGui,textvariable=ment)
#mEntry.grid(row=3,column=0)
mGui.mainloop()
Tkinter programming becomes much less messy and confusing once you learn to use classes. Use Tkinter's after() method to call a function every "x" amount of time until the allotted time has elapsed.
import random
import sys
if sys.version_info[0] < 3:
import Tkinter as tk ## Python 2.x
else:
import tkinter as tk ## Python 3.x
class ButtonDisappear():
def __init__(self, root):
self.root=root
self.startbutton=tk.Button(root, text='disappear', fg='black',
bg='green', command=self.disappear)
self.startbutton.grid(row=2,column=1)
self.lab=tk.Label(self.root, text="", bg="lightblue")
def disappear(self):
## remove button
self.startbutton.grid_forget()
## grid label for time
self.lab.grid(row=0, column=0)
## "random" number
self.stop_time=random.choice([1, 2, 3, 4, 5])*1000
self.elapsed=0
self.root.after(100, self.time_it)
def time_it(self):
self.elapsed += 100
## function calls itself until time has finished
if self.elapsed < self.stop_time:
self.lab["text"]="%d of %d" % (self.elapsed, self.stop_time)
self.root.after(100, self.time_it)
## time elapsed so remove label and restore button
else:
self.lab.grid_forget()
self.startbutton.grid(row=2,column=1)
m_gui = tk.Tk()
m_gui.geometry('450x450+200+200')
m_gui.title('Letters')
B=ButtonDisappear(m_gui)
m_gui.mainloop()

Python Variable in tkinter window

I am building a device that counts how many parts are made off a machine and then turns the machine off at a specific number. I am using an Arduino for all the I/O work and then importing the serial data into Python as variable partCount. I would like to create a simple GUI in tkinter to show the number of parts that have been made and the total number needed. The problem is that I keep getting an error on the label lines that include a variable instead of just a text. I've done a lot of research on it, but I just can't get it for some reason. Any advice would be appreciated.
import serial
import csv
import datetime
import tkinter
#Part Variables
partNumber = "A-33693" #Part Number
stockupTotal = 10
arduinoSerialData = serial.Serial('com3',9600) #Serial Variable
now = datetime.datetime.now()
#GUI
window = tkinter.Tk()
window.title("Troy Screw Products")
titleLabel = tkinter.Label(window, text="Davenport Machine Control")
partNumberLabel = tkinter.Label(window, text="Part #:")
stockUpTotalLabel = tkinter.Label(window, text="Stockup Total:")
partCountLabel = tkinter.Label(window, text="Current Part Count:")
partNumberInfo = tkinter.Label(window, partNumber)
stockUpTotalInfo = tkinter.Label(window, stockupTotal)
partCountInfo = tkinter.Label(window, partCount)
titleLabel.pack()
partNumberLabel.pack()
partNumberInfo.pack()
stockUpTotalLabel.pack()
stockUpTotalInfo.pack()
partCountLabel.pack()
partCountInfo.pack()
window.mainloop()
#Write to CSV File
def writeCsv():
with open("machineRunData.csv", "a") as machineData:
machineDataWriter = csv.writer(machineData)
machineDataWriter.writerow([partNumber, "Stockup Complete",now.strftime("%Y-%m-%d %H:%M")])
machineData.close()
#Serial Import
while (1==1):
if (arduinoSerialData.inWaiting()>0):
partCount = arduinoSerialData.readline()
partCount = int(partCount)
if partCount == 999999:
writeCsv()
print(partCount)
There are several options you can give to a Label widget, as you can see here. You should specify all parameters after the first by name:
partNumberInfo = tkinter.Label(window, text=PartNumber)
To use Python variables in Tkinter you need to special Tk objects, of which there are four: BooleanVar, DoubleVar, IntVar, and StringVar. See this answer for a good example.
So after a ton of research over the last week, I found a solution. The problem was that the program would run the GUI, but not run the code for the serial import until the GUI window was closed. I needed a way to get both the GUI running and updated and get the serial data importing at the same time. I resolved this by creating a thread for both operations. There's probably an easier way to do this, but this what I came up with. The code is below for anyone having a similar problem.
import serial
import time
import threading
from tkinter import *
#Part Variables
partNumber = "A-33693" #Part Number
stockupTotal = 10
partCount = 0
def countingModule():
global partCount
while (1==1):
time.sleep(2)
partCount += 1
print(partCount)
def gui():
global partCount
root = Tk()
pc = IntVar()
pc.set(partCount)
titleLabel = Label(root, text="Machine Control")
partNumberLabel = Label(root, text="Part #:")
stockUpTotalLabel = Label(root, text="Stockup Total:")
partCountLabel = Label(root, text="Current Part Count:")
partNumberInfo = Label(root, text=partNumber)
stockUpTotalInfo = Label(root, text=stockupTotal)
partCountInfo = Label(root, textvariable=pc)
titleLabel.pack()
partNumberLabel.pack()
partNumberInfo.pack()
stockUpTotalLabel.pack()
stockUpTotalInfo.pack()
partCountLabel.pack()
partCountInfo.pack()
def updateCount():
pc.set(partCount)
root.after(1, updateCount)
root.after(1, updateCount)
root.mainloop()
op1 = threading.Thread(target = countingModule)
op2 = threading.Thread(target = gui)
op1.start()
op2.start()

Resources