Python 3.5.1 Tkinter // Entry widget/Frame management issue - python-3.x

So I want my program to be able to reset the password. The problem is that everything works just fine with the first reset process, but if you wanted to reset the password one more time it gets wicked and I can't find a mistake or solution. What the program does the first time and what it should do:
When button3 is pressed show the first frame where you have to enter you current pw. If the enter key on the keyboard is pressed and the pw is right show the second frame. There, set a new pw and by hitting enter show the third frame. Type in the same pw you used in the second frame, the pw set frame. If both pw are the same go back to the title screen.
Second+ time and the PROBLEM: button3 is pressed, it shows the first frame, but the cursor is not in the entry field like it is the first time (entry.focus_set() is in the code), BUT the cursor is in the third entry field (of the last, the pw confirm, frame) without it even being .pack()ed or .raised or anything beyond. If I click the entry of the first frame (where I actually would be at this point) and confirm it shows the second frame like it is supposed to be, but the cursor now is in the entry of the first frame which is not even active anymore because it just .pack_forget(). If I proceed to the third frame the cursor is in the entry of the second frame.
from tkinter import *
root = Tk()
pw_actual = ""
pw_first = ""
def ssc_title():
sc_pw.pack_forget()
sc_pw_second.pack_forget()
sc_title.pack()
def ssc_pw_change():
sc_title.pack_forget()
sc_pw_testing.pack()
def ssc_pw_first():
sc_pw_testing.pack_forget()
sc_pw_first.pack()
def ssc_pw_second():
sc_pw_first.pack_forget()
sc_pw_second.pack()
#-----------------------------------
def ev_pw_testing(event):
pw_proof = en_pw_testing.get()
if pw_proof == pw_actual:
en_pw_testing.delete(0, END)
ssc_pw_first()
else:
en_pw_testing.delete(0, END)
def ev_pw_first(event):
global pw_first
pw_first = en_pw_first.get()
if pw_first == "":
errormessage("Please enter a password")
else:
en_pw_first.delete(0, END)
ssc_pw_second()
def ev_pw_second(event):
global pw_first
pw_second = en_pw_second.get()
if pw_first == pw_second:
write(pw_second)
en_pw_second.delete(0, END)
ssc_title()
else:
errormessage("Passwords don't match")
en_pw_second.delete(0, END)
#------------------
sc_pw_testing = Frame(root, bg=bgclr)
lab_pw_testing = Label(sc_pw_testing, text="Please enter the password", bg=bgclr, fg=fontclr, font=font_12).pack(ipady=10)
en_pw_testing = Entry(sc_pw_testing, show="X")
en_pw_testing.pack()
en_pw_testing.focus_set()
en_pw_testing.bind("<Return>", ev_pw_testing)
sc_pw_first = Frame(root, bg=bgclr)
lab_pw_first = Label(sc_pw_first, text="Set new password", bg=bgclr, fg=fontclr, font=font_12).pack(ipady=10)
en_pw_first = Entry(sc_pw_first, show="X")
en_pw_first.pack()
en_pw_first.focus_set()
en_pw_first.bind("<Return>", ev_pw_first)
sc_pw_second = Frame(root, bg=bgclr)
lab_pw_second = Label(sc_pw_second, text="Confirm password", bg=bgclr, fg=fontclr, font=font_12).pack(ipady=10)
en_pw_second = Entry(sc_pw_second, show="X")
en_pw_second.pack()
en_pw_second.focus_set()
en_pw_second.bind("<Return>", ev_pw_second)
sc_title = Frame(root, bg=bgclr)
bttn3 = Button(sc_title, text="Password reset", bg=bgclr, fg=fontclr, font=font_12, relief=FLAT, command=ssc_pw_change).pack()
#------------
root.mainloop()
(Here I didn't include irrelevant functions etc like the errormessage toplevel funtion which justs create a window or the pickle stuff, because then it'll be too long and the issue does not seem to be laying there.)
Like I was saying - the first time changing the password works just fine. Every other time after the first time the same problem occures every time. The only way to get it to work again is to restart the program. I was trying following solutions:
resetting the global variables before entering the reset process again
resetting the global variables after switching the frame and being used
putting each frame (or page if you will) into a function
putting each frame (or page) into a class following the Switch between two frames in tkinter tutorial
leaving the frame/page switcing functions out and put the .pack()s and .pack_forget()s right in the pw checking (event) functions
instead of packing using a grid
using ("", lambda event: function())
Each solution worked just like the code right now, no changes at all, and the problem stays just as well.
I mean I could work around it, like disable the button so you can only reset the pw one time each session, but there is no reason why it should be like that, or even omit the entry.focus_set(), so you have to click the entry field no matter what and the problem never has a chance to occur (which is seriously annoying to click each entry field before entering characters each time).
I ran out of ideas what I could try to do differently. I feel like missing the forest for the trees. Come on guys, gimme a kick in my butt, what am I missing out here!?

