Set a hidden string value to an entry box in tkinter python - python

I'm trying to make my own spin on Hangman. A basic part of Hangman is that the user guesses a letter and if it is there, it appears in the entry box else it gets added to a list of incorrect guesses.
What I want to do now is assign a letter to each read-only entry box based on the random word generated. However, I also don't want the user to know what letter is assigned to it (obviously).
def press(num):
string1 = " "
string1+=str(num)
appears.set(string1)
appears = StringVar()
entrybox = Entry(hangman,state= 'readonly',textvariable = appears)
entrybox.place(x = 0, y = 0)
q = Button(hangman, text = 'Q', width = 4, command = lambda : press('Q'))
q.place(x = 200, y = 440)
This is what i've managed to do so far

You can use a dictionary to store a mapping from letters to StringVars. Then, look up the guessed letter and replace the text from a placeholder to the letter.
word = "tiger" # just an example
let2svar = {}
for let in word:
svar = StringVar()
svar.set("_")
entrybox = Entry(hangman, state='readonly', textvariable=svar)
entrybox.place(x=0, y=0)
if let not in let2svar:
let2svar[let] = []
let2svar[let].append(svar)
# Now when user guesses letter 'guess', check if it is in the word.
if guess in let2svar:
for svar in let2svar[guess]:
svar.set(guess)
You can also check out https://docs.python.org/3/library/collections.html#collections.defaultdict for a cleaner way to handle the dictionary.

Related

nested while loop in python tkinter

The following code searches a text file for a name and displays the related number in a tkinter entry box in Python.
so original text file includes:
bob 19
dan 20
shayne 17
I would like add another nested loop so that if there are two names the same then two numbers are returned to the entry box. Sorry, I am new to Python, have tried but always come up with an error.
bob 18
bob 19
dan 20
shayne 17
#https://www.youtube.com/watch?v=lR90cp1wQ1I
from tkinter import *
from tkinter import messagebox
race = []
def displayInfo(race, name):
found = False
pos = 0
while pos < len(race) and not found:
if race[pos][0] == name:
found = True
pos+=1
if found:
return race[pos-1][1]
else:
messagebox.showerror(message = "Invalid input, please try again.")
def clickArea():
fin.set(displayInfo(race, name.get()))
def createlist():
raceFile = open ("C:/python/files/number_list.txt", 'r')
for line in raceFile:
racer = line.split()
race.append(racer)
raceFile.close()
return race
root = Tk()
root.title("Read From text File/List GUI")
Label(root, text="Name").grid(row=0, column=0)
name = StringVar()
Entry(root, textvariable=name).grid(row=0, column =1)
Label(root, text="Finish Time").grid(row=2, column=0)
fin=IntVar()
Label(root, textvariable=fin).grid(row=2, column=1)
button = Button(root, text="Finish Time", command=clickArea)
button.grid(row=3, column=0, columnspan=2)
createlist()
print(race)
your question is not related to tkinter, so I made the code without it.
It works like this: you enter the name you're looking for, then it looks for matches using the count method. If there is a match, then the index is written to the 'B' array. Further, since there is a space between the name and number, we take the space index + 1 and start outputting the string from this position to the end.
name = input('who do you want to find: ') + " "
with open("number_list.txt", "r") as file:
A = file.readlines()
#remove program entry '\n'
for i in range(len(A)):
A[i] = A[i].strip()
#getting matching names
B = [] #the court records the names we need
for i in A:
if i.count(name): #truth check
#this notation is equivalent to the notationsi: if i.count(name) == 1:
B.append(i)
print('the following numbers match:')
for i in B:
index_space = i.index(' ') + 1
print(i[index_space:])
If you want to get all the values for a name, you need to go through all the items in race:
def displayInfo(race, name):
# go through the race list and return values for given name
found = [x[1] for x in race if x[0] == name]
if found:
# return the values separated by comma
return ", ".join(found)
# no item found for the given name
return "not found"

How to print Python Output in Entry widget?

