I'm creating a GUI application in Python 2.7 using Tkinter.
I have this piece of code:
vis=Label(pur,text='Purchase Added successfully',font=(8))
vis.place(x=150,y=460)
I wanna know if there's any way to display the Label 'Purchase Added Successfully' for a limited amount of time(~3 seconds) and then it would disappear. This is because I'm interested in adding a new 'purchase' after the current one, and don't want the success messages to overlap.
There are many ways depending on the project pattern, all based on the syntax :
vis=Label(pur,text='Purchase Added successfully',font=(8))
vis.place(x=150,y=460)
vis.after(3000, function_to_execute)
Total Destruction
If you don't want to wonder whether the label is already created, hidden or empty, and mostly avoid possible memory leaks (thanks to Bryan Oakley comment):
vis.after(3000, lambda: vis.destroy() )
But then you need to create a fresh new Label for every purchase.
Hide and Seek
The following method allows to disable the display of the Label without destroying it.
vis.after(3000, lambda: vis.place_forget() )
#vis.after(3000, lambda: vis.grid_forget() ) # if grid() was used
#vis.after(3000, lambda: vis.pack_forget() ) # if pack() was used
Then you can enable it again for the next purchase, with vis.place(x=150,y=460)
Text Eraser
Another way, maybe less interesting, unless you prefer keeping an empty Label in the container widget:
vis.after(3000, lambda: vis.config(text='') )
(Note that you can replace the text with vis.config(text='blabla') for the next purchase)
Related
Is there any way to add options in an OptionMenu object without using the command flag?
I have seen many posts, such as this one, showing how the add_command will update/remove/add options to an already existing OptionMenu object. For example, this snippet of code removes all options in serialPortOptionMenu and repopulates the option menu with different options:
serialPortOptionMenu["menu"].delete(0, "end")
for serialPort in serialPortsArray:
serialPortOptionMenu["menu"].add_command(label=serialPort[1], command=lambda v=serialPort: serialPortFunc(v))
However, something like this seems to overwrite the original command flag I wrote when creating the OptionMenu object:
serialPortOptionMenuValue = Tkinter.StringVar(optionMenuFrame)
serialPortOptionMenuValue.set(serialPorts[0])
serialPortOptionMenu = Tkinter.OptionMenu(optionMenuFrame, serialPortOptionMenuValue, *serialPorts, command=lambda *args: callbackFuncWhenOptionMenuSelectsAnotherOption(*args))
serialPortOptionMenu.grid(row=3, column=0, columnspan=2, sticky="we")
As I'm sure many people are wondering why I am setting a command within an OptionMenu (weird I know), it is because I want a callback function to be called when the user picks a new option.
"What about the trace option"-Everyone...Yes, I am aware of this as well, but because I am reading/writing new values to the Tkinter variable in code without having the OptionMenu solely change it, using trace within Tkinter would not be an effective substitute for just tracking when the user selects a new option in the OptionMenu.
Why am I changing the Tkinter value in code and not having the OptionMenu do it? Because I thought it would be nice to modify the Tkinter value string with something like a ~ at the end of the string to signify something is happening behind the scenes that isnt completed yet, and only when it is completed will the ~ go away, hence the reading and writing to the value without having the OptionMenu solely change it.
What I am primarily interested in is if anyone knows of other ways to add options to an OptionMenu object without using
myMenu["menu"].add_command(..., command=...)
As it seems to be removing my original callback function.
Note: Having two OptionMenus and performing grid_remove/grid on them to hide/reveal them also crossed my mind, but just seems to messy.
There is no other way. An option menu is nothing more than a menubutton and a menu with a custom binding. You can create your own OptionMenu and add any methods you want.
I don't know if it will be useful to you anymore, but maybe somebody else will be looking for this as I was. You actually just need to put the command as a callback parameter in the command you use to add the item (I found tk._setit):
def refresh_dropdown():
# Reset var and delete all old options
var.set('')
dropdown['menu'].delete(0, 'end')
# Insert list of new options (tk._setit hooks them up to var)
new_choices = ['a', 'b', 'c', '...'] # you can make a dictionary[key_from_previous_dropdown.get()] as I did in my case
for choice in new_choices:
dropdown['menu'].add_command(label=choice, command=_setit(var, choice, new_command))
dropdown = OptionMenu(app, var, command=new_command)
Using Python 3.5. I have a tkinter form. The user clicks a button to import many files into a Listbox. Another button loops thru the files and reads and extracts data from them.
I have a Label on the form that indicates the status of the loop. The status, for the most part, works as expected except that extra characters are added on the end. I'm not sure where the characters come from. I also print() the same content as the Label back to the screen and the print() displays exactly what it should.
My question is why is my Label not displaying the correct string?
My code, greatly shortened:
class tk_new_db:
def __init__(self, master):
self.master = master # sets 'root' to the instance variable 'master'
self.var_text_2 = StringVar()
self.var_text_2.set('STATUS: Active')
self.label_6 = Label(master, textvariable=self.var_text_2,
font=self.font_10)
self.label_6.grid(row=15, sticky=W, padx=15)
def execute_main(self): # extract data from files
file_num = 0
nf = len(self.listbox_1.get(0, END))
for fr in li:
file_num += 1
print('STATUS: Extracting Loads from File '
'{} in {}'.format(file_num, nf))
self.var_text_2.set('STATUS: Extracting Loads from File '
'{} in {}'.format(file_num, nf))
self.master.update_idletasks()
The print() is writing the following:
STATUS: Extracting Loads from File 1 in 5
The Label is writing the following:
STATUS: Extracting Loads from File 1 in 5 nce...
It always adds ' nce...' on the Form.
EDIT:
I do use self.var_text_2 earlier in the program. The ' nce...' looks like it is a fragment of the previous string. I've since tried resetting the variable in two different ways, but I'm still getting the same result.
self.var_text_2.set('STATUS: Checking .F06 Files for Convergence...')
self.master.update_idletasks()
self.var_text_2.__del__()
self.var_text_2.set('STATUS: Checking .F06 Files for Convergence...')
self.master.update_idletasks()
self.var_text_2.set('')
How do you properly delete the StringVar() for reuse?
The only explanation I can think of is that you're stacking labels on top of each other in the same row and column, so when you add a long string that ends in "nce..." and later update the screen by adding a shorter label in the same grid cell, the trailing text of the longer label underneath is showing through.
The reasons I draw this conclusion are:
this is a common mistake I've seen several times
there are no bugs in tkinter that would cause this
there is nothing in the code that you've shown that could possibly add "nce..." to the end of the string
the fact that you use sticky="w" rather than sticky="ew", which will cause a shorter label to not completely overlay a longer label if placed in the same row and column
I think I can explain. Root.mainloop repeatedly calls root.update. Root.update executes tasks on the 'main' event queue. In brief, it only executes idletasks when the event queue is empty after checking for file events (on non-Windows), window-gui events, and timer events. Idletasks, which are not well documented but seem to primarily be screen update tasks, are never added to the main event queue. It is therefore possible that idletasks will remain undone indefinitely. Root.update_idletasks exists to force execution of idletasks anyway.
When mainloop is running, calling update within a task run by update is usually unnecessary and possibly worse. It is something only for adventurous experts. Hence the warnings you have read (which assume that you are running mainloop).
When mainloop is not running and update is not being called repeatedly, either because you never called mainloop or because you block it with a long running loop, then you likely must call update yourself. What you seem to have discovered is that part of completely handling a StringVar update is a main event and not just an idletask.
I sympathize with your desire to do repeated tasks with a for loop, but doing so currently means taking responsibility for event handling yourself. I hope in the future that it will be possible to have 'async for' (new in 3.5) interoperate with Tk.mainloop, but it is not as easy as I hoped.
In the meanwhile, you could put the body of the for loop into a callback and loop with root.after.
I am new in Python and in tkinter so the question may seems naive: is it ok to create and place widgets at the same time if I don't need to change them?
It works but is it a good practice? And if not why?
An example of what I mean:
import tkinter as tk
window=tk.Tk()
tk.Label(window,text='Lost Label').pack()
window.mainloop()
To expand upon #Skynet's answer....
Whenever you do Widget(*args, **kwargs).pack() the pack() method returns None as would other geometry managers, so if you tried to assign this to a variable the variable would be None.
In this case then probably not, since you probably actually want to be storing the reference to the widget.
If you don't need a reference then there's not really a problem with it. As the other answer notes you don't need a definitve reference to every single widget in your GUI unless you plan to use this reference in some way. Unless I plan on changing the label text / modifying it in someway then I typically use your method to save some space. No need to write more code than you have to!
For example you're creating a Button widget.
btn = Button(blabla, text="Button1")
This returns a button object and if you need later to configure it or get information about it you can do it by through the btn variable.
But if you use something like btn = Button(blabla, text="Button1").pack() it returns None and not a button object so you won't be able to change anything about the button or get information about it later.
Another example is with the Entry widget
entry = Entry(blabla)
Using that later you can do entry.get() to get the text inside the entry
but you won't be able to do it if you use entry = Entry(blabla).pack() since it doesn't return an entry object, it just packs the widget and you won't be able to access it for later use.
There is nothing wrong with that approach and I have already seen it quite a few times. You don't have to keep a reference to every widget in your GUI.
I have an Entry widget on a simple calculator. The user can choose to enter an equation via the keypad. I was wondering if there was a way to detect a character(from the keypad in my case) being typed into the Entry widget. So, focus is on the widget, user presses '4', it comes up on the widget... can I detect this act, for basic purposes of logging the input?
Every time you press a key inside a Tkinter window, a Tkinter.Event instance is created. All you need to do is access that instance. Here is a simple script that demonstrates just how:
from Tkinter import Tk, Entry
root = Tk()
def click(key):
# print the key that was pressed
print key.char
entry = Entry()
entry.grid()
# Bind entry to any keypress
entry.bind("<Key>", click)
root.mainloop()
key (being a Tkinter.Event instance) contains many different attributes that can be used to get almost any type of data you want on the key that was pressed. I chose to use the .char attribute here, which will have the script print what each keypress is.
Yes. There are a few different ways to do this, in fact.
You can create a StringVar, attach it to the Entry, and trace it for changes; you can bind all of the relevant events; or you can add a validation command that fires at any of several different points in the sequence. They all do slightly different things.
When a user types 4, there's a key event with just the 4 in it (which doesn't let you distinguish whether the user was adding 4 to the end, or in the middle, or replacing a whole selected word, or…), and then a modification event is fired with the old text,* and then the "key" or "all" validation function is called with the (proposed) new text, and the variable is updated with the (accepted) new text (unless the validation function returned false, in which case the invalidcommand is called instead).
I don't know which one of those you want, so let's show all of them, and you can play around with them and pick the one you want.
import Tkinter as tk
root = tk.Tk()
def validate(newtext):
print('validate: {}'.format(newtext))
return True
vcmd = root.register(validate)
def key(event):
print('key: {}'.format(event.char))
def var(*args):
print('var: {} (args {})'.format(svar.get(), args))
svar = tk.StringVar()
svar.trace('w', var)
entry = tk.Entry(root,
textvariable=svar,
validate="key", validatecommand=(vcmd, '%P'))
entry.bind('<Key>', key)
entry.pack()
root.mainloop()
The syntax for variable trace callbacks is a bit complicated, and not that well documented in Tkinter; if you want to know what the first two arguments mean, you need to read the Tcl/Tk docs, and understand how Tkinter maps your particular StringVar to the Tcl name 'PY_VAR0'… Really, it's a lot easier to just build a separate function for each variable and mode you want to trace, and ignore the args.
The syntax for validation functions is even more complicated, and a lot more flexible than I've shown. For example, you can get the inserted text (which can be more than one character, in case of a paste operation), its position, and all kinds of other things… but none of this is described anywhere in the Tkinter docs, so you will need to go the Tcl/Tk docs. The most common thing you want is the proposed new text as the argument, and for that, use (vcmd, '%P').
Anyway, you should definitely play with doing a variety of different things and see what each mechanism gives you. Move the cursor around or select part of the string before typing, paste with the keyboard and with the mouse, drag and drop the selection, hit a variety of special keys, etc.
* I'm going to ignore this step, because it's different in different versions of Tk, and not very useful anyway. In cases where you really need a modified event, it's probably better to use a Text widget and bind <<Modified>>.
If you just need to do simple things without using trace module you can try
def objchangetext(self, textwidget):
print(textwidget.get()) #print text out to terminal
text1 = tk.Entry(tk.Tk())
text1.bind("<KeyRelease>", lambda event, arg=(0): objchangetext(text1))
This is related to another question I found here that seems to be inactive for a few months, so I think it's worth asking again.
I have created a simple QDialog that has a QTextEdit and a QPushButton. This pops up in my application when a user right-clicks and selects the option to "add comments". I want them to be able to write free-form text and I'll just save whatever they write as a long string with no concern for new lines, etc.
When the user clicks the button, it executes code like this:
self.connect(accept_button,QtCore.SIGNAL('clicked()'),lambda arg=str(view_textedit.toPlainText()): self.updateGroupComments(arg))
def updateGroupComments(self,new_comment_str):
print "Updating user comment to have new string: " + new_comment_str
self.group_entry.list_of_user_comments[self.currentFrameCounter] = new_comment_str
This is not detecting the TextEdit text that is visible (it only detects whatever the text edit text is set to when it is created). How do I make a simple command that returns the currently visible text from a QTextEdit. Again, the function
toPlainText()
is not working correctly... it doesn't find the currently visible text, only whatever text was on screen before changes or additions started being made by the user.
If this can't be done without subclassing and appealing to cursor positions, it makes the whole thing seem worthless... so please keep suggestions only to those implemented without subclassing or manipulating cursors. It should be really simple and straightforward to just return all currently visible text... what am I missing?
Objects that are being bound to default arguments are evaluated at the definition time. The function is working correctly, it returns whatever was in the text field when it was executed. Your code simply calls it at the wrong moment. If you want to use lambda, then do:
self.connect(
accept_button, QtCore.SIGNAL('clicked()'),
lambda: self.updateGroupComments(str(view_textedit.toPlainText()))
)
Or make view_textedit an instance attribute instead, and do simply
self.connect(
accept_button, QtCore.SIGNAL('clicked()'), self.updateGroupComments
)
And change updateGroupComments to call self.view_textedit.toPlainText instead of taking an argument.
BTW, this is not PyQt specific, this is how Python works in general.
To illustrate my last comment, that lambda can very well be replaced with:
def slot():
self.updateGroupComments(str(view_textedit.toPlainText()))
self.connect(accept_button, QtCore.SIGNAL('clicked()'), slot)