Related

Checkbutton command binding to wrong values when instantiated in loop

When I click on a checkbutton in my project, it is not executing the correct functionality. The project can be found at https://github.com/shitwolfymakes/Endless-Sky-Mission-Builder/ (indev branch)
I am building an application using tkinter, and am working on a function to dynamically place ttk.Entry objects next to ttk.Checkbutton objects, and then link them together.
I have already rewritten this function a few times, and even added a special case for when self.numMandatory is 0, but nothing has worked.
This is taken from guiutils.py, line 323.
# add the optional fields
for i in range(self.numMandatory, self.numFields):
print(self.rowNum)
self.listEntryStates.append(BooleanVar())
self.listEntryData.append(StringVar())
self.listEntryData[-1].set(self.listDefaultEntryData[i])
self.listEntries.append(ttk.Entry(self, textvariable=self.listEntryData[-1], state=DISABLED, style="D.TEntry"))
self.listEntries[-1].grid(row=self.rowNum, column=1, sticky="ew")
#print(self.listEntryStates[-1])
#print(self.listEntries)
self.listCheckbuttons.append(ttk.Checkbutton(self, onvalue=1, offvalue=0, variable=self.listEntryStates[-1],
command=lambda: self.cbValueChanged(self.listEntryStates[-1],
[self.listEntries[-1]])))
self.listCheckbuttons[-1].grid(row=self.rowNum, column=2, sticky="e")
print(self.listCheckbuttons[-1].__str__(), end=" is bound to: ")
print(self.listEntries[-1].__str__(), self.listEntryStates[-1])
self.rowNum += 1
# end for
This is taken from guiutils.py, line 349
def cbValueChanged(self, entryState, modifiedWidgets):
for widget in modifiedWidgets:
print("The value of %s is:" % widget, end="\t\t")
print(entryState.get())
if type(widget) is str:
break
elif entryState.get() is True:
widget.config(state='enabled', style='TEntry')
elif entryState.get() is False:
widget.config(state='disabled', style='D.TEntry')
#end for
#end cbValueChanged
In the main window, when I scroll down and click "add trigger", the new window appears properly. But when I click on the checkbutton next to the Entry that says "[<base#>]", that entry should be enabled by cbValueChanged.
For some reason, when the loop to add the optional fields runs, the command= section binds only the last entry in self.listEntries (but the entry it's binding each checkbutton to isn't created until the very last time through the loop)
I'm not sure where else I could ask a question like this, and I know this is asking more than most questions. If there is any more information you need, I'll be happy to provide it.
You ~~can't~~ edit: shouldn't use lambda in a loop. Frankly you shouldn't use it at all. Use functools.partial or make a real closure.
from functools import partial
self.listCheckbuttons.append(ttk.Checkbutton(self, onvalue=1, offvalue=0, variable=self.listEntryStates[-1],
command=partial(self.cbValueChanged,self.listEntryStates[-1],[self.listEntries[-1]])))

Is there a way I can make buttons in Tkinter with a for loop, while also giving each one a different command?

