Why does my altered tkinter widget not place correctly? - python

Im creating a user management system that allows the admin to create user / employee accounts. When using the default tkinter "Entry" widget it places correctly. correct placement
However when i use my version of the tk entry (LabeledEntry), it places like this
this is the code for my altered entry widget:
class LabeledEntry(tk.Entry): #creates a version of the tkEntry widget that allows for "ghost" text to be within the entry field.
def __init__(self, master=None, label = ""): #which instructs the user on what to enter in the field.
tk.Entry.__init__(self)
self.label = label
self.on_exit()
self.bind('<FocusOut>', self.on_exit)
self.bind('<FocusIn>', self.on_entry)
def on_entry(self, event=None):
if self.get() == self.label: #checks if the given label is present
self.delete(0, tk.END) #If the text field of the entry is the same as the label, it is deleted to allow for user input
self.configure(fg = "black") #sets the text color to black
def on_exit(self, event=None):
if not self.get(): #checks if user entered anything into the entry when clicked into it.
self.insert(0, self.label) #If they did not, sets the text to the original label
self.configure(fg = "grey") #and changes the color of the text to grey.
Is there a way to resolve this issue?

You aren't passing master to the superclass ctor, which is probably part of the issue:
class LabeledEntry(tk.Entry):
def __init__(self, master=None, label = ""):
super().__init__(master=master)
# ...

Related

Saving selected items from a listbox for use in another class

I'm writing a portion of code which is supposed to behave the following way:
Display a listbox of items.
The user selects one or more items from the listbox.
The user clicks a seperate button widget to confirm the selection.
The toplevel window closes once the user clicked the button.
The selection is saved so that other portions of the program can access it.
I have the following code (code not relevant to the question is omitted):
class MainWin(tk.Tk):
# ... omitted code
def dia(self):
'''
This creates a toplevel window that displays the listbox.
'''
TL = dialogWin(self)
MainWin.wait_window(TL)
class dialogWin(tk.Toplevel):
'''
This window creates and displays the listbox.
'''
def __init__(self, MainWin):
# ... omitted code
# ...
B = tk.Button(self, text = 'Confirm', command = self.getChoices).grid(row = 3) #This is the button that the user clicks to confirm the selection
#create listbox
self.LB = tk.Listbox(self, height = 10, width = 45, selectmode = 'multiple')
# ... code to configure the listbox appearence
def getChoices(self):
'''
This is the callback function the listbox uses.
'''
choice = self.LB.curselection()
self.destroy()
return choice
That's the code that I have but I can't seem to find a way to actually access the information stored in choice. For instance if I edit dia(self) to confirm the return value, like so:
def dia(self):
TL = dialogWin(self)
MainWin.wait_window(TL)
print(TL.getChoices())
I get the error _tkinter.TclError: invalid command name ".!dialogwin.!listbox". TL.getChoices().get() gives me the same error. What I want is the selected information to be saved as something like (1, 2, 3) where I can then access the tuple from the main window. Is there something I've done wrong here?
You are very close. You can no longer use tkinter widget methods after a window is destroyed, but you can use your own methods and attributes:
import tkinter as tk
class MainWin(tk.Tk):
def __init__(self):
super().__init__()
tk.Button(self, text='click me', command=self.dia).pack()
self.geometry('200x200')
def dia(self):
TL = dialogWin(self)
MainWin.wait_window(TL)
print(TL.choice)
class dialogWin(tk.Toplevel):
def __init__(self, MainWin):
super().__init__(MainWin)
B = tk.Button(self, text = 'Confirm', command = self.getChoices).grid(row = 3) #This is the button that the user clicks to confirm the selection
#create listbox
self.LB = tk.Listbox(self, height = 10, width = 45, selectmode = 'multiple')
self.LB.grid()
for x in range(10):
self.LB.insert(tk.END, f"this is item {x}")
def getChoices(self):
self.choice = self.LB.curselection() # <== save the choice as an attribute
self.destroy()
MainWin().mainloop()
You could also save it as an attribute of the main window if you wanted to:
def getChoices(self):
self.master.choice = self.LB.curselection()
self.destroy()
Or any other place really.
Since you have used .wait_window(), it means that you want the Toplevel() acts like a modal dialog.
Below is modified dialogWin() to act like a modal dialog:
class dialogWin(tk.Toplevel):
'''
This window creates and displays the listbox.
'''
def __init__(self, MainWin):
super().__init__(MainWin)
# initialize empty selected choice
self.choice = ()
# ... omitted code
# ...
tk.Button(self, text='Confirm', command=self.getChoices).grid(row=3) #This is the button that the user clicks to confirm the selection
#create listbox
self.LB = tk.Listbox(self, height=10, width=45, selectmode='multiple')
# ... code to configure the listbox appearence
def getChoices(self):
'''
This is the callback function the listbox uses.
'''
# save the selected choices
self.choice = self.LB.curselection()
self.destroy()
# added function to be called like a dialog
def show(self):
self.wait_visibility()
self.grab_set()
self.master.wait_window(self)
# return the selected choices
return self.choice
Then you can use dialogWin as a modal dialog inside MainWin:
class MainWin(tk.Tk):
def __init__(self):
super().__init__()
# ... omitted code
def dia(self):
'''
This creates a toplevel window that displays the listbox.
'''
choice = dialogWin(self).show()
print(choice)

