Python Tkinter Event Binding - python

I am playing around with Python Tkinter, and Event Binding does not work in my script. Here is my script:
import tkinter as tk
from tkinter import ttk
def quit():
exit(0)
class ChartApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side='top', fill='both', expand=True)
menubar = tk.Menu(container)
filemenu = tk.Menu(menubar, tearoff=0)
filemenu.add_command(label="Exit", command=quit)
menubar.add_cascade(label="File", menu=filemenu)
tk.Tk.config(self, menu=menubar)
self.frames = {}
# This is not an error to have 2 StartPage. I am planning to implement multiple pages afterwards
for F in (StartPage, StartPage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky='nsew')
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
def printsomething(msg):
print(msg + " is selected!")
pg1_button = ttk.Button(self, text="Page 1", command=lambda: printsomething("Page 1"))
pg1_button.grid(row=1, column=0, pady=10, padx=10, sticky='e')
pg2_button = ttk.Button(self, text="Page 2", command=lambda: printsomething("Page 2"))
pg2_button.grid(row=1, column=1, pady=10, padx=10, sticky='w')
self.bind("a", lambda event: self.focus_set())
self.bind("a", printsomething("Page 1"))
app = ChartApp()
app.mainloop()
I want to press button "a" on my keyboard as if I click on pg1_buttion to execute the printsomething function. However, it does not work. Also, once I run my script, I have the following output automatically.
Output:
Page 1 is selected!
Page 1 is selected!
I am sure the auto output is related to these 2 lines but I don't know why
self.bind("a", lambda event: self.focus_set())
self.bind("a", printsomething("Page 1"))
Another question, I have a menu bar File -> Exit. I want to press the button "f" then "e" to exit the GUI. I tried accelerator option in 'filemenu.add_command' but it does not work.
So what can I try next? Thank you!

Apparently the displayed frame does not have focus. I tried adding focus_set to the show_frame() method and that seems to work.
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
frame.focus_set()
But maybe you should bind events to the root window instead.
Also, it looks like you are trying to bind the key "a" to more than one callback. You can do this but it's a little more complicated:
self.bind("a", first_callback)
self.bind("a", second_callback, "+") # Add callback
The "+" at the end makes bind understand that it should not substitute the first_callback but to ADD the second_callback.

Related

How to swap between pages / windows in Tkinter

I have tried googling and everyone seems to have structured their code completely differently. I understand the base level tkinter, however I do not understand how people are using classes and def's to swap pages. How can I swap from my main window to my second one? (and not open the second one after the main is closed)
import tkinter as tk
main = tk.Tk()
main.title("Main Program")
firstlabel = tk.Label(main, text="This is a program!")
firstlabel.pack()
main.mainloop()
second = tk.Tk()
second.title("Second Program")
firstlabel = tk.Label(second, text="This is another program!")
firstlabel.pack()
second.mainloop()
EDIT: (solution)
import tkinter as tk
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageOne, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self,parent)
label = tk.Label(self, text="Start Page", font=("Consolas", 30))
label.pack(pady=10,padx=10)
button = tk.Button(self, text="Visit Page 1", command=lambda: controller.show_frame(PageOne))
button.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Page One!!!", font=("Consolas", 30))
label.pack(pady=10,padx=10)
button1 = tk.Button(self, text="Back to Home", command=lambda: controller.show_frame(StartPage))
button1.pack()
app = Application()
app.mainloop()
One way do this is by clearing everything (every widget), with this function:
def clear(app):
# Delete everything else in app
widget_list = app.winfo_children()
for item in widget_list:
if item.winfo_children():
widget_list.extend(item.winfo_children())
for item in widget_list:
item.pack_forget()
And then puting in the new window you want to swap to (every window should hav its own frame to make it simpler).
Try this::::
tk.Toplevel(main)

Parameter 'event' value is not used