I'm making a revision system for school, and I want it to be able to use a modular amount of subjects just in case a subject is added to the system, so I need a way to make a number of buttons with different subject names, and be able to differentiate between those buttons using tkinter. So for example, if they were to click the Mathematics button, it would take them to another bit of code specially suited for mathematics(Although, it can't be solely for Mathematics, since then I would need definitions for subjects that haven't even been added yet)
First I simply tried setting the command to "print(subjectnames[subcount-1])", thinking it would print the name of the button, but that just printed both names out straight away without even pressing a button. Then I tried changing the variable name subject to the name of the button, which I didn't expect to work, I was just stumped and desperate
Here I started setting up the definition
def chooseQuiz():
clearWindow()
subjectnames=[]
button=[]
This was probably unimportant, just labels for the title and spacing
Label(mainWindow, text="Which quizzes would you like to take?", bg='purple3', font=('constantia',25,"bold")).grid(row=0, column=0, padx=100, pady=0)
Label(mainWindow, bg='purple3').grid(row=1, column=0, padx=0, pady=15)
Here I extract data from an SQL table to get all subject names from all topics, again probably unimportant but here is where most of the variables are made
c.execute("SELECT Subject_name FROM topics")
for row in c.fetchall():
if row[0] in subjectnames:
pass
elif row[0] not in subjectnames:
subjectnames.append(row[0])
else:
messagebox.showerror("Error", "subjectnames are not appending")
chooseQuiz()
This is the main part of this question, where I tried to form a fluid number of buttons all with different commands, but to no avail
for subcount in range(len(subjectnames)):
button.append(Button(mainWindow, text=str(subjectnames[subcount-1]), bg='grey', fg='black', font=('cambria',15), width=25, command=(subject==subjectnames[subcount-1])))
button[-1].grid(row=subcount+2,column=0, padx=0, pady=15)
I expected the subject variable to be the same as the button I pressed, but it remained at 0(original value). I think this is due to wrong use of the command function in tkinter on my part. The buttons still showed up fine(only 2 subjects currently, Mathematics and Physics, and both showed up fine).
Yes, it is possible.
The following example creates a window with a reset button; upon clicking reset, a frame is populated with buttons corresponding to a random number of buttons chosen randomly from possible subjects. Each button has a command that calls a display function that redirects the call to the proper topic, which, in turn prints the name of its topic to the console, for simplicity of the example. You could easily create functions/classes corresponding to each topic, to encapsulate more sophisticated behavior.
Adding subjects, is as simple as adding a key-value pair in SUBJECTS
Pressing reset again, removes the current button and replaces them with a new set chosen randomly.
import random
import tkinter as tk
from _tkinter import TclError
SUBJECTS = {'Maths': lambda:print('Maths'),
'Physics': lambda:print('Physics'),
'Chemistry': lambda:print('Chemistry'),
'Biology': lambda:print('Biology'),
'Astronomy': lambda:print('Astronomy'),
'Petrology': lambda:print('Petrology'),}
topics = []
def topic_not_implemented():
print('this topic does not exist')
def get_topics():
"""randomly creates a list of topics for this example
"""
global topics
topics = []
for _ in range(random.randrange(1, len(SUBJECTS))):
topics.append(random.choice(list(SUBJECTS.keys())))
return topics
def reset_topics():
global topics_frame
try:
for widget in topics_frame.winfo_children():
widget.destroy()
topics_frame.forget()
topics_frame.destroy()
except UnboundLocalError:
print('error')
finally:
topics_frame = tk.Frame(root)
topics_frame.pack()
for topic in get_topics():
tk.Button(topics_frame, text=topic, command=lambda topic=topic: display(topic)).pack()
def display(topic):
"""redirects the call to the proper topic
"""
SUBJECTS.get(topic, topic_not_implemented)()
root = tk.Tk()
reset = tk.Button(root, text='reset', command=reset_topics)
reset.pack()
topics_frame = tk.Frame(root)
topics_frame.pack()
root.mainloop()

Why does tkinter sometimes work without mainloop in Python 3?

Here's code for a little game. You're supposed to hit buttons that say 'click' to score points and avoid hitting buttons that say 'clack' or 'cluck' which cause you to lose points. The first weird thing is that the program works fine under IDLE even though it doesn't include a call to mainloop. The second is that it stops working if we add the following line at the bottom:
input('Hit enter to continue')
No game window appears, even after we hit enter. Adding a different line at the end like
print('hello')
does not have this effect.
Can anyone explain to me what's going on? I know that GUI programs used to run under IDLE's own mainloop, but that was a long time ago in Python 2 and definitely wasn't generally true under Python 3, at least last time I checked.
from tkinter import *
import random
score = 0
root = Tk()
scoreFrame = Frame(root)
scoreFrame.pack(expand = YES, fill = BOTH)
scoreLabel = Label(scoreFrame)
scoreLabel.pack()
def showScore():
scoreLabel['text'] = 'Score: {0}'.format(score)
scoreLabel.pack()
clickFrame = Frame(root)
clickFrame.pack(side = BOTTOM, expand = YES, fill = BOTH)
def changeLabels():
for button in buttons:
button['text'] = random.choice(['click', 'clack', 'cluck'])
button['bg'] = buttonDefaultColor
root.after(1500, changeLabels)
def makeButton():
button = Button(clickFrame)
def cmd():
global score
if button['bg'] == buttonDefaultColor:
if button['text'] == 'click':
score += 10
button['bg'] = 'light green'
else:
score -= 10
button['bg'] = 'light yellow'
button['command'] = cmd
button.pack(side = LEFT, expand = YES, fill = BOTH)
return button
buttons = [makeButton() for i in range(5)]
buttonDefaultColor = buttons[0]['bg']
changeLabels()
showScore()
Added: The first three comments all suggest that IDLE is running the event loop for me, making mainloop unnecessary but (1) I remember clearly when this suddenly stopped being true some years back (not that IDLE stopped running mainloop, but that GUI programs running under it did not need to run mainloop themselves) and (2) no one has explained why the input statement at the end breaks the program.

tkinker optionmenu not showing chosen result

