Passing a variable from combobox to called function - python

I am trying to pass a variable from a tkinter combobox to a function called when clicking a 'Run' button. I am relatively new to python and every option I have tried creates an error - mostly that the variable is not defined. I believe this is because I am not defining it in the correct place. Any help is greatly appreciated.
from tkinter import *
from tkinter import ttk
from URL_Generator import crawl_site
listFile = open('regions1.txt','r')
root = Tk()
root.configure()
varItems = StringVar(root, value='')
class MainWindow(Frame):
def __init__(self,master = None):
Frame.__init__(self,master)
self.master = master
self.grid()
self.create_widgets()
def create_widgets(self):
"""Create Window Layout"""
self.label = Label(self, text="List Items").pack()
self.itemCombo = ttk.Combobox(self, width = 16, textvariable = varItems)
self.itemCombo.bind("<Return>", self.itemCombo_onEnter)
self.itemCombo.bind('<<ComboboxSelected>>',self.itemCombo_onEnter)
self.itemCombo['values'] = [l.strip() for l in listFile.readlines()]
self.itemCombo.pack()
self.blank = Label(self,text='').pack()
"""I want to pass the value selected in the combobox to the crawl_region() function when pushing Run"""
self.RunButton = Button(self, text="Run",command = crawl_site.crawl_region(region))
self.RunButton.pack()
def itemCombo_onEnter(self,event):
varItems.set(varItems.get().lower().strip())
mytext = varItems.get().strip()
vals = self.itemCombo.cget('values')
self.itemCombo.select_range(0,END)
print(mytext)
region = mytext
"""I want to pass mytext to the function called when pushing Run"""
if not vals:
self.itemCombo.configure(values = (mytext,))
elif mytext not in vals:
with open('regions1.txt', 'w') as f:
self.itemCombo.configure(values=vals + (mytext,))
f.write("\n".join(vals + (mytext,)))
f.close()
return 'break'
app = MainWindow(root)
root.mainloop()
Sample function called (crawl_site.crawl_region()):
class crawl_site():
def crawl_region(region):
print('passed region '+ str(region))
passed region [] is immediately returned, but nothing happens when I make a selection or press the Run button.

Try the below code.
I've created a class property self.mytext which is set when the combo button is entered itemCombo_onEnter. When the button is pressed the onRunButton function is called. if self.mytext has been set, it will call the crawl_region function with self.mytext as an argument.
from tkinter import *
from tkinter import ttk
from URL_Generator import crawl_site
listFile = open('regions1.txt','r')
root = Tk()
root.configure()
varItems = StringVar(root, value='')
class MainWindow(Frame):
def __init__(self,master = None):
Frame.__init__(self,master)
self.master = master
self.grid()
self.create_widgets()
def create_widgets(self):
"""Create Window Layout"""
self.label = Label(self, text="List Items").pack()
self.itemCombo = ttk.Combobox(self, width = 16, textvariable = varItems)
self.itemCombo.bind("<Return>", self.itemCombo_onEnter)
self.itemCombo.bind('<<ComboboxSelected>>',self.itemCombo_onEnter)
self.itemCombo['values'] = [l.strip() for l in listFile.readlines()]
self.itemCombo.pack()
self.blank = Label(self,text='').pack()
"""I want to pass the value selected in the combobox to the crawl_region() function when pushing Run"""
self.RunButton = Button(self, text="Run",command = self.onRunButton)
self.RunButton.pack()
def onRunButton(self):
if self.mytext:
crawl_site.crawl_region(self.mytext)
def itemCombo_onEnter(self,event):
varItems.set(varItems.get().lower().strip())
mytext = varItems.get().strip()
vals = self.itemCombo.cget('values')
self.itemCombo.select_range(0,END)
print(mytext)
self.mytext = mytext
"""I want to pass mytext to the function called when pushing Run"""
if not vals:
self.itemCombo.configure(values = (mytext,))
elif mytext not in vals:
with open('regions1.txt', 'w') as f:
self.itemCombo.configure(values=vals + (mytext,))
f.write("\n".join(vals + (mytext,)))
f.close()
return 'break'
app = MainWindow(root)
root.mainloop()
Your code wasn't working because of this line
self.RunButton = Button(self, text="Run",command = crawl_site.crawl_region(region))
This immediately calls the method crawl_region with the region as an argument and tries to set the callback of the button to the result of that method.
Another way to 'fix' your problem without creating another function would be to use lambda but I think my method is more readable.

Related

solution communication between two windows