Create background text in Entry Tkinter [duplicate]

This question already has answers here:
How to add placeholder to an Entry in tkinter?
(10 answers)
Closed 2 years ago.
Can you help me to display a text in my entry, which disappears after clicking on the entry?
For this I have the following example in the entry with the text "Search". I would like to have exactly this text displayed in my entry.
Thanks a lot!
What your looking for is called placeholders. Unfortunately there is no default option with tkinter on placeholders, but a simple placeholder would look like:
import tkinter as tk
root = tk.Tk()
placeholder = 'Your text here'
def erase(event=None):
if e.get() == placeholder:
e.delete(0,'end')
def add(event=None):
if e.get() == '':
e.insert(0,placeholder)
e = tk.Entry(root)
e.pack(padx=10,pady=10)
dummy = tk.Entry(root) #dummy widget just to see other widget lose focus
dummy.pack(padx=10,pady=10)
add()
e.bind('<FocusIn>',erase)
e.bind('<FocusOut>',add)
root.mainloop()
But this code has many downfalls as the entry methods wont work properly if your looking to manipulate with more data, so what I prefer to do is to make a class and use it, instead of using the default Entry class.
This is a code that I had made to work for similar situations, I dont make any claim that this is any how perfect, but this could get your job done.
import tkinter as tk
from tkinter import ttk as ttk
class PlaceholderEntry(ttk.Entry):
'''
Custom modern Placeholder Entry box, takes positional argument master and placeholder along with\n
textcolor(default being black) and placeholdercolor(default being grey).\n
Use acquire() for getting output from entry widget\n
Use shove() for inserting into entry widget\n
Use remove() for deleting from entry widget\n
Use length() for getting the length of text in the widget\n
BUG 1: Possible bugs with binding to this class\n
BUG 2: Anomalous behaviour with config or configure method
'''
def __init__(self, master, placeholder,textcolor='black',placeholdercolor='grey', **kwargs):
self.text = placeholder
self.__has_placeholder = False # placeholder flag
self.placeholdercolor = placeholdercolor
self.textcolor = textcolor
# style for ttk widget
self.s = ttk.Style()
# init entry box
ttk.Entry.__init__(self, master, style='my.TEntry', **kwargs)
self.s.configure('my.TEntry',forground=self.placeholdercolor)
# add placeholder if box empty
self._add()
# bindings of the widget
self.bind('<FocusIn>', self._clear)
self.bind('<FocusOut>', self._add)
self.bind_all('<Key>', self._normal)
self.bind_all('<Button-1>', self._cursor)
def _clear(self, *args): # method to remove the placeholder
if self.get() == self.text and self.__has_placeholder: # remove placeholder when focus gain
self.delete(0, tk.END)
self.s.configure('my.TEntry', foreground='black',
font=(0, 0, 'normal'))
self.__has_placeholder = False #set flag to false
def _add(self, *args): # method to add placeholder
if self.get() == '' and not self.__has_placeholder: # if no text add placeholder
self.s.configure('my.TEntry', foreground=self.placeholdercolor,
font=(0, 0, 'bold'))
self.insert(0, self.text) # insert placeholder
self.icursor(0) # move insertion cursor to start of entrybox
self.__has_placeholder = True #set flag to true
def _normal(self, *args): #method to set the text to normal properties
self._add() # if empty add placeholder
if self.get() == self.text and self.__has_placeholder: # clear the placeholder if starts typing
self.bind('<Key>', self._clear)
self.icursor(-1) # keep insertion cursor to the end
else:
self.s.configure('my.TEntry', foreground=self.textcolor,
font=(0, 0, 'normal')) # set normal font
def acquire(self):
"""Custom method to get the text"""
if self.get() == self.text and self.__has_placeholder:
return 'None'
else:
return self.get()
def shove(self, index, string):
"""Custom method to insert text into entry"""
self._clear()
self.insert(index, string)
def remove(self, first, last):
"""Custom method to remove text from entry"""
if self.get() != self.text:
self.delete(first, last)
self._add()
elif self.acquire() == self.text and not self.__has_placeholder:
self.delete(first, last)
self._add()
def length(self):
"""Custom method to get the length of text in the entry widget"""
if self.get() == self.text and self.__has_placeholder:
return 0
else:
return len(self.get())
def _cursor(self, *args): # method to not allow user to move cursor when placeholder exists
if self.get() == self.text and self.__has_placeholder:
self.icursor(0)
#usage
if __name__ == '__main__':
root = tk.Tk()
e = PlaceholderEntry(root,placeholder='Your text')
e.pack(padx=10,pady=10)
dummy = tk.Entry(root) #dummy widget just to see other widget lose focus
dummy.pack(padx=10,pady=10)
root.mainloop()
Though I recommend you use the latter example once you understand whats going on behind the hood.
PS:- The example with class is faulty when making more than one instance of the class.

