Python TextBox entry Hint(Invisible text) [duplicate] - python

I have the following Entry box where due to obtaining values I have put a list option in for textvariable.
However I was wondering if it would be possible to put a default text in the background so to show which values are required in each box (like a greyscale text, 'Value 1, value 2 etc..).
self.numbers = [StringVar() for i in xrange(self.number_boxes) ] #Name available in global scope.
box=Entry(self.frame_table,bg='white',borderwidth=0, width=10, justify="center", textvariable=self.numbers[i])
Can I add in maybe something change 'textvariable' upon a mouse click inside the box or can I simply just add in another textvariable or text to set a default text?
self.box = []
for i in xrange(self.number_boxes):
self.clicked = False
self.box.append(Entry(self.frame_table,bg='white',borderwidth=0, width=10, justify="center", textvariable=self.numbers[i], fg='grey'))
self.box[i].grid(row=row_list,column=column+i, sticky='nsew', padx=1, pady=1)
self.box[i].insert(0, "Value %g" % float(i+1))
self.box[i].bind("<Button-1>", self.callback)

In order to put default text in your Entry widget, you can use the insert() method as described here.
box.insert(0, "Value 1") # Set default text at cursor position 0.
Now in order to change the contents of box after the user performs a mouse click inside the box, you will need to bind an event to the Entry object. For example, the following code deletes the contents of the box when it is clicked. (You can read about event and bindings here.) Below I show a full example of this.
Note that deleting the text in the box is probably only practical for the first click (i.e. when deleting the default contents), so I created a global flag clicked to keep track of whether it has been clicked.
from tkinter import Tk, Entry, END # Python3. For Python2.x, import Tkinter.
# Use this as a flag to indicate if the box was clicked.
global clicked
clicked = False
# Delete the contents of the Entry widget. Use the flag
# so that this only happens the first time.
def callback(event):
global clicked
if (clicked == False):
box[0].delete(0, END) #
box[0].config(fg = "black") # Change the colour of the text here.
clicked = True
root = Tk()
box = [] # Declare a list for the Entry widgets.
box.append(Entry(fg = "gray")) # Create an Entry box with gray text.
box[0].bind("<Button-1>", callback) # Bind a mouse-click to the callback function.
box[0].insert(0, "Value 1") # Set default text at cursor position 0.
box.append(Entry(fg = "gray")) # Make a 2nd Entry; store a reference to it in box.
box[1].insert(0, "Value 2")
box[0].pack() #
box[1].pack()
if __name__ == "__main__":
root.mainloop()

Related

Why does my selection callback get called on my second Listbox?

from tkinter import *
from tkinter.ttk import *
root = Tk()
listbox = None
listboxMultiple = None
listboxStr = None
listboxMultipleStr = None
def main():
global root
global listboxStr
global listboxMultipleStr
global listbox
global listboxMultiple
root.protocol("WM_DELETE_WINDOW", exitApplication)
root.title("Title Name")
root.option_add('*tearOff', False) # don't allow tear-off menus
root.geometry('1600x300')
listboxStr = StringVar()
listboxStr.set("ABCD")
listbox = Listbox(root, name="lb1", listvariable=listboxStr, width=120)
listbox.pack(side=LEFT)
listbox.bind("<<ListboxSelect>>", selectListItemCallback)
listboxMultipleStr = StringVar()
listboxMultipleStr.set("")
listboxMultiple = Listbox(root, name="lb2", listvariable=listboxMultipleStr, width=120)
listboxMultiple.pack(side=LEFT)
root.mainloop()
def selectListItemCallback(event):
global listboxMultipleStr
global listbox
global listboxMultiple
print("event.widget is {} and listbox is {} and listboxMultiple is {}\n".format(event.widget, listbox, listboxMultiple))
selection = event.widget.curselection()
listboxMultipleStr.set("")
if selection:
index = selection[0]
data = event.widget.get(index)
newvalue = "{}\n{}".format(data,"SOMETHING")
print("selected \"{}\"\n".format( data ))
print("newvalue is \"{}\"\n".format( newvalue ))
listboxMultiple.insert(END, "{}".format(data))
listboxMultiple.insert(END, "SOMETHING")
#listboxMultipleStr.set( newvalue )
else:
pass
def exitApplication():
global root
root.destroy()
if __name__ == "__main__":
main()
Using python3 on Windows 7. I've setup a callback "selectListItemCallback" for one of my two listbox widgets. And yet, when I click on the text in "lb1" it works as expected, I update "lb2" with the same selected text plus I add another line to "lb2".
The issue is, when I then select the item in "lb2", it still calls the callback and the event.widget is "lb1" and not "lb2".
My intent is to have a list of items in 'lb1' and when I select any of them, then 'lb2' gets filled with info related to the selected 'lb1' item. And, I don't want a selection callback invoked in my 'lb2' widget.
Can you see what I'm doing wrong that could be causing this strange behavior?
Thank you.
I've posted the code; and this does run on my Windows 7 machine using python3. Python 3.8.6 to be exact.
It is because the event fires when the first list box loses the selection when you click on the other list box. The event fires whenever the selection changes, not just when it is set.
If you don't want the first listbox to lose its selection when you click in the second listbox, set exportselection to False for the listboxes. Otherwise, tkinter will only allow one to have a selection at a time.

Make Tkinter run a command without clicking or pressing a button

I am making a simple tkinter popup where you can type a message.
In the textbox itself, I inserted the text "Type your message here" with a grey colour and when clicked, the inserted text is deleted so the user can type in their own message. In addition, the colour of the text typed by the user is set to black.
However, when I was testing I realised that this will only happen if they click the textbox with a mouse button. My question is, is there a way for tkinter to automatically run a command when a condition is changed? For example, if the textbox is empty, the font colour should be set to black.
I tried putting if-statements in the tk.mainloop, but sadly that didn't work.
Any ideas?
this is my (hopefully) simplified version of the code:
from tkinter import *
def changecolor(event):
if textbox.get("1.0", "end-1c") == "Type your message here":
textbox.delete("1.0", "end")
textbox.config(fg='black')
root = Tk()
canvas = Canvas(root, height=400, width=600)
canvas.pack()
textbox = Text(canvas, font=40, fg="grey")
textbox.insert(1.0, "Type your message here")
textbox.bind("<Button-1>", changecolor)
textbox.pack()
root.mainloop()
~finally found out how to format code here.
Take a look at this class I created that does similar to what your code does, do try it on.
from tkinter import *
class PlaceholderText(Text):
def __init__(self,master,placeholder,placeholdercolor='black',fg='grey',**kwargs):
Text.__init__(self,master,**kwargs) #init the text widget
self.placeholder = placeholder
self.fgcolor = fg
self.placeholdercolor = placeholdercolor
self.has_placeholder = False #make flag
self.add() #run the function to add placeholder
self.bind('<FocusIn>',self.clear) #binding to focusin and not button-1
self.bind('<FocusOut>',self.add) #function wil get triggered when widget loses focus
def clear(self,event=None):
if self.get('1.0','end-1c') == self.placeholder and self.has_placeholder: #condition to clear a placeholder
self.delete('1.0','end-1c') #delete the placeholder
self.config(fg=self.fgcolor) #change the color
self.has_placeholder = False #set flag to flase
def add(self,event=None):
if self.get('1.0','end-1c') == '' and not self.has_placeholder: #condition to add placeholder
self.insert('1.0',self.placeholder) #add placeholder
self.has_placeholder = True #set flag to true
self.config(fg=self.placeholdercolor) #change text color to what you specify?
def ret(self,index1,index2):
if self.get('1.0','end-1c') == self.placeholder and self.has_placeholder: #gives none if there is nothing in the widget
return 'None'
else:
return self.get(index1,index2) #else gives the text
root = Tk()
pl = PlaceholderText(root,placeholder='Type something here...')
pl.pack()
e = Entry(root) #dummy widget to switch focus and check
e.pack(padx=10,pady=10)
root.mainloop()
I've explained to through the comments. But keep in mind its not the best of classes yet, you have to do add a lot more methods in to make it more efficient.
Just if your wondering on how to do this without classes, then:
from tkinter import *
has_placeholder = False #make flag
placeholder = 'Type Something Here...' #the text to be inserted
def clear(event=None):
global has_placeholder
if a.get('1.0','end-1c') == placeholder and has_placeholder: #condition to clear a placeholder
a.delete('1.0','end-1c') #delete the placeholder
a.config(fg='grey') #change the color
has_placeholder = False #set flag to flase
def add(event=None):
global has_placeholder
if a.get('1.0','end-1c') == '' and not has_placeholder: #condition to add placeholder
a.insert('1.0',placeholder) #add placeholder
has_placeholder = True #set flag to true
a.config(fg='black') #change text color to normal
root = Tk()
a = Text(root)
a.pack()
add() #add the placeholder initially
a.bind('<FocusIn>',clear) #binding to focus and not button-1
a.bind('<FocusOut>',add)
e = Entry(root) #dummy widget to show focus loss
e.pack()
root.mainloop()
Why not to use classes if the latter method is more easier? This is not reusable, say you want to add one more Text widget, that cannot have such property, while using a custom class with custom class you can have as many as text widgets with same properties you like.
Do let me know if any doubts.
You can simply add a <Key> binding to your text widget and use your changecolor function to determine what state your textbox is in.
#Give a hoot. Don't pollute. :D
import tkinter as tk
txtmsg = "Type your message here"
def changecolor(event):
text = textbox.get("1.0", "end-1c")
#customize accordingly
if text:
if text == txtmsg:
print("text is txtmsg")
else:
print("text is user text")
else:
print("text is empty")
#FYI:
#whether this was a button press or key press DOES NOT have string equality
#if you need to create button vs key conditions
#use tk.EventType.ButtonPress and tk.EventType.KeyPress
#or learn the .value and compare that
print(event.type, type(event.type), event.type.value)
root = tk.Tk()
textbox = tk.Text(root, font=40, fg="grey")
textbox.insert(1.0, txtmsg)
textbox.pack()
#add events
for ev in ['<Key>', '<1>']:
textbox.bind(ev, changecolor)
root.mainloop()

Tkinter - Creating multiple check boxes using a loop

I am trying to create a program that allows the user to select any number of check boxes and hit a button to return a random result from those check boxes. Since I am basing my list off the roster of Smash bros ultimate, I am trying to avoid creating 70+ variables just to place check boxes. However, I am unable to figure out how to iterate this. The various values set for rows are just placeholders until I can figure this out. I would also like to have a reset button at the top that allows the user to automatically uncheck every box. This code is what I have so far. Any help would be greatly appreciated.
#!/usr/bin/python3
from tkinter import *
window = Tk()
#window name and header
window.title("Custom Random SSBU")
lbl = Label(window, text="Select the fighters you would like to include:")
lbl.grid(column=1, row=0)
f = [] #check boxes
ft = open("Fighters.txt").readlines() #list of all the character names
fv=[0]*78 #list for tracking what boxes are checked
ff=[] #list to place final character strings
def reset():
for i in fv:
fv[i]=0
rst = Button(window, text="Reset", command=reset)
rst.grid(column=0, row=3)
for y in range (0,77):
f[y] = Checkbutton(window, text = ft[y], variable = fv[y])
f[y].grid(column=0, row=4+y)
def done():
for j in fv:
if fv[j] == 1:
ff.append(fv[j])
result = random.choice(ff)
r=Label(window, text=result)
d = Button(window, text="Done", command=done)
d.grid(column=0, row = 80)
window.mainloop()
Unfortunately I'm afraid you are going to have to create variables for each checkbox.
tkinter has special purpose Variable Classes for holding different types of values, and if you specify an instance of one as the variable= option when you create widgets like Checkbutton, it will automatically set or reset its value whenever the user changes it, so all your program has to do is check its current value by calling its get() method.
Here's an example of the modifications to your code needed to create them in a loop (and use them in the done() callback function):
import random
from tkinter import *
window = Tk()
#window name and header
window.title("Custom Random SSBU")
lbl = Label(window, text="Select the fighters you would like to include:")
lbl.grid(column=1, row=0)
with open("Fighters.txt") as fighters:
ft = fighters.read().splitlines() # List of all the character names.
fv = [BooleanVar(value=False) for _ in ft] # List to track which boxes are checked.
ff = [] # List to place final character strings.
def reset():
for var in fv:
var.set(False)
rst = Button(window, text="Reset", command=reset)
rst.grid(column=0, row=3)
for i, (name, var) in enumerate(zip(ft, fv)):
chk_btn = Checkbutton(window, text=name, variable=var)
chk_btn.grid(column=0, row=i+4, sticky=W)
def done():
global ff
ff = [name for name, var in zip(ft, fv) if var.get()] # List of checked names.
# Randomly select one of them.
choice.configure(text=random.choice(ff) if ff else "None")
d = Button(window, text="Done", command=done)
d.grid(column=0, row=len(ft)+4)
choice = Label(window, text="None")
choice.grid(column=1, row=3)
window.mainloop()
I wasn't sure where you wanted the Label containing the result to go, so I just put it to the right of the Reset button.
variable = fv[y]
This looks up the value of fv[y] - i.e, the integer 0 - at the time the Checkbutton is created, and uses that for the variable argument.
You need to use an instance of one of the value-tracking classes provided by TKinter, instead. In this case we want BooleanVar since we are tracking a boolean state. We can still create these in a list ahead of time:
text = open("Fighters.txt").readlines()
# Let's not hard-code the number of lines - we'll find it out automatically,
# and just make one for each line.
trackers = [BooleanVar() for line in text]
# And we'll iterate over those pair-wise to make the buttons:
buttons = [
Checkbutton(window, text = line, variable = tracker)
for line, tracker in zip(text, trackers)
]
(but we can not do, for example trackers = [BooleanVar()] * len(text), because that gives us the same tracker 78 times, and thus every checkbox will share that tracker; we need to track each separately.)
When you click the checkbox, TKinter will automatically update the internal state of the corresponding BooleanVar(), which we can check using its .get() method. Also, when we set up our options for random.choice, we want to choose the corresponding text for the button, not the tracker. We can do this with the zip trick again.
So we want something more like:
result_label = Label(window) # create it ahead of time
def done():
result_label.text = random.choice(
label
for label, tracker in zip(text, trackers)
if tracker.get()
)

Replacing Buttons linked to input boxes with checkboxes

This is my current code for selecting different options and have them appearing in the box (Minecraft ArmorStand Generator).
from tkinter import *
default = "/summon ArmorStand ~ ~ ~ {CustomNameVisible:1}"
NoAI = ",NoAI:1"
inputbox = Entry()
inputbox.place(x=10,y=10,width=900,height=50)
root = Tk()
def addNOAI():
inputbox.insert(45, NoAI)
inputbox = Entry()
inputbox.place(x=10,y=10,width=900,height=50)
Button(text="Add NoAI",command=addNOAI,relief = FLAT, bg = "#eF651A", fg = "white", width= 25, height = 2).place(x=10,y=123)
root.title("WIP")
root.wm_state('zoomed')
root.mainloop()
What I'd like to to do is replace the buttons with tick boxes, to prevent the buttons being pressed multiple times. If they click the button, add the text, if they untick, remove it.. I'm not sure where to start with this so any hint in the right direction would be nice.
I've got a working solution, you can try it below.
from tkinter import *
default = "/summon ArmorStand ~ ~ ~ {CustomNameVisible:1}"
NoAI = ",NoAI:1"
inputbox = Entry()
inputbox.place(x=10,y=10,width=900,height=50)
root = Tk()
def addNOAI():
state = var.get()
if state == 1: #if the state is checked
inputbox.insert(45, NoAI) #then add the text
else: #if the state is not check
inputbox.delete(0, 7) #delete the text
inputbox = Entry()
inputbox.place(x=10,y=10,width=900,height=50)
var = IntVar() #sets up variable for check button
c = Checkbutton(text="Add NoAI", command=addNOAI, variable=var) #defining check button variable and command
c.place(x=10,y=123)
root.title("WIP")
root.wm_state('zoomed')
root.mainloop()
The only problem is at the moment, you are deleting everything in the entry box (more accurately from position 0, to position 7). I assume that there will be multiple check buttons, all adding their own strings to the entry box.
As a solution, I would suggest extracting everything from the entry box, finding the string you want, taking it out, and putting everything back in again. Here's an example.
def addNOAI():
state = var.get()
if state == 1: #if the state is checked
inputbox.insert(45, NoAI) #then add the text
else: #if the state is not check
contents = inputbox.get() #gets all of contents
position = contents.find(NoAI) #finds the first position of desired string to remove
newcontents = contents[:position]+contents[position+7:] #gets string before the word, and after the word, and joins them
inputbox.delete(0, 'end') #clears input box for new entry
inputbox.insert(45, newcontents) #re-inserts the string
Here, when the user unchecks the box, the program finds the starting position of the string within the contents of the inputbox. Because you know how long the string will be (in this case 7), you can remove the string from the current contents of the inputbox, and place this inside a new variable. Now you have a new string, without the one that was unchecked, you can clear the inputbox, and put the new one in.
Hope this helps!

How to set the text/value/content of an `Entry` widget using a button in tkinter

I am trying to set the text of an Entry widget using a button in a GUI using the tkinter module.
This GUI is to help me classify thousands of words into five categories. Each of the categories has a button. I was hoping that using a button would significantly speed me up and I want to double check the words every time otherwise I would just use the button and have the GUI process the current word and bring the next word.
The command buttons for some reason are not behaving like I want them to. This is an example:
import tkinter as tk
from tkinter import ttk
win = tk.Tk()
v = tk.StringVar()
def setText(word):
v.set(word)
a = ttk.Button(win, text="plant", command=setText("plant"))
a.pack()
b = ttk.Button(win, text="animal", command=setText("animal"))
b.pack()
c = ttk.Entry(win, textvariable=v)
c.pack()
win.mainloop()
So far, when I am able to compile, the click does nothing.
You might want to use insert method. You can find the documentation for the Tkinter Entry Widget here.
This script inserts a text into Entry. The inserted text can be changed in command parameter of the Button.
from tkinter import *
def set_text(text):
e.delete(0,END)
e.insert(0,text)
return
win = Tk()
e = Entry(win,width=10)
e.pack()
b1 = Button(win,text="animal",command=lambda:set_text("animal"))
b1.pack()
b2 = Button(win,text="plant",command=lambda:set_text("plant"))
b2.pack()
win.mainloop()
If you use a "text variable" tk.StringVar(), you can just set() that.
No need to use the Entry delete and insert. Moreover, those functions don't work when the Entry is disabled or readonly! The text variable method, however, does work under those conditions as well.
import Tkinter as tk
...
entry_text = tk.StringVar()
entry = tk.Entry( master, textvariable=entry_text )
entry_text.set( "Hello World" )
You can choose between the following two methods to set the text of an Entry widget. For the examples, assume imported library import tkinter as tk and root window root = tk.Tk().
Method A: Use delete and insert
Widget Entry provides methods delete and insert which can be used to set its text to a new value. First, you'll have to remove any former, old text from Entry with delete which needs the positions where to start and end the deletion. Since we want to remove the full old text, we start at 0 and end at wherever the end currently is. We can access that value via END. Afterwards the Entry is empty and we can insert new_text at position 0.
entry = tk.Entry(root)
new_text = "Example text"
entry.delete(0, tk.END)
entry.insert(0, new_text)
Method B: Use StringVar
You have to create a new StringVar object called entry_text in the example. Also, your Entry widget has to be created with keyword argument textvariable. Afterwards, every time you change entry_text with set, the text will automatically show up in the Entry widget.
entry_text = tk.StringVar()
entry = tk.Entry(root, textvariable=entry_text)
new_text = "Example text"
entry_text.set(new_text)
Complete working example which contains both methods to set the text via Button:
This window
is generated by the following complete working example:
import tkinter as tk
def button_1_click():
# define new text (you can modify this to your needs!)
new_text = "Button 1 clicked!"
# delete content from position 0 to end
entry.delete(0, tk.END)
# insert new_text at position 0
entry.insert(0, new_text)
def button_2_click():
# define new text (you can modify this to your needs!)
new_text = "Button 2 clicked!"
# set connected text variable to new_text
entry_text.set(new_text)
root = tk.Tk()
entry_text = tk.StringVar()
entry = tk.Entry(root, textvariable=entry_text)
button_1 = tk.Button(root, text="Button 1", command=button_1_click)
button_2 = tk.Button(root, text="Button 2", command=button_2_click)
entry.pack(side=tk.TOP)
button_1.pack(side=tk.LEFT)
button_2.pack(side=tk.LEFT)
root.mainloop()
Your problem is that when you do this:
a = Button(win, text="plant", command=setText("plant"))
it tries to evaluate what to set for the command. So when instantiating the Button object, it actually calls setText("plant"). This is wrong, because you don't want to call the setText method yet. Then it takes the return value of this call (which is None), and sets that to the command of the button. That's why clicking the button does nothing, because there is no command set for it.
If you do as Milan Skála suggested and use a lambda expression instead, then your code will work (assuming you fix the indentation and the parentheses).
Instead of command=setText("plant"), which actually calls the function, you can set command=lambda:setText("plant") which specifies something which will call the function later, when you want to call it.
If you don't like lambdas, another (slightly more cumbersome) way would be to define a pair of functions to do what you want:
def set_to_plant():
set_text("plant")
def set_to_animal():
set_text("animal")
and then you can use command=set_to_plant and command=set_to_animal - these will evaluate to the corresponding functions, but are definitely not the same as command=set_to_plant() which would of course evaluate to None again.
One way would be to inherit a new class,EntryWithSet, and defining set method that makes use of delete and insert methods of the Entry class objects:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
class EntryWithSet(tk.Entry):
"""
A subclass to Entry that has a set method for setting its text to
a given string, much like a Variable class.
"""
def __init__(self, master, *args, **kwargs):
tk.Entry.__init__(self, master, *args, **kwargs)
def set(self, text_string):
"""
Sets the object's text to text_string.
"""
self.delete('0', 'end')
self.insert('0', text_string)
def on_button_click():
import random, string
rand_str = ''.join(random.choice(string.ascii_letters) for _ in range(19))
entry.set(rand_str)
if __name__ == '__main__':
root = tk.Tk()
entry = EntryWithSet(root)
entry.pack()
tk.Button(root, text="Set", command=on_button_click).pack()
tk.mainloop()
e= StringVar()
def fileDialog():
filename = filedialog.askopenfilename(initialdir = "/",title = "Select A
File",filetype = (("jpeg","*.jpg"),("png","*.png"),("All Files","*.*")))
e.set(filename)
la = Entry(self,textvariable = e,width = 30).place(x=230,y=330)
butt=Button(self,text="Browse",width=7,command=fileDialog).place(x=430,y=328)

Categories

Resources