Using tkinter as a start menu for another python program - python

Hello I'm new to tkinter and coding. I am creating a project called a google bike. I found an instructable on it and I wanted it to look nicer by adding a start menu. It uses python and I am using tkinter to create a start page of some sort for it. I am trying to create a button where I would just press it in order to launch the program. This is the code I used to make the tkinter to make it look nice and sort of like a start menu.
import Tkinter as tk
from Tkinter import *
import ttk
def OpenServer():
execfile("C:/users/Broadway Jewelry/Downloads/server/server2/server.py")
class Camacho(tk.Tk):
def __init__(app):
tk.Tk.__init__(app)
container = tk.Frame(app, width=800, height=500)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
app.frames = {}
for F in (StartPage, Instructionspage, OptionsPage, DemoPage):
frame = F(container, app)
app.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
app.show_frame(StartPage)
def show_frame(app, cont):
frame = app.frames[cont]
frame.tkraise()
class StartPage(Frame):
def __init__(app, parent, controller):
Frame.__init__(app, parent, background='black', width=800, height=500)
earth = tk.PhotoImage(file="C:/Users/Broadway Jewelry/Desktop/Bluestamp/earth.gif")
BGlabel = tk.Label(app,image=earth)
BGlabel.image = earth
BGlabel.place(x=300,y=0,width=400,height=400)
StartProgram = Button(app, text="Start Google Bike",
bd=10, command=OpenServer)
StartProgram.place(x=100,y=75,width=200,height=44)
Instructions = Button (app, text="Instructions", bd=10,
command=lambda: controller.show_frame(Instructionspage))
Instructions.place(x=100,y=150,width=200,height=44)
Options = Button (app, text="Options/Setup", bd=10,
command=lambda: controller.show_frame(OptionsPage))
Options.place(x=100,y=225,width=200,height=44)
Quit = Button (app, text="Exit Program", command=quit, bd=10)
Quit.place(x=100,y=300,width=200,height=44)
class Instructionspage(Frame):
def __init__(app, parent, controller):
Frame.__init__(app, parent, background='black', width=800, height=500)
label = tk.Label(app, text="Instructions\n \nPedal=go forward\npress button=reverse\nbutton side to steer",
font=(200), background='black', fg='white')
label.place(x=300,y=0,width=400,height=400)
StartProgram = Button(app, text="Start Google Bike", bd=10, command=OpenServer)
StartProgram.place(x=100,y=225,width=200,height=44)
GoBack = Button (app, text="Go Back", bd=10,
command=lambda: controller.show_frame(StartPage))
GoBack.place(x=100,y=300,width=200,height=44)
class OptionsPage (Frame):
def __init__(app, parent, controller):
Frame.__init__(app, parent, background='black', width=800, height=500)
GoBack = Button (app, text="Go Back", width=50, bd=10,
command=lambda: controller.show_frame(StartPage))
GoBack.place(x=100,y=300,width=200,height=44)
StartProgram = Button(app, text="Start Google Bike", bd=10, command=OpenServer)
StartProgram.place(x=100,y=225,width=200,height=44)
ShowDemo = Button (app, text="Show Demo Screen", bd=10,
command=lambda: controller.show_frame(DemoPage))
ShowDemo.place(x=100,y=150,width=200,height=44)
class DemoPage (Frame):
def __init__(app, parent, controller):
Frame.__init__(app, parent, background='black', width=800, height=500)
earth = tk.PhotoImage(file="C:/Users/Broadway Jewelry/Desktop/Bluestamp/Google-Bike.gif")
BGlabel = tk.Label(app,image=earth)
BGlabel.image = earth
BGlabel.place(x=300,y=0,width=400,height=400)
GoBack = Button (app, text="Go Back", width=50, bd=10,
command=lambda: controller.show_frame(OptionsPage))
GoBack.place(x=100,y=300,width=200,height=44)
app = Camacho()
app.mainloop()
I am thinking of finding some way to close the tkinter window and just have the python running the google bike. If anyone can help thank you very much

