Trouble with Tkinter event handler inside of a class - python

My problem is that I have a class that creates a Tkinter topclass object and then puts a field into it and I want to add an event handler that runs a method (that is also in the class) every time a button is pressed but when the event is called it says
AttributeError: Toplevel instance has no attribute 'updateSearch'
class EditStudentWindow():
def __init__(self):
searchResultList = ['student1', 'student2', 'student3'] # test list
##### window attributes
# create window
self = Tkinter.Toplevel()
#window title
self.title('Edit Students')
##### puts stuff into the window
# text
editStudentInfoLabel = Tkinter.Label(self,text='Select the student from the list below or search for one in the search box provided')
editStudentInfoLabel.grid(row=0, column=0)
# entry box
searchRepositoryEntry = Tkinter.Entry(self)
searchRepositoryEntry.grid(row=1, column=0)
# list box
searchResults = Tkinter.Listbox(self)
searchResults.grid(row=2, column=0)
##### event handler
right here
searchRepositoryEntry.bind('<Key>',command = self.updateSearch)
# search results
for result in searchResultList:
searchResults.insert(Tkinter.END, result)
def updateSearch(self, event):
print('foo')

Judging solely on the indentation of your example, it appears that updateSearch is indeed not part of the class definition.
Assuming the indentation is a markup mistake, and based on the error message you report, the other problem is that you redefine self, so 'self.updateSearch' points to the toplevel rather than the EditStudentWindow class. Note that the message says Toplevel instance has no attribute 'updateSearch' rather than EditStudentWindow instance...
Typically, such widgets are created with inheritance rather than composition. You might want to consider refactoring your code to look something like:
class EditStudentWindowClass(Tkinter.Toplevel):
def __init__(self, *args, **kwargs):
Tkinter.Toplevel.__init__(self, *args, **kwargs)
self.title('Edit Students')
...

Related

Usage of inherited button subclass with a click-event-method and its syntax in python tkinter

The program aims to finally become a calculator where the Entry box displays the letter/number which the button represents. There is always the brute force way of defining every button's parameters. I want a customized button class that has certain properties defined so that I don't need to redefine for every button. So I inherit the Button class to make a subclass. I decide to put the click-event-method inside the class which is shown below. But it seems to throw an error when I click the button when the main window is displayed. Can you please help me figure out what is wrong. The complete code is below.
import tkinter as tk
import tkinter.font as tkFont
root = tk.Tk()
root.title("Simple Calculator")
myFont = tkFont.Font(family='Helvetica', size=19, weight='bold')
e = tk.Entry(root, width = 20,font=myFont)
e.grid(column=0,row=0,columnspan=4)
class calc_button(tk.Button):
def __init__(self, parent, *args, **kwargs):
tk.Button.__init__(self, parent, *args, **kwargs)
self.parent = parent
self["height"]=1
self["width"]=6
self["font"]=myFont
def ClickMe(self):
print(f'The text is {self["text"]} ')
button1 = calc_button(root,text="1",command=calc_button.ClickMe)
button1.grid(row=4,column=1)
root.mainloop()
Error is as shown below :
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\subramaniantr\AppData\Local\Programs\Python\Python38-32\lib\tkinter\__init__.py", line 1883, in __call__
return self.func(*args)
TypeError: ClickMe() missing 1 required positional argument: 'self'
When you do command=calc_button.ClickMe you're calling the method on a class, not an instance of the class. You need to be creating an instance.
Since you're using a class so that you don't need to redefine the same property for every instance, you can define the command method inside the __init__, like so:
class calc_button(tk.Button):
def __init__(self, parent, *args, **kwargs):
tk.Button.__init__(self, parent, *args, **kwargs)
...
self["command"]=self.ClickMe
If you wish to define the command outside of the __init__, like you are currently trying to do, it will require two steps since you can't reference the button before it is created:
button1 = calc_button(root,text="1")
button1.configure(,command=button1.ClickMe)
Unrelated to the question being asked, your code will be easier for others to comprehend if you stick to PEP8 naming standards. For example, class names should always begin with an uppercase, and function names should always begin with a lowercase.

Destroying login page window and opening a new one