I've searched through the whole Internet for how to do this, and nothing came to me. There were some similar topics, when programmers asked of how to parse 'int' numbers to the Entry output. But it is much simpler because you just use getters, then insert() - and voila.
I am trying to do the following thing. I print the text written in one line. And for each word I want to count how many times it appeared in the same text.
E.g., I print in my first Entry "one two one two three" - I get "0 0 1 1 0" in the second Entry widget.
Any non-space sequence of characters is considered a word.
from tkinter import *
class DM_3_1:
def __init__(self):
root = Tk()
root.geometry('250x150')
root.title("DiscreteMaths_3_1")
usertext = StringVar()
Label_1 = Label(root, text="Input")
Label_2 = Label(root, text="Output")
inputField = Entry(root, textvariable = usertext)
outputField = Entry(root)
inputField.bind('<Return>', lambda _: printLine())
def printLine():
counter = {}
for word in inputField.get():
counter[word] = counter.get(word, 0) + 1
Ans = print(counter[word] - 1, end=' ')
outputField.insert(0, str(Ans))
Label_1.grid(row = 0)
Label_2.grid(row = 1)
inputField.grid(row = 0, column = 1)
outputField.grid(row = 1, column = 1)
root.mainloop()
DM_3_1()
What I get in the output now: Here is the screenshot
As far as you can see, the application works, but there's 'NoneNoneNone...'(depends on the number of characters, including whitespaces) instead of '0 0 1 1 0'. How do I solve my problem? Where's a logical mistake? I guess, it's about the function, but I don't actually see the mistake.
You have set Ans to be equal to print rather than the value it was supposed to be. Also your for loop was getting every character rather than every word.
Corrected code:
from tkinter import *
class DM_3_1:
def __init__(self):
root = Tk()
root.geometry('250x150')
root.title("DiscreteMaths_3_1")
usertext = StringVar()
Label_1 = Label(root, text="Input")
Label_2 = Label(root, text="Output")
inputField = Entry(root, textvariable = usertext)
outputField = Entry(root)
inputField.bind('<Return>', lambda _: printLine())
def printLine():
counter = {}
words=inputField.get().split()
for word in words:
counter[word] = counter.get(word, 0) + 1
Ans = counter[word] - 1
print(Ans, end=" ")
outputField.insert(END, str(Ans))
Label_1.grid(row = 0)
Label_2.grid(row = 1)
inputField.grid(row = 0, column = 1)
outputField.grid(row = 1, column = 1)
root.mainloop()
DM_3_1()
edit:
As Mike-SMT pointer out its easier to use .split
code edited to use .split
So my solutions to this kind of counter is to track each word and keep a list of all the words. Then keep a list of all the unique words. The count each time a unique word appears in the complete list.
I restructured your code a bit to conform a bit better with standards.
I rewrote your printLine method to keep track of all the words in a string and create a dictionary that contains a list of all the unique words and how many times they show up in the string.
When writing a class you will need to learn to use self. to convert standard variables into class attributes. Class attributes can be accessed from anywhere in the class including methods within the class. Using regular variables will likely cause problems as they are not available to methods after __init__ has completed.
take a look at the below code.
import tkinter as tk
class DM_3_1:
def __init__(self, parent):
self.root = parent
self.root.geometry('250x150')
self.root.title("DiscreteMaths_3_1")
Label_1 = tk.Label(self.root, text="Input")
Label_2 = tk.Label(self.root, text="Output")
Label_1.grid(row=0)
Label_2.grid(row=1)
self.inputField = tk.Entry(self.root)
self.outputField = tk.Entry(self.root)
self.inputField.grid(row=0, column=1)
self.outputField.grid(row=1, column=1)
self.inputField.bind('<Return>', self.printLine)
def printLine(self, Event):
word_list = []
counter = 0
unique_words_in_string = []
total_times_word_appears = {}
for word in self.inputField.get().split():
word_list.append(word)
if word not in unique_words_in_string:
unique_words_in_string.append(word)
for word in unique_words_in_string:
counter = 0
for other_word in word_list:
if word == other_word:
counter += 1
total_times_word_appears[word]=counter
self.outputField.delete(0, "end")
self.outputField.insert("end", total_times_word_appears)
if __name__ == "__main__":
root = tk.Tk()
DM_3_1(root)
root.mainloop()

TkInter Entry Box Being Disabled