import tkinter
window = tkinter.Tk()
def abc(event):
ans=0
numberss=['7','8','9']
omenu2['menu'].delete(0, 'end')
for number in numberss:
omenu2['menu'].add_command(label=numberss[ans], command=efg)
ans=ans+1
def efg(event=None):
print('yee')
numbers = ['1','2', '3']
number=['4','5','6']
var = tkinter.StringVar(window)
var1 = tkinter.StringVar(window)
omenu = tkinter.OptionMenu(window, var, *numbers, command = abc)
omenu.grid(row=1)
omenu2 = tkinter.OptionMenu(window, var1, *number, command = efg)
omenu2.grid(row=2)
after you have entered the first option menu, it will update the second one. when you enter data into the second one, it runs the command, but doesn't show you what you entered. i do not want to include a button, and i know that the command works and not on the second
i found some code that changed the options of the second menu, however when i ran this, the command wouldn't work as it was changed to tkinter.setit (i would also like to know what is does. i do not currently understand it)
omenu2['menu'].add_command(label=numberss[ans], command=tkinter._setit(var1, number))
this has been taken from a larger piece of code, and has thrown the same error
You should set your StringVar(var1) new value.
def abc(event):
numberss=['7','8','9']
omenu2['menu'].delete(0, 'end')
for number in numberss:
omenu2['menu'].add_command(label=number, command=lambda val=number: efg(val))
def efg(val, event=None):
print('yee')
var1.set(val)
You are using for loop so you don't need ans(at least not in this code) since it iterates over items themselves.

Stopping, restarting and changing variables in a thread from the main program (Python 3.5)

I'm very new to threading am and still trying to get my head around how to code most of it. I am trying to make what is effectively a text editor-type input box and so, like every text editor I know, I need a cursor-bar thing to indicate the location at which the text is being typed to. Thus I also want to be able to flicker/blink the cursor, which i thought would also prove good practice for threading.
I have a class cursor that creates a rectangle on the canvas based on the bounding box of my canvas text, but I then need to change it's location as more characters are typed; stop the thread and instantaneously hide the cursor rectangle when the user clicks outside of the input box; and lastly restart the thread/a loop within the thread (once again, sharing a variable) - the idea here being that the cursor blinks 250 times and after then, disappears (though not necessary, I thought it would make a good learning exercise).
So assuming that I have captured the events needed to trigger these, what would be the best way to go about them? I have some code, but I really don't think it will work, and just keeps getting messier. My idea being that the blinking method itself was the thread. Would it be better to make the whole class a thread instead? Please don't feel restricted by the ideas in my code and feel free to improve it. I don't think that the stopping is working correctly because every time I alt+tab out of the window (which i have programmed to disengage from the input box) the Python shell and tkinter GUI stop responding.
from tkinter import *
import threading, time
class Cursor:
def __init__(self, parent, xy):
self.parent = parent
#xy is a tuple of 4 integers based on a text object's .bbox()
coords = [xy[2]] + list(xy[1:])
self.obj = self.parent.create_rectangle(coords)
self.parent.itemconfig(self.obj, state='hidden')
def blink(self):
blinks = 0
while not self.stop blinks <= 250:
self.parent.itemconfig(self.obj, state='normal')
for i in range(8):
time.sleep(0.1)
if self.stop: break
self.parent.itemconfig(self.obj, state='hidden')
time.sleep(0.2)
blinks += 1
self.parent.itemconfig(self.obj, state='hidden')
def startThread(self):
self.stop = False
self.blinking = threading.Thread(target=self.blink, args=[])
self.blinking.start()
def stopThread(self):
self.stop = True
self.blinking.join()
def adjustPos(self, xy):
#I am not overly sure if this will work because of the thread...
coords = [xy[2]] + list(xy[1:])
self.parent.coords(self.obj, coords)
#Below this comment, I have extracted relevant parts of classes to global
#and therefore, it may not be completely syntactically correct nor
#specifically how I initially wrote the code.
def keyPress(e):
text = canvas.itemcget(textObj, text)
if focused:
if '\\x' not in repr(e.char) and len(e.char)>0:
text += e.char
elif e.keysym == 'BackSpace':
text = text[:-1]
canvas.itemconfig(textObj, text=text)
cursor.adjustPos(canvas.bbox(textObj))
def toggle(e):
if cursor.blinking.isAlive(): #<< I'm not sure if that is right?
cursor.stopThread()
else:
cursor.startThread()
if __name__=="__main__":
root = Tk()
canvas = Canvas(root, width=600, height=400, borderwidth=0, hightlightthickness=0)
canvas.pack()
textObj = canvas.create_text(50, 50, text='', anchor=NW)
root.bind('<Key>', keyPress)
cursor = Cursor(canvas, canvas.bbox(textObj))
#Using left-click event to toggle thread start and stop
root.bind('<ButtonPress-1', toggle)
#Using right-click event to somehow restart thread or set blinks=0
#root.bind('<ButtonPress-3', cursor.dosomething_butimnotsurewhat)
root.mainloop()
If there is a better way to do something written above, please also tell me.
Thanks.

Resources