Load information into tkinter by checking Entry box

Is there any way to get a tkinter widget to update after an input into an Entry widget is completed?
https://i.stack.imgur.com/egSX6.png
The original Elo program was done with a Form in Access. When the player entries are filled, the Label/Entries denoted by the $ would search through the database and display information.
Is there some way of having the Label update while the GUI is running? A trigger for it could be when character count in the Entry field is 3 characters. I don't know how/if it's possible to make a Label/Entry update after the GUI is already running.
Edit:
def update_winner():
cursor = conn.cursor()
winner = winner_id.get()
school = school_name.get()
temp = school+winner
if len(temp) == 5:
cursor.execute("SELECT Rating FROM KIDS WHERE LocalID = ?", temp)
rating=cursor.fetchval()
cursor.execute("SELECT FirstName FROM KIDS WHERE LocalID = ?", temp)
name=cursor.fetchval()
winner_name.set(name)
loser_id.trace("w",update_loser)
winner_id.trace("w",update_winner)
ratings.mainloop()
When I run the code like this, as soon as I enter text into the winner_id box I get this error: TypeError: update_winner() takes 0 positional arguments but 3 were given
You can associate an instance of StringVar to the entry widget and then put a trace on the variable. The trace will be called whenever the variable value changes, and the value changes whenever the user types into the entry widget.
In the function that is called, you can change the value that is displayed in a label with the configure method.
Here's a brief example. In this example, when you type into the entry widget, the label will be updated to display what is entered.
import tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.v1 = tk.StringVar()
self.e1 = tk.Entry(self, textvariable=self.v1)
self.l1 = tk.Label(self)
self.e1.pack(side="top", fill="x")
self.l1.pack(side="top", fill="x")
self.v1.trace("w", self.on_change)
def on_change(self, *args):
self.l1.configure(text="You entered: '%s'" % self.v1.get())
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
You can of course do anything you want in the variable trace, such as look up values in a database.