I'm trying to close a window while creating a new one. The following function runs once a login has been entered. Do you have any suggestions on how to close this window while the MenuPage(window) is opened?
from tkinter import *
class LoginPage():
def __init__(self):
window = Tk()
#UI to retrieve login details
#While loop when submit is clicked.
if UserLogins:
#Maybe here a close window function is ran
window = Toplevel()
start= MenuPage(window)
else:
print('Incorrect Login')
class MenuPage(object):
#Menu UI
if __name__ == '__main__':
LoginPage()
Hello and welcome to StackOverflow.
Your main issue is your class LoginPage.
You do initialize Tk() inside the __init__ of your class. As soon as this object is destroyed, the Tkinter Instance gets destroyed as well.
Consider using the following approach:
Create a Main Widget (e.g. based on Toplevel or Tk)
Create a LoginPage based on e.g. Frame inside your Main Widget
After login attempt, either destroy the LoginPage and create another Frame holding new data. (MenuPage)
Using this approach your Tkinter Main Instance Runs all the time, and you delete or add objects / widgets to it.
If you do not use your MainWidget (that should persist) as Tk-Instance, an "unnamed" Instance will be used for that. Alternatively use Tk() as Parent for your LoginPage, but do not embed it inside it.
Pseudo Code Example:
import tkinter as tk
class MainWidget(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__()
self.__load_widgets()
def __load_widgets(self):
self.page = LoginPage(self)
self.page.bind("&ltLoginEvent>", self.show_menu)
self.page.grid(sticky=tk.NW+tk.SE)
def show_menu(self, event=None):
self.page.destroy()
self.page=MenuPage(self)
self.page.grid(sticky=tk.NW+tk.SE)
class LoginPage(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
def raise_login_result(self):
""" some event may raise the event you can use from main widget to bind to """
# ...
raise("&ltLoginEvent>")
class MenuPage(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
if __name__ == "__main__":
app = MainWidget()
app.mainloop()
Edit
As your question expands into geometry managers, I hereby refer to one corresponding documentation on geometry managers(that is e.g. grid/pack/place). This documentation shows the corresponding interfaces.

How do you make a custom popup in tkinter using two seperate classes and inheritance?

I have tried to look for an answer to this question for the last hour and I have not found anything. (I feel like this should be on the internet).
I am trying to make a custom popup when you click a button on the parent GUI frame. I then want the user to input variables, and then have those variables be returned to the original class. I am essentially making a popup to enter options. However, I should note that not all of the options have to be entered, it just depends on the user's situation. The user can click the "options" button at any time as well, so when the popup is quit, I would like it to return to the parent GUI at the time the options button was originally hit. What I was doing is the following:
from Tkinter import *
class Home(Frame): #This class defines the main(master) GUI
def __init__(self, master):
Frame.__init__(self, master)
#Other methods in class "Home" that get, variables, etc.
def call_popup(self):
options_popup = Options(self)
#wait_window does not work here
self.master.wait_window(options_popup)
class Options(Toplevel): #This class defines the popup GUI
def __init__(self, master):
Toplevel.__init__(self, master)
self.popup = self
#Other methods in class "Options"
def get_variable(self): #Just an example methods in class "Options"
#Simplified code but lets say I now have the variable foo in self.foo, unique to class "Options"
#I want to pass this variable back to class "Home"
def exit_popup(self):
self.popup.destroy()
#I want it to return to the spot that the user was originally at
#when they hit the "options button" in the home GUI. I also want all
#values inputted in the "options" popup to return to the class "Home"
root = Tk()
Home(root)
root.mainloop()
I believe a (big) part of the problem is that I am pretty new with classes (as well as Tkinter).
I have looked at a lot of questions about tkinter, popups, inheritance with classes and wait_window and I unfortunately still can't figure it out.
I have also tried passing the class "Options" like this: def __init__(self, master, options): (line 3), yet that exceeded the number of arguments.
So, main questions:
-How do I pass variables from popup/class "Options" to class "Home"?
-How do you have the popup/class "Options" window wait until closed to resume back to the class "Home"?
Okay. I believe that I've implemented the stuff you are having problems with in your code, see below for further explanations:
from Tkinter import *
class Home(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.entryvalue = None # this will store the popop entry value
self.call_popup(self)
print("Popup closed!")
print("Entry value:" + self.entryvalue)
def call_popup(self, master):
options_popup = Options(master)
options_popup.wait_window()
class Options(Toplevel):
def __init__(self, master):
Toplevel.__init__(self, master)
self.master = master
# add an entry widget
self.e1 = Entry(self)
self.e1.pack()
# add a button
b1 = Button(self, text="Popup button", command=self.buttonpressed)
b1.pack()
def buttonpressed(self):
self.master.entryvalue = self.e1.get()
self.exit_popup()
def exit_popup(self):
self.destroy()
root = Tk()
Home(root)
root.mainloop()
For your first question. You need to provide the popup/options object with a reference to the object where you want your data to end up, in this case in the home object. As you can see I'm passing this as a new argument to the init method of the popup. I've then added a command to the button which will then set the entryvalue variable in the home object. This could also have been a function call or similiar.
For having the code wait for the window to finish, you should call the wait_window() method on the options_popup object, as this is the one you are waiting for. You could also move it to the init method of the popup window and invoke it on self instead.
For more information regarding dialog windows, you should look at this article. This dialog support class example is pretty good I think.
Hope it helps!

TKInter checkbox variable is always 0

I'm using Python's TkInter module for a GUI. Below is a simple checkbox code.
def getCheckVal():
print cbVar.get()
windowTime=Tk.Tk()
cbVar = Tk.IntVar()
btnC = Tk.Checkbutton(windowTime, text="Save", variable = cbVar, command=getCheckVal)
btnC.grid()
windowTime.mainloop()
This code works fine. Each time I tick the checkbox, I get 1, else 0.
However, when I run the same code in a function that is called from another TkInter command (when a button is pressed), it stops working. I always get 0 as the value.
class GUIMainClass:
def __init__(self):
'''Create the main window'''
self.window = Tk.Tk()
def askUser(self):
def getCheckVal():
print cbVar.get()
windowTime=Tk.Tk()
cbVar = Tk.IntVar()
btnC = Tk.Checkbutton(windowTime, text="Save", variable = cbVar,
command=getCheckVal)
btnC.grid()
windowTime.mainloop()
def cmdWindow(self):
frameShow=Tk.Frame(self.window)
frameShow.grid()
btnSwitch = Tk.Button(frameShow, text='Show Plots', command=self.askUser)
btnSwitch.grid()
self.window.mainloop()
GUIObj=GUIMainClass()
GUIObj.cmdWindow()
This is very unusual. What could be going wrong?
EDIT: I've used 2 mainloops because I want a separate window (windowTime) to open up when I click "Show Plots" button. This new window should have the checkbox in it.
Your windowTime, cbVar, etc. variables are defined in the function's local scope. When askUser() completes execution, those values are thrown away. Prepend self. to them to save them as instance variables.
There should only be one mainloop() in your program, to run the main Tkinter root object. Try putting it as the very last line in the program. I recommend doing some reading on Effbot for how to set up a Tkinter application.
I'm not sure what all you're trying to do, but one problem is that the TK.IntVar called cbVar that you create in your askUser() method will be deleted when the function returns, so you need to attach it to something that will still exist after that happens. While you could make it a global variable, a better choice would be to make it an attribute of something more persistent and has a longer "lifespan".
Another likely issue is that generally there should only be one call to mainloop() in a single Tkinter application. It appears what you want to do is display what is commonly known as a Dialog Window, which Tkinter also supports. There's some standard ones built-in, plus some more generic classes to simplify creating custom ones. Here's some documentation I found which describes them in some detail. You may also find it helpful to look at their source code.
In Python 2 it's in the /Lib/lib-tk/tkSimpleDialog.py file and
in Python 3 the code's in a file named /Lib/tkinter/simpledialog.py.
Below is code that takes the latter approach and derives a custom dialog class named GUIButtonDialog from the generic one included the Tkinter library which is simply named Dialog.
try:
import Tkinter as Tk # Python 2
from tkSimpleDialog import Dialog
except ModuleNotFoundError:
import tkinter as Tk # Python 3
from tkinter.simpledialog import Dialog
class GUIButtonDialog(Dialog):
"""Custom one Button dialog box."""
def __init__(self, btnText, parent=None, title=None):
self.btnText = btnText
Dialog.__init__(self, parent, title)
def getCheckVal(self):
print(self.cbVar.get())
def body(self, master):
"""Create dialog body."""
self.cbVar = Tk.IntVar()
self.btnC = Tk.Checkbutton(master, text=self.btnText, variable=self.cbVar,
command=self.getCheckVal)
self.btnC.grid()
return self.btnC # Return the widget to get inital focus.
def buttonbox(self):
# Overridden to suppress default "OK" and "Cancel" buttons.
pass
class GUIMainClass:
def __init__(self):
"""Create the main window."""
self.window = Tk.Tk()
def askUser(self):
"""Display custom dialog window (until user closes it)."""
GUIButtonDialog("Save", parent=self.window)
def cmdWindow(self):
frameShow = Tk.Frame(self.window)
frameShow.grid()
btnSwitch = Tk.Button(frameShow, text='Show Plots', command=self.askUser)
btnSwitch.grid()
self.window.mainloop()
GUIObj = GUIMainClass()
GUIObj.cmdWindow()

{PYTHON + TKINTER} "Type cast" problem: How to retrieve Custom frame objects from a Pmw.NoteBook?

I am writing a GUI Python application.
I am using Tkinter + PythonMegaWidgets for semplicity reasons.
Going straight to the point, I need to extend Tkinter.Frame baseclass, by adding some custom member functions which provides extra-functionalities.
These "Custom Frames" will be added to single tabs of a Pmw.NoteBook object.
Official related docs can be found at: http://pmw.sourceforge.net/doc/NoteBook.html
Later, I need to retrieve "Custom Frame" instances from NoteBook, invoking custom member functions that I have added; here the problems begins...
despite the principle of Duck Typing, I can not access ANY of these "extra-methods" because the methods of Pmw.NoteBook class can only return Frame objects!.
I can not find any solution.
Below a piece of sample (more simplified) code which describes in detail my issue.
from Tkinter import *
from Pmw import NoteBook
# I define a custom frame, by extending Tkinter.Frame baseclass
class CustomPanel(Frame):
def __init__(self, master, _text):
Frame.__init__(self, master)
self.label = Label(self, text=_text)
self.localVariable = "Hello, world!" # I define a new local variable
self.label.pack()
self.pack()
# I define a custom member function, which I _ABSOLUTELY_ want to be accessible.
def customMethod(self):
print self.localVariable
# main frame of application
class MyFrame(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.noteBook = NoteBook(self) # I create a NoteBook object...
tab1 = self.noteBook.add("tab 1") # then, I add one (empty) tabs to it
panel1 = CustomPanel(tab1, "hello")
self.button = Button(self, text="Call CustomMethod()!", command=self.callCustomMethod) # I add a button test
self.noteBook.grid()
self.button.grid(row=1)
self.pack()
self.mainloop()
# I define click handler for button,
def callCustomMethod(self):
panel1 = self.noteBook.page(0) # I try to get frame contained in current tab
# pane11 is supposed to be a 'CustomPanel' object;
try:
panel1.customMethod() # ...then, custom method should be accessible
except AttributeError:
print 'AttributeError!'
# for illustration purpose only, I show that Panel1 is a 'Frame'(superclass) object only!
print panel1.__class__
frame = MyFrame() # create a MyFrame instance
Pressing the button, console output is:
AttributeError!
Tkinter.Frame
to anticipate objections:
1- Set panel1.class attribute, as showed below,
try:
panel1.__class__ = CustomPanel
panel1.customMethod() # ...then, custom method should be accessible
except AttributeError:
print 'AttributeError!'
DON'T work, because customMethod() could not access in any case to localVariable, which is declared in CustomPanel subclass only;
2- I can not even recall CustomPanel constructor, because this will RESET original member variables, which I want to retrieve with their original values.
Any help is appreciated.
IT
I don't think you need to change the class of the notebook tab, you can just add your custom frame inside that tab. Just tell the frame what your inner frame is, and remember to pack your inner frame inside the tab frame.
For example:
class MyFrame(Frame):
def __init__(self, master=None):
...
panel1 = CustomPanel(tab1, "hello")
panel1.pack(fill="both", expand=True)
tab1.inner_panel = panel1
...
def callCustomMethod(self):
tab1 = self.noteBook.page(0)
panel1 = tab1.inner_panel
...

Categories