I'm developing a program in python 2.7 and have run into an issue on Pycharm. In the program, I want the user to be able to navigate through different screens by either clicking buttons, or by pressing the "enter" key. I tried implementing it into my program, and it works but Pycharm is giving the error
import Tkinter as Tk
class MemoryGameApp(Tk.Tk):
def __init__(self, *args, **kwargs):
Tk.Tk.__init__(self, *args, **kwargs)
Tk.Tk.wm_title(self, "2 screens")
container = Tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class StartPage(Tk.Frame):
def __init__(self, parent, controller):
Tk.Frame.__init__(self, parent)
label = Tk.Label(self, text="Page 1")
label.pack()
global button
button = Tk.Button(self, text="Next", width=5, command=lambda: controller.show_frame(PageTwo))
button.pack(pady=100, padx=100)
button.focus_set()
def press_enter(event):
controller.show_frame(PageTwo)
button2.focus_set()
button.bind("<Return>", press_enter)
class PageTwo(Tk.Frame):
def __init__(self, parent, controller):
Tk.Frame.__init__(self, parent)
label_title2 = Tk.Label(self, text="Page 2")
label_title2.pack()
global button2
button2 = Tk.Button(self, text="Back", width=5, command=lambda: controller.show_frame(StartPage))
button2.pack(pady=100, padx=100)
def press_enter(event):
controller.show_frame(StartPage)
button.focus_set()
button2.bind("<Return>", press_enter)
app = MemoryGameApp()
app.mainloop()
It has given the error in def press_enter(event):
It claims that event is not used, but if I remove it from the program, the program does not function correctly
I know that it works when it is implemented, I'm just interested in seeing if there's anyway that I can remove this issue.
Thanks
To remove this warning, you could replace the argument 'event' by '_', or just do something with the object, like printing it to console.
Even though the argument of the function seems useless, it cannot be removed: It belongs to the signature of a valid event function.
It is sufficient to change event to _event.

Tkinter: Code stops and window doesn't show up

