How to implement listener using tkinter? - python

I am trying to develop a dialog box to be used with tkinter that has an entry box and a button. I want to be able to enter a value in the entry box and have the entered value "returned" when the dialog is destroyed. The following code works, but does not perform as I've described. There are two buttons on the gui. The first launches the dialog box and the second retrieves the entered value. I wish to eliminate the second button and have a listener activate the getValues method when the "Save Input" button in the dialog is pressed. Here is the code:
from tkinter import *
class myDialog():
def __init__(self):
self.t = Toplevel()
self.t.title("Sample")
self.answer = None
self.v1 = StringVar()
self.e1 = Entry(self.t, textvariable=self.v1)
self.e1.focus()
self.e1.pack()
self.saveButton = Button(self.t, text="Save Input", command=self.buttonPressed)
self.saveButton.pack()
def buttonPressed(self):
print("Popup Button Pressed")
self.answer = self.e1.get()
self.t.destroy()
class myTk(Tk):
def __init__(self):
Tk.__init__(self)
self.button = Button(text="Display Dialog", command = self.displayPopButton)
self.button.pack()
self.getButton = Button(text="Print Values", command=self.getValues)
self.getButton.pack()
def displayPopButton(self):
self.b1 = myDialog()
def getValues(self):
print(self.b1.answer)
myTk().mainloop()

You could be passing in your main object as a param in your Dialog, and call the master method within the buttonPressed method:
class myDialog():
def __init__(self, master):
self.t = Toplevel()
self.master = master
# ... #
def buttonPressed(self):
print("Popup Button Pressed")
self.answer = self.e1.get()
self.master.getValues()
self.t.destroy()
class myTk(Tk):
# ... #
def displayPopButton(self):
self.b1 = myDialog(self)
This achieves what you want, but personally I don't think it's good OOP. It makes your Dialog reliant on having the expected master type and the required method. You could organize it a little differently to be more explicit::
class myDialog():
def __init__(self, func_to_call):
self.t = Toplevel()
self.btn_func = func_to_call
# ... #
def buttonPressed(self):
print("Popup Button Pressed")
self.answer = self.e1.get()
func_to_call()
self.t.destroy()
class myTk(Tk):
# ... #
def displayPopButton(self):
self.b1 = myDialog(self.getValues)
In any case, I would at least subclass myDialog as a Toplevel. And perhaps rethink how I want the objects to refer to each other.

You can make myDialog a modal dialog using grab_set() and wait_window():
from tkinter import *
class myDialog():
def __init__(self):
self.t = Toplevel()
self.t.title("Sample")
self.answer = None
self.v1 = StringVar()
self.e1 = Entry(self.t, textvariable=self.v1)
self.e1.focus()
self.e1.pack()
self.saveButton = Button(self.t, text="Save Input", command=self.buttonPressed)
self.saveButton.pack()
# handle user clicking the 'x' button at the title bar
self.t.protocol('WM_DELETE_WINDOW', self.buttonPressed)
def buttonPressed(self):
print("Popup Button Pressed")
self.answer = self.e1.get()
self.t.destroy()
def show(self):
# make the toplevel act like a modal dialog
self.t.grab_set()
self.t.wait_window(self.t)
# return the answer when the toplevel is closed/destroyed
return self.answer
class myTk(Tk):
def __init__(self):
Tk.__init__(self)
self.button = Button(text="Display Dialog", command = self.displayPopButton)
self.button.pack()
def displayPopButton(self):
self.b1 = myDialog().show()
print(self.b1)
myTk().mainloop()

Related

how to use tkinter button as a method [duplicate]