I want to have two tkinter windows. A button should be in the first window, and a reaction text should be in the second window.
My questions:
Must the second window have no modal?
How do I make the second window movable?
How can I give information to second window via callback function?
Thanks in advance for answers and advice!
Here is some code that may help you:
from tkinter import *
class App:
def __init__(self):
self.window1 = Tk()
self.window2 = Toplevel()
self.button = Button(self.window1, bd = 5, text = "Click Me!", command = self.update)
self.button.pack()
self.label = Label(self.window2, bd = 5, text = "Button has not been clicked.")
self.label.pack()
def update(self):
self.label.config(text = "Button has been clicked!")
self.window2.update()
app = App()
Explanation:
The first line imports tkinter
In the next line, we create a class. At the bottom of the code, we create an object using that class. This is useful because when the object is created, the functions in the class are already defined, so the function definition can be after when it is called.
After we declare our class, in __init__, we write code that will run when an object is created from that class. The code creates two windows. One contains a button, and the other one contains a label. The button has a command parameter to run the class function, update.
In update, we change the label text and update the window.
I have not next questions. My problems solution is here:
import tkinter as tk
class ViewOnMoon(tk.Toplevel):
def __init__(self, parent = None, draw = None):
tk.Toplevel.__init__(self, parent)
self.transient(parent)
self.title('View')
self.minsize(height = 300, width = 300)
fr_canvas = tk.Frame(self)
fr_canvas.place(relx=0.23, rely=0.01, anchor="nw")
self.canv_w = 200
self.canv_h = 200
self.canvas = tk.Canvas(fr_canvas, bg='white', width = self.canv_w, height=self.canv_h)
self.canvas.grid(column = 0, row = 0)
return
class GuiMoonMove(tk.Frame):
def __init__(self, master):
mon_h = 600
mon_w = 1250
tk.Frame.__init__(self, master)
self.frame = tk.Frame(master, width=1000, height=200, bd=2)
self.master.title('Move')
self.master.minsize(height = mon_h, width = mon_w)
fr_canvas = tk.Frame(self.master)
fr_canvas.place(relx=0.23, rely=0.01, anchor="nw")
fr_button = tk.Frame(self.master)
fr_button.place(relx=0.02, rely=0.06, anchor="nw")
self.canv_h = 600
self.canv_w = 950
self.lbl_view = tk.BooleanVar()
chb_view_on_moon = tk.Checkbutton(fr_button, text="Pohled na Měsíc", variable = self.lbl_view, \
onvalue=True, offvalue=False,command = self.callback)
chb_view_on_moon.grid(column= 0, row= 4,pady = 10)
self.canvas = tk.Canvas(fr_canvas, bg='white', width = self.canv_w, height=self.canv_h)
self.canvas.grid(column = 0, row = 0)
def callback(self,*args):
if self.lbl_view.get()==True:
self.view_on_moon = ViewOnMoon(parent = self.master)
else:
self.vom.destroy()
if __name__=="__main__":
root = tk.Tk()
app = GuiMoonMove(master = root)
app.mainloop()

Clearing specific widgets in tkinter

I am attempting to have a function in python that clears the screen upon a button being pressed. I am aware of grid_remove but am unsure of how to use it. Also is there a way to clear everything from a specific function, ie both "hi" and "clear"?
from tkinter import *
class Movies:
def __init__(self, master):
hi = Label(text = "Hello")
hi.grid(row = 0, column = 0)
clear = Button(text = "Click", command=self.clear)
clear.grid(row = 1, column = 0)
def clear(self):
hi.grid_remove()
root = Tk()
gui = Movies(root)
root.geometry("100x200+0+0")
root.mainloop()
You could use the built in winfo_children method if you're just wanting to toggle hiding / showing all of the widgets in whatever parent holds the widgets. Small example:
from tkinter import *
class Movies:
def __init__(self, master):
self.master = master
self.state = 1
for i in range(5):
Label(self.master, text='Label %d' % i).grid(row=0, column=i)
self.magic_btn = Button(self.master, text='Make the Magic!',
command=self.magic)
self.magic_btn.grid(columnspan=5)
def magic(self):
self.state = not self.state
for widget in self.master.winfo_children(): #iterate over all child widgets in the parent
#Comment out to clear the button too, or leave to toggle widget states
if widget != self.magic_btn: #or some other widget you want to stay shown
if self.state:
widget.grid()
else:
widget.grid_remove()
print(self.state)
root = Tk()
gui = Movies(root)
root.mainloop()