I'm working on a GUI Python program using Tkinter.
I have a function that is called when a button is pressed (and when the program is loaded). The program is currently unfinished and only checks data validation at this current point. As the default entry is current invalid, it throws an error.
However, after this point, the entry box is disabled and will not let me enter any data. I cannot figure out why this is happening and I was wondering if someone could tell me the reason so I can work on a solution.
Thanks
import sys
import random
from tkinter import *
from tkinter import ttk
from tkinter import messagebox
root = Tk()
root.title("COSC110 - Guessing Game")
hint = StringVar()
guesses = []
guess_input = ''
def loadWordList(filename): #Load the words from a file into a list given a filename.
file = open(filename, 'r')
line = file.read().lower()
wordlist = line.split()
return wordlist
word = random.choice(loadWordList('words.txt'))
def getHint(word, guesses): #Get hint function, calculates and returns the current hint.
hint = ' '
for letter in word:
if letter not in guesses:
hint += '_ '
else:
hint += letter
return hint
def guessButton(guess, word, guesses):
guess = str(guess_input)
guess = guess.lower()
if not guess.isalpha():
is_valid = False
elif len(guess) !=1:
is_valid = False
else:
is_valid = True
while is_valid == False:
messagebox.showinfo("Error:","Invalid input. Please enter a letter from a-z.")
break
hint.set(getHint(word, guesses))
return hint
label_instruct = Label(root, text="Please enter your guess: ")
label_instruct.grid(row=1,column=1,padx=5,pady=10)
guess_input = Entry(root,textvariable=guess_input)
guess_input.grid(row=1, column=2)
guess_button = Button(root, text="Guess", width=15, command=guessButton(guess_input,word,guesses))
guess_button.grid(row=1, column=3,padx=15)
current_hint = Label(root, textvariable=hint)
current_hint.grid(column=2,row=2)
label_hint = Label(root, text="Current hint:")
label_hint.grid(column=1,row=2)
label_remaining = Label(root, text="Remaining guesses: ")
label_remaining.grid(column=1,row=3)
root.mainloop() # the window is now displayed
Any tips are appreciated.
There are two apparent problems.
Firstly, you shouldn't use
guess_button = Button(root, text="Guess", width=15, command=guessButton(guess_input,word,guesses))
because you can't call a function with arguments on the command config.
My suggestion would be to take a look here and use one of the proposed methods, I particularly like the one using functools and partial:
from functools import partial
#(...)
button = Tk.Button(master=frame, text='press', command=partial(action, arg))
with action being the function you want to call and arg the parameters you want to call separated by a comma.
Secondly, you are using
guess = str(guess_input)
which doesn't return the Entry typed text, use instead
guess = guess_input.get()
PS: Albeit not directly related to your question, you should use
if var is False:
instead of
if var == False:

How to allow each character to be entered once?