This question already has answers here:
Why is my Button's command executed immediately when I create the Button, and not when I click it? [duplicate]
(5 answers)
Closed 5 months ago.
I want to use a tkinter button as a method but the function the button is meant to call runs immediately after starting the program and does not run subsequently.
import tkinter as tk
class Button():
def __init__(self,window):
self.window = window
def add_button(self, func):
tk.Button(self.window, text='print', command=func).pack()
def do_something(the_thing):
print(f'{the_thing}')
return
root = tk.Tk()
button_o = Button(root)
button_o.add_button(do_something(the_thing='ook'))
root.mainloop()
You can use lambda function here
import tkinter as tk
class Button():
def __init__(self,window):
self.window = window
def add_button(self, func):
tk.Button(self.window, text='print', command=func).pack()
def do_something(the_thing):
print(f'{the_thing}')
return
root = tk.Tk()
button_o = Button(root)
button_o.add_button(lambda:do_something(the_thing='ook'))
root.mainloop()
just realized i could decorate the function.
import tkinter as tk
class Button():
def __init__(self,window):
self.window = window
def add_button(self, func):
tk.Button(self.window, text='print', command=func).pack()
def decorator(the_thing):
def do_something():
print(f'{the_thing}')
return do_something
root = tk.Tk()
button_o = Button(root)
button_o.add_button(decorator(the_thing='ook'))
root.mainloop()

Resizing ttk.frame of a ttk notebook, frame defined in a other class than the notebook