Display text when cursor is placed over button in python

How to display text in the window when the cursor is placed over the button.
I have below code, when put cursor over the button "Ok" it has to show text as "Check details filled before pressing Ok".
import Tkinter
class Example(Tkinter.Frame):
def __init__(self, *args, **kwargs):
Tkinter.Frame.__init__(self, *args, **kwargs)
self.l1 = Tkinter.Label(self, text="Enter name")
self.l2 = Tkinter.Label(self, text="", width=40)
self.l1.pack(side="top")
self.l2.pack(side="top", fill="x")
self.b1 = Tkinter.Button(root, text="Ok")
self.b1.bind("<Enter>", self.on_enter)
self.b1.bind("<Leave>", self.on_leave)
self.b1.pack()
def on_enter(self, event):
self.l2.configure(text="Check details filled before pressing Ok")
def on_leave(self, enter):
self.l2.configure(text="")
if __name__ == "__main__":
root = Tkinter.Tk()
Example(root).pack(side="top", fill="both", expand="true")
root.mainloop()
The same code works fine if I write for display text when cursor is placed over label l1. Is there any other way to proceed for displaying when cursor placed on button or any modifications??
If I understand the question correctly, you want to be able to display different messages when the mouse is over different widgets. The simplest solution is to add a custom attribute to each widget object that contains the message, and then get that message from the attribute in the bound function.
For example:
class Example(...):
def __init__(...):
...
self.l1.description = "This is label 1"
self.l2.description = "This is label 2"
self.b1.description = "This is the OK button"
for widget in (self.l1, self.l2, self.b1):
widget.bind("<Enter>", self.on_enter)
widget.bind("<Leave>", self.on_leave)
...
def on_enter(self, event):
description = getattr(event.widget, "description", "")
self.l2.configure(text=description)
As far as I know, Tk doesn't have a built in construct for button ToolTips
In Qt (PyQt), which is another GUI framework, this a a built in feature - for example:
button1 = QtGui.QPushButton("This is button1", self)
button1.setToolTip("You have moused over Button1)
There are some workarounds for adding this type of functionality to Tk but it may take you some time to directly implement them into your program
Essentially you create your own class ToolTip() and a function in your module to add a new ToolTip, addToolTip()
Here are two references for doing this:
ref 1
ref 2
Edit: Note that ref1 here is the same link that is the accepted answer in the question Martineau links to in the comments for this question.

TypeError when re-configuring a text widget using information from another widgets entry field

I have a Tkinter GUI that is composed of two widgets, the first is a widget class with an entry field and a button and the second is a scrollable text widget class. I have combined these two widget classes to make a single GUI. Each of these widget classes works correctly as individuals.
The text field of the GUI is being used to display the contents of a specific index of a list. I want to be able to enter an index number in the entry field and upon pressing the button the text in the text field is re-configured to show the contents of the list for the specified index. However when I press the button I get the following error message:
" File "/Users/BioASys/BioasysDB/testgui.py", line 25, in
fetch_update_text
article_review_widget_assembled.update_text(article_index) TypeError:
unbound method update_text() must be called with
article_review_widget_assembled instance as first argument (got int
instance instead)"
When I initialize (mainloop) the GUI I have the text widget set to display the 0 index of the list.
I have written simplified code that exhibits my problem. My original code is too complex to sensibly post here. Because of the layout of all the individual widgets in my original code it was necessary to combine multiple widget classes to achieve my desired widget layout.
If anybody has any thoughts on how to get these two widgets to communicate I would greatly appreciate the assistance.
Here is the simplified code I have written which exhibits the issue I am having.
from Tkinter import *
# This list contains the sample text
# that is to be placed in the individual text widgets,
# referenced by index
text_list = [['text0AAAAAAA'], ['text1AAAAAAA'], ['text2AAAAAAA']]
# This class creates the widget that will be designated top_left
# in the article_review_widget_assembled widget
class article_review_entry_button_widget(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.pack(expand=YES, fill=BOTH)
self.index = 2
self.makeWidgets()
def handleList(self, event):
index = self.listbox.curselection()
label = self.listbox.get(index)
self.runCommand(label)
def fetch(self):
# This def gets the value placed in the entry field
print 'Input => "%s"' % self.ent.get()
article_index = self.ent.get()
def fetch_update_text(self):
# This def gets the value placed in the entry field
# and also attempts to access the update_text def
# to update the text in the top_right widget
print 'Input => "%s"' % self.ent.get()
article_index = int(self.ent.get())
# It is this line that is generating the TypeError
# It is this line that is generating the TypeError
article_review_widget_assembled.update_text(article_index)
def makeWidgets(self):
self.ent = Entry(self)
btn = Button(self, text='Next Article', command=self.fetch_update_text)
self.ent.insert(0, 0)
self.ent.pack(side=TOP, fill=BOTH)
self.ent.focus()
self.ent.bind('<Return>', (lambda event: self.fetch()))
value = self.ent.get()
btn.pack(side=TOP)
class ScrolledText(Frame):
# This class creates a text widget that can be scrolled,
# I use this as the basis for the other text widgets in this GUI
def __init__(self, parent=None, text='', file=None,
width='', height=''):
Frame.__init__(self, parent)
self.pack(expand=YES, fill=BOTH)
self.width = width
self.height = height
self.makewidgets()
self.settext(text, file)
def makewidgets(self):
sbar = Scrollbar(self)
text = Text(self, relief=SUNKEN, width=self.width, height=self.height)
sbar.config(command=text.yview)
text.config(yscrollcommand=sbar.set)
sbar.pack(side=RIGHT, fill=Y)last
text.pack(side=LEFT, expand=YES, fill=BOTH)
self.text = text
def settext(self, text='', file=None):
self.text.delete('1.0', END)
self.text.insert('1.0', text)
self.text.mark_set(INSERT, '1.0')
self.text.focus()
def gettext(self)
return self.text.get('1.0', END+'-1c')
class article_review_widget_assembled(Frame):
# This class uses the previous classes
# to create the final assemnbeld GUI widget
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.pack(expand=YES, fill=BOTH)
self.text = text_list[0]
self.makeWidgets()
# This is the def that is called by the fetch_update_text def
# in the article_review_entry_button_widget class
def update_text(index):
self.top_right.configure(text=text_list[index])
print "The index is:", index
def makeWidgets(self):
self.top_left = article_review_entry_button_widget(self).pack(side=LEFT)
self.top_right = ScrolledText(self, text= self.text, width=50, height=15).pack(side=LEFT)
if __name__ == '__main__':
#article_review_entry_button_widget().mainloop()
#ScrolledTextComposite().mainloop()
article_review_widget_assembled().mainloop()
Your problem is that you created a class (article_review_widget_assembled), created an instance of that class (article_review_widget_assembled()), but then tried to access a method by treating it like a static method (article_review_widget_assembled.update_text(article_index)).
The proper way to call update_text as you have defined it is via an instance of that class. That is what the message TypeError: unbound method update_text() must be called with article_review_widget_assembled instance as first argument (got int instance instead) is telling you. "unbound method" means you're calling a method designed to be "bound" to an instance, but you're not calling it via an instance.
The solution is to call it via an instance. Fortunately, in your case you're passing the reference to article_review_entry_button_widget as the parent parameter, so it looks like you can call it like self. parent.update_text() if you also set self.parent to the value of the parent parameter:
class article_review_entry_button_widget(Frame):
def __init__(self, parent=None):
self.parent = parent
Frame.__init__(self, parent)
...
def fetch_update_text(self):
...
self.parent.update_text(article_index)
Bottom line: when you get an "unbound method" error, it means you're calling a method via a class rather than via an instance of the class. These errors are a bit easier to spot if you adhere to the convention of starting class names with uppercase characters and instances with lowercase. This makes your code much easier to read and understand (eg: Article_review_entry_button, etc)

Categories