I have an encryption code, which consists of 26 letters, and an option which allows the user to change it if they wish. After trying out my program several times, I came across an error, a logical one. The user can change the code, but also, they can enter the same character 26 times or at least 1 character more than once which could ruin my entire program. Is there any way to only allow the user to type each letter exactly once? Here's what I have so far:
import tkinter
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
encryption_code = 'LFWOAYUISVKMNXPBDCRJTQEGHZ'
letters += letters.lower()
encryption_code += encryption_code.lower()
window = tkinter.Tk()
encrypt_entry = tkinter.Entry(window)
encrypt_entry.pack()
def code_change(event):
global encrypt_entry
global encryption_code
encryptget = encrypt_entry.get()
if len(encryptget) == 26:
print("You have changed the encryption code")
encryption_code = encryptget
encryption_code += encryption_code.lower()
enc = dict(zip(letters, encryption_code))
dec = dict(zip(encryption_code, letters))
elif len(encryptget) < 26 or len(encryptget) > 26:
print("Please enter 26 characters")
encrypt_entry.delete(0, tkinter.END)
window.bind('<Return>', code_change)
EDIT
I have tried the following but now if I try typing the alphabet or encryption_code the elif statement does nothing.
import tkinter
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
encryption_code = 'LFWOAYUISVKMNXPBDCRJTQEGHZ'
letters += letters.lower()
encryption_code += encryption_code.lower()
window = tkinter.Tk()
encrypt_entry = tkinter.Entry(window)
encrypt_entry.pack()
def code_change(event):
global encrypt_entry
global encryption_code
encryptget = encrypt_entry.get()
if len(set(encryptget)) == 26 and encryptget != encryption_code and encryptget != letters:
print("You have changed the encryption code")
encryption_code = encryptget
encryption_code += encryption_code.lower()
enc = dict(zip(letters, encryption_code))
dec = dict(zip(encryption_code, letters))
elif len(set(encryptget)) != 26 and encryptget == encryption_code and encryptget == letters:
print("Please enter each character exactly once")
encrypt_entry.delete(0, tkinter.END)
window.bind('<Return>', code_change)
Tkinter has a feature specifically for this sort of validation. You're able to have it call a function for every insertion, and and this function can either accept or reject that insertion based on whatever criteria you want.
In your case the criteria is "no duplicate characters". An easy way to determine that is to convert the string to a set (by definition, a set has no duplicates), and then compare the length of the set to the length of the string.
To call this function each time the user presses a key, set the validate and validatecommand options of the entry widget. There's an extra step where you have to register the command, which tells tkinter which of several special arguments you want your command to receive.
The solution looks something like this:
# Define a command to be run whenever the user edits the entry
# N.B. d will be "1" for an insert, "0" for delete, and "-1"
# for anything else. P will be the value of the entry if this
# edit is allowed.
#
# Note: this function must always return either True or False
def encryption_validation(d, P):
if d == "1": # ie: an insert
unique_chars = set(P)
if len(P) > len(unique_chars):
return False
return True
# register the command with tkinter
vcmd = (window.register(encryption_validation), '%d', '%P')
# configure the entry widget to use this command
encrypt_entry.configure(validate="key", validatecommand=vcmd)
With the above, it will be impossible for the user to enter any character twice. Note that this solution also prevents the user from pasting a string with duplicate characters.
For a more exhaustive explanation of entry validation, see this answer on stackoverflow: https://stackoverflow.com/a/4140988/7432
Correct me if I'm mistaken, but it sounds like you just want to make sure that the string of characters the user inputs for the code don't contain any duplicates?
I don't personally know about the validation commands, but I think this could work to achieve your goal:
def IsCodeValid(encryption_code):
c = [0]*26
pos = 0
for i in range(len(encryption_code)):
pos = ord(encryption_code[i])-ord('A') #find placement relative to A in unicode
c[pos] = c[pos] + 1 #increment counter for that letter
j = 0
ValidCode = True
while j<26 and ValidCode:
if c[j]>1: #check to see if any letter occurred more than once
ValidCode = False
else:
j += 1
return ValidCode
Mind you, this also expects that all letters are entered capitalized. But you can fix that by normalizing the data before accepting it. Alternatively, you could complicate the logic to check both upper & lower case.
Edit: This is assuming that you don't want the code to run if the encryption_code is invalid, you could use this flag to request a new encryption_code from the user before running the rest of the program.

Label not updating in python tkinter

I'm writing a tkinter program, and I'm trying to update my label on the ui. However I can't get it to work. Here's the code:
from tkinter import *
import random, functools, string
root = Tk()
word_list = ["APPLE", "PEAR", "BANNANA"]
word = word_list [random.randint(0,2)]
hidden_word = ["_ "] * len(word)
print (word)
abc = ['_ '] * len(word)
guessed_letters = []
#Functions
def click_1 (key):
if key in word:
guessed_letters = ''.join([key])
global abc
abc = ''.join([key if key in guessed_letters else "_" for key in word])
else:
print ("Nope") ####TESTING#####
#Frames
hangman_frame = Frame(root)
hangman_frame.grid(row=0, column=0, sticky=N)
letter_frame = Frame(root)
letter_frame.grid(row=1, column=0, sticky=S)
#Label
letters_label = Label(hangman_frame, textvariable=abc)
letters_label.grid(row=0, column=0, sticky=W)
(Just an excerpt, not all)
My question is that when ran, this section appears not to work:
letters_label = Label(hangman_frame, textvariable=abc)
where:
abc = ['_ '] * len(word)
guessed_letters = []
#Functions
def click_1 (key):
if key in word:
guessed_letters = ''.join([key])
global abc
abc = ''.join([key if key in guessed_letters else "_" for key in word])
And nothing shows up, whereas when this is put:
letters_label = Label(hangman_frame, text=abc)
The label shows up, but does not update when the function click_1 is called.
Any reason to this? Thanks in advance.
The reason is that the textvariable option requires an instance of StringVar or IntVar. You can't just pass it the name of a normal variable.
Generally speaking, you never need to use the textvariable option unless you specifically need the features of a StringVar or IntVar (such as having two widgets share the same data, or doing traces on the variable). I know lots of examples use it, but it just adds another object that you don't really need.
In order to update the text on a label, you would do this:
letters_label = Label(..., text="initial value")
...
def click_1(...):
...
abc = ...
letters_label.configure(text=abc)

Categories

Resources