I have a main class for my gui and I added a ttk.NoteBook after a label:
class MainApplication(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('1000x500')
self.configure(background='#F0F8FF')
#TOP LABEL
load = Image.open("my_image")
load = load.resize((200, 67), Image.ANTIALIAS)
self.render = ImageTk.PhotoImage(load)
self.Label_top = tk.Label(self, image=self.render, compound=tk.LEFT, text="TOOL")
self.Label_top.pack()
#--Notebook---------
self.notebook = ttk.Notebook(self)
self.Page1 = Page1(self.notebook)
self.Page2 = Page2(self.notebook)
self.Page3 = Page3(self.notebook)
self.Page4 = Page4(self.notebook)
self.notebook.add(self.Page1, text='PAGE1')
self.notebook.add(self.Page2, text='PAGE2')
self.notebook.add(self.Page3, text='PAGE3')
self.notebook.add(self.Page4, text='PAGE4')
self.notebook.pack(fill='x', side=TOP)
#expand=True create empty space between my top label and my notebook, even with side=TOP
And I defined each frame in a class like this :
class Page1(ttk.Frame):
def __init__(self, container):
super().__init__()
self.(width=400, height=280) #Error message
self.pack(expand=True) #Doesn't work
Do you know how can I expand my frame for that it fills my page and pack the notebook just after my top label
I think this will do what you want. I've incorporated most of the things #Bryan Oakley mentioned in his answer except I also added a BasePage class and derived all the other Page classes from it. This was done to provide a place to put code that would otherwise need to be repeated each of the subclasses.
I also changed some of your variable names to conform to PEP 8 Naming Conventions.
import tkinter as tk
import tkinter.ttk as ttk
from tkinter.constants import *
class BasePage(ttk.Frame):
def __init__(self, container):
super().__init__(container, width=400, height=280)
classname = type(self).__name__
tk.Label(self, text=f'Welcome to {classname}').place(relx=0.5, rely=0.25,
anchor=CENTER)
class Page1(BasePage):
def __init__(self, container):
super().__init__(container)
class Page2(BasePage):
def __init__(self, container):
super().__init__(container)
class Page3(BasePage):
def __init__(self, container):
super().__init__(container)
class Page4(BasePage):
def __init__(self, container):
super().__init__(container)
class MainApplication(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('1000x500')
self.configure(background='#F0F8FF')
#--Notebook---------
self.notebook = ttk.Notebook(self)
self.page1 = Page1(self.notebook)
self.page2 = Page2(self.notebook)
self.page3 = Page3(self.notebook)
self.page4 = Page4(self.notebook)
self.notebook.add(self.page1, text='Page1')
self.notebook.add(self.page2, text='Page2')
self.notebook.add(self.page3, text='Page3')
self.notebook.add(self.page4, text='Page4')
self.notebook.pack(expand=True, fill=BOTH)
app = MainApplication()
app.mainloop()
I see three problems.
First, each "page" needs to be a child of the notebook. You do that by making sure the notebook is passed to the __init__ of the frame:
class Page1(ttk.Frame):
def __init__(self, container):
super().__init__(container)
Second, you need to not call pack on the page. self.notebook.add is already adding the frame to the notebook. So, remove the line self.pack(expand=True) from each page.
Third, self.(width=400, height=280) needs to be self.configure(width=400, height=280)

Make a Tkinter Gui using classes

I want to create a GUI in a class that can create pages using another class that creates frames and widgets. The pages are set using a class for them. I want to be able to GUI to be able to switch between the different set of pages. I can't create a button for the class of the Login_page that with switch the Login_page with the class of the Sign_page.
from tkinter import *
import random
class maingui:
def __init__(self,title, geometry,):
self.root = Tk()
self.root.title(title)
self.root.geometry(geometry)
self.pageshow = Login_Page(self.root)
self.root.mainloop()
def changepage(self, page):
self.page = page
if self.page == 0:
self.pageshow = Login_Page(self.root)
if self.page == 1:
self.pageshow = Sign_Page(self.root)
self.root.mainloop()
class createWindow:
def __init__(self,root, title, geometry,):
self.root = root
self.root.title(title)
self.root.geometry(geometry)
self.root.mainloop()
class createFrame:
def __init__(self,window):
self.window = window
self.frame = Frame(self.window)
self.frame.pack()
print('c')
def clear(self):
pass
def createlabel(self,message,postion = None):
self.message =message
self.postion = postion
self.label= Label(self.frame, text =self.message)
if self.postion == None:
self.label.pack()
print('a')
else:
print('b')
def createbutton(self, text, command):
self.text = text
self.command = command
self.button = Button(self.frame, text = self.text, command =self.command)
class Login_Page():
def __init__(self,window):
self.window = window
self.frame = createFrame(self.window)
self.frame.createlabel("Hello World")
self.frame.createbutton("1",maingui.changepage(self.window,1))
class Sign_Page():
def __init__(self,window):
self.window = window
self.frame = createFrame(self.window)
self.frame.createlabel("Hello ")
maingui = maingui("Rpg", "400x400")
Edit: Exception has occurred: AttributeError
'_tkinter.tkapp' object has no attribute 'root'
On line
if self.page == 1:
self.pageshow = Sign_Page(self.root)
On this line
self.frame.createbutton("1",maingui.changepage(self.window,1))
I try to create a button using a class and the command is from a different class.
Since you pass self.root (instance of Tk()) of maingui class to Login_Page and assign it to self.window. Then self.window is used in maingui.changepage(self.window, 1) inside Login_Page class. Since you use class name to access changepage(), self.window will be treat as the self argument of changepage() and used in Sign_Page(self.root). That means you want to access the attribute root of self (instance of Tk()) but Tk does not have attribute root.
Suggest to make maingui interit from Tk instead of creating it inside __init__().
Below is an updated example based on yours:
from tkinter import *
#import random
class maingui(Tk):
def __init__(self, title, geometry):
super().__init__()
self.title(title)
self.geometry(geometry)
self.pageshow = Login_Page(self)
def changepage(self, page):
self.page = page
if self.page == 0:
self.pageshow = Login_Page(self)
if self.page == 1:
self.pageshow = Sign_Page(self)
class createFrame:
def __init__(self,window):
self.window = window
self.frame = Frame(self.window)
self.frame.pack()
print('c')
def clear(self):
pass
def createlabel(self,message,postion = None):
self.message =message
self.postion = postion
self.label= Label(self.frame, text =self.message)
if self.postion == None:
self.label.pack()
print('a')
else:
print('b')
def createbutton(self, text, command):
self.text = text
self.command = command
self.button = Button(self.frame, text = self.text, command =self.command)
self.button.pack() ###
class Login_Page():
def __init__(self,window):
self.window = window
self.frame = createFrame(self.window)
self.frame.createlabel("Hello World")
###self.frame.createbutton("1", maingui.changepage(self.window, 1)) ###
self.frame.createbutton("1", lambda: self.window.changepage(1)) ###
class Sign_Page():
def __init__(self,window):
self.window = window
self.frame = createFrame(self.window)
self.frame.createlabel("Hello ")
maingui = maingui("Rpg", "400x400")
maingui.mainloop()
Here is a working code, I would also suggest you read and practice classes before moving further.
(Note: I have not defined your clear method or added any new method, I have just corrected your existing code and rectified your error. you may further if you want, define your clear method and destroy your widgets).
from tkinter import *
import random
class maingui:
def __init__(self, root, title, geometry):
self.root = root
self.root.title(title)
self.root.geometry(geometry)
self.pageshow = Login_Page(self, self.root)
def changepage(self, page):
self.page = page
if self.page == 0:
self.pageshow = Login_Page(self, self.root)
if self.page == 1:
self.pageshow = Sign_Page(self, self.root)
class createWindow:
def __init__(self,root, title, geometry):
self.root = root
self.root.title(title)
self.root.geometry(geometry)
class createFrame:
def __init__(self, parent, window):
self.window = window
self.frame = Frame(self.window)
self.frame.pack()
self.label = Label(self.frame)
self.button = Button(self.frame)
self.parent = parent
def clear(self):
pass
def createlabel(self,message,postion = None):
self.message =message
self.postion = postion
self.label.config(text=self.message)
if self.postion == None:
self.label.pack()
else:
pass
def createbutton(self, text='Login', cmd=0):
self.text = text
self.button.configure(text=self.text, command=lambda :self.parent.changepage(cmd))
self.button.pack()
class Login_Page():
def __init__(self, parent, window):
self.window = window
self.frame = createFrame(parent, self.window)
self.frame.createlabel("Hello World bye")
self.frame.createbutton("Welcome", 1)
class Sign_Page():
def __init__(self, parent, window):
self.window = window
self.frame = createFrame(parent, self.window)
self.frame.createlabel("Hello ")
def main():
root = Tk()
maingui(root, "Rpg", "400x400")
root.mainloop()
if __name__ =='__main__':
main()
update: here is a way you can switch between pages(in my case you can switch between the sign-up and login page). click on login it will direct you to sign-up and vice-versa.
from tkinter import *
import random
class maingui:
def __init__(self, root, title, geometry):
self.root = root
self.root.title(title)
self.root.geometry(geometry)
self.pageshow = Login_Page(self, self.root)
def changepage(self, page):
self.page = page
if self.page == 0:
#del self.pageshow
self.pageshow = Login_Page(self, self.root)
if self.page == 1:
#del self.pageshow
self.pageshow = Sign_Page(self, self.root)
class Login_Page:
def __init__(self, parent, window):
self.parent = parent
self.frame = Frame(window)
self.frame.pack()
self.welcm_lbl = Label(self.frame, text='welcome')
self.welcm_lbl.grid(row=0, column=1)
self.name_lbl = Label(self.frame, text='name:')
self.name_lbl.grid(row=1, column=0)
self.name_entry = Entry(self.frame)
self.name_entry.grid(row=1, column=1)
self.sbt = Button(self.frame, text='login', command=self.clicked)
self.sbt.grid(row=2, column=1)
def clicked(self):
self.frame.destroy()
self.parent.changepage(1)
class Sign_Page():
def __init__(self, parent, window):
self.parent = parent
self.frame = Frame(window)
self.frame.pack()
self.welcm_lbl = Label(self.frame, text='welcome sign-up')
self.welcm_lbl.grid(row=0, column=1)
self.name_lbl = Label(self.frame, text='name:')
self.name_lbl.grid(row=1, column=0)
self.name_entry = Entry(self.frame)
self.name_entry.grid(row=1, column=1)
self.sbt = Button(self.frame, text='sign-up', command=self.clicked)
self.sbt.grid(row=2, column=1)
def clicked(self):
self.frame.destroy()
self.parent.changepage(0)
def main():
root = Tk()
maingui(root, "Rpg", "400x400")
root.mainloop()
if __name__ =='__main__':
main()

Clear/ delete redirected text in tkinter text widget

as the title says, I have a text widget where I redirect print() to.
To do so I use
class TextRedirector(object):
def write(self, str):
main_text.insert(1.0, str)
and then
sys.stdout = TextRedirector()
What I cannot do is to clear the text before I insert the new one.
To do this I add main_text.delete(1.0, "end") as it follows
class TextRedirector(object):
def write(self, str):
main_text.delete(1.0, "end")
main_text.insert(1.0, str)
This results in nothing printed at all.
I also tried to just print a text in the widget like so
text = "my test is here"
class TextRedirector(object):
def write(self, str):
main_text.delete(1.0, "end")
main_text.insert(1.0, text)
And it works just fine. It clears whatever I type in (as I keep it state="normal") and prints my text.
Any help will be much appreciated!
EDIT:
Here is the code from this post that you can use as an example.
import tkinter as tk
import sys
class ExampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
toolbar = tk.Frame(self)
toolbar.pack(side="top", fill="x")
b1 = tk.Button(self, text="print to stdout", command=self.print_stdout)
b1.pack(in_=toolbar, side="left")
self.text = tk.Text(self, wrap="word")
self.text.pack(side="top", fill="both", expand=True)
sys.stdout = TextRedirector(self.text)
def print_stdout(self):
print("This is my text")
class TextRedirector(object):
def __init__(self, widget, tag="data"):
self.widget = widget
self.tag = tag
def write(self, str):
self.widget.configure(state="normal")
#self.widget.delete(1.0, "end")
self.widget.insert("end", str, (self.tag,))
app = ExampleApp()
app.mainloop()
Same as in my code, if you remove the # from def write(self, str) function, it will not print the text.
What I want is to redirect print() to the text widget but every time to be in a clear textbox and not as a new line.
That means, If i press the b1 button from the above code 4 times I will have this in the textbox
This is my text
and not this
This is my text
This is my text
This is my text
This is my text
Use the default end=\n wich print ist using to set a flag when to delete.
Live-Demo: reply.it
import tkinter as tk
import sys, threading
class ExampleApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
toolbar = tk.Frame(self)
toolbar.pack(side="top", fill="x")
b1 = tk.Button(self, text="print to stdout", height=5, command=self.print_stdout)
b1.pack(in_=toolbar, side="left")
self.text = tk.Text(self, wrap="word")
self.text.pack(side="top", fill="both", expand=True)
self._count = 1
self._stdout = sys.stdout
sys.stdout = TextRedirector(self.text)
def print_stdout(self):
print("This my text {}".format(self._count))
self._count += 1
class TextRedirector(object):
def __init__(self, widget, tag="data"):
self.widget = widget
self.tag = tag
self.eof = False
def write(self, str):
if self.eof:
self.eof = False
self.widget.delete(1.0, "end")
if str == "\n":
str += " thread_ident: {}".format(threading.get_ident())
self.eof = True
self.widget.insert("end", str, (self.tag,))
app = ExampleApp()
app.mainloop()

Tkinter passing callback function result to another class

I wonder how to pass return value from one class to another class in tkinter.
In my program I have DataChosenForm class where I want to choose option in Combobox and pass this result to another class ReturnData to set a variable in a Label.
import tkinter as tk
from tkinter import ttk
class DataChosenForm(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
chosen = tk.LabelFrame(self, text="wybór")
chosen.grid(row=0)
self.combo = ttk.Combobox(chosen)
self.combo['values'] = ('wizz', 'ryanair', 'lot')
self.combo.grid(row=0, column=2, padx=80, pady=10)
self.combo.bind("<<ComboboxSelected>>", self.callback)
def callback(self, event=None):
if event.widget.get() == 'wizz':
print('wizz')
return 'wizz'
elif event.widget.get() == 'ryanair':
print('ryanair')
return 'ryanair'
elif event.widget.get() == 'lot':
print('lot')
return 'lot'
class ReturnData(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
var = tk.StringVar()
message_box = tk.LabelFrame(self, text="wynik")
message_box.grid(row=1)
mb = tk.Label(message_box, textvariable=var,anchor='nw')
mb.pack(padx=120, pady=30)
class Application(tk.Tk):
def __init__(self):
super().__init__()
self.title("program do wyszukiwania cen lotów")
self.geometry('300x200')
self.resizable(width=False, height=False)
DataChosenForm(self).grid(row=0, column=0)
ReturnData(self).grid(row=1)
if __name__ == "__main__":
app = Application()
app.mainloop()
You could first display the combobox DataChosenForm(self).grid(row=0, column=0) without calling the ReturnData in the Application class.
Then, in the callback() method collect the choice choice = event.widget.get() and pass it to ReturnData. This would mean, however, that the LabelFrame is displayed only after a choice is made.
import tkinter as tk
from tkinter import ttk
class DataChosenForm(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
chosen = tk.LabelFrame(self, text="wybór")
chosen.grid(row=0)
self.combo = ttk.Combobox(chosen)
self.combo['values'] = ('wizz', 'ryanair', 'lot')
self.combo.grid(row=0, column=2, padx=80, pady=10)
self.combo.bind("<<ComboboxSelected>>", self.callback)
def callback(self, event=None):
choice = event.widget.get()
print(choice)
ReturnData(self, choice).grid(row=1)
class ReturnData(tk.Frame):
def __init__(self, parent, choice):
super().__init__(parent)
message_box = tk.LabelFrame(self, text="wynik")
message_box.grid(row=1)
mb = tk.Label(message_box, text=choice, anchor='nw')
mb.pack(padx=120, pady=30)
class Application(tk.Tk):
def __init__(self):
super().__init__()
self.title("program do wyszukiwania cen lotów")
self.geometry('300x200')
self.resizable(width=False, height=False)
DataChosenForm(self).grid(row=0, column=0)
if __name__ == "__main__":
app = Application()
app.mainloop()
I think #Julia has the basically the right architecture for your tkinter application, but her answer could be improved by using a tkinter Variable — because all widgets associated with one will automatically update their displayed value whenever the Variable is changed (by one of them or something else).
Here's a little documentation on Variable Classes. Note that since ttk.Combobox with have all the methods of a tk.Entry widgets, here's a bit of documentation about them (which also happens to illustrate the "pattern" of using of a StringVar in conjunction with one so it also applies to a ttk.Comboboxs).
Generally, you can tell a widget to use a tkinter Variable by specifying an instance of one as the option textvariable= keyword argument when the widget is created. You can also set the option using the partial dictionary interface most widgets support, so assignments like widget['textvariable'] = variable are another way to make use of them — the code below makes use of both of these ways.
Here's Julia's code modified to use a tk.StringVar. Note the Combobox doesn't need a callback function to bind the <<ComboboxSelected>> event to it, so all that complexity has been eliminated.
import tkinter as tk
from tkinter import ttk
class DataChosenForm(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
choice = tk.LabelFrame(self, text="wybór")
choice.grid(row=0)
self.combo = ttk.Combobox(choice)
self.combo['textvariable'] = parent.var # Use shared variable.
self.combo['values'] = ('wizzair', 'ryanair', 'lot')
self.combo.grid(row=0, column=2, padx=80, pady=10)
class ReturnData(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
message_box = tk.LabelFrame(self, text="wynik")
message_box.grid(row=1)
mb = tk.Label(message_box, textvariable=parent.var, # Use shared variable.
anchor='nw', width=20)
mb.pack(padx=120, pady=30)
class Application(tk.Tk):
def __init__(self):
super().__init__()
self.title("program do wyszukiwania cen lotów")
self.geometry('300x200')
self.resizable(width=False, height=False)
self.var = tk.StringVar(value='Dokonać wyboru') # Create shared variable.
DataChosenForm(self).grid(row=0, column=0)
ReturnData(self).grid(row=1)
if __name__ == "__main__":
app = Application()
app.mainloop()
You can just pass other class or it's field to __init__ of the DataChosenForm, and to callback function from there, where then you can change class/field directly. Here's what I mean, but I use TreeView:
import tkinter as tk
from tkinter import ttk
class ConnectedClass:
def __init__(self):
self.received = "will be changed"
class TreeViewWrapper(ttk.Treeview):
def __init__(self, master, connected_class, **kw):
super().__init__(master, **kw)
parents = []
for i in range(10):
parent = self.insert("", "end", text="Item %s" % i, tags=str(i))
for i in range(3):
self.insert(parent, "end", text="Item %s" % i, tags=str(i))
self.bind("<Control-r>", lambda e: self.pass_to_other(e, connected_class))
def pass_to_other(self, _, connected_class):
items = self.selection()
connected_class.received = items
class App:
def __init__(self):
self.root = tk.Tk()
self.con_class = ConnectedClass()
self.tree = TreeViewWrapper(self.root,self.con_class)
self.tree.pack()
self.root.bind("<Control-p>",lambda e:print(self.con_class.received))
self.root.mainloop()
if __name__ == "__main__":
app = App()

Categories

Resources