Just add a single line to OpenServer (which seems to be the function that runs the real program):
def OpenServer():
execfile("C:/users/Broadway Jewelry/Downloads/server/server2/server.py")
app.destroy()
I would also recommend that, within any given class, you refer to an instance of that class as the standard self rather than app. As it is, it's giving the impression that the Camacho object you instantiate at the end of the program is being passed around and referred to by its app name. However, that is not actually the case: in Camacho, app could refer to any Camacho object, even if you did something like test = Camacho(). Within Camacho, that test object would refer to itself as app. In the other classes, it's even more misleading, as the Camacho object named app is actually not app in there at all, but rather controller.
Confused? Even I find it messy - that's why it's better to use self, and avoid using the same apparent variable name in multiple scopes.

Related

Is it possible to resize an input box (entry) when clicked with tkinter?

Reading through other stackoverflow questions, and other sources I do see that bind can be used to call a function. Currently I'm working on a program that will communicate with a database (most likely mongodb), and so far I've set up a frame that has 2 inputs per row (key-value). I haven't completely decided whether I want one row per document, or one row per field. Right now, if a user has a lot to type then it wouldn't be ideal for them because you can't see everything you write. So what I was thinking is that, if the user clicks on the entry widget, then the box would become bigger and show them everything they have written. My current line of thinking is that maybe I could create another frame for it and somehow pass onto the information to that?
This is what it currently looks like
Then what I'd ideally want it to look like
Here's the code if interested how I made it (Images are from the "CreatePage" section):
from tkinter import *
import tkinter as tk
class Database_Project(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
stack_frame_container = tk.Frame(self)
stack_frame_container.grid_columnconfigure(0, weight=1)
stack_frame_container.grid_rowconfigure(0, weight=1)
stack_frame_container.pack(side="top", fill="both", expand=True)
self.frameslist = {}
for frame in (MainPage, CreatePage):
frame_occurrence = frame.__name__
active_frame = frame(parent=stack_frame_container, controller=self)
self.frameslist[frame_occurrence] = active_frame
active_frame.grid(row=0, column=0, sticky="snew")
self.current_frame("MainPage")
def current_frame(self, frame_occurrence):
active_frame = self.frameslist[frame_occurrence]
active_frame.tkraise()
class MainPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label_create = tk.Label(self, text="Create and insert data").grid(row=0, column=0, padx=50, pady=(50,0))
create_button = tk.Button(self, text="CREATE", command=lambda: controller.current_frame("CreatePage")).grid(row=1, column=0)
label_read = tk.Label(self, text="Query over data").grid(row=0, column=1, padx=50, pady=(50,0))
read_button = tk.Button(self, text="READ").grid(row=1, column=1)
label_update = tk.Label(self, text="Modify existing data").grid(row=2, column=0, padx=50, pady=(50,0))
update_button = tk.Button(self, text="UPDATE").grid(row=3, column=0, pady=(0,50))
label_delete = tk.Label(self, text="Remove data").grid(row=2, column=1, padx=50, pady=(50,0))
delete_button = tk.Button(self, text="DELETE").grid(row=3, column=1, pady=(0,50))
class CreatePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.inputlist = []
self.newinputlist = []
labels = [tk.Label(self, text="Enter unique field"), tk.Label(self, text="Enter corresponding the value/s")]
self.inputlist.append(labels[:])
for toplabels in range(1):
self.inputlist[toplabels][0].grid(row=toplabels, column=0, padx=10, pady=5)
self.inputlist[toplabels][1].grid(row=toplabels, column=1, padx=10, pady=5)
for entries in range(2):
for entrynum in range(0, 1):
print("column:", entries)
print("row", entrynum)
self.newinputlist.append(tk.Entry(self, borderwidth=5))
for x in range(len(self.newinputlist)):
self.newinputlist[x].grid(row=1, column=x, padx=10, pady=5)
self.inputlist.append(self.newinputlist[:])
button_input_1 = [tk.Button(self, text="ADD FIELD/VALUE", command=self.add_insert), tk.Button(self, text="BACK", command=lambda: controller.current_frame("MainPage"))]
self.inputlist.append(button_input_1[:])
button_input_2 = [tk.Button(self, text="IMPORT FILE"), tk.Button(self, text="SUBMIT DATA")]
self.inputlist.append(button_input_2[:])
for button in range(len(self.inputlist) - 2, len(self.inputlist)):
self.inputlist[button][0].grid(row=button, column=0, padx=10, pady=5)
self.inputlist[button][1].grid(row=button, column=1, padx=10, pady=5)
def add_insert(self):
add_input = [tk.Entry(self, borderwidth=5), tk.Entry(self, borderwidth=5)]
self.inputlist.insert(-2, add_input)
self.newinputlist.append(add_input)
for widget in self.children.values():
widget.grid_forget()
for index, widgets in enumerate(self.inputlist):
widget_one = widgets[0]
widget_two = widgets[1]
print(str(index), widget_one, widget_two)
widget_one.grid(row=index, column=0, padx=10, pady=5)
widget_two.grid(row=index, column=1, padx=10)
if __name__ == "__main__":
NoSQL_Project = Database_Project()
NoSQL_Project.title("NoSQL Database Project")
NoSQL_Project.mainloop()
It's pointless to resize an Entry widget since they can only ever hold a single line. I'll give an example using the Text widget instead, though the technique works with any widget.
There's really no trick, just bind to <FocusIn> and <FocusOut>. In the following example I've created two Text widgets that have this resize behavior:
import tkinter as tk
def resizer(event):
if event.widget == event.widget.focus_get():
event.widget.configure(height=8)
else:
event.widget.configure(height=1)
root = tk.Tk()
root.geometry("400x200")
text1 = tk.Text(root, height=1, width=20)
text2 = tk.Text(root, height=1, width=20)
text1.pack(side="left")
text2.pack(side="right")
for widget in (text1, text2):
widget.bind("<FocusIn>", resizer)
widget.bind("<FocusOut>", resizer)
root.mainloop()
The actual behavior depends on how you've laid out your widget. This could cause widgets to jump around or the window resize, but every app will be different so it's hard to give a solution that works everywhere.

Python moving class to separate file causes nameError

I am simply trying to move class ScreenThree to a separate file 9 I will soon have many more)....However for the lambdas I get nameError ...how to fix?
I've tried many arrangements, but allways get some sort of nameError. For this post, I have deleted ScreenTwo, since these basically all look the same.
When moving this class to its own file what needs to be changed? I used import, which seemed to work & screen3 shows. , However the button lambda is where it fails
import tkinter as tk
LARGE_FONT = ("Verdana", 12) # font's family is Verdana, font's size is 12
class MainWindow(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("Fuzzy System") # set the title of the main window
self.geometry("300x300") # set size of the main window to 300x300 pixels
# this container contains all the screens
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1) # make the cell in grid cover the entire window
container.grid_columnconfigure(0,weight=1) # make the cell in grid cover the entire window
self.frames = {} # these are screens we want to navigate to
for F in (ScreenOne, ScreenTwo,ScreenThree): # for each screen
frame = F(container, self) # create the screen
self.frames[F] = frame # store into frames
frame.grid(row=0, column=0, sticky="nsew") # grid it to container
self.show_frame(ScreenOne) # let the first screen is ScreenOne
def show_frame(self, name):
frame = self.frames[name]
frame.tkraise()
class ScreenOne(tk.Frame):
def __init__(self, parent, controller):
self.controller=controller
tk.Frame.__init__(self, parent)
label = tk.Label(self, text='This is ScreenOne', font=LARGE_FONT)
label.pack(pady=10, padx=10) # center alignment
# when click on this button, call the show_frame method to make screenOne appear
button1 = tk.Button(self, text='Visit screen two', command=lambda : controller.show_frame(ScreenTwo))
button1.pack() # pack it in
self.button2 = tk.Button(self, text='GOTO Screen Three', command=lambda : controller.show_frame(ScreenThree))
self.button2.place(relx=0.5, rely=0.29, height=41, width=144)
self.button2.configure(background="#911218")
class ScreenThree(tk.Frame):
def __init__(self, parent, controller):
self.controller=controller
tk.Frame.__init__(self, parent)
label = tk.Label(self, text='This is screen Three', font=LARGE_FONT)
label.pack(pady=10, padx=10)
button1 = tk.Button(self, text='GOTO ScreenTwo', command=lambda : controller.show_frame(ScreenTwo))
button1.pack()
button2 = tk.Button(self, text='GOTO Screen One', command=lambda : controller.show_frame(ScreenOne))
button2.pack()
if __name__ == '__main__':
app = MainWindow()
app.mainloop()
I have several classes, all looking similar to the following. They all work fine, no problems. However I want to move them to individual files, since they will soon become lengthy. I moved the following to file `scr3.py`.
I then added the following to my main file:
from scr3 import ScreenThree
Screen one and two work fine and my buttons in `screen3` show up. However when pushing on the screen three buttons I get a `NameError: name 'ScreenOne' is not defined` and similar for `screen2` (see the lambda funcs). These worked fine when all was in one file. `Screen1` and `2` (still in the `main` file) continue to work fine.
Why does it work fine when this same code is in the `main` file , but now fails? It has only been moved. What is the workaround?
import tkinter as tk
LARGE_FONT = ("Verdana", 12) # font's family is Verdana, font's size is 12
class ScreenThree(tk.Frame):
def __init__(self, parent, controller):
self.controller=controller
tk.Frame.__init__(self, parent)
label = tk.Label(self, text='This is screen Three', font=LARGE_FONT)
label.pack(pady=10, padx=10)
button1 = tk.Button(self, text='GOTO ScreenTwo', command=lambda : controller.show_frame(ScreenTwo))
button1.pack()
button2 = tk.Button(self, text='GOTO Screen One', command=lambda : controller.show_frame(ScreenOne))
button2.pack()
The code of ScreenThree is no longer in the same namespace as e.g. ScreenOne. You might fix this by passing a reference to ScreenOne and ScreenTwo as arguments to the __init__.

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.