Python Tkinter Input Box

Good day.
I am trying to create my own input box for use in my project.
basically what i am trying to do is run my main form which will call the second. the user will provide some data on the second and when the press the ok/close button on the second for the data will be passed back to the first. similar in functionality to the inputbox.
here is what i have created, but being new to python i am not sure where i am going wrong/nor can i quick figure out when to put the return.
My Class is here
import tkinter as tk
class MainWindow():
def __init__(self, parent):
top = self.top = tk.Toplevel(parent)
self.myLabel = tk.Label(top, text='Enter a Grouping Name')
self.myLabel.pack()
self.myEntryBox = tk.Entry(top)
self.myEntryBox.focus_set()
self.myEntryBox.pack()
self.mySubmitButton = tk.Button(top, text='OK', command=self.DestWin)
self.mySubmitButton.pack()
def DestWin(self):
self.top.destroy()
The method to call it is here
abc=configurator.MainWindow(root)
Not exactly sure what you are trying to achieve, but if you are trying to get values from one window to another, below you can find an extended example based on your code.
import tkinter as tk
class MainWindow():
def __init__(self, parent):
top = self.top = tk.Toplevel(parent)
self.myLabel = tk.Label(top, text='Enter a Grouping Name')
self.myLabel.pack()
self.myEntryBox = tk.Entry(top)
self.myEntryBox.focus_set()
self.myEntryBox.pack()
self.mySubmitButton = tk.Button(top, text='OK', command=self.DestWin)
self.mySubmitButton.pack()
def DestWin(self):
# call callback function setting value in MyFrame
self.callback(self.myEntryBox.get())
self.top.destroy()
def set_callback(self, a_func):
self.callback = a_func
class MyFrame(tk.Frame):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.pack()
self.myLabel1 = tk.Label(parent, text='Click OK to enter the group name')
self.myLabel1.pack()
self.mySubmitButton1 = tk.Button(parent, text='OK', command=self.get_group_name)
self.mySubmitButton1.pack()
def get_group_name(self):
mw = MainWindow(None)
# provide callback to MainWindow so that it can return results to MyFrame
mw.set_callback(self.set_label)
def set_label(self, astr = ''):
self.myLabel1['text'] = astr
root = tk.Tk()
mf = MyFrame(root)
root.mainloop()
The screenshot:
The text from the right window, when OK is pressed, will be shown in the left window. This is achieved through callbacks. MainWindow takes a callback function, and when you press OK, it is executed. The callback is set_label from MyFrame.
Hope this helps.

How to add placeholder to an Entry in tkinter?