I am working on a very basic interface on Python with Tkinter, that displays two input boxes and a button to login. I try to do it by creating different frames and change the frame when the user is logged. It was working nearly fine but then the code started to execute itself not entirely sometimes and entirely but without the Tkinter window. I looked into it and saw nothing shocking but I am not an expert so I am looking for help.
This is the code to run my class that implement Tkinter window:
print 1
app = Skeleton("HomePage")
print 2
app.mainloop()
print 3
The skeleton Class that implement the Tkinter window:
class Skeleton(Tk):
def __init__(self, f,*args, **kwags):
Tk.__init__(self,*args, **kwags)
self.title(f)
container = Frame(self, width=512, height=512)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
frameName = {"home","upload","retrieve","deconnected"}
self.frames["HomePage"] = HomePage(parent= container, controller=self)
self.frames["HomePage"].grid(row=0, column=0, sticky="nsew")
print 321
self.show_frame("HomePage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
print "Je vais te montrer mon frame"
frame = self.frames[page_name]
frame.tkraise()
And the code of the Home Page frame:
class HomePage(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.parent = parent
self.controller = controller
#print ("Construction de la page dáccueil")
#LABEL
self.username = Label(self, text="Username:")
self.username.grid(row =0,column =0)
self.username.pack()
#ENTRY
self.username_txb = Entry( self)
self.username_txb.focus_set()
self.username_txb.grid(row =0,column =1)
self.username_txb.pack(side=LEFT)
#LABEL
self.pass_lbl = Label(self, text="Password:")
self.pass_lbl.grid(row =0,column =2)
#ENTRY
self.password_txb = Entry( self, text="Password", show = "*")
self.password_txb.grid(row =0,column =3)
self.password_txb.pack(side=LEFT)
#LOGIN BUTTON
self.login_btn = Button(self, text="Login", command=lambda: controller.show_frame("UploadPage"))
self.login_btn.grid(row =0,column =4)
self.login_btn.pack(side=LEFT)
self.info_pane = PanedWindow()
self.info_pane.grid(row =1,column =0)
self.info_pane.pack(fill="none", expand=True, side=BOTTOM)
self.info_lbl = Label(self, text="More information about access:", fg="blue", cursor="hand2")
self.contact_lbl = Label(self, text="Contact us", fg="blue", cursor="hand2")
self.contact_lbl.grid(row =2,column =0)
self.contact_lbl.pack()
self.contact_lbl.bind("<Button-1>", self.callback)
print ("123Construction de la page dáccueil")
#self.parent.update()
def callback(self, event):
pass
def connect(self,controller ):
login = self.username_txb.get()
pwd = self.password_txb.get()
if(login == "a" and pwd == "a"):
print "Valid account"
self.controller.show_frame("UploadPage")
#UploadPage frame is implemented
The output everytime I execute the code is as following:
1
123Construction de la page dáccueil
Thank you in advance for the help. Hope this will help other people.
First lets address your use of pack() and grid().
Due to how tkinter is set up you cannot use both pack() and grid() on the same widget in a frame or window at one time.
You may use for example pack() to pack the main frame and grid() on the widgets inside that frame but you cannot use both in side the frame.
If one of your issues is where each widget is located and if it is expanding with the window you can manage all that inside of grid() so we can just use grid() here as its what I prefer when writing up a GUI.
Next we need to look at your call to show_frame as you are attempting to show a frame that does not exist in self.frames in the code you have presented us.
I have created a new class so your program can be tested with this line of code:
self.controller.show_frame("UploadPage")
The new class just makes a basic frame with a label in it showing that the frame does rise properly with tkrise().
I did some general clean up as your show_frame method was taking unnecessary steps to raise the frame, your method of importing tkinter is not the best option and some other quality corrections.
Instead of using:
frame = self.frames[page_name]
frame.tkraise()
We can simplify this method with just one line like this:
self.frames[page_name].tkraise()
I have also changed how you are importing tkinter as importing with * can sometimes cause problems if you inadvertently override build in methods. The best option is to import tkinter like this:
import tkinter as tk
Take a look at the below code and let me know if you have any questions. It should provide the info you need to allow the HomePage frame and UploadPage frame to work as intended.
import tkinter as tk
class Skeleton(tk.Tk):
def __init__(self, f,*args, **kwags):
tk.Tk.__init__(self,*args, **kwags)
self.title(f)
self.container = tk.Frame(self, width=512, height=512)
self.container.grid(row=0, column=0, sticky="nsew")
self.container.grid_rowconfigure(0, weight=1)
self.container.grid_columnconfigure(0, weight=1)
self.frames = {}
self.frames["HomePage"] = HomePage(parent=self.container, controller=self)
self.frames["HomePage"].grid(row=0, column=0, sticky="nsew")
self.frames["UploadPage"] = UploadPage(parent=self.container)
self.frames["UploadPage"].grid(row=0, column=0, sticky="nsew")
self.show_frame("HomePage")
def show_frame(self, page_name):
self.frames[page_name].tkraise()
class HomePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.parent = parent
self.controller = controller
self.username = tk.Label(self, text="Username:")
self.username.grid(row =0,column =0)
self.username_txb = tk.Entry(self)
self.username_txb.focus_set()
self.username_txb.grid(row=0, column=1)
self.pass_lbl = tk.Label(self, text="Password:")
self.pass_lbl.grid(row =0,column =2)
self.password_txb = tk.Entry(self, text="Password", show="*")
self.password_txb.grid(row =0,column =3)
self.login_btn = tk.Button(self, text="Login", command=self.connect)
self.login_btn.grid(row=0, column=4)
self.info_pane = tk.PanedWindow()
self.info_pane.grid(row=1, column=0)
self.info_lbl = tk.Label(self, text="More information about access:", fg="blue", cursor="hand2")
self.contact_lbl = tk.Label(self, text="Contact us", fg="blue", cursor="hand2")
self.contact_lbl.grid(row=2, column=0)
self.contact_lbl.bind("<Button-1>", self.callback)
def callback(self, event):
pass
# webbrowser.open_new("https://www.tno.nl/nl/")
# I do not have the import for this webbrowser so I disabled it for testing.
def connect(self):
login = self.username_txb.get()
pwd = self.password_txb.get()
if(login == "a" and pwd == "a"):
self.controller.show_frame("UploadPage")
class UploadPage(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
tk.Label(self, text="This upload frame is a test to see if your code is working").grid(row=0, column=0)
if __name__ == "__main__":
app = Skeleton("HomePage")
app.mainloop()

Change number binding depending on class within Tkinter Frame

I have a Tkinter GUI that is comprised of several classes all contained within one frame. All the classes have the same dimensions and are all loaded simultaneously on top of one another. However, user input determines which class is visible. Each class has the same layout and number of buttons as the others. ie: 3x3 grid of buttons.
I've started implementing .bind() functions that associate each button with the respective key on the number pad. The problem is that the .bind() functions remain static throughout all the classes because of the one frame.
How would I add .bind() functions to the following script to adjust depending on the currently visible class? Effectively changing the commands in order to match up with the corresponding 3x3 button grid.
import subprocess
import Tkinter as tk
class MainApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.grid(column=5, row=15)
container.grid_rowconfigure(0)
container.grid_columnconfigure(0)
self.frames = {}
for F in (StartPage, PageOne, PageTwo):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nswe")
self.show_frame(StartPage)
self.bind('0',(lambda event: self.show_frame(StartPage)))
self.bind('1',(lambda event: self.show_frame(PageOne)))
self.bind('2',(lambda event: self.show_frame(PageTwo)))
def show_frame(self, c):
frame = self.frames[c]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
def randomfunction0():
subprocess.Popen('foobar', shell=True)
def randomfunction1():
subprocess.Popen('foobar', shell=True)
StartPageButton0 = tk.Button(self, compound="top", image=self.StartImage0, text="foobar0", fg="black", command=lambda: subprocess.Popen('foobar0', shell=True))
StartPageButton1 = tk.Button(self, compound="top", image=self.StartImage1, text="foobar1", fg="black", command=lambda: subprocess.Popen('foobar1', shell=True))
StartPageButton2 = tk.Button(self, compound="top", image=self.StartImage2, text="foobar2", fg="black", command=lambda: subprocess.Popen('foobar2', shell=True))
StartPageButton0.grid(row=1, column=1, padx=20, pady=10)
StartPageButton1.grid(row=1, column=2, padx=20, pady=10)
StartPageButton2.grid(row=1, column=3, padx=20, pady=10)
controller.bind('1',(lambda event: subprocess.Popen('foobar0', shell=True)))
controller.bind('2',(lambda event: subprocess.Popen('foobar1', shell=True)))
controller.bind('3',(lambda event: subprocess.Popen('foobar2', shell=True)))
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
Page1Button0 = tk.Button(self, compound="top", image=self.Page1Image0, text="foobar0", fg="black", command=lambda: subprocess.Popen('foobar0', shell=True))
Page1Button1 = tk.Button(self, compound="top", image=self.Page1Image1, text="foobar1", fg="black", command=lambda: subprocess.Popen('foobar1', shell=True))
Page1Button2 = tk.Button(self, compound="top", image=self.Page1Image2, text="foobar2", fg="black", command=lambda: subprocess.Popen('foobar2', shell=True))
Page1Button0.grid(row=1, column=1, padx=20, pady=10)
Page1Button1.grid(row=1, column=2, padx=20, pady=10)
Page1Button2.grid(row=1, column=3, padx=20, pady=10)
controller.bind('1',(lambda event: subprocess.Popen('foobar0', shell=True)))
controller.bind('2',(lambda event: subprocess.Popen('foobar1', shell=True)))
controller.bind('3',(lambda event: subprocess.Popen('foobar2', shell=True)))
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# PageOne Repeated with different subprocess.Popen commands.
if __name__ == "__main__":
app = MainApp()
app.title("foobar title")
app.mainloop()
While we are at it, the one Frame setup also doesn't allow me to change the title displayed at the top of the GUI window. Would I be able to change app.title("foobar title") within each class? Opposed to having just one title displayed across all classes.
EDIT: I've tried using controller.bind() and self.bind() however self.bind doesn't change anything. The initial bindings in the MainApp() class are what get executed regardless of page focus.
If you do self.bind rather than controller.bind, the page-specific function should run, since only the page with focus will see the click. This is normally how you handle bindings -- only bind to the widgets that the bindings should apply to. In this case you don't want global bindings, you want bindings for each page.
The other solution is to bind them all to controller.handleKey(1), controller.handleKey(1), etc. handleKey is a generic function whose only function is to figure out which page is current, and call the handleKey of the current frame.

tkinter access widget on a different window

So I'm fairly new to the tkiner programming and trying my hands at a GUI with more than one window wich should scan Wifi-Hotspots and show me a list of them. I've copied an example from Switch between two frames in tkinter to get the different windows
I have a Main Menu to enable Monitor Mode for the Wifi-Card and to start the scan. I'm calling another frame with a Listbox to show the results.
My problem now is, that the function startScan(self) is called within StartPage, while the Listbox is in PageOne. How can I adress it there and add entrys to it?
class PyWiFi(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageOne, PageTwo):
frame = F(container, self)
self.frames[F] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, c):
'''Show a frame for the given class'''
frame = self.frames[c]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Main Menu", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
self.var = tk.IntVar()
monCheck = tk.Checkbutton(self, text="Monitor Mode", variable=self.var, command=self.monSwitch)
scanButton = tk.Button(self, text="Start Scan", command=self.startScan)
quitButton = tk.Button(self, text="Quit", command=self.master.quit)
monCheck.pack()
scanButton.pack()
quitButton.pack()
def monSwitch(self):
if(self.var.get()):
print "Monitor Modus an"
check_call(["airmon-ng", "start", "wlan0"])
else:
print "Monitor Modus aus"
check_call(["airmon-ng", "stop", "mon0"])
def startScan(self):
print "Scan gestartet"
app.show_frame(PageOne)
output=check_output('iwlist wlan0 scan | grep -E "Channel:|ESSID:"', shell=True)
netze = output.split()
print netze
for i in range(0,(len(netze)/2)-1):
string = netze[2*i]+" "+netze[2*i+1]
app.frames[PageOne].netzList.insert(END, string)
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Scanergebnisse", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
menuButton = tk.Button(self, text="Menu", command=lambda: controller.show_frame(StartPage))
quitButton = tk.Button(self, text="Quit", command=self.master.quit)
button = tk.Button(self, text="P2", command=lambda: controller.show_frame(PageTwo))
netzList = tk.Listbox(self, width=30)
netzList.pack()
quitButton.pack(side=LEFT)
menuButton.pack(side=LEFT)
button.pack(side=LEFT)
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="This is page 2", font=TITLE_FONT)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame(StartPage))
button.pack()
if __name__ == "__main__":
app = PyWiFi()
app.resizable(0, 0)
app.geometry("320x240")
app.mainloop()
netzlist and all other widgets defined in PageOne are local to __init__. You should name them self.netzlist etc. to be able to reference them as class attributes of PageOne later on.

Categories