Switching between frames in tkinter menu

I've used Tkinter to create a GUI with different menu options (a similar example is produced below). Each menu has different commands, which when clicked create a new frame. Now what is happening is if I switch to a different command, the new frame stacks below the current frame instead of replacing the old one.
I want to know what is the best way to move forward.
import Tkinter as tkinter
root = tkinter.Tk()
root.minsize(400,300)
welcome = tkinter.Frame(root).grid()
label = tkinter.Label(welcome, text="Welcome to my program").grid(row=0, column=3)
button = tkinter.Button(welcome,text="Exit",command=root.destroy).grid(row=3, column=1)
def newFrame():
newFrame = tkinter.Frame(root).grid()
newFrame_name = tkinter.Label(newFrame, text="This is another frame").grid()
menu = tkinter.Menu(root)
root.config(menu=menu)
main_menu = tkinter.Menu(menu)
menu.add_cascade(label="Main Menu", menu= main_menu)
main_menu.add_command(label="New Frame", command=newFrame)
main_menu.add_command(label="Another Frame", command=newFrame)
#menu.add_command(label="Exit", command=root.destroy, menu= filemenu)
root.mainloop()
Now if I switch between New Frame and Another Frame, the windows stack up, but I want one window to replace the other.
Any ideas? Thanks.
Here is a minimal example of one method I used recently; the key is in PythonGUI.show_frame, which moves the appropriate frame to the front for display.
import Tkinter as tk
class BaseFrame(tk.Frame):
"""An abstract base class for the frames that sit inside PythonGUI.
Args:
master (tk.Frame): The parent widget.
controller (PythonGUI): The controlling Tk object.
Attributes:
controller (PythonGUI): The controlling Tk object.
"""
def __init__(self, master, controller):
tk.Frame.__init__(self, master)
self.controller = controller
self.grid()
self.create_widgets()
def create_widgets(self):
"""Create the widgets for the frame."""
raise NotImplementedError
class ExecuteFrame(BaseFrame):
"""The application home page.
Attributes:
new_button (tk.Button): The button to switch to HomeFrame.
"""
def create_widgets(self):
"""Create the base widgets for the frame."""
self.new_button = tk.Button(self,
anchor=tk.W,
command=lambda: self.controller.show_frame(HomeFrame),
padx=5,
pady=5,
text="Home")
self.new_button.grid(padx=5, pady=5, sticky=tk.W+tk.E)
class HomeFrame(BaseFrame):
"""The application home page.
Attributes:
new_button (tk.Button): The button to switch to ExecuteFrame.
"""
def create_widgets(self):
"""Create the base widgets for the frame."""
self.new_button = tk.Button(self,
anchor=tk.W,
command=lambda: self.controller.show_frame(ExecuteFrame),
padx=5,
pady=5,
text="Execute")
self.new_button.grid(padx=5, pady=5, sticky=tk.W+tk.E)
class PythonGUI(tk.Tk):
"""The main window of the GUI.
Attributes:
container (tk.Frame): The frame container for the sub-frames.
frames (dict of tk.Frame): The available sub-frames.
"""
def __init__(self):
tk.Tk.__init__(self)
self.title("Python GUI")
self.create_widgets()
self.resizable(0, 0)
def create_widgets(self):
"""Create the widgets for the frame."""
# Frame Container
self.container = tk.Frame(self)
self.container.grid(row=0, column=0, sticky=tk.W+tk.E)
# Frames
self.frames = {}
for f in (HomeFrame, ExecuteFrame): # defined subclasses of BaseFrame
frame = f(self.container, self)
frame.grid(row=2, column=2, sticky=tk.NW+tk.SE)
self.frames[f] = frame
self.show_frame(HomeFrame)
def show_frame(self, cls):
"""Show the specified frame.
Args:
cls (tk.Frame): The class of the frame to show.
"""
self.frames[cls].tkraise()
if __name__ == "__main__":
app = PythonGUI()
app.mainloop()
exit()
There are two basic ways to solve the problem:
stack all of the frames on top of each other (eg: put in the same grid cell, or use place with the same options) and then raise the one that should be visible to the top of the stack (using frame.lift()). An example of the technique is in this answer: Switch between two frames in tkinter
Whenever you show a new frame, destroy (with .destroy() or hide (with pack_forget or grid_forget) the old frame.
I have a simple example. I'm sorry, because I don't know how to do this code by class. I just using simple code. But it's works!
:D
Here it is:
from Tkinter import *
def gantihal(frame):
frame.tkraise()
root = Tk()
f1 = Frame(root)
f2 = Frame(root)
f3 = Frame(root)
f4 = Frame(root)
for frame in (f1, f2, f3, f4):
frame.grid(row=0, column=0, sticky='news')
Button(f1, text='goto frame 2', command=lambda:gantihal(f2)).pack()
Label(f1, text='this is in frame 1').pack()
Label(f2, text='this is in frame two').pack()
Button(f2, text='goto frame 3', command=lambda:gantihal(f3)).pack()
Label(f3, text='this is in frame 3').pack(side='left')
Button(f3, text='next frame :)', command=lambda:gantihal(f4)).pack(side='left')
Label(f4, text='fourth frame').pack()
Button(f4, text='goto 1st frame', command=lambda:gantihal(f1)).pack()
gantihal(f1)
root.mainloop()

Categories