I have created a login window in tkinter which has two Entry field, first one is Username and second one is Password.
code
from tkinter import *
ui = Tk()
e1 = Entry(ui)
#i need a placeholder "Username" in the above entry field
e1.pack()
ui.mainloop()
I want a placeholder called "Username" in the Entry, but if you click inside the entry box, the text should disappear.
You can create a class that inherits from Entry like below:
import tkinter as tk
class EntryWithPlaceholder(tk.Entry):
def __init__(self, master=None, placeholder="PLACEHOLDER", color='grey'):
super().__init__(master)
self.placeholder = placeholder
self.placeholder_color = color
self.default_fg_color = self['fg']
self.bind("<FocusIn>", self.foc_in)
self.bind("<FocusOut>", self.foc_out)
self.put_placeholder()
def put_placeholder(self):
self.insert(0, self.placeholder)
self['fg'] = self.placeholder_color
def foc_in(self, *args):
if self['fg'] == self.placeholder_color:
self.delete('0', 'end')
self['fg'] = self.default_fg_color
def foc_out(self, *args):
if not self.get():
self.put_placeholder()
if __name__ == "__main__":
root = tk.Tk()
username = EntryWithPlaceholder(root, "username")
password = EntryWithPlaceholder(root, "password", 'blue')
username.pack()
password.pack()
root.mainloop()
You need to set a default value for this entry. Like this:
from tkinter import *
ui = Tk()
e1 = Entry(ui)
e1.insert(0, 'username')
e1.pack()
ui.mainloop()
Then if you want to delete the content when you click the entry, then you have to bind a mouse click event with an event handler method to update content of this entry.
Here is a link for you.
Updated (Improved Answer):
Use the on_focus_out function to reinsert the placeholder if the text field is empty (if you don't want this to happen, you can use the method from the older code)
import tkinter as tk
def on_focus_in(entry):
if entry.cget('state') == 'disabled':
entry.configure(state='normal')
entry.delete(0, 'end')
def on_focus_out(entry, placeholder):
if entry.get() == "":
entry.insert(0, placeholder)
entry.configure(state='disabled')
root = tk.Tk()
entry_x = tk.Entry(root, width=50)
entry_x.pack(pady=10)
entry_x.insert(0, "Place Holder X")
entry_x.configure(state='disabled')
entry_y = tk.Entry(root, width=50)
entry_y.pack(pady=10)
entry_y.insert(0, "Place Holder Y")
entry_y.configure(state='disabled')
x_focus_in = entry_x.bind('<Button-1>', lambda x: on_focus_in(entry_x))
x_focus_out = entry_x.bind(
'<FocusOut>', lambda x: on_focus_out(entry_x, 'Place Holder X'))
y_focus_in = entry_y.bind('<Button-1>', lambda x: on_focus_in(entry_y))
y_focus_out = entry_y.bind(
'<FocusOut>', lambda x: on_focus_out(entry_y, 'Place Holder Y'))
root.mainloop()
Note:
It is discouraged to import *, so we should import like this import tkinter as tk.
I have created two Entry widgets to depict the changes.
Old (Not Recommended):
This will work for any placeholder you want.
from tkinter import *
root = Tk()
my_entry = Entry(root, width=50)
my_entry.pack()
my_entry.insert(0, "Place Holder")
my_entry.configure(state=DISABLED)
def on_click(event):
my_entry.configure(state=NORMAL)
my_entry.delete(0, END)
# make the callback only work once
my_entry.unbind('<Button-1>', on_click_id)
on_click_id = my_entry.bind('<Button-1>', on_click)
root.mainloop()
My solution is to subclass the tk.Entry and control the content and color, binding the <FocusIn> and <FocusOut> events to methods that fill and clear the text as necessary. This is the behavior:
Here the complete example code:
import tkinter as tk
class PlaceholderEntry(tk.Entry):
def __init__(self, master=None, placeholder='', cnf={}, fg='black',
fg_placeholder='grey50', *args, **kw):
super().__init__(master=None, cnf={}, bg='white', *args, **kw)
self.fg = fg
self.fg_placeholder = fg_placeholder
self.placeholder = placeholder
self.bind('<FocusOut>', lambda event: self.fill_placeholder())
self.bind('<FocusIn>', lambda event: self.clear_box())
self.fill_placeholder()
def clear_box(self):
if not self.get() and super().get():
self.config(fg=self.fg)
self.delete(0, tk.END)
def fill_placeholder(self):
if not super().get():
self.config(fg=self.fg_placeholder)
self.insert(0, self.placeholder)
def get(self):
content = super().get()
if content == self.placeholder:
return ''
return content
class App(tk.Frame):
def __init__(self, master=None):
self.root = master
super().__init__(master, borderwidth=0, relief=tk.RAISED)
self.root.title('Placeholder example')
self.pack_propagate(False)
self.pack()
self.entry = PlaceholderEntry(self.root, placeholder='This text is a placeholder')
self.entry.pack()
self.btn = tk.Button(self.root, text='Nothing', highlightcolor='cyan')
self.btn.pack()
root = tk.Tk()
app = App(master=root)
app.root.mainloop()
A working placeholder class. What this does is that it binds to <FocusIn> and <FocusOut> so that when you put focus on it if there is no text it will insert your placeholder into it. You can also change the color on if it is selected or not.
class Placeholder:
def __init__(self,master,placeholder='',placeholdercolor='grey',color='black',**kwargs):
self.e = Entry(master,fg=placeholdercolor,**kwargs)
self.e.bind('<FocusIn>',self.focus_in)
self.e.bind('<FocusOut>',self.focus_out)
self.e.insert(0, placeholder)
self.placeholder = placeholder
self.placeholdercolor=placeholdercolor
self.color = color
def pack(self,side=None,**kwargs):
self.e.pack(side=side,**kwargs)
def place(self,side=None,**kwargs):
self.e.place(side=side,**kwargs)
def grid(self,column=None,**kwargs):
self.e.grid(column=column,**kwargs)
def focus_in(self,e):
if self.e.get() == self.placeholder:
self.e.delete(0,END)
self.e.configure(fg=self.color)
def focus_out(self,e):
if self.e.get() == '':
self.e.configure(fg=self.placeholdercolor)
self.e.delete(0,END)
self.e.insert(0,self.placeholder)
from tkinter import *
root=Tk()
root.geometry("300x200+600+250")
root.config(background="#E0FFFF")
root.resizable(False,False)
def userText(event):
e1.delete(0,END)
usercheck=True
def passText(event):
e2.delete(0, END)
passcheck=True
a=StringVar()
b=StringVar()
usercheck=False
passcheck=False
Label(root,text="User name",bg="#E0FFFF").place(x=20,y=50)
e1= Entry(root,textvariable=a)
e1.place(x=100,y=50)
e1.insert(0,"Enter username")
e1.bind("<Button>",userText)
Label(root,text="Password",bg="#E0FFFF").place(x=20,y=95)
e2= Entry(root,textvariable=b)
e2.place(x=100,y=95)
e2.insert(0,"Enter password")
e2.bind("<Button>",passText)
root.mainloop()
For a more compact solution than the above listed, I suggest that you create a function that would erase the text box on a click event (lambda), as shown here.
from tkinter import *
def clear_entry(event, entry):
entry.delete(0, END)
entry.unbind('<Button-1>', click_event)
ui = Tk()
entry = Entry(ui)
entry.pack()
placeholder_text = '<enter-placeholder>'
entry.insert(0, placeholder_text)
entry.bind("<Button-1>", lambda event: clear_entry(event, entry))
ui.mainloop()
The "<Button-1>" stands for when you left-click the entry box, so do not alter it, and once you click on the box, it will trigger the event and run the function clear_entry. You have to declare the function and the entry element before defining placeholder_text and using entry.insert. Hopefully, this is a viable solution to this problem.
Joining Nae and Stephen Lins solutions with text field
text = Text(ui)
text.pack()
text.insert('1.0', 'text placeholder')
text.bind("<FocusIn>", lambda args: text.delete('1.0', 'end'))
# This is a easy way to add placeholder to an Entry
from tkinter import *
w = Tk()
w.title("Demo")
w.geomerty("500x500")
EntrySetText = StringVar()
EntryText = Entry(w)
EntryText.pack()
EntrySetText.set("Hello World!")
w.mainloop()
If you combine the entry field with a string variable all of this becomes a lot easier
from tkinter import *
from tkinter import ttk
from typing import Optional
class PlaceholderEntry:
def __init__(self, root: Tk | Frame | ttk.Frame | ttk.LabelFrame | Toplevel, string_variable: Optional[StringVar] = None, style: str = '', width: int = 25, placeholder_text: str = '', text_color: str = 'black', placeholder_color: str = 'grey50'):
self._placeholder_text = placeholder_text
self._placeholder_color = placeholder_color
self._text_color = text_color
self._text_var = string_variable if string_variable else StringVar(root)
self.__text_var.set(placeholder_text)
self.__entry = ttk.Entry(root, textvariable=self.__text_var, style=style, width=width)
self._entry.bind('<FocusOut>', self._focus_out)
self._entry.bind('<FocusIn>', self._focus_in)
self._change_color(False)
#property
def entry(self) -> ttk.Entry:
return self.__entry
#property
def string_var(self) -> StringVar:
return self.__text_var
def _change_color(self, placeholder: bool) -> None:
if placeholder:
self.__entry.configure(foreground=self._text_color)
else:
self.__entry.configure(foreground=self._placeholder_color)
def _focus_in(self, event: Event) -> None:
if self.__text_var.get() == self._placeholder_text:
self.__text_var.set('')
self._change_color(True)
def _focus_out(self, event: Event) -> None:
if not self._entry.get():
self.__text_var.set(self._placeholder_text)
if self.__entry.get() == self._placeholder_text:
self._change_color(False)
else:
self._change_color(True)

Why don't I get the value of self.entry when I press my "Enter" button?

Right now I'm trying to test my entry widget by simply printing the value it holds but .get() seems to not be working. What am I overlooking?
from tkinter import *
class Game(Frame):
'Number guessing application'
def __init__(self,parent=None):
'constructor'
Frame.__init__(self, parent)
self.pack()
Game.make_widgets(self)
Game.new_game(self)
self.number = random.randrange(1,100)
self.pack()
def make_widgets(self):
self.entry = Entry(self, width = 25, bg = 'purple',fg= 'green')
self.entry.pack()
self.button = Button(self,text = 'Enter',relief = 'ridge',command = self.reply())
self.button.pack()
def new_game(self):
pass
def reply(self):
print(self.entry.get())
Game().mainloop()
You have command = self.reply(), which calls self.reply() and uses the return value (in this case an implicit None) as the action.
Change it to command = self.reply, which will make it call the reply method every time.

Categories